diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index db94cdbe0..ee520088e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -196,6 +196,31 @@ an assistant does not reach for it again. the published versions that carried them yanked ([the decision](../crates/windows-thread-ambient-sys/DESIGN-NOTES.md#d-transaction-removed)). +## DOCUMENTED CONTRACT — refuse on what the page says, never on what a run showed + +**A refusal made ahead of a platform call rests on the documented contract. A measurement is +documentation, never validation.** The engineer, 2026-10-18, for the whole repository; the rule and its +reasoning are in [DESIGN-NOTES.md](../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract). + +- **Refuse ahead of the call only what a page calls invalid, on the arguments alone**, and carry the + citation (page, condition, date read). Re-read the sentence before writing the refusal; a recalled + citation is not one. Only the call's own pages count: a lower layer's page (`ZwCreateFile` beneath + `CreateFileW`) is a lead for an experiment, not the contract. +- **Evidence that the page is wrong withholds the refusal.** Where a measurement shows the platform + accepting what the page calls invalid, do not embody the condition; document the discrepancy beside + it and keep the evidence test (the engineer, 2026-10-18). +- **A condition on the target is the call's to judge** -- existence, file system, device, local or + remote, token, sharing. Checking it first is a guess or a second open with a race between them. +- **Our own narrowing is refused as ours**, with an error kind distinct from a contract refusal, and + never framed as predicting the platform's rejection. +- **"Self-defeating", "ignored", "has no effect", and ambiguous sentences are not invalidity.** They + pass through and are documented. +- **What a run showed goes in documentation and an evidence test**, and never creates a check -- not + even when the kernel refused something the page allows. It can only withhold one, as above. +- **Default mechanism:** one pure check at the build step over a table of (predicate, kind, citation), + tested in memory. Make a condition structural only on a type parameter the component already has; do + not add a type parameter for a flag. + ## Line endings in tool parameters All text content passed to tpu tools (`content`, `replacement`, `data` in edit ops) is @@ -805,7 +830,7 @@ clean ASCII** (every byte in `0x00`-`0x7F`; no code point above `U+007F`). This governs: - checklist files: `CHECKLIST.md`, `CHECKLIST-.md`, - `COMPLETED-CHECKLIST.md`; + `COMPLETED-CHECKLIST.md`, and the parked-work file `PARKED.md`; - plan indexes: `PLANS.md`, `COMPLETED-PLANS.md`; - design docs: `DESIGN-NOTES.md`, `DESIGN-NOTES-*.md`, `DESIGN-RATIONALE.md`, `DESIGN-INSTRUCTIONS.md`, any other `DESIGN-*.md`, and every file under a @@ -890,6 +915,14 @@ points the file already has. A file may sit two thresholds up and still be right whole, and a file below every threshold may still be worth splitting when it obviously wants to be two things. Never reach for the byte count to justify a cut. +**Archival documents are exempt at every threshold.** `COMPLETED-CHECKLIST.md` and +`COMPLETED-PLANS.md` (and any other append-only history file, such as +`RESOLVED-TEST-FAILURES.md` and `DESIGN-NOTES-AGED-OUT.md`) are records, not modules: they have +no fracture points, they grow by appending, and their `` anchors are linked from stubs and +other documents, so a split buys nothing and breaks links. Never queue a CHECKLIST item to split +one, never hold an archive entry back because of one's size, and never record a size decision for +one. The scale governs source files only. + | Size on disk | Name | Default posture | |---|---|---| | < 32 KiB | normal | No action. | @@ -1205,6 +1238,7 @@ Examples: - Always form a plan in the form of a CHECKLIST.md, at the lowest common source-component for the change - Keep the plan up to date as you execute on the plan - Keep a file at the root of each component, called PLANS.md, which tracks all the CHECKLIST.md files in the repository and their status (not started, in progress, completed). If it does not exist, create it. If it does exist, update it with the new CHECKLIST.md file and its status. +- PLANS.md also tracks every `PARKED.md` (see "Parked work lives in `PARKED.md`" under CHECKLIST file hygiene), as its own row with the status "parked". When you create a `PARKED.md`, add its row in the same commit; when you delete an empty one, remove its row in the same commit. A `PARKED.md` never moves to COMPLETED-PLANS.md, because it is never completed: it is emptied and deleted. - When a CHECKLIST.md file is completed, move it to a table in a different file called COMPLETED-PLANS.md in the same directory, with a brief summary of the work completed, and remove it from PLANS.md. @@ -1217,7 +1251,7 @@ COMPLETED-PLANS.md format (markdown table): | Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | |---|---|---|---| -Status values: "not started", "in progress", "completed" +Status values: "not started", "in progress", "completed", and "parked" (for a `PARKED.md` row only) Design Notes column: Path(s) to DESIGN-NOTES.md file(s) that document the work, or "N/A" if none exist @@ -1290,22 +1324,25 @@ gradient has nothing to say about it. Design notes (DESIGN-NOTES.md, DESIGN-RATIONALE.md, and related files) record *decisions* — what was chosen and why. They steer future work, but they do **not** schedule it. The -only mechanism that queues work on existing code is a CHECKLIST.md item. A decision that is -recorded only in a design note, with the work it implies never transcribed into a checklist -item, is effectively orphaned: nothing will ever cause that work to be picked up. +only mechanism that queues work on existing code is an item in a CHECKLIST.md, or, for work that +is parked, an item in the component's `PARKED.md` (see "Parked work lives in `PARKED.md`" under +CHECKLIST file hygiene). A decision that is recorded only in a design note, with the work it +implies never transcribed into a checklist or parked item, is effectively orphaned: nothing will +ever cause that work to be picked up. This matters because the repository is worked by multiple people and multiple automated agent sessions, often in parallel and on different machines. None of them share local or session-private memory. The only directive any contributor — human or agent — can rely on seeing is what is committed to the repository. Therefore work must be queued in committed -CHECKLIST.md files, never parked in an agent's memory, a chat thread, or a design note that -no one is obligated to act on. +CHECKLIST.md files, or in a committed `PARKED.md` when it is not actionable now, never held in an +agent's memory, a chat thread, or a design note that no one is obligated to act on. When recording a decision that implies a change to existing code: - In the same change that records the decision, ensure the implied work exists as a - CHECKLIST.md item (creating or updating the checklist as needed), and reference the - decision from that item so the two can be traced to each other. + CHECKLIST.md item (creating or updating the checklist as needed), or as a `PARKED.md` item + with its gate when the work is not actionable now, and reference the decision from that item + so the two can be traced to each other. - If a decision deliberately schedules **no** work — a reservation, a deferral, or a "leave as-is for now" choice — state that explicitly in the decision so the absence of a checklist item is visibly intentional rather than an oversight. @@ -1662,7 +1699,7 @@ recollection must not.** Run the command, paste nothing, and where the claim mat - **Rung: integration tests**, and the first attempt got that wrong, which is the lesson. `the_probes_that_emit_a_machine_readable_row` in `windows-platform-probes` runs every registered - probe binary except two it names with a reason, and asserts which ones emit a row. It replaced a + probe binary under a deadline, and asserts that each emits a row. It replaced a *unit* test that walked `src/bin` and grepped for a substring -- cheaper, and unsound twice over: the walk was shallow, so it never saw the one probe whose classification the test existed to pin down, and a bare substring matched @@ -1754,6 +1791,10 @@ one-line **stub-with-link** (see "Completed-item stubs (move-with-link)" below). those single-sentence stubs, never leave historical records, prose summaries, rationale, or context in a CHECKLIST file. +A CHECKLIST file also holds **only work that is in scope now**. Parked work -- anything gated +(`M{n}+`), on the horizon (`M-inf`), or deferred by a ruling -- never appears in it; it lives in +the component's `PARKED.md`, under the rules in "Parked work lives in `PARKED.md`" below. + Checklists for work more than 2-3 items long should be organized into milestones. Milestones should generally be sized to about 5 work items (suggestion, not a rule) and should end with integration tests only when the milestone includes behavior that meets @@ -1844,8 +1885,9 @@ you are about to check, skip, or defer an item, first decide which case you are - **There is still an action to take in *this* milestone** → leave the item **unchecked**. It is pending work and stays open until that work is done. - **There is no action to take in this milestone** → the item does **not** belong here. Do one of: - 1. **Move** the item to the milestone in which it will actually be acted upon (use the `M{n}+` / - `M∞` notation below when that milestone is not yet numbered), **or** + 1. **Move** the item to the milestone in which it will actually be acted upon, if that milestone + is in `CHECKLIST.md`; when it is not yet numbered, or the item was deferred, move it to the + component's `PARKED.md` under the `M{n}+` / `M∞` notation below, **or** 2. If a real action *was* completed here that merely **spawns** later follow-up — e.g. a design decision recorded in a design milestone that implies later implementation, or an ABI-freeze obligation met by reserving a field whose *behavioral* decision is deferred — check the item @@ -1856,8 +1898,9 @@ you are about to check, skip, or defer an item, first decide which case you are The post-condition is mechanical and must hold after you touch any item: **no checklist contains a checked item whose remaining work is unscheduled, and no milestone contains a no-action item that -belongs to a different milestone.** A deferred item is therefore *always* either moved, or replaced -by a checked-plus-spawned pair — never silently checked in place. +belongs to a different milestone.** A deferred item is therefore *always* either moved (to a later +milestone in `CHECKLIST.md`, or to `PARKED.md`), or replaced by a checked-plus-spawned pair — never +silently checked in place. ### Milestone naming for future and horizon work @@ -1875,14 +1918,60 @@ distinct from real milestones (`M1`, `M2.3`, …): files this token is written **`M-inf`** (see the 7-bit ASCII rule above). Items in `M{n}+` / `M∞` are **parked, not pending**: they are not open obligations of any current -milestone, and the milestone that unblocks them **pulls them in (graduates them to a numbered ID)** -when it is authored. Rejected alternatives, recorded so they are not re-proposed: `M.Next` +milestone, they live in the component's `PARKED.md` and **never** in `CHECKLIST.md`, and the +milestone that unblocks them **pulls them in (moves them into `CHECKLIST.md` and graduates them to +a numbered ID)** when it is authored. Rejected alternatives, recorded so they are not re-proposed: `M.Next` (relative and ambiguous — "next" relative to which milestone? — and two files' `M.Next` mean different things); `M{n}.5` (implies a *defined* intermediate milestone and collides with the `RC-1.1` decimal sub-step notation); a bare `Backlog` / `TODO` (loses the gating deliverable and does not sort with the milestones). `M{n}+` keeps the gate visible and graduates cleanly; `M∞` is the catch-all when there is no gate. +### Parked work lives in `PARKED.md`, never in `CHECKLIST.md` + +`CHECKLIST.md` is the file a contributor reads to choose the next piece of work, so it holds +**only items that are in scope now**. Every item that is not -- gated on a deliverable (`M{n}+`), +horizon work (`M∞`, written `M-inf`), or deferred by a ruling -- lives in a sibling file named +**`PARKED.md`**, in the same directory as the component's `CHECKLIST.md`, one per component. These +rules are mandatory, and apply to every component: + +1. **Never put a parked item in `CHECKLIST.md`.** When you create, defer or discover an item that + no milestone in `CHECKLIST.md` will act on, write it to `PARKED.md` (creating the file, and its + PLANS.md row, if absent). When you find an `M{n}+` or `M-inf` section, or any parked item, in a + `CHECKLIST.md` you are editing, move it to `PARKED.md` -- byte-for-byte, as a pure relocation -- + in its own `docs:` commit before or after your change, never mixed into it. +2. **Every parked item states its gate and its reason.** Group items under a heading that names the + gate (`## M7+ -- (parked behind M7)`, or `## M-inf -- The horizon`), and say in the item + either the deliverable it waits on or the ruling that deferred it, quoted with who and when. An + item with no stated gate or reason is a defect: fix it when you see it. +3. **The gate points back to the parked work.** In `CHECKLIST.md`, the item whose completion + unblocks a `PARKED.md` section ends with this callout, in a blockquote so it stands out: + + > **-> UNBLOCKS PARKED WORK:** completing this unblocks `
` in [PARKED.md](PARKED.md#). + + Add the callout in the same commit that parks the work. Horizon (`M-inf`) and ruling-deferred + items have no gate in `CHECKLIST.md`; the PLANS.md row is their pointer. +4. **When you complete an item that carries an UNBLOCKS callout, act on it in the same session:** + graduate the named parked items into a milestone in `CHECKLIST.md` (giving placeholder IDs a + numbered one), or, if they still cannot be acted on, say in `PARKED.md` what they now wait for. + Never leave the callout's section unexamined. +5. **Graduate by moving.** An item leaves `PARKED.md` only by being moved into a `CHECKLIST.md` + milestone that will act on it, in the commit that authors or edits that milestone, or by being + **withdrawn** by a ruling, in which case it is archived to `COMPLETED-CHECKLIST.md` with the + ruling quoted, as a completed decision. Never check an item off inside `PARKED.md`. +6. **`PARKED.md` holds no checked items and no history.** When it is empty, delete it and remove + its PLANS.md row in the same commit. +7. **Same format as a checklist:** item IDs, decimal sub-step notation, a blank line between + multi-line items, 7-bit ASCII, clickable relative links. A parked item keeps its ID when it moves + in either direction, so references to it stay true; a link that named the item's old file is + updated in the same commit that moves it. +8. **Reading order.** To choose the next work, read `CHECKLIST.md` only. Read `PARKED.md` when you + complete an item with an UNBLOCKS callout, when you author a milestone, when you defer an item, + and when you are asked about deferred or future work. + +The file is `PARKED.md` and not `CHECKLIST-PARKED.md` on purpose: a `CHECKLIST-.md` is a +feature checklist that is deleted once it completes, and a parked list never completes. + ### Completed-item stubs (move-with-link) To keep the active checklist navigable without losing the in-place spine of "what we recently @@ -1920,9 +2009,12 @@ When a large item completes: so neither the summary nor the link target can drift. **Never** use a line-number fragment (`#L123`) as the link target — list items shift, headings -do not; anchor only to the heading's ``. When a milestone finally completes, its remaining -stubs migrate (are deleted) with the group as usual: a stub carries no information not already in -the archive, so nothing is lost. +do not; anchor only to the heading's ``. When a milestone finally completes, **delete its +remaining checked stubs; do not copy them into `COMPLETED-CHECKLIST.md`.** A stub carries no +information not already in the archive, so nothing is lost (the engineer, 2026-10-19: a checked +placeholder left after its item was archived "does not have to move, it may simply be deleted"). +Anything in the group that is *not* a stub -- an item body never archived, a preamble worth keeping +-- still moves to the archive. ### Sub-step notation @@ -1937,7 +2029,12 @@ When a group of related items is fully complete: `## Moved YYYY-MM-DD HH:MM:SS ±hh:mm — `, taken from `Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"`. Carry the offset, per "Timestamps carry their offset" below; existing headings without one are fine and are not worth going back to change. -3. `COMPLETED-CHECKLIST.md` is **append-only**; always add new groups at the bottom. +3. `COMPLETED-CHECKLIST.md` is **append-only**; always add new groups at the bottom. Its headings + and `` anchors are **immutable**, every heading has a blank line above it, and no `- [ ]` + remains in it (tick a verbatim copy of an item rather than leaving its box open). Body lines may + be corrected -- a link repointed when its target moves, a fact fixed -- but a heading or anchor + never changes. `tools/check-archives.ps1` enforces all of this against the merge base, and CI runs + it; run it before committing an archive change. 4. Leave only the remaining pending or in-progress items in the source `CHECKLIST.md`. Named feature files (`CHECKLIST-.md`) should be **deleted entirely** once all items are diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22cc40209..5ec764bee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,34 @@ jobs: shell: pwsh run: ./tools/check-encoding.ps1 + # Every COMPLETED-CHECKLIST.md: a blank line above each heading, no heading + # glued onto a line, no open box, and no heading or anchor changed against the + # merge base (root CHECKLIST.md, M34.18). The last needs the base commit, so the + # checkout is full. On a pull request the base is the target branch; on a push + # it is the previous tip, and a push with none (a new branch) skips that rule + # rather than failing on a missing commit. + archives: + name: completed-checklist archives + runs-on: windows-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Run test-check-archives.ps1 + shell: pwsh + run: ./tools/test-check-archives.ps1 + - name: Run check-archives.ps1 + shell: pwsh + env: + ARCHIVE_BASE: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || github.event.before }} + run: | + if (-not $env:ARCHIVE_BASE -or $env:ARCHIVE_BASE -match '^0+$') { + ./tools/check-archives.ps1 -NoHistory + } else { + ./tools/check-archives.ps1 -Base $env:ARCHIVE_BASE + } + exit $LASTEXITCODE + # release-please decides which crates are versioned and tagged; # publish-crate.yml decides which tags publish. Nothing connected the two, so # a crate could be release-managed and unpublishable at once -- and the @@ -314,6 +342,35 @@ jobs: RUST_BACKTRACE: 1 RUST_LIB_BACKTRACE: 1 run: cargo test -p win-durable-ioring --locked --lib -- --include-ignored raw_volume raw_disk + # The ETW provider wrapper against real trace sessions (WE-D-3): each test starts a session + # that logs to a file, enables a provider in it, and reads the file back. Starting a session + # needs an elevated process or the Performance Log Users group, which the runner's + # administrator account has and a developer's usually does not, so the tests are ignored by + # default. A failure here is a result: if the runner stops being able to start a session, the + # answer is to say so, not to delete the tests. + - name: cargo test (win-etw-sys, trace sessions) + if: ${{ matrix.isolated }} + env: + RUST_BACKTRACE: 1 + RUST_LIB_BACKTRACE: 1 + run: cargo test -p win-etw-sys --locked --test session -- --include-ignored + # The same, for the provider's sink (DE-D-4): each test starts a session, enables the + # provider in it, reports through the sink and decodes what the session recorded. They share + # the one provider, so they serialize themselves. + - name: cargo test (durable-ioring-events, trace sessions) + if: ${{ matrix.isolated }} + env: + RUST_BACKTRACE: 1 + RUST_LIB_BACKTRACE: 1 + run: cargo test -p durable-ioring-events --locked --test session -- --include-ignored + # The driver's default sink writes to that provider (DI-12.1.6): an instance built with no sink + # of its own, run against a session that is listening. Ignored by default for the same reason. + - name: cargo test (win-durable-ioring, default sink reaches a trace session) + if: ${{ matrix.isolated }} + env: + RUST_BACKTRACE: 1 + RUST_LIB_BACKTRACE: 1 + run: cargo test -p win-durable-ioring --locked --lib -- --include-ignored an_instance_with_no_chosen_sink_writes_its_events_to_etw # windows-file-watcher-example-test-harness is a normal workspace member # that depends on windows-file-watcher with the test-util feature, so # the `--workspace` steps above unify that feature onto @@ -420,20 +477,44 @@ jobs: # `#[ignore]`d tests, and the binaries that print the magnitudes. # # Every measurement in this workspace was originally taken on ARM64, so - # running them on this x64 runner is what answers "which findings are + # running them on an x64 runner is what answers "which findings are # architecture-dependent" (CHECKLIST-thread-ambient.md, M27.5). # + # The ARM64 entry is the other half (windows-platform-probes M2.15). Every + # other job here only BUILDS aarch64, and architecture is the one report + # shape a test corpus cannot vary, because it is fixed at compile time: a + # fixture that hard-codes one architecture passes on that host and no other. + # Only running the suite on a second architecture can see that class. The + # windows-11-arm label is a GitHub-hosted Windows 11 ARM64 runner, free for + # public repositories. + # # A FAILURE HERE IS A RESULT, NOT A BUILD BREAK TO PATCH AWAY. The ignored # tests assert the *shape* of a measured platform fact, so a failure means # the fact differs on this architecture and the design note resting on it # needs revisiting. Read the output before changing an assertion. - name: platform probes (x64, ignored tier + magnitudes) - runs-on: windows-latest + name: ${{ matrix.name }} + runs-on: ${{ matrix.runner }} + strategy: + # One architecture's result must not cancel the other's: a difference + # between the two is the finding, and it needs both halves. + fail-fast: false + matrix: + include: + - name: platform probes (x64, ignored tier + magnitudes) + runner: windows-latest + - name: platform probes (arm64, ignored tier + magnitudes) + runner: windows-11-arm # Bounded so a probe that wedges reports rather than burning the job's # default six-hour ceiling. The suite takes seconds when healthy. timeout-minutes: 15 env: RUSTUP_TOOLCHAIN: stable + # The probes' build script stamps these into the binaries (M4.8), so a + # report from this job names the commit it was built from and says it came + # from CI. The commit is what the checkout built: on a pull request, the + # merge commit. + PLATFORM_PROBES_COMMIT: ${{ github.sha }} + PLATFORM_PROBES_SOURCE: ci steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable @@ -466,6 +547,8 @@ jobs: # probe-cancel-io is DELIBERATELY ABSENT and must stay that way. It is # binary-only because CancelSynchronousIo can block indefinitely -- that # is the finding -- so running it here would hang the job by design. + # (The row census in the test step above does run it, under a deadline, + # reading its row as it is written rather than waiting for it to exit.) # # One step per probe, deliberately, rather than one `run: |` block listing # them. These steps take the Windows runner's default shell, `pwsh`, where @@ -533,13 +616,13 @@ jobs: - name: probe magnitudes (topology) if: "!cancelled() && steps.build.outcome == 'success'" run: cargo run -p windows-platform-probes --bin probe-topology --locked - # `--release` on the next two, and ONLY on those two, because they are the - # only probes here that report nanoseconds. Everything above measures + # `--release` on the next four, and ONLY on those four, because they are + # the only probes here that report nanoseconds. Everything above measures # BEHAVIOUR -- what an API refuses, what a handle reports, which errors # appear -- and that does not change with the optimisation level, so those # keep the dev profile and the build time it saves. # - # These two do not have that luxury. They compare operations a few tens of + # These do not have that luxury. The two cost probes compare operations a few tens of # nanoseconds apart, and in an unoptimized build the loop, the closure # indirection, and the request construction around each measured call # carry overhead that does not shrink uniformly across them. The RATIOS @@ -572,6 +655,22 @@ jobs: - name: probe magnitudes (request cost) if: "!cancelled() && steps.build.outcome == 'success'" run: cargo run -p windows-platform-probes --bin probe-request-cost --locked --release + # The two coherence probes, run here so that every build's log carries a + # run of each on BOTH architectures -- this job's arm64 entry is the only + # ARM64 hardware this workspace reaches, and the peer-index probe's finding + # is one that inverts between the two (windows-platform-probes M4.11). + # + # The queue probe is limited to 1, 2 and 4 producers because a hosted + # runner has four processors: rows above that would measure the scheduler + # and report it as contention. Its report says the run was not the + # defaults, and prints the processors it had. The full curve is still run + # by hand on a known machine. + - name: probe magnitudes (queue contention, 1 to 4 producers) + if: "!cancelled() && steps.build.outcome == 'success'" + run: cargo run -p windows-platform-probes --bin probe-queue-contention --locked --release -- --producers 1,2,4 + - name: probe magnitudes (peer index cache) + if: "!cancelled() && steps.build.outcome == 'success'" + run: cargo run -p windows-platform-probes --bin probe-peer-index-cache --locked --release # Both halves of the long-path pair, deliberately. Either alone says # nothing: the finding is the *difference* between two executables that # differ only in whether `build.rs` embedded the `longPathAware` manifest, diff --git a/.github/workflows/publish-crate.yml b/.github/workflows/publish-crate.yml index bb662614f..e1d29563d 100644 --- a/.github/workflows/publish-crate.yml +++ b/.github/workflows/publish-crate.yml @@ -5,16 +5,20 @@ on: push: tags: - 'durable-ioring-v*' + - 'durable-ioring-events-v*' - 'io-buffers-v*' - 'win-authorization-sys-v*' + - 'win-debugging-sys-v*' - 'win-durable-ioring-v*' - 'win-error-codes-v*' + - 'win-etw-sys-v*' - 'win-handles-sys-v*' - 'win-multi-file-tail-watcher-v*' - 'win-numa-sys-v*' - 'win-pipes-sys-v*' - 'win-shared-os-owned-handle-v*' - 'win-sync-sys-v*' + - 'win-text-sys-v*' - 'win-time-sys-v*' - 'win-volume-sys-v*' - 'windows-file-enumeration-sys-v*' @@ -43,16 +47,20 @@ on: type: choice options: - durable-ioring + - durable-ioring-events - io-buffers - win-authorization-sys + - win-debugging-sys - win-durable-ioring - win-error-codes + - win-etw-sys - win-handles-sys - win-multi-file-tail-watcher - win-numa-sys - win-pipes-sys - win-shared-os-owned-handle - win-sync-sys + - win-text-sys - win-time-sys - win-volume-sys - windows-file-enumeration-sys @@ -135,7 +143,7 @@ jobs: - name: Wait for workspace-sibling dependencies on crates.io shell: bash run: | - workspace_crates="durable-ioring io-buffers win-authorization-sys win-durable-ioring win-error-codes win-handles-sys win-multi-file-tail-watcher win-numa-sys win-pipes-sys win-shared-os-owned-handle win-sync-sys win-time-sys win-volume-sys windows-file-enumeration-sys windows-file-watcher windows-file-watcher-example-test-harness windows-impersonation-token-sys windows-ioring-sys windows-namespace-request-sys windows-overlapped-io-sys windows-thread-ambient-sys windows-threadpool-sys windows-topology-sys windows-waitable-queues wtf-string" + workspace_crates="durable-ioring durable-ioring-events io-buffers win-authorization-sys win-debugging-sys win-durable-ioring win-error-codes win-etw-sys win-handles-sys win-multi-file-tail-watcher win-numa-sys win-pipes-sys win-shared-os-owned-handle win-sync-sys win-text-sys win-time-sys win-volume-sys windows-file-enumeration-sys windows-file-watcher windows-file-watcher-example-test-harness windows-impersonation-token-sys windows-ioring-sys windows-namespace-request-sys windows-overlapped-io-sys windows-thread-ambient-sys windows-threadpool-sys windows-topology-sys windows-waitable-queues wtf-string" metadata="$(cargo metadata --no-deps --format-version 1)" # `tr -d '\r'` is load-bearing on the Windows runner: jq.exe writes # CRLF, and `read` splits on LF alone, so without this the last field diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 76af591e1..c8aa1b5c5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -15,6 +15,10 @@ "crates/win-handles-sys": "0.1.0", "crates/win-durable-ioring": "0.3.2", "crates/win-sync-sys": "0.0.2", + "crates/win-text-sys": "0.0.1", + "crates/win-debugging-sys": "0.0.1", + "crates/win-etw-sys": "0.0.1", + "crates/durable-ioring-events": "0.0.1", "crates/win-time-sys": "0.1.0", "crates/win-multi-file-tail-watcher": "0.2.0", "crates/win-volume-sys": "0.1.1", diff --git a/CHECKLIST-thread-ambient.md b/CHECKLIST-thread-ambient.md index e16184b63..564f06ede 100644 --- a/CHECKLIST-thread-ambient.md +++ b/CHECKLIST-thread-ambient.md @@ -18,7 +18,7 @@ Authoritative decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md) and, for the f parked no longer: every `M26+` item was gated on the namespace-facility design branch reaching `main`, and both [crates/windows-namespace-request-sys](crates/windows-namespace-request-sys) and [crates/windows-thread-ambient-sys](crates/windows-thread-ambient-sys) are on `main` now, so the -gate has lifted and the three items are pending. They keep the `M26+` id until someone graduates +gate has lifted and the items still unchecked are pending. They keep the `M26+` id until someone graduates them to a number, which `M34.7` in [CHECKLIST.md](CHECKLIST.md) owns. ## M26+ -- Was gated on the namespace-facility design branch landing; that gate has lifted @@ -40,22 +40,4 @@ them to a number, which `M34.7` in [CHECKLIST.md](CHECKLIST.md) owns. measured trap in the same edit: an invalid bit fails the whole `SetThreadErrorMode` call, so a forced-plus-transplanted combination installs nothing if the transplanted part is invalid. -- [ ] **M26+.3** -- Make the merge-or-delete decision on the duplicated path preparation. M24.4 copied - `windows-file-enumeration-sys`'s `path.rs` into `windows-namespace-request-sys` rather than depending on - it, because that crate is released and this one was not, and this branch exists to reach publication with - minimal impact on what already ships. **The de-duplication happens after this branch merges with `main`**, - which is what gates this item -- not a release of the new crate. Decide then: either make the enumeration - crate consume it and delete the older copy, or keep both and record what makes them genuinely separate. Do **not** let the duplication become permanent by nobody circling back -- that is - the failure mode the duplicate-then-decide procedure exists to prevent, and it is why this item is here - rather than only in a design note. Until it is settled, a fix to either copy must be applied to both. - See [crates/windows-namespace-request-sys/DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md) - -> `D-9`. - - **Decided 2026-10-15, at the engineer's request: the enumeration crate consumes the request crate's - path contract and deletes its older copy.** The branch has merged with `main`, which was the gate. - The work is `PATH-1` in [crates/windows-file-enumeration-sys/CHECKLIST.md](crates/windows-file-enumeration-sys/CHECKLIST.md), - after the request crate's `NR-3.3` to `NR-3.6` - ([CHECKLIST.md](crates/windows-namespace-request-sys/CHECKLIST.md)); this item is done when `PATH-1` - is. The request crate's path contract is itself replaced by - [D-19](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-19), so what the enumeration crate - consumes is that one, not the copy that was made. +- [x] **M26+.3** -- The duplicated path preparation is merged: `windows-file-enumeration-sys` consumes the request crate's `AnchoredPath` and its older copy is deleted. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#m263) diff --git a/CHECKLIST.md b/CHECKLIST.md index 233f124e5..e2f7d0f22 100644 --- a/CHECKLIST.md +++ b/CHECKLIST.md @@ -86,25 +86,9 @@ be settled rather than discovered later. bits stay forced. Note `SEM_NOALIGNMENTFAULTEXCEPT`'s behaviour is architecture-dependent, so this needs measurement on both ARM64 and x64 rather than reasoning. -- [ ] **M21.3** -- Replace `windows-file-enumeration-sys`'s inline `open_directory` with the general - facility's catalogue operation. The direction is settled, not open: that open is the committed first - consumer, and the inline path exists only until the general one is proven. The replacement must preserve - everything the shipped open already gets right, each of which is a constraint on the catalogue rather - than an implementation detail: arbitrary access, share mode and flags (it opens with - `FILE_LIST_DIRECTORY`, not `GENERIC_READ`, and requires `FILE_FLAG_BACKUP_SEMANTICS` to obtain a - directory handle at all); an **unassociated** handle, since `GetFileInformationByHandleEx` is - synchronous; the raw Win32 code unaltered, because `ERROR_FILE_NOT_FOUND` means three different things - across the open and the first and later queries and only the consumer can disambiguate; and - `GetLastError` captured *before* the context is restored, which the shipped code does deliberately. - Its D-15 Globazog acceptance gate must still pass afterwards. - -- [ ] **M21.4** -- Add the `FileBasicInfo` query as its own catalogue entry, and have the enumeration - crate sequence it after the open rather than performing it inline. It is a blocking namespace call, so - leaving it inline would keep a blocking call on the consumer's worker and only half-solve the problem - this facility exists for. One entry per Win32 call: this is **not** a compound open-and-classify - operation, and the sequencing is logical and client-side -- the crate submits the open, observes its - completion, then submits the query. A compound entry is reserved for a measured performance argument - and would be a fusion of these two entries rather than a capability they lack. Depends on M21.3. +- [x] **M21.3** -- The enumeration crate's inline `open_directory` is the request crate's `OpenFile`, keeping the access, share mode, flags and unassociated handle it needed. -> [completed 2026-10-20](COMPLETED-CHECKLIST.md#m213) + +- [x] **M21.4** -- The `FileBasicInfo` query is its own catalogue entry, `QueryFileBasicInfo`, and the enumeration crate sequences it after the open. -> [completed 2026-10-20](COMPLETED-CHECKLIST.md#m214) ## M34 -- Tooling @@ -274,6 +258,8 @@ changes. - [x] **M34.17** -- Teach the scoped sabotage run as the default local check. -> [completed 2026-10-19](COMPLETED-CHECKLIST.md#m3417) +- [x] **M34.18** -- Lint every `COMPLETED-CHECKLIST.md` mechanically, locally and in CI. -> [completed 2026-10-19](COMPLETED-CHECKLIST.md#m3418) + ## M37 -- Discharge the failable-call standard across the workspace Numbered M37, not M22. This section arrived from PR #84, which numbered it M22 without knowing @@ -504,6 +490,38 @@ as one domain -- so it does not belong in a `-sys` crate. It is built on the per which two spaces in one pool may share; a RAID controller's cache is not visible as a separate domain. The engineer's own VHDX layer composes its domains the same way, from its backing file. +- [ ] **M39.3** -- **Predict a directory's domains, and confirm each file's by its handle.** A consumer presents storage + roots as directories and creates files in them by name, so the helper maps a directory once to the domains a file created + in it will have: the directory's volume, mount points included, by the traversal of `M39.2` from the directory's handle. + The directory's path is resolved again at each create, so a junction swapped between the two sends the file elsewhere; + an open relative to the directory's handle would close that, and is parked as + [DI-15.3](crates/durable-ioring/PARKED.md) for want of a documented call. So after each open the helper reads the new + file's volume from its own handle (`GetFileInformationByHandleEx` with `FileIdInfo`, whose volume serial number is the + documented field) and compares it with the prediction, reporting a mismatch before the file is declared to dioring. From + [the design session](design-sessions/DESIGN-SESSION-2026-10-21-router-domains-and-paired-events.md#the-real-world-model); + the engineer, 2026-10-21: "The volume id refinement is a good catch for that race condition." + +## M42 -- Apply the documented-contract rule to the refusals that already exist + +The rule is in [DESIGN-NOTES.md](DESIGN-NOTES.md#refusals-rest-on-the-documented-contract) (the engineer, +2026-10-18): a refusal ahead of a platform call rests on the documented contract and carries its citation, +a component's own narrowing is named as its own, and a measurement is never a check. These items apply it to +code written before it was stated. `win-durable-ioring`'s open is not here; it is +[DI-14.4](crates/durable-ioring/CHECKLIST.md). + +- [ ] **M42.1** -- **Census every refusal made ahead of a platform call, across the workspace.** Every place a + component returns an error, panics or asserts on its arguments before the call they are for. The census is + produced by a search recorded with it, not from recollection, and lists each site with its crate. + +- [ ] **M42.2** -- **Classify each one against the rule:** a documented condition on the arguments alone (with + the citation, re-read), the component's own narrowing, or neither -- a check resting on a measurement or on + a target condition judged ahead of the call. The classification is recorded beside the census. + +- [ ] **M42.3** -- **Bring each site of the third kind to the engineer**, with what it rests on and what + removing it would let through, before anything is changed. Then carry out what is ruled, one crate per + commit, and give each contract refusal its citation and each narrowing its own error kind where it lacks + them. + ## M-inf -- Parked Ungated work with no identified predecessor deliverable. diff --git a/COMPLETED-CHECKLIST.md b/COMPLETED-CHECKLIST.md index 04e05ee24..cd899a2d3 100644 --- a/COMPLETED-CHECKLIST.md +++ b/COMPLETED-CHECKLIST.md @@ -893,7 +893,7 @@ across fifteen consecutive runs; targeted all-target Clippy and ### FE-8 -- Allocate the fixed native buffer and get one directory open and reading. *(completed 2026-08-27 20:12:34 UTC-04:00)* -[buffer.rs](crates/windows-file-enumeration-sys/src/buffer.rs) allocates the +`buffer.rs` (deleted by the enumeration crate's `SAFE-2`) allocated the staging buffer at admission, fallibly and as `u64` words so its base address is 8-byte aligned by construction rather than by hope; the ordinary growable-vector path would abort the process on failure and guarantee only byte alignment. @@ -1704,6 +1704,7 @@ Related: [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) M-inf.4, which is wh the tests, which is why the patch is now printed on every unexpected result; and a **too-short timeout manufactures a false "caught"**, crediting tests with catching a defect they never ran against, so the bound errs generous. + ## Moved 2026-09-01 -- Thread ambient mutation gaps ### M23.6 -- Close mutation gaps with deterministic fault injection and exhaustive assertions. *(completed 2026-09-01 20:25:29 UTC-04:00)* @@ -2185,6 +2186,7 @@ mutation wrapper. The conversion's three silent fallbacks are replaced by one ru Queued rather than left as a note precisely because a half-adopted abstraction is the state most likely to be forgotten -- the next probe author will see twelve neighbours printing directly and reasonably conclude that is the house style. + ## Moved 2026-09-07 -- M34.4: the native-command guard became shared support, proven on both hosts ### M34.4 -- Share the native-command guard through a dot-sourced `tools/common.ps1`, route every capture site through it, and prove it on both PowerShell hosts. *(completed 2026-09-07 21:16:31 -04:00)* @@ -2362,6 +2364,7 @@ namespace operation is. `#[cfg(doctest)] #[doc = include_str!("../README.md")]`, so a contract change breaks the build instead of leaving the README teaching the old answer. Verify by sabotage that the README examples are genuinely executed rather than merely parsed. M23.4 retains the *composite's* documentation. + ## M23 -- `windows-thread-ambient-sys`: the composite - [x] **M23.1** -- Implement the capture set and its named default, covering only the capturable aspects. @@ -2550,6 +2553,7 @@ assumed: holding a prepared path, two captured handles, and captured security attributes, outliving every input at once and still working on a thread that saw none of them -- plus the crate example, the README example compiled as a doctest, and confirmation that the changelog baseline matches its siblings. + ## M25 -- `windows-namespace-request-sys`: the handle-producing entries Entries 1-4 of the audited list. Each depends on M24's foundations and on nothing else. @@ -2641,8 +2645,8 @@ Entries 5-9 of the audited list. All but the last take a handle, so all but the Two constraints that are not negotiable and are already solved in this repository, so bind to the precedent rather than re-deriving it. The buffer must be **8-byte aligned**: a `Vec` fails the very first query with `ERROR_NOACCESS`, which is why - [crates/windows-file-enumeration-sys/src/buffer.rs](crates/windows-file-enumeration-sys/src/buffer.rs) - backs its storage with `Vec`. And the call **reports no written length** -- a batch is walked by its + `crates/windows-file-enumeration-sys/src/buffer.rs` (deleted by `SAFE-2`, whose `AlignedBuf` states the alignment) + backed its storage with `Vec`. And the call **reports no written length** -- a batch is walked by its own next-entry offsets -- so the completion returns the whole buffer and the consumer bounds its own reads, rather than the entry inventing a byte count it cannot know. @@ -4039,3 +4043,143 @@ The repository instructions' sabotage guidance in [.github/copilot-instructions. [tools/README-sabotage.md](tools/README-sabotage.md) say to verify a change with `-Since origin/main`, that `-List` shows which entries and why, that a clone takes every entry of a touched crate until it has swept the manifest once, and to sweep the whole manifest for a change scoping cannot see. + +## Moved 2026-10-18 20:52:23 -04:00 -- The duplicated path preparation is merged + +### M26+.3 -- The duplicated path preparation is merged: `windows-file-enumeration-sys` consumes the request crate's `AnchoredPath` and its older copy is deleted. *(completed 2026-10-18 20:52:23 -04:00)* + +Decided 2026-10-15 at the engineer's request, as the first of the two options the item named. The request +crate's side is its `NR-3.8` and +[D-30](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-30); the enumeration crate's is its `PATH-1` +and [D-22](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-22), which also records what that crate +gave up. The workspace principle the enumerator used to exemplify is amended at +[A layer's path contract is decided by whether it constructs paths](DESIGN-NOTES.md#path-contracts-follow-path-construction). + +## Moved 2026-10-19 17:42:21 -04:00 -- M34.18: the archives are linted + +### M34.18 -- Lint every `COMPLETED-CHECKLIST.md` mechanically, locally and in CI. *(completed 2026-10-19 17:42:21 -04:00)* + +Moved here from `windows-platform-probes`, where it was `M2.13`. An investigation of the 23 archives +before building found that two of the three checks as first written would have failed on records kept +on purpose, and the engineer settled four rules on 2026-10-19: + +1. A heading is preceded by a blank line, as style; the breaches were fixed. Applied to every heading + level, since the reason is the same at each: 49 blank lines across seven archives, inserted in a + `chore` commit of their own so released crates' archives earned no release. +2. No `- [ ]` remains. The 18 open boxes were all verbatim copies of items "as they stood at + completion", in `durable-ioring` and `io-buffers`; they were ticked. +3. Headings and `` anchors are immutable against the merge base, in place of the proposed "zero + deleted lines": since August, 62 of the 417 commits touching an archive deleted lines from one, + including link repointing this repository's own rules require. A heading glued onto a line is + checked too. +4. A checked stub left after its item was archived may simply be deleted; recorded in the copilot + instructions beside the archive rules, which now also state rules 1 to 3 and name the tool. + +`tools/check-archives.ps1` checks all four, untracked archives included, and exits 0, 1 or 2 like its +siblings; `tools/test-check-archives.ps1` covers each rule in both directions against throwaway git +repositories, and a manual sabotage of the heading rule turned its case red. The `archives` CI job runs +both, against the target branch on a pull request and the previous tip on a push. + +## Moved 2026-10-20 15:42:27 -04:00 -- M21.3 and M21.4: the enumeration crate's open and basic-info query are the request crate's entries + +### M21.3 -- The enumeration crate's inline `open_directory` is the request crate's `OpenFile`, keeping the access, share mode, flags and unassociated handle it needed. *(completed 2026-10-20 15:42:27 -04:00)* + +The item as written: + +Replace `windows-file-enumeration-sys`'s inline `open_directory` with the general + facility's catalogue operation. The direction is settled, not open: that open is the committed first + consumer, and the inline path exists only until the general one is proven. The replacement must preserve + everything the shipped open already gets right, each of which is a constraint on the catalogue rather + than an implementation detail: arbitrary access, share mode and flags (it opens with + `FILE_LIST_DIRECTORY`, not `GENERIC_READ`, and requires `FILE_FLAG_BACKUP_SEMANTICS` to obtain a + directory handle at all); an **unassociated** handle, since `GetFileInformationByHandleEx` is + synchronous; the raw Win32 code unaltered, because `ERROR_FILE_NOT_FOUND` means three different things + across the open and the first and later queries and only the consumer can disambiguate; and + `GetLastError` captured *before* the context is restored, which the shipped code does deliberately. + Its D-15 Globazog acceptance gate must still pass afterwards. + +Done by [SAFE-2](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-2): the open is `OpenFile` built from the +request's `AnchoredPath`, with listing access, every share, an existing directory and backup semantics and no overlapped +flag, performed under the captured token; the open's code is read before the token is restored, by the request +crate's entry; and the Globazog acceptance gate passes. The constraints above are pinned by a unit test of the request the +open builds, and by sabotage entries in the crate's [sabotage.json](crates/windows-file-enumeration-sys/sabotage.json). + +### M21.4 -- The `FileBasicInfo` query is its own catalogue entry, `QueryFileBasicInfo`, and the enumeration crate sequences it after the open. *(completed 2026-10-20 15:42:27 -04:00)* + +The item as written: + +Add the `FileBasicInfo` query as its own catalogue entry, and have the enumeration + crate sequence it after the open rather than performing it inline. It is a blocking namespace call, so + leaving it inline would keep a blocking call on the consumer's worker and only half-solve the problem + this facility exists for. One entry per Win32 call: this is **not** a compound open-and-classify + operation, and the sequencing is logical and client-side -- the crate submits the open, observes its + completion, then submits the query. A compound entry is reserved for a measured performance argument + and would be a fusion of these two entries rather than a capability they lack. Depends on M21.3. + +Done in two steps. The entry is the request crate's `QueryFileBasicInfo`, built as +[NR-6.4](crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-64-basic) (its D-33), and the enumeration crate +sequences it after the open as [SAFE-2](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-2): the open +completes, then the directory check is a separate query on a clone of the same handle, client-side, with no compound +open-and-classify entry. + +## Moved 2026-10-20 16:10:00 -04:00 -- M43.1: win-text-sys is created + +### M43.1 -- `win-text-sys` is created, with `compare_string_ordinal` over `CompareStringOrdinal`. *(completed 2026-10-20 16:10:00 -04:00)* + +The item's text, with the engineer's ruling on the name and the placement it records, is the crate's +[WT-D-1](crates/win-text-sys/DESIGN-NOTES.md#wt-d-1), and the first wrapper's shape and its one refusal are +[WT-D-2](crates/win-text-sys/DESIGN-NOTES.md#wt-d-2). + +What was done. [crates/win-text-sys](crates/win-text-sys/Cargo.toml) is a new workspace member, registered for +release-please and the publish workflow, with `compare_string_ordinal` taking two `&[u16]` and a `Case` and giving +an `Ordering`. The count is always passed, so a NUL is a unit. A slice of more than `i32::MAX` units is refused as +`CompareStringOrdinalError::TooLong` with the page's syntax block as the citation, and the call's failure is +`CompareStringOrdinalError::Call` with a `Win32Error`. Tests: equal and unequal strings both ways, case folded and not +(ASCII and beyond), embedded NULs, unpaired surrogates, a slice shorter than its buffer, a long string, the +reversal and reflexivity of the order, the refusal and the order in which it names the strings, the mapping of every +result and the call's own failure through the page's invalid value of `bIgnoreCase`. Evidence, in +[sabotage.json](crates/win-text-sys/sabotage.json): eleven entries, all caught, and a control. The two sites that call +`CompareStringOrdinal` themselves move to it in `SAFE-4` and `M17.1`. + +## Moved 2026-10-20 17:19:24 -04:00 -- M43: shared handles, and safe wrappers under the enumeration crate + +Opened 2026-10-18. The engineer ruled a request crate's handle duplication legacy, to be removed rather than +kept for compatibility ([D-32](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-32)), and set +`windows-file-enumeration-sys`' target as reaching Windows only through safe wrappers, under +`forbid(unsafe_code)` ([D-23](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-23)). The work is in six +crates' checklists; this is only the order and the links, and each item's own checkbox is its status. + +1. [NR-6.1](crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-61-shared) -- request entries hold a `SharedHandle`; + additive for callers that build entries. +2. The consumers move off `CapturedHandle`, in any order: + [DI-16.1](crates/durable-ioring/COMPLETED-CHECKLIST.md#di-161), [M7.1](crates/windows-platform-probes/COMPLETED-CHECKLIST.md#m71), + [SAFE-1](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-1). +3. [NR-6.2](crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-62-capture) -- capture deleted. `NR-6.3` to `NR-6.5` may land + at any point before the release. +4. The request crate's release, a new minor. A release action, not an item. +5. [SAFE-2](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-2) -- the directory's calls through the request crate. +6. Independently of 1 to 5: [M-T15.1](crates/windows-threadpool-sys/CHECKLIST.md) then + [WS-2.2](crates/win-sync-sys/COMPLETED-CHECKLIST.md#ws-22) (reordered 2026-10-18: the shared wait target comes first), then + [SAFE-3](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-3); and `M43.1` below, then + [SAFE-4](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-4) and the file watcher's + [M17.1](crates/windows-file-watcher/COMPLETED-CHECKLIST.md#m171). +7. [SAFE-5](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-5) -- `forbid(unsafe_code)`, last. + +- [x] **M43.1** -- `win-text-sys` is created, with `compare_string_ordinal` over `CompareStringOrdinal`. -> [completed 2026-10-20](COMPLETED-CHECKLIST.md#m431) + + > **-> CROSS-COMPONENT HANDOFF:** next work is + > [SAFE-4](crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-4) in `crates/windows-file-enumeration-sys` and + > [M17.1](crates/windows-file-watcher/COMPLETED-CHECKLIST.md#m171) in `crates/windows-file-watcher`. + +## Moved 2026-10-21 23:21:20 -04:00 -- M44: one handle duplication for the workspace + +`win-handles-sys` is where the Win32 handle functions live, and three crates had written their own same-process +`DuplicateHandle`. The decision is [WH-D-6](crates/win-handles-sys/DESIGN-NOTES.md#wh-d-6-duplicate_handle-chooses-the-access-and-the-inheritance-and-is-the-one-duplicatehandle-call). + +- [x] **M44.1** -- `win-handles-sys`: `duplicate_handle`, choosing the access (the source's or a mask) and the inheritance. + +- [x] **M44.2** -- `windows-file-watcher`: its queue's `duplicate` becomes `duplicate_handle`, and the crate depends on `win-handles-sys`. + +- [x] **M44.3** -- `windows-waitable-queues`: its doorbell's `duplicate` becomes `duplicate_handle`, and the crate depends on `win-handles-sys`. + +- [x] **M44.4** -- `win-job-launcher`: `duplicate_inheritable` becomes `duplicate_handle`, and the crate depends on `win-handles-sys`. diff --git a/COMPLETED-PLANS.md b/COMPLETED-PLANS.md index 32b8885c3..4a23f87ca 100644 --- a/COMPLETED-PLANS.md +++ b/COMPLETED-PLANS.md @@ -28,3 +28,15 @@ and in [crates/windows-threadpool-sys/COMPLETED-CHECKLIST.md](crates/windows-thr | [CHECKLIST.md](COMPLETED-CHECKLIST.md#pb-1) -> `PB-1` | 2026-09-19 14:29:03 -07:00 | Automated calendar-versioned probe binary releases, architecture-checked platform ZIPs, guarded attestation/upload jobs, and release-route checks. Local gates pass; hosted publication follows merge and the release PR. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-probe-releases) | | [crates/io-buffers/CHECKLIST.md](crates/io-buffers/CHECKLIST.md) | 2026-10-12 | Buffers for asynchronous I/O, as a portable crate: the `IoBuf` and `IoBufMut` contract moved out of `windows-ioring-sys` so `durable-ioring`'s portable core can name it without a Windows dependency, adopted by the ring crate, `win-numa-sys` and `windows-overlapped-io-sys`, plus one owned aligned buffer that replaced the aligned allocations in `windows-overlapped-io-sys`, the ring crate's tests and `windows-namespace-request-sys`. | [crates/io-buffers/DESIGN-NOTES.md](crates/io-buffers/DESIGN-NOTES.md) | | [CHECKLIST-sabotage-job-launcher.md](COMPLETED-CHECKLIST.md#jl-11) (deleted on completion; the link opens its archived entry) -> `JL` | 2026-10-07 15:52:29 -04:00 | The sabotage harness kills a hung run through a kill-on-close job object, owned by the new Rust launcher `win-job-launcher`, in place of a WMI process-tree walk, the only unbounded step on the path of a sweep that once sat for an hour. | [tools/README-sabotage.md](tools/README-sabotage.md#how-a-hung-run-is-killed) | +| [crates/windows-platform-probes/CHECKLIST.md](crates/windows-platform-probes/CHECKLIST.md) | 2026-10-19 20:00:57 -04:00 | M3: the encoded topology row became the contract -- diagnostic lists as objects with stable codes, one typed writer with total escaping, and no structured data read back out of prose. M4 and M5: the leftovers re-sequenced from M2, among them every `parse_incomplete` condition as a blocking state, interleaved queue passes, sampling on the command line, a report that names its build, and the probe suite on Windows ARM64; M5's archive lint moved to the root checklist as M34.18. M6 and M7: the `request_cost` probe rewritten against the path design, then stopped capturing. M8: every probe's normal run emits an `x-probe-*` row of raw data, and the row census runs every registered probe under a deadline and fails for any without one. Milestones are archived in [crates/windows-platform-probes/COMPLETED-CHECKLIST.md](crates/windows-platform-probes/COMPLETED-CHECKLIST.md). | [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md#d-every-probe-emits-a-row) | +| [crates/win-error-codes/CHECKLIST.md](crates/win-error-codes/CHECKLIST.md) | 2026-10-15 | The Win32 error, `LStatus`, `HResult` and `NtStatus` types, one per number space, with an exact `io::Error` round trip for every code, built to replace the per-crate copies that disagreed about a code of 2^31 or more. The crates that adopt them are planned in their own checklists. | [DESIGN-NOTES.md](crates/win-error-codes/DESIGN-NOTES.md) | +| [crates/win-pipes-sys/CHECKLIST.md](crates/win-pipes-sys/CHECKLIST.md) | 2026-10-18 | Memory-safe Rust over the Win32 pipe API, the Learn section *System Services > Interprocess Communications > Pipes > Pipe Reference > Pipe Functions*: named and anonymous pipes, connecting and disconnecting, the handle's info and state, peeking and transacting, waiting on a name, and the queries of who is at each end. The workspace's other sites that called `CreateNamedPipeW` or `ConnectNamedPipe` themselves move to it, each in a commit of its own crate. | [DESIGN-NOTES.md](crates/win-pipes-sys/DESIGN-NOTES.md) | +| [crates/windows-namespace-request-sys/CHECKLIST.md](crates/windows-namespace-request-sys/CHECKLIST.md) | 2026-09-10 | NR-1: pinned the `=X:` arm of drive-relative rooting. Raised as deferred work and completed the same round, because the blocker dissolved on measurement -- `GetFullPathNameW` writes the per-drive entry itself, so a test that sets it adds no hazard. The test now pins verbatim honouring, drive-root fallback, and the write-back. | [DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-18), [DESIGN-RATIONALE.md](crates/windows-namespace-request-sys/DESIGN-RATIONALE.md) | +| [crates/windows-namespace-request-sys/CHECKLIST.md](crates/windows-namespace-request-sys/CHECKLIST.md) | 2026-10-16 | NR-5: measured how `CreateFileW` reads the prefix of a path and recorded it as D-28 (`NR-5.1`), which corrected the reason for D-19 rule 6. The public path-form classifier planned after it (`NR-5.2` to `NR-5.4`) was withdrawn by the engineer the same day, because durable-ioring dropped the early remote refusal it was for. | [DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-28), [DESIGN-RATIONALE.md](crates/windows-namespace-request-sys/DESIGN-RATIONALE.md) | +| [crates/windows-namespace-request-sys/CHECKLIST.md](crates/windows-namespace-request-sys/CHECKLIST.md) | 2026-10-18 | NR-2: storage-locality entries for durable-ioring's flush domains, written here and then moved to `win-volume-sys` (`NR-2.6`, `NR-2.8`). NR-4: `Win32Error` is `win-error-codes`'. NR-3: what durable-ioring's open surface needs -- the SDDL conversion, `ReOpenFile`, the path regularisation (`AnchoredPath`, public since `NR-3.8`, which ended the enumeration crate's copy), `QueryFileType` and `QueryFileIdInfo`. | [DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md) (D-19, D-25 to D-31), [DESIGN-RATIONALE.md](crates/windows-namespace-request-sys/DESIGN-RATIONALE.md) | +| [crates/windows-namespace-request-sys/CHECKLIST.md](crates/windows-namespace-request-sys/CHECKLIST.md) | 2026-10-19 | NR-6: every handle-taking entry holds a `SharedHandle` and capture is deleted (D-32, graduated from `NR-inf.1`, after the consumers moved); `OpenFile` and `WatchDirectory` from an `AnchoredPath`; `QueryFileBasicInfo`, a typed `FileBasicInfo` read (D-33); and `QueryFileInformation::perform_into`, into a caller's buffer (D-34). What the enumeration crate needs to read through these entries. | [DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md) (D-30, D-32 to D-34), [DESIGN-RATIONALE.md](crates/windows-namespace-request-sys/DESIGN-RATIONALE.md) | +| [crates/windows-file-enumeration-sys/CHECKLIST.md](crates/windows-file-enumeration-sys/CHECKLIST.md) | 2026-10-18 | The crate's own work: `PATH-1`, which takes the request crate's `AnchoredPath` path contract and deletes the older copy of the preparation, and `ERR-1`, which makes `Win32Error` `win_error_codes::Win32Error`. `REVIEW-1` is parked in [PARKED.md](crates/windows-file-enumeration-sys/PARKED.md). | [DESIGN-NOTES.md](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-22) | +| [crates/win-shared-os-owned-handle/CHECKLIST.md](crates/win-shared-os-owned-handle/CHECKLIST.md) | 2026-10-21 | A shared owner of a Windows handle: std's `OwnedHandle` behind a reference count, which std lacks and crates here had each wrapped under their own name. Published as 0.1.0 on 2026-10-07; the ring crate, `win-durable-ioring` and the other crates that wrapped `Arc` themselves hold a `SharedHandle`, and none is left. | [crates/win-shared-os-owned-handle/DESIGN-NOTES.md](crates/win-shared-os-owned-handle/DESIGN-NOTES.md) | +| [crates/win-multi-file-tail-watcher/CHECKLIST.md](crates/win-multi-file-tail-watcher/CHECKLIST.md) | 2026-10-18 | Report length changes across many files on a caller-owned beat, and read the bytes they gain through an I/O ring: M1 to M10, from the roster and the poll engine through the thread pool, shutdown, the reading layer and its public sizes, to the findings of their reviews. The two decisions of `M2+` are parked in [PARKED.md](crates/win-multi-file-tail-watcher/PARKED.md). | [DESIGN-NOTES.md](crates/win-multi-file-tail-watcher/DESIGN-NOTES.md), [DESIGN-RATIONALE.md](crates/win-multi-file-tail-watcher/DESIGN-RATIONALE.md) | +| [crates/win-volume-sys/CHECKLIST.md](crates/win-volume-sys/CHECKLIST.md) | 2026-10-18 | Memory-safe Rust over the Win32 volume device controls: the disks a volume sits on, its sector size and its lock, which durable-ioring's raw volume backing is built on (`WV-M1`), the storage-locality entries taken in from the namespace crate (`WV-M2`), and what telling a volume or a disk from a file needs (`WV-M3`). The calls not yet wrapped are parked in [PARKED.md](crates/win-volume-sys/PARKED.md). | [DESIGN-NOTES.md](crates/win-volume-sys/DESIGN-NOTES.md) | +| [crates/win-handles-sys/CHECKLIST.md](crates/win-handles-sys/CHECKLIST.md) | 2026-10-21 | Memory-safe Rust over the Win32 handle and object API, the Learn section *System Services > Windows System Information > Handles and Objects*: duplicating a handle with a chosen access (moved here from `win-sync-sys`), reading and setting a handle's flags, asking whether two handles name one object, and closing a handle with its result. | [crates/win-handles-sys/DESIGN-NOTES.md](crates/win-handles-sys/DESIGN-NOTES.md) | diff --git a/Cargo.lock b/Cargo.lock index f0994ad88..92694ca36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,15 @@ dependencies = [ "io-buffers", ] +[[package]] +name = "durable-ioring-events" +version = "0.0.1" +dependencies = [ + "durable-ioring", + "win-error-codes", + "win-etw-sys", +] + [[package]] name = "io-buffers" version = "0.1.0" @@ -125,18 +134,32 @@ dependencies = [ "wtf-string", ] +[[package]] +name = "win-debugging-sys" +version = "0.0.1" +dependencies = [ + "win-debugging-sys", + "windows-sys", +] + [[package]] name = "win-durable-ioring" version = "0.3.2" dependencies = [ "durable-ioring", + "durable-ioring-events", "io-buffers", + "win-authorization-sys", + "win-debugging-sys", "win-durable-ioring", "win-error-codes", + "win-etw-sys", + "win-handles-sys", "win-shared-os-owned-handle", "win-sync-sys", "win-time-sys", "win-volume-sys", + "windows-file-enumeration-sys", "windows-ioring-sys", "windows-namespace-request-sys", "windows-sys", @@ -151,6 +174,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "win-etw-sys" +version = "0.0.1" +dependencies = [ + "win-error-codes", + "win-etw-sys", + "windows-sys", +] + [[package]] name = "win-handles-sys" version = "0.1.0" @@ -162,6 +194,7 @@ dependencies = [ name = "win-job-launcher" version = "0.0.0" dependencies = [ + "win-handles-sys", "windows-sys", ] @@ -203,6 +236,16 @@ version = "0.1.0" name = "win-sync-sys" version = "0.0.2" dependencies = [ + "win-handles-sys", + "win-shared-os-owned-handle", + "windows-sys", +] + +[[package]] +name = "win-text-sys" +version = "0.0.1" +dependencies = [ + "win-error-codes", "windows-sys", ] @@ -239,8 +282,13 @@ dependencies = [ name = "windows-file-enumeration-sys" version = "0.2.2" dependencies = [ + "io-buffers", "win-error-codes", + "win-shared-os-owned-handle", + "win-sync-sys", + "win-text-sys", "windows-impersonation-token-sys", + "windows-namespace-request-sys", "windows-sys", "windows-threadpool-sys", "wtf-string", @@ -253,6 +301,10 @@ dependencies = [ "log", "serde", "serde_json", + "win-error-codes", + "win-handles-sys", + "win-shared-os-owned-handle", + "win-text-sys", "windows-overlapped-io-sys", "windows-sys", "windows-threadpool-sys", @@ -279,6 +331,7 @@ dependencies = [ name = "windows-impersonation-token-sys" version = "0.1.1" dependencies = [ + "win-shared-os-owned-handle", "windows-sys", ] @@ -289,6 +342,7 @@ dependencies = [ "io-buffers", "serde_json", "win-numa-sys", + "win-pipes-sys", "win-shared-os-owned-handle", "windows-guard-alloc", "windows-overlapped-io-sys", @@ -329,6 +383,7 @@ name = "windows-overlapped-io-sys" version = "0.3.0" dependencies = [ "io-buffers", + "win-pipes-sys", "win-shared-os-owned-handle", "windows-sys", "wtf-string", @@ -351,6 +406,7 @@ version = "2026.1020.0" dependencies = [ "serde", "serde_json", + "win-pipes-sys", "win-shared-os-owned-handle", "windows-namespace-request-sys", "windows-placement-probe", @@ -401,6 +457,8 @@ dependencies = [ name = "windows-threadpool-sys" version = "0.3.0" dependencies = [ + "win-pipes-sys", + "win-shared-os-owned-handle", "win-sync-sys", "win-time-sys", "windows-overlapped-io-sys", @@ -421,6 +479,7 @@ name = "windows-waitable-queues" version = "0.1.2" dependencies = [ "portable-atomic", + "win-handles-sys", "windows-sys", ] diff --git a/Cargo.toml b/Cargo.toml index a14337d8c..32fe96af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,10 @@ members = [ "crates/win-durable-ioring", "crates/win-shared-os-owned-handle", "crates/win-sync-sys", + "crates/win-text-sys", + "crates/win-debugging-sys", + "crates/win-etw-sys", + "crates/durable-ioring-events", "crates/win-time-sys", "crates/win-volume-sys", "crates/windows-file-enumeration-sys", diff --git a/DESIGN-NOTES.md b/DESIGN-NOTES.md index 45921fd27..d0f834cc9 100644 --- a/DESIGN-NOTES.md +++ b/DESIGN-NOTES.md @@ -406,6 +406,57 @@ the existing ones stays open. The clarified scope adds a question to that migrat `windows-*` crates are facilities rather than direct layers, and for those the answer may be a name with no platform prefix rather than `win-`. The rationale for the clarification is in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md#why-the-win-prefix-is-scoped-to-direct-layers). +## A safe wrapper of a platform API is placed by the Microsoft Learn section the API is documented under, as of a dated snapshot + +**The engineer's decision, 2026-10-17.** The question was where a new safe wrapper belongs when no +existing crate's scope contains it (the case that raised it was the two read-backs of +[DI-14.8](crates/durable-ioring/CHECKLIST.md): `GetSecurityInfo` and `GetHandleInformation`). The first +proposal, 2026-10-17, was to follow Learn's grouping; the engineer ruled it "the rule to follow", stayed +undecided between naming by header and naming by documentation section, and then ruled: "classify by learn +section as of this date", with a local record of Learn's table of contents to apply it by. + +**The rule.** A wrapper of a platform API goes in the crate for the Learn section that documents the API. +The section is read from the dated snapshot [WIN32-LEARN-MAP-DATA.txt](WIN32-LEARN-MAP-DATA.txt), which +[WIN32-LEARN-MAP.md](WIN32-LEARN-MAP.md) describes and +[tools/snapshot-learn-toc.ps1](tools/snapshot-learn-toc.ps1) regenerates. No existing crate's scope +contains it: a new crate, named by this repository for the section's subject and by the `win-` rule above. + +**Microsoft's strings classify and never name.** Neither a header name nor a section title appears in a +crate name, a module path or any public identifier. They are carried in one place, the snapshot, so that +Microsoft renaming a section or moving a symbol to another header is a new snapshot and not a release, +and no release is compelled by the change. This is the permanent indirection the choice of key could not +avoid, kept where it costs a regeneration. The header is recorded beside the symbol as an attribute that +can be checked against the binding the wrapper calls, and is not the key. The engineer's reasoning: +Microsoft may change the header that carries a symbol while `Windows.h` stays the public header clients +include, and a key by header would then compel this repository to follow the change, where a section +retitled in Learn can be absorbed by a new snapshot. + +**Granularity.** A crate covers a second-level topic under a Learn area (*Synchronization*, *Handles and +Objects*, *Authorization*), which is how `win-sync-sys` and `win-volume-sys` are already cut. Learn +sections that are very small may share a crate, and a topic too large for one crate may split; either +is a judgement written in the crate's notes. One crate for the whole of Win32 with a feature per area, and +one crate per symbol, were considered and rejected: the first has one version for areas that change at +different rates and decisions that are authored, where `windows-sys`'s are generated, and the second has +no meaning. + +**Going forward only.** Existing crates are not moved to match. Where the rule shows that an existing +crate holds something filed elsewhere, which it does for the SDDL conversions and `SecurityDescriptor` in +`windows-namespace-request-sys` (documented under *Authorization*) and for +`duplicate_handle_with_access` in `win-sync-sys` (`DuplicateHandle` is documented under *Handles and +Objects*), the move is a separate scheduled item and is not done as a side effect of adding a wrapper. + +**Learn's listings are a relation, not a function (the engineer, 2026-10-17).** The documentation's job is +to convey meaning to people, so the same symbol can appear under several sections and different symbols +can share a title; neither is an error. The snapshot therefore keeps every listing, and the rule does not +say which one is "the" section: a symbol listed more than once is placed by judgement of its meaning, and +the crate's notes name the section chosen and the others that list it. + +**Not decided, and scheduling no work:** an umbrella crate that only re-exports the others, which would +decide no placement and is not needed to follow the rule; and a machine check that a crate's contents match +the snapshot, which is possible and has no consumer until there is more than one wrapper placed by it. Any +such check has to treat the listings as many-to-many, and report a symbol listed elsewhere as information +and not as a violation. + ## Windows SDK model and constraints This crate targets the object-based thread pool API (introduced in Windows Vista) rather than the legacy @@ -842,6 +893,37 @@ is a callback finding *its own* handle already closed, so each callback captures and looks for that. Each ordering test additionally asserts that teardown actually blocked, which is what stops it passing vacuously when a callback happens to finish before teardown begins. +## A wait may hold its handle shared + +**Decided by the engineer, 2026-10-18: "take the decision as yes now"**, to the question whether a `ThreadpoolWait` +takes a `SharedHandle` as part of removing handle duplication from `windows-file-enumeration-sys` (its `SAFE-3`) +and from the request crate ([D-32](crates/windows-namespace-request-sys/DESIGN-NOTES.md#d-32)). The work is +[M-T15.1](crates/windows-threadpool-sys/CHECKLIST.md). + +**What is decided.** A `WaitTarget` may be shared: the wait holds a clone of a `SharedHandle`, and a caller that +must also signal or inspect the object keeps another, instead of keeping a duplicate made for the purpose. The +requirement exclusive ownership met, that nothing closes the handle while a wait on it is pending, is met by the +count: the handle closes only when the last holder drops, and the wait's clone is one of them. + +**What does not change.** The waitability guarantee still comes from the type a target is built from, as +[the provenance decision](#a-safe-wait-constructor-takes-proven-wait-provenance-not-any-handle) requires: a shared +`win-sync-sys` `Event` converts safely because it is known to be an event, and a shared handle of unknown kind +needs the caller's `unsafe` assertion. The teardown order needs nothing new: releasing the wait's clone is the +target's destructor, which [the close-routine decision](#a-wait-target-owns-its-close-routine-because-not-every-waitable-handle-closes-with-closehandle) +already orders after the drain on both teardown paths. + +**The seam and the way back out, settled in `M-T15.1` by the assistant within the engineer's decision.** A shared +handle of unknown kind enters through `unsafe fn assume_waitable_shared(SharedHandle)`, beside `assume_waitable` +and `assume_waitable_with`, and its contract is the waitability one alone: exclusivity, which `assume_waitable` +also asks for, is what the count replaces. `into_handle` gives a shared target's handle back only to its last +holder, as `SharedHandle::try_into_owned` does, and otherwise returns the wrapper unchanged, still holding its +clone; this is [the same rule](#into_handle-now-declines-rather-than-lying) as a custom-close target's, a refusal +that hands the value back rather than an answer that is wrong. + +**Where the safe conversion from an `Event` lands.** `From` builds a shared target, since `win-sync-sys`' +`WS-2.2` made an `Event`'s clones share one handle ([its WS-D-10](crates/win-sync-sys/DESIGN-NOTES.md#ws-d-10)): the +wait and every clone the caller keeps hold the one handle, and no duplicate is made to signal through. + ## Re-arming is gated against teardown, under the same lock that arms Both the timer and the wait let a callback re-arm the object from inside itself, which is what the SDK requires @@ -2825,7 +2907,7 @@ backstop. The layers, cheapest first, are the items from - The post-merge sweep plans only the manifests a change could have reached, and within each, only the entries. A manifest takes every entry when the change touched an input every sweep shares (the workspace manifest and lockfile, the toolchain, the sabotage tools, the sweep workflow, and - `win-job-launcher`, which supervises every sweep); a crate its crate builds on; another package + `win-job-launcher`, which supervises every sweep, and `win-handles-sys`, which it builds on); a crate its crate builds on; another package whose tests it runs, or one that builds on; its crate's own build settings (`Cargo.toml`, `build.rs`, `.cargo/`), which can switch a test off without touching it; or its own settings. A manifest's `testArgs` replace `test -p `, so the packages it runs are the ones they name, @@ -3372,8 +3454,10 @@ by construction, so the binding would be absent exactly where completion occurs. ## A layer's path contract is decided by whether it constructs paths -Two crates here treat a caller's path in opposite ways, and side by side they look -like an inconsistency somebody should tidy up. They are not. Each follows from one +**Amended 2026-10-18 by [D-22](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-22): `windows-file-enumeration-sys` builds no path and no longer has the path-building contract; that contract is for a traversal layer, which its `REVIEW-1` reviews.** + +Two crates here treated a caller's path in opposite ways, and side by side they looked +like an inconsistency somebody should tidy up. They were not. Each follows from one question -- **does this layer ever build a path the caller did not give it?** -- and the answer differs, so the contracts differ. This is recorded once, here, because the failure mode is a well-meant "harmonisation" that breaks whichever @@ -3406,13 +3490,15 @@ mid-traversal is the worst possible time, so the contract front-loads it: resolv ordinary inputs once, cap them where behaviour is unambiguous, and tell the caller to supply a fully qualified `\\?\` path for anything longer -- an actionable synchronous error rather than a `NotFound` that names nothing. -`windows-file-enumeration-sys` is this shape. See -[D-7](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-7) and its -[Request path contract](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#request-path-contract). +No crate here is this shape now. `windows-file-enumeration-sys` was, under +[D-7](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-7), until +[D-22](crates/windows-file-enumeration-sys/DESIGN-NOTES.md#d-22) recognised that it enumerates +one directory and builds nothing, and took the request crate's verbatim contract instead. The +question is open again for a layer that traverses, and is that crate's `REVIEW-1`. The two also differ on the manifest, and deliberately in opposite directions. The watcher lets behaviour follow the host, because it only ever hands the caller's -own path back to Win32. The enumerator refuses to: it requires both the input and +own path back to Win32. The enumerator, under D-7, refused to: it required both the input and the resolved form to fit the ordinary limit specifically **so that acceptance does not depend on the host executable's manifest**, which keeps one input meaning one thing in every host that links it. A layer whose results feed further path @@ -3424,7 +3510,7 @@ into `\\?\` form, it must convert from a form Win32 has *already* normalised und the caller's own parsing mode -- `GetFullPathNameW`'s output, or `GetFinalPathNameByHandleW`'s -- never by prefixing the caller's raw string. Doing it after normalisation preserves what the path meant; doing it before silently -reinterprets it. Both crates arrived at this independently, which is why it is +reinterprets it. Both crates arrived at this independently, the enumerator under D-7, which is why it is written down once. ## Shared support in `tools/` is dot-sourced, never a module @@ -3788,3 +3874,65 @@ explains are binding where they are stated; the encodings taken at the time of w `is_nameable_in_a_mask` and `undirected` as single definitions, and an integration test asserting which probes emit a machine-readable row -- are in the commits that added this section and the one that corrected it. + +## A refusal rests on the documented contract; a measurement is documentation + +**The engineer, 2026-10-18, for the whole repository:** "If something has only been discovered experimentally, +it is anecdotal data which we can provide as documentation but it must not become part of our own validation, +as a rule. Now, if it leads to a combination of behaviors that we do not support, we should refuse it early but +that is an implementation choice to narrow our focus, not attempting to embody the kernel's anticipated +rejection." And: the principle "applies across the entire repository and should be encoded to be applied +uniformly." + +It arose from `win-durable-ioring`'s open ([DI-14.4](crates/durable-ioring/CHECKLIST.md)), where measurements +had already shown the kernel both stricter and more lenient than its pages. It is the consumer-side rule of +PLATFORM INTEGRITY ("depend on specified primitives, never on incidental current behavior") applied to the +platform itself: what one Windows build accepts on one machine is incidental behaviour, and the documented +contract is the specification. It is the sibling of +[DOCUMENTED APIS ONLY](.github/copilot-instructions.md), which governs which calls are made; this governs what +is refused before one. + +**Three kinds of refusal, and one thing that is never a refusal.** + +1. **The documented contract.** Where a page says a combination of a call's arguments is invalid, the + repository may refuse it before the call. Each such refusal carries its citation -- the page, the condition + as the page states it, and the date it was read -- and is written only after the sentence has been read + again, not recalled. When the page changes, the refusal is changed on purpose, never discovered by a test. + **Amended by the engineer, 2026-10-18:** "If we have evidence that the documentation is wrong, I believe + the remedy is to not embody the documentation structurally. This is unfortunate." So a documented + condition that a measurement contradicts -- the platform accepts what the page calls invalid -- is not + refused; the discrepancy is documented beside the condition and pinned by the evidence test that shows it. + (This replaces the first wording, that the page was refused even where this machine accepted it.) + **Only the call's own pages are its contract.** A condition stated for a lower layer this repository does + not call -- `ZwCreateFile` beneath `CreateFileW`, for instance -- is not direct evidence of the upper + call's contract (the engineer, 2026-10-18); it is a good lead for an experiment whose findings are then + documented as such. +2. **Our own narrowing.** Where a combination leads to behaviour a component does not support, it is refused as + early as the information to judge it exists, and named as the component's choice -- a separate error kind + from (1), since a narrowing is revisable and a contract refusal moves only when the contract does. It does + not claim to predict the platform's rejection. +3. **The platform's own refusal.** Everything else reaches the call, and its error reaches the caller as the + platform gave it, refined into a typed error after the fact where the code is specific enough. +4. **Never a refusal: a measurement.** Behaviour discovered by running is anecdote. It is documented, and pinned + by an evidence test so that a change is seen, but it never creates a refusal. It can only withhold one: a + measurement that shows the documentation wrong keeps the documented condition from being embodied, as (1) + says. + +**What a check ahead of the call can judge.** Only a condition on the arguments alone. A condition on the +target -- whether a file exists, which file system or device it is on, whether it is local, the caller's token, +who else holds it -- is the call's to judge: a check ahead of it is a guess, or a second open with a race window +between them. Such a documented condition stays documentation, and the platform's error answers it. + +**What is not a contract of invalidity.** "Self-defeating", "is ignored", "has no effect", and a sentence that +can be read two ways. Those pass through, and are documented as what the page says. + +**The default mechanism**, the assistant's proposal given leeway by the engineer on 2026-10-18: one pure check +at the build step, over a table of conditions each with its predicate, its kind and its citation, tested in +memory. A condition is made structural only where it falls on a type parameter the component already has; a +type parameter is not added for a flag, since every one multiplies the conversions that carry the others. A +component may choose otherwise and says why. + +**Scope.** Every component that validates ahead of a platform call. A `-sys` crate that decides nothing refuses +nothing and is consistent with the rule as it stands. Applying it to refusals that already exist is +[M42](CHECKLIST.md) in the root checklist; applying it to the open of `win-durable-ioring` is +[DI-14.4](crates/durable-ioring/CHECKLIST.md). diff --git a/DESIGN-RATIONALE.md b/DESIGN-RATIONALE.md index 6dcecd19b..c6d1ca72e 100644 --- a/DESIGN-RATIONALE.md +++ b/DESIGN-RATIONALE.md @@ -883,6 +883,17 @@ safe, or something new built on such crates?* The decision says that judgement i crate's own naming decision, so each answer can be argued with. The design session that raised it is [DESIGN-SESSION-2026-10-05-epoch-ring.md](design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md). +## Why a wait may hold its handle shared + +The question arose while planning the removal of handle duplication from `windows-file-enumeration-sys` +([DESIGN-NOTES.md](DESIGN-NOTES.md#a-wait-may-share-its-handle)). Its completion ring keeps a doorbell event it +sets and resets, and gives a client a `ThreadpoolWait` on the same event; because a wait owned its handle +exclusively, the ring made a `DuplicateHandle` copy for the wait. `WaitableHandle::event`'s own documentation told +callers to do the same with `Event::try_clone`. Exclusive ownership was the means, not the requirement: what the +pool needs is that the handle stays open while a wait is pending, and a reference count gives that. The engineer +first answered "let's assume yes" and the assistant wrote the item to test the assumption; the engineer then took +the decision outright. + ## A link that could not open its output: why the harness builds a second time A full sabotage sweep of `win-multi-file-tail-watcher` reported, once per sweep, an entry as diff --git a/PLANS.md b/PLANS.md index 038e8fe80..9554281dd 100644 --- a/PLANS.md +++ b/PLANS.md @@ -2,14 +2,19 @@ Master tracker for every active checklist in the repository. Each source-component also keeps its own plans tracker: [crates/durable-ioring/PLANS.md](crates/durable-ioring/PLANS.md), +[crates/durable-ioring-events/PLANS.md](crates/durable-ioring-events/PLANS.md), [crates/io-buffers/PLANS.md](crates/io-buffers/PLANS.md), [crates/win-authorization-sys/PLANS.md](crates/win-authorization-sys/PLANS.md), +[crates/win-debugging-sys/PLANS.md](crates/win-debugging-sys/PLANS.md), [crates/win-error-codes/PLANS.md](crates/win-error-codes/PLANS.md), +[crates/win-etw-sys/PLANS.md](crates/win-etw-sys/PLANS.md), [crates/win-handles-sys/PLANS.md](crates/win-handles-sys/PLANS.md), [crates/win-multi-file-tail-watcher/PLANS.md](crates/win-multi-file-tail-watcher/PLANS.md), +[crates/win-numa-sys/PLANS.md](crates/win-numa-sys/PLANS.md), [crates/win-pipes-sys/PLANS.md](crates/win-pipes-sys/PLANS.md), [crates/win-shared-os-owned-handle/PLANS.md](crates/win-shared-os-owned-handle/PLANS.md), [crates/win-sync-sys/PLANS.md](crates/win-sync-sys/PLANS.md), +[crates/win-text-sys/PLANS.md](crates/win-text-sys/PLANS.md), [crates/win-time-sys/PLANS.md](crates/win-time-sys/PLANS.md), [crates/win-volume-sys/PLANS.md](crates/win-volume-sys/PLANS.md), [crates/windows-file-enumeration-sys/PLANS.md](crates/windows-file-enumeration-sys/PLANS.md), @@ -33,21 +38,24 @@ plans tracker: [crates/durable-ioring/PLANS.md](crates/durable-ioring/PLANS.md), | [CHECKLIST-mutation-survivors.md](CHECKLIST-mutation-survivors.md) | not started | Work queued from the workspace-wide cargo-mutants sweep of 2026-09-02, whose findings are kept in [mutation-sweeps/2026-09-02/](mutation-sweeps/2026-09-02/README.md) rather than re-derived -- the run took roughly fourteen hours. 2,792 caught, 1,112 survived, 198 timed out. **The headline numbers mislead in three ways and the README says how**: a timeout in a blocking-API crate is usually a detection that lost its name rather than a gap (measured: one of `windows-waitable-queues`' 120 timeouts fails four tests in 0.00s when re-injected alone), a low score on an executable probe crate is measuring the wrong thing, and three kinds of survivor -- equivalent mutants, unreachable code, and constants that want a `const` assertion -- are not missing tests at all. M1 covers the shipping crates; M2 holds the two crates that are not libraries and whose scope is an engineer's decision; M3 re-runs and prunes rather than hand-editing the tool's output into a second source of truth. | [mutation-sweeps/2026-09-02/README.md](mutation-sweeps/2026-09-02/README.md) | | [crates/topology-planner/CHECKLIST.md](crates/topology-planner/CHECKLIST.md) | in progress | **Planned, not built** -- the directory holds a plan and no code, and becomes a crate when M2 begins. Owns the mapping from a **dataflow description of an application** -- its input, output and processing code paths, the shape settled 2026-09-23 as `EP-D-6` and discharging the deferral `EP-D-4` named -- plus an abstracted idealized machine description, to **one or more suggested** sets of execution domains: which processors host a domain, where each thread pins, which memory node it allocates from, what channel connects each pair, and where each channel's buffer lives. `EP-D-6` also split planning into two stages -- connectivity inferred with no machine in hand, then realization against a machine model -- which adds `EP-1+.5` (the connectivity graph is a value and needs a type and a home) and `EP-1+.6` (the answer is a set, so what a caller receives and what `M2+.2` renders are sets). Filed because that mapping was **unowned**: [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) M32 lists the contracts "the runtime cannot be written without" and all of them concern the queue, while M33+.1 opens with "one pinned thread, its `IoRing`, its node-local registered pool, its shard" -- presupposing a plan nothing computed. Separate from `windows-topology-sys` because that crate states **facts** and this one applies **policy**; fusing them is what produced `outermost_partitioning_cache`, a policy answer sitting in the facts crate that three consumers then re-derived differently (SH-16.9). M1 was a *requirements* milestone -- it states what the topology must answer, and it fed the locality-model session, which has since concluded as `D-13`..`D-21`. **The component was deferred past PR #56 by direction**, contributing only planning documents there; #56 then closed unmerged on 2026-09-15 and its content is landing in peeled pieces instead, so the component is still unlanded and goes in its own pull request. Per `D-21` the topology reshape lands without it, since `windows-topology-sys` publishes a refined view of what the platform publishes and an adapter absorbs the rest. M2+ and M3+ are parked on that session concluding, and are additionally **awaiting a re-cut**: EP-D-4 and EP-D-5 re-scoped the component into four parts (`topology-model` holding the abstract machine description, the planner's traits and the plan type; `topology-planner`; an inward Windows adapter; an outward realizer), and only M1 has been reconciled with that. EP-1.1 is done and already earned its keep: checking the shard-set query against the model found `Processor::capacity` using `0` as both a valid efficiency class and a "not known" sentinel, which collide on every non-hybrid machine (filed as SH-16.12). | [crates/topology-planner/DESIGN-NOTES.md](crates/topology-planner/DESIGN-NOTES.md), [design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md](design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md) | | [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) | in progress | **M30 is complete and archived** in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md): the queue crate's name, skeleton and SPSC shape. M31 built the bounded-array MPSC (with a lazily created manual-reset doorbell whose reset cannot be separated from the observation that there is nothing to take -- achieved by ordering plus a re-check rather than by a lock, per D-9 and D-15) and is done but for `M31.6`, the `loom` verification, which is re-homed as `M30.4` in [CHECKLIST.md](CHECKLIST.md). M32 remains: the contract decisions -- ordering, correlation, backpressure among them -- the domain runtime cannot be written without. M33+ parks the runtime itself, the creation-time-affinity thread builder, the namespace `Outcome` extension, and the client-side `ThreadpoolWait` fan-in helper; the durability crate it also parked (`M33+.5`) was transferred on 2026-10-05 to [crates/durable-ioring](crates/durable-ioring/COMPONENT.md), reshaped to build on `IoRing` directly. M-inf holds items each gated on a specific measurement rather than on taste. The N=1 path is the whole first deliverable and depends on no NUMA hardware. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-SESSION-2026-08-30-numa-sharded-io-execution-domains.md](design-sessions/DESIGN-SESSION-2026-08-30-numa-sharded-io-execution-domains.md) | -| [CHECKLIST.md](CHECKLIST.md) | in progress | M19: propagate the 2026-08-27 platform measurements (IoRing registration replaces the table; the completion-port/`IoRing` fork; `runs_long` as the growth mechanism; the measured 512 default maximum) into the crates whose code or documentation currently assumes otherwise. M20: decide the session-independent path form, now that path resolution is measured to follow the impersonated token's logon session. M21: reconcile with the impersonation and enumeration crates that landed during the session. M34 carries the review-driven repairs raised while shipping the placement tool: M34.1 (the reusable sabotage harness) is done, and M34.2 (route the placement tool's output through a sink rather than writing to stdout from many sites) and M34.3 (archive the completed item bodies still carried by the three root checklists) are open. M37: discharge the failable-call standard across the workspace. M30: find out how much of this workspace's algorithm correctness can be machine-checked -- a survey matching each argued-but-unchecked algorithm to a class of tool (TLA+/PlusCal, loom, bounded proof, `const` assertions), one pilot chosen because parameter shrinking makes an untestable property exhaustive, and a named list of what the pilot could not reach, which is the deliverable. Scoped as an instrument for narrowing hand-inspection rather than replacing it, and explicitly not a reversal of [D-31](crates/windows-waitable-queues/DESIGN-NOTES.md#d-31). Also re-homes `M31.6`, the `loom` verification the queue crate promises adopters before 1.0: it was previously untracked, referenced from that crate's design notes, a source file and its sabotage manifest with no live checklist item anywhere, and is now queued as M30.4. M30's rationale is in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md#machine-checking-what-is-argued) -- Tier 2, because no decision is taken yet; M30.5 is what produces one. M38 labels Model A and Model B at their first use in live files. M39 builds a locality helper that derives a file's flush domains from Windows' storage topology for durable-ioring. | [DESIGN-NOTES.md](DESIGN-NOTES.md#remoting-synchronous-namespace-operations) for M19-M21 and M37; N/A for M30 | +| [CHECKLIST.md](CHECKLIST.md) | in progress | M42: apply the documented-contract rule to the refusals made ahead of platform calls that already exist. M19: propagate the 2026-08-27 platform measurements (IoRing registration replaces the table; the completion-port/`IoRing` fork; `runs_long` as the growth mechanism; the measured 512 default maximum) into the crates whose code or documentation currently assumes otherwise. M20: decide the session-independent path form, now that path resolution is measured to follow the impersonated token's logon session. M21: reconcile with the impersonation and enumeration crates that landed during the session. M34 carries the review-driven repairs raised while shipping the placement tool: M34.1 (the reusable sabotage harness) is done, and M34.2 (route the placement tool's output through a sink rather than writing to stdout from many sites) and M34.3 (archive the completed item bodies still carried by the three root checklists) are open. M37: discharge the failable-call standard across the workspace. M30: find out how much of this workspace's algorithm correctness can be machine-checked -- a survey matching each argued-but-unchecked algorithm to a class of tool (TLA+/PlusCal, loom, bounded proof, `const` assertions), one pilot chosen because parameter shrinking makes an untestable property exhaustive, and a named list of what the pilot could not reach, which is the deliverable. Scoped as an instrument for narrowing hand-inspection rather than replacing it, and explicitly not a reversal of [D-31](crates/windows-waitable-queues/DESIGN-NOTES.md#d-31). Also re-homes `M31.6`, the `loom` verification the queue crate promises adopters before 1.0: it was previously untracked, referenced from that crate's design notes, a source file and its sabotage manifest with no live checklist item anywhere, and is now queued as M30.4. M30's rationale is in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md#machine-checking-what-is-argued) -- Tier 2, because no decision is taken yet; M30.5 is what produces one. M38 labels Model A and Model B at their first use in live files. M39 builds a locality helper that derives a file's flush domains from Windows' storage topology for durable-ioring. | [DESIGN-NOTES.md](DESIGN-NOTES.md#remoting-synchronous-namespace-operations) for M19-M21 and M37; N/A for M30 | | [CHECKLIST-ship-topology-and-queues.md](CHECKLIST-ship-topology-and-queues.md) | in progress | Release `windows-topology-sys` 0.2.0 and `windows-waitable-queues` 0.1.0, which everything shorter-term depends on. **Both reached crates.io on 2026-09-05**, by a route this file does not describe, since PR #56 closed unmerged; `SH-4.15` owns reconciling M4 with what shipped. Deliberately redundant with [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md): that file plans the design, this one plans the release, and a release has failure modes a design checklist does not surface. Two were found while writing it -- `windows-waitable-queues-v*` is missing from the publish workflow's tag list, so release-please would tag it and nothing would publish it, silently; and `windows-ioring-sys` is published against `windows-topology-sys = "0.1.0"`. (That second finding was later **corrected at SH-2.2**: the pin is a *dev*-dependency, which consumers never resolve, so it obliges a pin update but no release.) M1 settled the public surface before it was public (done, archived); M2 repairs the plumbing; M3 lands the branch; M4 releases; M5 verifies from outside the workspace; M6 is long-running validation and gates the queue crate's release specifically. M7-M13 were seven PR #56 review rounds (done, archived); M14, M15 and M16 are the three later rounds and carry the file's open work -- M15 owns the fix for an ABA hole that ships **disclosed rather than fixed**, so it does not block the release. M16 is the SH-3.1.1 diff review, the first to read the branch as a diff rather than react to a comment: seven findings, six fixed, including a publish-workflow regression this branch had introduced two commits earlier and a soundness hole in the crate about to freeze its API. Its remaining four are blocked on [design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md](design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md), which began by asking whether collapsing a seven-kind, any-depth topology onto a single cache boundary is the right projection and has since settled that presence and observation must be modeled rather than collapsed into an `Option`. **That work gated the merge, and has since discharged**: unlike M14 and M15, which concern a defect in an implementation that can ship disclosed, M16 concerned the shape of the public model `windows-topology-sys` 0.2.0 would publish, and a published model cannot be reshaped without another break. It became the `MMT-*` plan, which has landed; the session has concluded and 0.2.0 shipped the new model, so M3 no longer waits on M16. The file opens with a status table. | [crates/windows-topology-sys/DESIGN-NOTES.md](crates/windows-topology-sys/DESIGN-NOTES.md), [crates/windows-waitable-queues/DESIGN-NOTES.md](crates/windows-waitable-queues/DESIGN-NOTES.md) | | [CHECKLIST-placement-tool.md](CHECKLIST-placement-tool.md) | in progress | A small Windows tool a stranger can download, run once, and send back one structured result -- distributed as a GitHub release binary, never to a registry, per `PT-5.6` -- so this workspace can collect placement and NUMA-hop measurements from hardware it does not own. The gap is concrete: every host available here has one NUMA node, so the whole `cross NUMA node` row and the inter-node hop matrix are unmeasured and no local work will change that. Extracted rather than published in place, because [crates/windows-platform-probes](crates/windows-platform-probes) is deliberately "an experiment, not a component". **M1-M4 and M36 are complete and archived** in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md): the name and what the record says about a machine, `(group, number)` processor identity so a machine with more than 64 of them is not silently miscounted, each NUMA hop measured in both directions with the ring placed deliberately, the move of the measurement modules, a schema-versioned submission record, the runner's experience and trust, and redaction of the secondary metadata by default. What remains is M5 (distribute the binary), M6 (are "equivalent" processors actually equivalent?) and M7 (report what Windows contradicts about itself). **M5+ is WITHDRAWN**: the canonical artifact is a CI-built binary compiled from this repository through `path` dependencies, so it needs nothing published, and an earlier version of this row wrongly gated the whole tool on releasing `windows-topology-sys` and `windows-waitable-queues`. | [crates/windows-placement-probe/DESIGN-NOTES.md](crates/windows-placement-probe/DESIGN-NOTES.md), [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md) | | [CHECKLIST-thread-ambient.md](CHECKLIST-thread-ambient.md) | in progress | **M22-M29 are complete and archived** in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md): `windows-thread-ambient-sys` (a standalone layer that captures a thread's ambient state and applies it on another thread), `windows-namespace-request-sys` (marshalable Win32 namespace call parameter sets, over an entry list audited from three real consumers rather than guessed), `windows-platform-probes` (a durable home for the measurements this workspace's designs rest on), and the defects the audit of those three found. What remains is **pending**: the three `M26+` items were each gated on the namespace-facility design branch reaching `main`, and it has, so the gate has lifted -- reconciling the imported design background, applying the M22.2 narrowing to M21.2, and making the merge-or-delete decision on the duplicated path preparation. The file is deleted outright once those land. | [crates/windows-thread-ambient-sys/DESIGN-NOTES.md](crates/windows-thread-ambient-sys/DESIGN-NOTES.md) | | [crates/durable-ioring/CHECKLIST.md](crates/durable-ioring/CHECKLIST.md) | in progress | A ring for file I/O with durability scheduled into its work, layered on `windows-ioring-sys`'s `IoRing` -- the durability crate C-3 planned and `M33+.5` queued, transferred here and built on the ring directly rather than on the parked domain runtime ([DI-D-1](crates/durable-ioring/DESIGN-NOTES.md#di-d-1)). It reports durability truthfully, lets the consumer resolve failures, and inventories the writes a failure puts in doubt; retaining and rewriting data is a separate layer above it ([DI-D-5](crates/durable-ioring/DESIGN-NOTES.md#di-d-5)). The contract ([crates/durable-ioring/CONTRACT.md](crates/durable-ioring/CONTRACT.md)) and the API shape ([crates/durable-ioring/API.md](crates/durable-ioring/API.md)) are designed. | [crates/durable-ioring/DESIGN-NOTES.md](crates/durable-ioring/DESIGN-NOTES.md), [design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md](design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md) | +| [crates/durable-ioring/PARKED.md](crates/durable-ioring/PARKED.md) | parked | Work in the durable-ioring component not in scope now: gated on a milestone, on the horizon, or deferred by a ruling. Each section names its gate. | [crates/durable-ioring/DESIGN-NOTES.md](crates/durable-ioring/DESIGN-NOTES.md) | | [crates/windows-overlapped-io-sys/CHECKLIST.md](crates/windows-overlapped-io-sys/CHECKLIST.md) | not started | M14: finish the contract audit -- categories 1, 2, 6, 8, 9 were not examined -- and sweep `outstanding()` for the advisory-predicate hazard. Observers sharing an endpoint's handle, and the queries they offer, are complete and archived in that crate's COMPLETED-CHECKLIST.md. | [crates/windows-overlapped-io-sys/DESIGN-NOTES.md](crates/windows-overlapped-io-sys/DESIGN-NOTES.md) | -| [crates/windows-platform-probes/CHECKLIST.md](crates/windows-platform-probes/CHECKLIST.md) | in progress | M1 (streaming reports) is done and archived: every probe now writes into the sink as it measures, through a `fmt::Write` adapter that left every `writeln!` call site untouched, and the `catch_unwind`/`resume_unwind` pair is gone because there is no longer a buffer to rescue. Measured with a control: a probe killed part-way through a run keeps its banner and heading on every run of the new build, where the previous build kept nothing -- see [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md#d-streaming-report) for the figures. M2 built the report oracle -- one executable definition of the correspondences between a report's prose and NDJSON halves, bound inside the renderers so every test that renders inherits it -- along with a derived fact set and a corpus of report shapes. It is complete; its ten unrelated leftovers -- CI hygiene, a doc repair, probe-prose corrections -- were re-sequenced into M4 (gated on M3) and M5 (gated on nothing). M3 then supersedes its central rule. Re-reading M2's own evidence showed that both defects which motivated the oracle were defects in the ENCODED ROW, not in the relation between two renderings, and that the row published its three diagnostic lists as bare counts -- so a survey reading `"parse_incomplete":1` could not tell a probe self-bug from host flakiness. The row is the machine contract and gets the facts and the invariants; the prose is for a reader and gets review. M3 is complete and archived (ten items): those three fields publish arrays of OBJECTS, each carrying a stable `code` plus the values its variant holds -- `{"code":"partitioning_summary_missing","level":9}` rather than the bare `"partitioning_summary_missing"` of the superseded M3.1 form; the surviving correspondences became invariants over the observation rather than over two renderings, so a rule that reads the diagnostic lists -- which would be a restatement of `verdict()` and blind to a deleted push site -- was rewritten to read the observation; the row is emitted from a typed value through one writer with total escaping, which is the crate's only defence against caller text reaching the mined artifact; the prose oracle and every parser serving it were deleted, and no test extracts structured data from prose anywhere in the crate. Four later items came from reviews and are the more instructive half: three instruments were found asserting less than their names claimed, `BlockingState::ALL` was found to be a census the compiler did not check despite a doc comment claiming it did, and the row's hand-written JSON well-formedness check was measured against a real parser over 1807 generated corruptions -- 159 disagreements, every one a FALSE accept -- and replaced by `serde_json`, after which the remaining hand-written string scanners were deleted too. What remains is M4 (four M2 leftovers M3 gated, now unblocked and re-scoped) and M5 (six ungated hygiene items). | [crates/windows-platform-probes/DESIGN-NOTES.md](crates/windows-platform-probes/DESIGN-NOTES.md#d-streaming-report), [#d-encoded-row-is-the-contract](crates/windows-platform-probes/DESIGN-NOTES.md#d-encoded-row-is-the-contract) | -| [crates/win-numa-sys/CHECKLIST.md](crates/win-numa-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Windows NUMA APIs, and the first crate to take the `win-` prefix ([DESIGN-NOTES.md](DESIGN-NOTES.md#new-crates-take-the-win-prefix)). Created because `VirtualAllocExNuma` had been written twice in shipping library code, in `windows-ioring-sys` and `windows-placement-probe`, which do not depend on each other -- and a third time before that, in a sample, which `M22.3` had hoisted. `NumaBuffer` moved here; `windows-ioring-sys` re-exports it and supplies the `IoBuf`/`IoBufMut` impls via the orphan rule, which keeps `M6+.6`'s deferred trait-merge decision untouched. **The duplication is not yet removed, only relocated**: `N-1.1` collapses the probe's copy, and `N-1.2` decides whether `QueryWorkingSetEx` observation moves with it. | [crates/win-numa-sys/README.md](crates/win-numa-sys/README.md) | -| [crates/win-multi-file-tail-watcher/CHECKLIST.md](crates/win-multi-file-tail-watcher/CHECKLIST.md) | in progress | New crate: report length changes across many files on a caller-owned beat, and read the bytes they gain. | [crates/win-multi-file-tail-watcher/DESIGN-NOTES.md](crates/win-multi-file-tail-watcher/DESIGN-NOTES.md) | -| [crates/win-shared-os-owned-handle/CHECKLIST.md](crates/win-shared-os-owned-handle/CHECKLIST.md) | in progress | A shared owner of a Windows handle: std's `OwnedHandle` behind a reference count, which std lacks and crates here had each wrapped under their own name. The crate itself is complete and published; what remains is its adoption by those crates. | [crates/win-shared-os-owned-handle/DESIGN-NOTES.md](crates/win-shared-os-owned-handle/DESIGN-NOTES.md) | +| [crates/win-numa-sys/CHECKLIST.md](crates/win-numa-sys/CHECKLIST.md) | not started | Memory-safe Rust over the Windows NUMA APIs, and the first crate to take the `win-` prefix ([DESIGN-NOTES.md](DESIGN-NOTES.md#new-crates-take-the-win-prefix)). Created because `VirtualAllocExNuma` had been written twice in shipping library code, in `windows-ioring-sys` and `windows-placement-probe`, which do not depend on each other -- and a third time before that, in a sample, which `M22.3` had hoisted. `NumaBuffer` moved here; `windows-ioring-sys` re-exports it and supplies the `IoBuf`/`IoBufMut` impls via the orphan rule, which keeps `M6+.6`'s deferred trait-merge decision untouched. **The duplication is not yet removed, only relocated**: `N-1.1` collapses the probe's copy, and `N-1.2` decides whether `QueryWorkingSetEx` observation moves with it. | [crates/win-numa-sys/README.md](crates/win-numa-sys/README.md) | +| [crates/win-multi-file-tail-watcher/PARKED.md](crates/win-multi-file-tail-watcher/PARKED.md) | parked | `M2+`: whether the diagnostic sink's machinery moves to a crate this one and durable-ioring share, and whether the buffers give chunks back after an unusually large poll. | [DESIGN-NOTES.md](crates/win-multi-file-tail-watcher/DESIGN-NOTES.md), [DESIGN-RATIONALE.md](crates/win-multi-file-tail-watcher/DESIGN-RATIONALE.md) | | [crates/windows-ioring-sys/CHECKLIST.md](crates/windows-ioring-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Windows `IoRing` submission/completion ring, as a new crate. M1-M19 are complete (0.2.0 shipped 2026-08-30, restoring availability after all three 0.1.x versions were yanked); M1-M19 are archived. **M20** queues documentation and policy-test repairs from the 2026-08-30 NUMA-sharding measurement, and the pinned-thread `M6+` work stays parked. M21-M24 (epoch-log review, arena and submission, and making the unit suite hermetic) are complete; M23, M25 and M26 are open, and **M27 was added 2026-09-23 as the first milestone queued by intent, and re-planned the same day**: the adaptivity the [adoption thesis](DESIGN-NOTES.md#the-adoption-thesis) asks for is owned by [topology-planner](crates/topology-planner/COMPONENT.md), so M27 is now what this crate owes a realizer rather than a policy surface of its own. | [crates/windows-ioring-sys/DESIGN-NOTES.md](crates/windows-ioring-sys/DESIGN-NOTES.md) | | [crates/win-time-sys/CHECKLIST.md](crates/win-time-sys/CHECKLIST.md) | in progress | Safe Rust over the Windows clocks, in two layers -- timelines (a tick and what zero means) and the getters that read them -- so the workspace reads its one time base, interrupt time, in one place. Created because durable-ioring forbids `unsafe` and the only reader was `windows-threadpool-sys`' crate-private one. The cycle counter is parked. | [crates/win-time-sys/DESIGN-NOTES.md](crates/win-time-sys/DESIGN-NOTES.md) | -| [crates/win-volume-sys/CHECKLIST.md](crates/win-volume-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 volume device controls: the disks a volume sits on, its sector size and its lock, which durable-ioring's raw volume backing is built on. | [crates/win-volume-sys/DESIGN-NOTES.md](crates/win-volume-sys/DESIGN-NOTES.md) | +| [crates/win-volume-sys/PARKED.md](crates/win-volume-sys/PARKED.md) | parked | `WV-inf.1` to `WV-inf.3`: the volume's length, enumerating the machine's volumes, and the rest of the storage stack, none gated and each graduating when taken up. | [DESIGN-NOTES.md](crates/win-volume-sys/DESIGN-NOTES.md) | | [crates/win-authorization-sys/CHECKLIST.md](crates/win-authorization-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 authorization API (Learn: Security and Identity > Authorization): the owned security descriptor, moved from the request crate, and the queries that read and write a descriptor on an object. | [crates/win-authorization-sys/DESIGN-NOTES.md](crates/win-authorization-sys/DESIGN-NOTES.md) | -| [crates/win-handles-sys/CHECKLIST.md](crates/win-handles-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 handle and object API (Learn: System Services > Windows System Information > Handles and Objects): duplicating a handle with a chosen access, moved from `win-sync-sys`, and the handle-information queries. | [crates/win-handles-sys/DESIGN-NOTES.md](crates/win-handles-sys/DESIGN-NOTES.md) | | [crates/win-sync-sys/CHECKLIST.md](crates/win-sync-sys/CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 synchronization API, which began with `Event`; `windows-threadpool-sys` builds its wait targets on it, and durable-ioring's readiness event is the reason it exists now. The other kernel objects follow. | [crates/win-sync-sys/DESIGN-NOTES.md](crates/win-sync-sys/DESIGN-NOTES.md) | +| [crates/windows-threadpool-sys/CHECKLIST.md](crates/windows-threadpool-sys/CHECKLIST.md) | in progress | Teardown, self-heal, and the pool stall. Which milestones are open, and what each covers, is what [CHECKLIST.md](crates/windows-threadpool-sys/CHECKLIST.md) says; the stall's evidence is in [STALL-TIMELINE.md](crates/windows-threadpool-sys/STALL-TIMELINE.md). | [DESIGN-NOTES.md](DESIGN-NOTES.md#teardown-drains), [STALL-TIMELINE.md](crates/windows-threadpool-sys/STALL-TIMELINE.md) | +| [crates/windows-threadpool-sys/PARKED.md](crates/windows-threadpool-sys/PARKED.md) | parked | `M-T-inf.1`: find the threshold the pool's close races, diagnostic work with no gating deliverable since the draining teardown was adopted. | [STALL-TIMELINE.md](crates/windows-threadpool-sys/STALL-TIMELINE.md) | +| [crates/windows-file-watcher/PARKED.md](crates/windows-file-watcher/PARKED.md) | parked | The `M-inf` horizon: `ReadDirectoryChangesExW` extended records, digest-based change verification, and a per-volume capability cache, outside v1 by D-19. | [DESIGN-NOTES.md](crates/windows-file-watcher/DESIGN-NOTES.md) (D-19) | +| [crates/wtf-string/PARKED.md](crates/wtf-string/PARKED.md) | parked | The `M-inf` horizon: a checked no-interior-NUL C-string companion type, optional by D-7. | [DESIGN-NOTES.md](crates/wtf-string/DESIGN-NOTES.md) (D-7) | +| [crates/windows-file-enumeration-sys/PARKED.md](crates/windows-file-enumeration-sys/PARKED.md) | parked | REVIEW-1: re-read against that contract, which no longer caps ordinary paths or rewrites any to `\\?\` form, and review it against a traversal layer before one is built. Raised from the `windows-file-watcher` side; schedules no change of its own. | [DESIGN-NOTES.md](crates/windows-file-enumeration-sys/DESIGN-NOTES.md), [workspace DESIGN-NOTES.md](DESIGN-NOTES.md#path-contracts-follow-path-construction) | Add a row here when new work is planned, against [CHECKLIST.md](CHECKLIST.md) or any crate's. diff --git a/WIN32-LEARN-MAP-DATA.txt b/WIN32-LEARN-MAP-DATA.txt new file mode 100644 index 000000000..99a174c3f --- /dev/null +++ b/WIN32-LEARN-MAP-DATA.txt @@ -0,0 +1,25770 @@ +WIN32-LEARN-MAP-DATA: where Microsoft Learn files each Win32 symbol. +Snapshot: 2026-10-17. Source: https://learn.microsoft.com/en-us/windows/win32/toc.json and each area's toc.json. Regenerate with tools/snapshot-learn-toc.ps1. +Layout: "# " lines are the path of section titles; under each, "kindheadername[=slug]" per symbol. +"=slug" appears only where the name in Learn differs from the page slug (the A and W forms). +Areas: 169. Symbols: callback=731 class=88 control-code=161 enum=2758 function=11127 interface=3490 struct=5117. +Not covered, no toc of their own: srvnodes, updateorchestrator, wia, wmp. + +# Desktop App Technologies > Windows Accessibility features reference > Accessibility Functions +function winuser.h RegisterPointerInputTarget +function winuser.h RegisterPointerInputTargetEx +function winuser.h UnregisterPointerInputTarget +function winuser.h UnregisterPointerInputTargetEx + +# Desktop App Technologies > Windows Accessibility features reference > Accessibility Structures +struct winuser.h ACCESSTIMEOUT +struct winuser.h FILTERKEYS +struct winuser.h HIGHCONTRAST =highcontrasta +struct winuser.h MOUSEKEYS +struct winuser.h SERIALKEYS =serialkeysa +struct winuser.h SOUNDSENTRY =soundsentrya +struct winuser.h STICKYKEYS +struct winuser.h TOGGLEKEYS + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation providers > Interfaces for providers +interface uiautomationcore.h IAccessibleEx +interface uiautomationcore.h IAccessibleHostingElementProviders +interface uiautomationcore.h IProxyProviderWinEventHandler +interface uiautomationcore.h IProxyProviderWinEventSink +interface uiautomationcore.h IRawElementProviderAdviseEvents +interface uiautomationcore.h IRawElementProviderFragment +interface uiautomationcore.h IRawElementProviderFragmentRoot +interface uiautomationcore.h IRawElementProviderHostingAccessibles +interface uiautomationcore.h IRawElementProviderHwndOverride +interface uiautomationcore.h IRawElementProviderSimple +interface uiautomationcore.h IRawElementProviderSimple2 +interface uiautomationcore.h IRawElementProviderSimple3 +interface uiautomationcore.h IRawElementProviderWindowlessSite + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation providers > Control Pattern Interfaces for Providers +interface uiautomationcore.h IAnnotationProvider +interface uiautomationcore.h IDockProvider +interface uiautomationcore.h IDragProvider +interface uiautomationcore.h IDropTargetProvider +interface uiautomationcore.h IExpandCollapseProvider +interface uiautomationcore.h IGridItemProvider +interface uiautomationcore.h IGridProvider +interface uiautomationcore.h IInvokeProvider +interface uiautomationcore.h IItemContainerProvider +interface uiautomationcore.h ILegacyIAccessibleProvider +interface uiautomationcore.h IMultipleViewProvider +interface uiautomationcore.h IObjectModelProvider +interface uiautomationcore.h IRangeValueProvider +interface uiautomationcore.h IScrollItemProvider +interface uiautomationcore.h IScrollProvider +interface uiautomationcore.h ISelectionItemProvider +interface uiautomationcore.h ISelectionProvider +interface uiautomationcore.h ISelectionProvider2 +interface uiautomationcore.h ISpreadsheetItemProvider +interface uiautomationcore.h ISpreadsheetProvider +interface uiautomationcore.h IStylesProvider +interface uiautomationcore.h ISynchronizedInputProvider +interface uiautomationcore.h ITableItemProvider +interface uiautomationcore.h ITableProvider +interface uiautomationcore.h ITextChildProvider +interface uiautomationcore.h ITextEditProvider +interface uiautomationcore.h ITextProvider +interface uiautomationcore.h ITextProvider2 +interface uiautomationcore.h ITextRangeProvider +interface uiautomationcore.h ITextRangeProvider2 +interface uiautomationcore.h IToggleProvider +interface uiautomationcore.h ITransformProvider +interface uiautomationcore.h ITransformProvider2 +interface uiautomationcore.h IValueProvider +interface uiautomationcore.h IVirtualizedItemProvider +interface uiautomationcore.h IWindowProvider + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation providers > Functions for Providers +function uiautomationcoreapi.h UiaClientsAreListening +function uiautomationcoreapi.h UiaDisconnectAllProviders +function uiautomationcoreapi.h UiaDisconnectProvider +function uiautomationcoreapi.h UiaGetReservedMixedAttributeValue +function uiautomationcoreapi.h UiaGetReservedNotSupportedValue +function uiautomationcoreapi.h UiaHostProviderFromHwnd +function uiautomationcoreapi.h UiaIAccessibleFromProvider +function uiautomationcoreapi.h UiaProviderForNonClient +function uiautomationcoreapi.h UiaProviderFromIAccessible +function uiautomationcoreapi.h UiaRaiseAsyncContentLoadedEvent +function uiautomationcoreapi.h UiaRaiseAutomationEvent +function uiautomationcoreapi.h UiaRaiseAutomationPropertyChangedEvent +function uiautomationcoreapi.h UiaRaiseChangesEvent +function uiautomationcoreapi.h UiaRaiseNotificationEvent +function uiautomationcoreapi.h UiaRaiseStructureChangedEvent +function uiautomationcoreapi.h UiaRaiseTextEditTextChangedEvent +function uiautomationcoreapi.h UiaReturnRawElementProvider + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > UI Automation Element Interfaces for Clients +interface uiautomationclient.h IUIAutomation +interface uiautomationclient.h IUIAutomation2 +interface uiautomationclient.h IUIAutomation3 +interface uiautomationclient.h IUIAutomation4 +interface uiautomationclient.h IUIAutomation5 +interface uiautomationclient.h IUIAutomation6 +interface uiautomationclient.h IUIAutomationCacheRequest +interface uiautomationclient.h IUIAutomationElement +interface uiautomationclient.h IUIAutomationElement2 +interface uiautomationclient.h IUIAutomationElement3 +interface uiautomationclient.h IUIAutomationElement4 +interface uiautomationclient.h IUIAutomationElement5 +interface uiautomationclient.h IUIAutomationElement6 +interface uiautomationclient.h IUIAutomationElement7 +interface uiautomationclient.h IUIAutomationElement8 +interface uiautomationclient.h IUIAutomationElement9 +interface uiautomationclient.h IUIAutomationElementArray +interface uiautomationclient.h IUIAutomationEventHandlerGroup +interface uiautomationclient.h IUIAutomationTreeWalker +interface uiautomationcore.h IUIAutomationRegistrar + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Property Condition Interfaces for Clients +interface uiautomationclient.h IUIAutomationAndCondition +interface uiautomationclient.h IUIAutomationBoolCondition +interface uiautomationclient.h IUIAutomationCondition +interface uiautomationclient.h IUIAutomationNotCondition +interface uiautomationclient.h IUIAutomationOrCondition +interface uiautomationclient.h IUIAutomationPropertyCondition + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Control Pattern Interfaces for Clients +interface uiautomationclient.h IUIAutomationAnnotationPattern +interface uiautomationclient.h IUIAutomationCustomNavigationPattern +interface uiautomationclient.h IUIAutomationDockPattern +interface uiautomationclient.h IUIAutomationDragPattern +interface uiautomationclient.h IUIAutomationDropTargetPattern +interface uiautomationclient.h IUIAutomationExpandCollapsePattern +interface uiautomationclient.h IUIAutomationGridItemPattern +interface uiautomationclient.h IUIAutomationGridPattern +interface uiautomationclient.h IUIAutomationInvokePattern +interface uiautomationclient.h IUIAutomationItemContainerPattern +interface uiautomationclient.h IUIAutomationLegacyIAccessiblePattern +interface uiautomationclient.h IUIAutomationMultipleViewPattern +interface uiautomationclient.h IUIAutomationObjectModelPattern +interface uiautomationclient.h IUIAutomationRangeValuePattern +interface uiautomationclient.h IUIAutomationScrollItemPattern +interface uiautomationclient.h IUIAutomationScrollPattern +interface uiautomationclient.h IUIAutomationSelectionItemPattern +interface uiautomationclient.h IUIAutomationSelectionPattern +interface uiautomationclient.h IUIAutomationSelectionPattern2 +interface uiautomationclient.h IUIAutomationSpreadsheetItemPattern +interface uiautomationclient.h IUIAutomationSpreadsheetPattern +interface uiautomationclient.h IUIAutomationStylesPattern +interface uiautomationclient.h IUIAutomationSynchronizedInputPattern +interface uiautomationclient.h IUIAutomationTableItemPattern +interface uiautomationclient.h IUIAutomationTablePattern +interface uiautomationclient.h IUIAutomationTextChildPattern +interface uiautomationclient.h IUIAutomationTextEditPattern +interface uiautomationclient.h IUIAutomationTextPattern +interface uiautomationclient.h IUIAutomationTextPattern2 +interface uiautomationclient.h IUIAutomationTextRange +interface uiautomationclient.h IUIAutomationTextRange2 +interface uiautomationclient.h IUIAutomationTextRange3 +interface uiautomationclient.h IUIAutomationTextRangeArray +interface uiautomationclient.h IUIAutomationTogglePattern +interface uiautomationclient.h IUIAutomationTransformPattern +interface uiautomationclient.h IUIAutomationTransformPattern2 +interface uiautomationclient.h IUIAutomationValuePattern +interface uiautomationclient.h IUIAutomationVirtualizedItemPattern +interface uiautomationclient.h IUIAutomationWindowPattern +interface uiautomationcore.h IUIAutomationPatternHandler +interface uiautomationcore.h IUIAutomationPatternInstance + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Event Handling Interfaces for Clients +interface uiautomationclient.h IUIAutomationActiveTextPositionChangedEventHandler +interface uiautomationclient.h IUIAutomationChangesEventHandler +interface uiautomationclient.h IUIAutomationEventHandler +interface uiautomationclient.h IUIAutomationFocusChangedEventHandler +interface uiautomationclient.h IUIAutomationNotificationEventHandler +interface uiautomationclient.h IUIAutomationPropertyChangedEventHandler +interface uiautomationclient.h IUIAutomationStructureChangedEventHandler +interface uiautomationclient.h IUIAutomationTextEditTextChangedEventHandler + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Proxy Factory Interfaces for Clients +interface uiautomationclient.h IUIAutomationProxyFactory +interface uiautomationclient.h IUIAutomationProxyFactoryEntry +interface uiautomationclient.h IUIAutomationProxyFactoryMapping + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Functions for Clients +function uiautomationcoreapi.h UiaHasServerSideProvider + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Deprecated Control Pattern Functions +function uiautomationcoreapi.h DockPattern_SetDockPosition +function uiautomationcoreapi.h ExpandCollapsePattern_Collapse +function uiautomationcoreapi.h ExpandCollapsePattern_Expand +function uiautomationcoreapi.h GridPattern_GetItem +function uiautomationcoreapi.h InvokePattern_Invoke +function uiautomationcoreapi.h ItemContainerPattern_FindItemByProperty +function uiautomationcoreapi.h LegacyIAccessiblePattern_DoDefaultAction +function uiautomationcoreapi.h LegacyIAccessiblePattern_GetIAccessible +function uiautomationcoreapi.h LegacyIAccessiblePattern_Select +function uiautomationcoreapi.h LegacyIAccessiblePattern_SetValue +function uiautomationcoreapi.h MultipleViewPattern_GetViewName +function uiautomationcoreapi.h MultipleViewPattern_SetCurrentView +function uiautomationcoreapi.h RangeValuePattern_SetValue +function uiautomationcoreapi.h ScrollItemPattern_ScrollIntoView +function uiautomationcoreapi.h ScrollPattern_Scroll +function uiautomationcoreapi.h ScrollPattern_SetScrollPercent +function uiautomationcoreapi.h SelectionItemPattern_AddToSelection +function uiautomationcoreapi.h SelectionItemPattern_RemoveFromSelection +function uiautomationcoreapi.h SelectionItemPattern_Select +function uiautomationcoreapi.h SynchronizedInputPattern_Cancel +function uiautomationcoreapi.h SynchronizedInputPattern_StartListening +function uiautomationcoreapi.h TextPattern_get_DocumentRange +function uiautomationcoreapi.h TextPattern_get_SupportedTextSelection +function uiautomationcoreapi.h TextPattern_GetSelection +function uiautomationcoreapi.h TextPattern_GetVisibleRanges +function uiautomationcoreapi.h TextPattern_RangeFromChild +function uiautomationcoreapi.h TextPattern_RangeFromPoint +function uiautomationcoreapi.h TextRange_AddToSelection +function uiautomationcoreapi.h TextRange_Clone +function uiautomationcoreapi.h TextRange_Compare +function uiautomationcoreapi.h TextRange_CompareEndpoints +function uiautomationcoreapi.h TextRange_ExpandToEnclosingUnit +function uiautomationcoreapi.h TextRange_FindAttribute +function uiautomationcoreapi.h TextRange_FindText +function uiautomationcoreapi.h TextRange_GetAttributeValue +function uiautomationcoreapi.h TextRange_GetBoundingRectangles +function uiautomationcoreapi.h TextRange_GetChildren +function uiautomationcoreapi.h TextRange_GetEnclosingElement +function uiautomationcoreapi.h TextRange_GetText +function uiautomationcoreapi.h TextRange_Move +function uiautomationcoreapi.h TextRange_MoveEndpointByRange +function uiautomationcoreapi.h TextRange_MoveEndpointByUnit +function uiautomationcoreapi.h TextRange_RemoveFromSelection +function uiautomationcoreapi.h TextRange_ScrollIntoView +function uiautomationcoreapi.h TextRange_Select +function uiautomationcoreapi.h TogglePattern_Toggle +function uiautomationcoreapi.h TransformPattern_Move +function uiautomationcoreapi.h TransformPattern_Resize +function uiautomationcoreapi.h TransformPattern_Rotate +function uiautomationcoreapi.h ValuePattern_SetValue +function uiautomationcoreapi.h VirtualizedItemPattern_Realize +function uiautomationcoreapi.h WindowPattern_Close +function uiautomationcoreapi.h WindowPattern_SetWindowVisualState +function uiautomationcoreapi.h WindowPattern_WaitForInputIdle + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Clients > Deprecated Node Functions +callback uiautomationcoreapi.h UiaEventCallback +callback uiautomationcoreapi.h UiaProviderCallback +function uiautomationcoreapi.h UiaAddEvent +function uiautomationcoreapi.h UiaEventAddWindow +function uiautomationcoreapi.h UiaEventRemoveWindow +function uiautomationcoreapi.h UiaFind +function uiautomationcoreapi.h UiaGetErrorDescription +function uiautomationcoreapi.h UiaGetPatternProvider +function uiautomationcoreapi.h UiaGetPropertyValue +function uiautomationcoreapi.h UiaGetRootNode +function uiautomationcoreapi.h UiaGetRuntimeId +function uiautomationcoreapi.h UiaGetUpdatedCache +function uiautomationcoreapi.h UiaHPatternObjectFromVariant +function uiautomationcoreapi.h UiaHTextRangeFromVariant +function uiautomationcoreapi.h UiaHUiaNodeFromVariant +function uiautomationcoreapi.h UiaLookupId +function uiautomationcoreapi.h UiaNavigate +function uiautomationcoreapi.h UiaNodeFromFocus +function uiautomationcoreapi.h UiaNodeFromHandle +function uiautomationcoreapi.h UiaNodeFromPoint +function uiautomationcoreapi.h UiaNodeFromProvider +function uiautomationcoreapi.h UiaNodeRelease +function uiautomationcoreapi.h UiaPatternRelease +function uiautomationcoreapi.h UiaRectIsEmpty +function uiautomationcoreapi.h UiaRectSetEmpty +function uiautomationcoreapi.h UiaRegisterProviderCallback +function uiautomationcoreapi.h UiaRemoveEvent +function uiautomationcoreapi.h UiaSetFocus +function uiautomationcoreapi.h UiaTextRangeRelease + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Structures +struct uiautomationclient.h ExtendedProperty +struct uiautomationcore.h UiaChangeInfo +struct uiautomationcore.h UiaPoint +struct uiautomationcore.h UiaRect +struct uiautomationcore.h UIAutomationEventInfo +struct uiautomationcore.h UIAutomationMethodInfo +struct uiautomationcore.h UIAutomationParameter +struct uiautomationcore.h UIAutomationPatternInfo +struct uiautomationcore.h UIAutomationPropertyInfo + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > Deprecated Structures +struct uiautomationcoreapi.h UiaAndOrCondition +struct uiautomationcoreapi.h UiaAsyncContentLoadedEventArgs +struct uiautomationcoreapi.h UiaCacheRequest +struct uiautomationcoreapi.h UiaCondition +struct uiautomationcoreapi.h UiaEventArgs +struct uiautomationcoreapi.h UiaFindParams +struct uiautomationcoreapi.h UiaNotCondition +struct uiautomationcoreapi.h UiaPropertyChangedEventArgs +struct uiautomationcoreapi.h UiaPropertyCondition +struct uiautomationcoreapi.h UiaStructureChangedEventArgs +struct uiautomationcoreapi.h UiaWindowClosedEventArgs + +# Desktop App Technologies > Windows Accessibility API reference > UI Automation > Reference > UI Automation Enumerations +enum uiautomationclient.h AutomationElementMode +enum uiautomationclient.h CoalesceEventsOptions +enum uiautomationclient.h ConnectionRecoveryBehaviorOptions +enum uiautomationclient.h PropertyConditionFlags +enum uiautomationclient.h TreeScope +enum uiautomationclient.h TreeTraversalOptions +enum uiautomationcore.h ActiveEnd +enum uiautomationcore.h AnimationStyle +enum uiautomationcore.h BulletStyle +enum uiautomationcore.h CapStyle +enum uiautomationcore.h CaretBidiMode +enum uiautomationcore.h CaretPosition +enum uiautomationcore.h DockPosition +enum uiautomationcore.h ExpandCollapseState +enum uiautomationcore.h FillType +enum uiautomationcore.h FlowDirections +enum uiautomationcore.h LiveSetting +enum uiautomationcore.h NavigateDirection +enum uiautomationcore.h NotificationKind +enum uiautomationcore.h NotificationProcessing +enum uiautomationcore.h OrientationType +enum uiautomationcore.h OutlineStyles +enum uiautomationcore.h ProviderOptions +enum uiautomationcore.h RowOrColumnMajor +enum uiautomationcore.h SayAsInterpretAs +enum uiautomationcore.h ScrollAmount +enum uiautomationcore.h StructureChangeType +enum uiautomationcore.h SupportedTextSelection +enum uiautomationcore.h SynchronizedInputType +enum uiautomationcore.h TextDecorationLineStyle +enum uiautomationcore.h TextEditChangeType +enum uiautomationcore.h TextPatternRangeEndpoint +enum uiautomationcore.h TextUnit +enum uiautomationcore.h ToggleState +enum uiautomationcore.h UIAutomationType +enum uiautomationcore.h VisualEffects +enum uiautomationcore.h WindowInteractionState +enum uiautomationcore.h WindowVisualState +enum uiautomationcore.h ZoomUnit +enum uiautomationcoreapi.h AsyncContentLoadedState +enum uiautomationcoreapi.h AutomationIdentifierType +enum uiautomationcoreapi.h ConditionType +enum uiautomationcoreapi.h EventArgsType +enum uiautomationcoreapi.h NormalizeState +enum uiautomationcoreapi.h ProviderType + +# Desktop App Technologies > Windows Accessibility API reference > Microsoft Active Accessibility > C/C++ Reference > Active Accessibility User Interface Services > Interfaces +interface oleacc.h IAccessible +interface oleacc.h IAccessibleHandler +interface oleacc.h IAccessibleWindowlessSite +interface oleacc.h IAccIdentity +interface oleacc.h IAccPropServer +interface oleacc.h IAccPropServices + +# Desktop App Technologies > Windows Accessibility API reference > Microsoft Active Accessibility > C/C++ Reference > Active Accessibility User Interface Services > Client Functions +callback winuser.h WinEventProc Callback Function =wineventproc +function oleacc.h AccessibleChildren +function oleacc.h AccessibleObjectFromEvent +function oleacc.h AccessibleObjectFromPoint +function oleacc.h AccessibleObjectFromWindow +function oleacc.h GetOleaccVersionInfo +function oleacc.h GetRoleText =getroletexta +function oleacc.h GetStateText =getstatetexta +function oleacc.h WindowFromAccessibleObject +function winuser.h SetWinEventHook +function winuser.h UnhookWinEvent + +# Desktop App Technologies > Windows Accessibility API reference > Microsoft Active Accessibility > C/C++ Reference > Active Accessibility User Interface Services > Server Functions +function oleacc.h AccNotifyTouchInteraction +function oleacc.h AccSetRunningUtilityState +function oleacc.h CreateStdAccessibleObject +function oleacc.h CreateStdAccessibleProxy =createstdaccessibleproxya +function oleacc.h LresultFromObject +function oleacc.h ObjectFromLresult +function winuser.h IsWinEventHookInstalled +function winuser.h NotifyWinEvent + +# Desktop App Technologies > Windows Accessibility API reference > Microsoft Active Accessibility > C/C++ Reference > Active Accessibility User Interface Services > Structures and Data Types +struct oleacc.h MSAAMENUINFO + +# Desktop App Technologies > Windows Accessibility API reference > Microsoft Active Accessibility > C/C++ Reference > Active Accessibility Text Services +interface msaatext.h IAccClientDocMgr +interface msaatext.h IAccDictionary +interface msaatext.h IAccServerDocMgr +interface msaatext.h ICoCreatedLocally +interface msaatext.h ICoCreateLocally +interface msaatext.h IVersionInfo + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > Extended Linguistic Services > Extended Linguistic Services Reference > Extended Linguistic Services Functions +callback elscore.h PFN_MAPPINGCALLBACKPROC +function elscore.h MappingDoAction +function elscore.h MappingFreePropertyBag +function elscore.h MappingFreeServices +function elscore.h MappingGetServices +function elscore.h MappingRecognizeText + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > Extended Linguistic Services > Extended Linguistic Services Reference > Extended Linguistic Services Structures +struct elscore.h MAPPING_DATA_RANGE +struct elscore.h MAPPING_ENUM_OPTIONS +struct elscore.h MAPPING_OPTIONS +struct elscore.h MAPPING_PROPERTY_BAG +struct elscore.h MAPPING_SERVICE_INFO + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > National Language Support > National Language Support Reference > National Language Support Functions +callback winnls.h EnumLanguageGroupLocalesProc =langgrouplocale_enumproca +callback winnls.h EnumLocalesProcEx =locale_enumprocex +callback winnls.h Geo_EnumNameProc +function datetimeapi.h GetDateFormat =getdateformata +function datetimeapi.h GetDateFormatEx +function datetimeapi.h GetTimeFormat =gettimeformata +function datetimeapi.h GetTimeFormatEx +function libloaderapi.h FindStringOrdinal +function stringapiset.h CompareString =comparestringw +function stringapiset.h CompareStringEx +function stringapiset.h CompareStringOrdinal +function stringapiset.h FoldString =foldstringw +function stringapiset.h GetStringTypeW +function winnls.h ConvertDefaultLocale +function winnls.h EnumCalendarInfo =enumcalendarinfoa +function winnls.h EnumCalendarInfoEx =enumcalendarinfoexa +function winnls.h EnumCalendarInfoExEx +function winnls.h EnumDateFormats =enumdateformatsa +function winnls.h EnumDateFormatsEx =enumdateformatsexa +function winnls.h EnumDateFormatsExEx +function winnls.h EnumLanguageGroupLocales =enumlanguagegrouplocalesa +function winnls.h EnumSystemCodePages =enumsystemcodepagesa +function winnls.h EnumSystemGeoID +function winnls.h EnumSystemGeoNames +function winnls.h EnumSystemLanguageGroups =enumsystemlanguagegroupsa +function winnls.h EnumSystemLocales =enumsystemlocalesa +function winnls.h EnumSystemLocalesEx +function winnls.h EnumTimeFormats =enumtimeformatsa +function winnls.h EnumTimeFormatsEx +function winnls.h FindNLSString +function winnls.h FindNLSStringEx +function winnls.h GetACP +function winnls.h GetCalendarInfo =getcalendarinfoa +function winnls.h GetCalendarInfoEx +function winnls.h GetCPInfo +function winnls.h GetCPInfoEx =getcpinfoexa +function winnls.h GetCurrencyFormat =getcurrencyformata +function winnls.h GetCurrencyFormatEx +function winnls.h GetDurationFormat +function winnls.h GetDurationFormatEx +function winnls.h GetGeoInfo =getgeoinfoa +function winnls.h GetGeoInfoEx +function winnls.h GetLocaleInfo =getlocaleinfoa +function winnls.h GetLocaleInfoEx +function winnls.h GetNLSVersion +function winnls.h GetNLSVersionEx +function winnls.h GetNumberFormat =getnumberformata +function winnls.h GetNumberFormatEx +function winnls.h GetOEMCP +function winnls.h GetStringScripts +function winnls.h GetStringTypeA +function winnls.h GetSystemDefaultLangID +function winnls.h GetSystemDefaultLCID +function winnls.h GetSystemDefaultLocaleName +function winnls.h GetThreadLocale +function winnls.h GetUserDefaultGeoName +function winnls.h GetUserDefaultLangID +function winnls.h GetUserDefaultLCID +function winnls.h GetUserDefaultLocaleName +function winnls.h GetUserGeoID +function winnls.h IdnToAscii +function winnls.h IdnToNameprepUnicode +function winnls.h IdnToUnicode +function winnls.h IsNLSDefinedString +function winnls.h IsNormalizedString +function winnls.h IsValidCodePage +function winnls.h IsValidLanguageGroup +function winnls.h IsValidLocale +function winnls.h IsValidLocaleName +function winnls.h IsValidNLSVersion +function winnls.h LCIDToLocaleName +function winnls.h LCMapString =lcmapstringa +function winnls.h LCMapStringEx +function winnls.h LoadStringByReference +function winnls.h LocaleNameToLCID +function winnls.h NormalizeString +function winnls.h NotifyUILanguageChange +function winnls.h ResolveLocaleName +function winnls.h SetCalendarInfo =setcalendarinfoa +function winnls.h SetLocaleInfo =setlocaleinfoa +function winnls.h SetThreadLocale +function winnls.h SetUserGeoID +function winnls.h SetUserGeoName +function winnls.h VerifyScripts + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > National Language Support > National Language Support Reference > National Language Support Macros +function winnls.h IS_HIGH_SURROGATE +function winnls.h IS_LOW_SURROGATE +function winnls.h IS_SURROGATE_PAIR +function winnt.h LANGIDFROMLCID +function winnt.h MAKELANGID +function winnt.h MAKELCID +function winnt.h MAKESORTLCID +function winnt.h PRIMARYLANGID +function winnt.h SORTIDFROMLCID +function winnt.h SORTVERSIONFROMLCID +function winnt.h SUBLANGID + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > National Language Support > National Language Support Reference > National Language Support Structures +struct winnls.h CPINFO +struct winnls.h CPINFOEX =cpinfoexa +struct winnls.h CURRENCYFMT =currencyfmta +struct winnls.h NLSVERSIONINFO =nlsversioninfo~r1 +struct winnls.h NLSVERSIONINFOEX +struct winnls.h NUMBERFMT =numberfmta + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > National Language Support > National Language Support Reference > National Language Support Enumeration Types +enum winnls.h NORM_FORM +enum winnls.h SYSGEOCLASS +enum winnls.h SYSGEOTYPE +enum winnls.h SYSNLS_FUNCTION + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > Spell Checking API > Spell Checking API Reference > Spell Checking Interfaces +interface spellcheck.h IEnumSpellingError +interface spellcheck.h IOptionDescription +interface spellcheck.h ISpellChecker +interface spellcheck.h ISpellChecker2 +interface spellcheck.h ISpellCheckerChangedEventHandler +interface spellcheck.h ISpellCheckerFactory +interface spellcheck.h ISpellingError +interface spellcheck.h IUserDictionariesRegistrar +interface spellcheckprovider.h IComprehensiveSpellCheckProvider +interface spellcheckprovider.h ISpellCheckProvider +interface spellcheckprovider.h ISpellCheckProviderFactory + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Globalization Services > Spell Checking API > Spell Checking API Reference > Spell Checking Enumerations +enum spellcheck.h CORRECTIVE_ACTION +enum spellcheck.h WORDLIST_TYPE + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Input Method Manager > Input Method Manager Reference > Input Method Manager Enumerations +enum msime.h IMEUCT + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Input Method Manager > Input Method Manager Reference > Input Method Manager Functions +callback imm.h IMCENUMPROC +callback imm.h REGISTERWORDENUMPROC =registerwordenumproca +function imm.h ImmAssociateContext +function imm.h ImmAssociateContextEx +function imm.h ImmConfigureIME =immconfigureimea +function imm.h ImmCreateContext +function imm.h ImmDestroyContext +function imm.h ImmDisableIME +function imm.h ImmDisableLegacyIME +function imm.h ImmDisableTextFrameService +function imm.h ImmEnumInputContext +function imm.h ImmEnumRegisterWord =immenumregisterworda +function imm.h ImmEscape =immescapea +function imm.h ImmGetCandidateList =immgetcandidatelista +function imm.h ImmGetCandidateListCount =immgetcandidatelistcounta +function imm.h ImmGetCandidateWindow +function imm.h ImmGetCompositionFont =immgetcompositionfonta +function imm.h ImmGetCompositionString =immgetcompositionstringa +function imm.h ImmGetCompositionWindow +function imm.h ImmGetContext +function imm.h ImmGetConversionList =immgetconversionlista +function imm.h ImmGetConversionStatus +function imm.h ImmGetDefaultIMEWnd +function imm.h ImmGetDescription =immgetdescriptiona +function imm.h ImmGetGuideLine =immgetguidelinea +function imm.h ImmGetIMEFileName =immgetimefilenamea +function imm.h ImmGetImeMenuItems =immgetimemenuitemsa +function imm.h ImmGetOpenStatus +function imm.h ImmGetProperty +function imm.h ImmGetRegisterWordStyle =immgetregisterwordstylea +function imm.h ImmGetStatusWindowPos +function imm.h ImmGetVirtualKey +function imm.h ImmInstallIME =imminstallimea +function imm.h ImmIsIME +function imm.h ImmIsUIMessage =immisuimessagea +function imm.h ImmNotifyIME +function imm.h ImmRegisterWord =immregisterworda +function imm.h ImmReleaseContext +function imm.h ImmSetCandidateWindow +function imm.h ImmSetCompositionFont =immsetcompositionfonta +function imm.h ImmSetCompositionString =immsetcompositionstringa +function imm.h ImmSetCompositionWindow +function imm.h ImmSetConversionStatus +function imm.h ImmSetOpenStatus +function imm.h ImmSetStatusWindowPos +function imm.h ImmSimulateHotKey +function imm.h ImmUnregisterWord =immunregisterworda +function immdev.h ImmRequestMessage =immrequestmessagea +function msime.h CreateIFECommonInstance +function msime.h CreateIFEDictionaryInstance +function msime.h CreateIFELanguageInstance + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Input Method Manager > Input Method Manager Reference > Input Method Manager Interfaces +interface imepad.h IImePad +interface imepad.h IImePadApplet +interface imepad.h IImeSpecifyApplets +interface msime.h IFECommon +interface msime.h IFEDictionary +interface msime.h IFELanguage +interface msimeapi.h IImePlugInDictDictionaryList + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Input Method Manager > Input Method Manager Reference > Input Method Manager Structures +struct imepad.h APPLETIDLIST +struct imepad.h IMEAPPLETCFG +struct imepad.h IMEAPPLETUI +struct imepad.h IMECOMPOSITIONSTRINGINFO +struct imm.h CANDIDATEFORM +struct imm.h CANDIDATELIST +struct imm.h COMPOSITIONFORM +struct imm.h IMECHARPOSITION +struct imm.h IMEMENUITEMINFO =imemenuiteminfoa +struct imm.h RECONVERTSTRING +struct imm.h REGISTERWORD =registerworda +struct imm.h STYLEBUF =stylebufa +struct msime.h IMEDLG +struct msime.h IMESHF +struct msime.h IMEWRD +struct msime.h POSTBL + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > International Fonts and Text Display > Uniscribe > Uniscribe Reference > Uniscribe Functions +function usp10.h ScriptApplyDigitSubstitution +function usp10.h ScriptApplyLogicalWidth +function usp10.h ScriptBreak +function usp10.h ScriptCacheGetHeight +function usp10.h ScriptCPtoX +function usp10.h ScriptFreeCache +function usp10.h ScriptGetCMap +function usp10.h ScriptGetFontAlternateGlyphs +function usp10.h ScriptGetFontFeatureTags +function usp10.h ScriptGetFontLanguageTags +function usp10.h ScriptGetFontProperties +function usp10.h ScriptGetFontScriptTags +function usp10.h ScriptGetGlyphABCWidth +function usp10.h ScriptGetLogicalWidths +function usp10.h ScriptGetProperties +function usp10.h ScriptIsComplex +function usp10.h ScriptItemize +function usp10.h ScriptItemizeOpenType +function usp10.h ScriptJustify +function usp10.h ScriptLayout +function usp10.h ScriptPlace +function usp10.h ScriptPlaceOpenType +function usp10.h ScriptPositionSingleGlyph +function usp10.h ScriptRecordDigitSubstitution +function usp10.h ScriptShape +function usp10.h ScriptShapeOpenType +function usp10.h ScriptString_pcOutChars +function usp10.h ScriptString_pLogAttr +function usp10.h ScriptString_pSize +function usp10.h ScriptStringAnalyse +function usp10.h ScriptStringCPtoX +function usp10.h ScriptStringFree +function usp10.h ScriptStringGetLogicalWidths +function usp10.h ScriptStringGetOrder +function usp10.h ScriptStringOut +function usp10.h ScriptStringValidate +function usp10.h ScriptStringXtoCP +function usp10.h ScriptSubstituteSingleGlyph +function usp10.h ScriptTextOut +function usp10.h ScriptXtoCP + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > International Fonts and Text Display > Uniscribe > Uniscribe Reference > Uniscribe Structures +struct usp10.h GOFFSET +struct usp10.h OPENTYPE_FEATURE_RECORD +struct usp10.h SCRIPT_ANALYSIS +struct usp10.h SCRIPT_CHARPROP +struct usp10.h SCRIPT_CONTROL +struct usp10.h SCRIPT_DIGITSUBSTITUTE +struct usp10.h SCRIPT_FONTPROPERTIES +struct usp10.h SCRIPT_GLYPHPROP +struct usp10.h SCRIPT_ITEM +struct usp10.h SCRIPT_LOGATTR +struct usp10.h SCRIPT_PROPERTIES +struct usp10.h SCRIPT_STATE +struct usp10.h SCRIPT_TABDEF +struct usp10.h SCRIPT_VISATTR +struct usp10.h TEXTRANGE_PROPERTIES + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > International Fonts and Text Display > Uniscribe > Uniscribe Reference > Uniscribe Enumeration Types +enum usp10.h SCRIPT_JUSTIFY + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Multilingual User Interface > Multilingual User Interface Reference > Multilingual User Interface Functions +callback winnls.h EnumUILanguagesProc =uilanguage_enumproca +function muiload.h FreeMUILibrary +function muiload.h GetUILanguageFallbackList +function muiload.h LoadMUILibrary =loadmuilibrarya +function winnls.h EnumUILanguages =enumuilanguagesa +function winnls.h GetFileMUIInfo +function winnls.h GetFileMUIPath +function winnls.h GetProcessPreferredUILanguages +function winnls.h GetSystemDefaultUILanguage +function winnls.h GetSystemPreferredUILanguages +function winnls.h GetThreadPreferredUILanguages +function winnls.h GetThreadUILanguage +function winnls.h GetUILanguageInfo +function winnls.h GetUserDefaultUILanguage +function winnls.h GetUserPreferredUILanguages +function winnls.h SetProcessPreferredUILanguages +function winnls.h SetThreadPreferredUILanguages +function winnls.h SetThreadUILanguage + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Multilingual User Interface > Multilingual User Interface Reference > Multilingual User Interface Macros +function winnls.h FILEMUIINFO_GET_CULTURE +function winnls.h FILEMUIINFO_GET_MAIN_TYPEID +function winnls.h FILEMUIINFO_GET_MAIN_TYPEIDS +function winnls.h FILEMUIINFO_GET_MAIN_TYPENAMES +function winnls.h FILEMUIINFO_GET_MUI_TYPEID +function winnls.h FILEMUIINFO_GET_MUI_TYPEIDS +function winnls.h FILEMUIINFO_GET_MUI_TYPENAMES + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Multilingual User Interface > Multilingual User Interface Reference > Multilingual User Interface Structures +struct winnls.h FILEMUIINFO + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Unicode and Character Sets > Unicode and Character Set Reference > Unicode and Character Set Functions +function gb18030.h NlsDllCodePageTranslation +function stringapiset.h MultiByteToWideChar +function stringapiset.h WideCharToMultiByte +function winbase.h IsTextUnicode +function wingdi.h GetTextCharset +function wingdi.h GetTextCharsetInfo +function wingdi.h TranslateCharsetInfo +function winnls.h IsDBCSLeadByte +function winnls.h IsDBCSLeadByteEx + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Unicode and Character Sets > Unicode and Character Set Reference > Unicode and Character Set Macros +function winnt.h TEXT + +# Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > Unicode and Character Sets > Unicode and Character Set Reference > Unicode and Character Set Structures +struct wingdi.h CHARSETINFO +struct wingdi.h FONTSIGNATURE +struct wingdi.h LOCALESIGNATURE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Direct Manipulation > Direct Manipulation Reference > Direct Manipulation Enumerations +enum directmanipulation.h DIRECTMANIPULATION_AUTOSCROLL_CONFIGURATION +enum directmanipulation.h DIRECTMANIPULATION_CONFIGURATION +enum directmanipulation.h DIRECTMANIPULATION_DRAG_DROP_CONFIGURATION +enum directmanipulation.h DIRECTMANIPULATION_DRAG_DROP_STATUS +enum directmanipulation.h DIRECTMANIPULATION_GESTURE_CONFIGURATION +enum directmanipulation.h DIRECTMANIPULATION_HITTEST_TYPE +enum directmanipulation.h DIRECTMANIPULATION_HORIZONTALALIGNMENT +enum directmanipulation.h DIRECTMANIPULATION_INPUT_MODE +enum directmanipulation.h DIRECTMANIPULATION_INTERACTION_TYPE +enum directmanipulation.h DIRECTMANIPULATION_MOTION_TYPES +enum directmanipulation.h DIRECTMANIPULATION_SNAPPOINT_COORDINATE +enum directmanipulation.h DIRECTMANIPULATION_SNAPPOINT_TYPE +enum directmanipulation.h DIRECTMANIPULATION_STATUS +enum directmanipulation.h DIRECTMANIPULATION_VERTICALALIGNMENT +enum directmanipulation.h DIRECTMANIPULATION_VIEWPORT_OPTIONS + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Direct Manipulation > Direct Manipulation Reference > Direct Manipulation Interfaces +interface directmanipulation.h IDirectManipulationAutoScrollBehavior +interface directmanipulation.h IDirectManipulationCompositor +interface directmanipulation.h IDirectManipulationCompositor2 +interface directmanipulation.h IDirectManipulationContent +interface directmanipulation.h IDirectManipulationDeferContactService +interface directmanipulation.h IDirectManipulationDragDropBehavior +interface directmanipulation.h IDirectManipulationDragDropEventHandler +interface directmanipulation.h IDirectManipulationFrameInfoProvider +interface directmanipulation.h IDirectManipulationInteractionEventHandler +interface directmanipulation.h IDirectManipulationManager +interface directmanipulation.h IDirectManipulationManager2 +interface directmanipulation.h IDirectManipulationManager3 +interface directmanipulation.h IDirectManipulationPrimaryContent +interface directmanipulation.h IDirectManipulationUpdateHandler +interface directmanipulation.h IDirectManipulationUpdateManager +interface directmanipulation.h IDirectManipulationViewport +interface directmanipulation.h IDirectManipulationViewport2 +interface directmanipulation.h IDirectManipulationViewportEventHandler + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Ink input > Ink presenter > Ink presenter classes > InkDesktopHost +function inkpresenterdesktop.h CreateAndInitializeInkPresenter method =iinkdesktophost-createandinitializeinkpresenter +function inkpresenterdesktop.h CreateInkPresenter method =iinkdesktophost-createinkpresenter +function inkpresenterdesktop.h QueueWorkItem method =iinkdesktophost-queueworkitem + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Ink input > Ink presenter > Ink presenter interfaces +interface inkpresenterdesktop.h IInkCommitRequestHandler +interface inkpresenterdesktop.h IInkDesktopHost +interface inkpresenterdesktop.h IInkHostWorkItem +interface inkpresenterdesktop.h IInkPresenterDesktop + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Ink input > Ink renderer > Ink renderer classes > InkD2DRenderer +function inkrenderer.h Draw method =iinkd2drenderer-draw + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Ink input > Ink renderer > Ink renderer interfaces +interface inkrenderer.h IInkD2DRenderer + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Input Feedback Configuration > Input Feedback Configuration Reference > Enumerations +enum winuser.h FEEDBACK_TYPE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Input Feedback Configuration > Input Feedback Configuration Reference > Functions +function winuser.h GetWindowFeedbackSetting +function winuser.h SetWindowFeedbackSetting + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Input Source Identification > Input Source Identification Reference > Enumerations +enum winuser.h INPUT_MESSAGE_DEVICE_TYPE +enum winuser.h INPUT_MESSAGE_ORIGIN_ID + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Input Source Identification > Input Source Identification Reference > Functions +function winuser.h GetCIMSSM +function winuser.h GetCurrentInputMessageSource + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Input Source Identification > Input Source Identification Reference +struct winuser.h INPUT_MESSAGE_SOURCE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Interaction Context > Interaction Context Reference > Interaction Context Enumerations +enum interactioncontext.h CROSS_SLIDE_FLAGS +enum interactioncontext.h CROSS_SLIDE_THRESHOLD +enum interactioncontext.h INERTIA_PARAMETER +enum interactioncontext.h INTERACTION_CONFIGURATION_FLAGS +enum interactioncontext.h INTERACTION_CONTEXT_PROPERTY +enum interactioncontext.h INTERACTION_FLAGS +enum interactioncontext.h INTERACTION_ID +enum interactioncontext.h INTERACTION_STATE +enum interactioncontext.h MANIPULATION_RAILS_STATE +enum interactioncontext.h MOUSE_WHEEL_PARAMETER + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Interaction Context > Interaction Context Reference > Interaction Context Functions +function interactioncontext.h AddPointerInteractionContext +function interactioncontext.h BufferPointerPacketsInteractionContext +function interactioncontext.h CreateInteractionContext +function interactioncontext.h DestroyInteractionContext +function interactioncontext.h GetCrossSlideParameterInteractionContext +function interactioncontext.h GetInertiaParameterInteractionContext +function interactioncontext.h GetInteractionConfigurationInteractionContext +function interactioncontext.h GetMouseWheelParameterInteractionContext +function interactioncontext.h GetPropertyInteractionContext +function interactioncontext.h GetStateInteractionContext +function interactioncontext.h ProcessBufferedPacketsInteractionContext +function interactioncontext.h ProcessInertiaInteractionContext +function interactioncontext.h ProcessPointerFramesInteractionContext +function interactioncontext.h RegisterOutputCallbackInteractionContext +function interactioncontext.h RemovePointerInteractionContext +function interactioncontext.h ResetInteractionContext +function interactioncontext.h SetCrossSlideParametersInteractionContext +function interactioncontext.h SetInertiaParameterInteractionContext +function interactioncontext.h SetInteractionConfigurationInteractionContext +function interactioncontext.h SetMouseWheelParameterInteractionContext +function interactioncontext.h SetPivotInteractionContext +function interactioncontext.h SetPropertyInteractionContext +function interactioncontext.h StopInteractionContext + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Interaction Context > Interaction Context Reference > Interaction Context Structures +struct interactioncontext.h CROSS_SLIDE_PARAMETER +struct interactioncontext.h INTERACTION_ARGUMENTS_CROSS_SLIDE +struct interactioncontext.h INTERACTION_ARGUMENTS_MANIPULATION +struct interactioncontext.h INTERACTION_ARGUMENTS_TAP +struct interactioncontext.h INTERACTION_CONTEXT_CONFIGURATION +struct interactioncontext.h INTERACTION_CONTEXT_OUTPUT +struct interactioncontext.h MANIPULATION_TRANSFORM +struct interactioncontext.h MANIPULATION_VELOCITY + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Interaction Context > Interaction Context Reference +callback interactioncontext.h INTERACTION_CONTEXT_OUTPUT_CALLBACK + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Device Input Stack > Pointer Device Input Stack Reference > Enumerations +enum winuser.h POINTER_DEVICE_CURSOR_TYPE +enum winuser.h POINTER_DEVICE_TYPE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Device Input Stack > Pointer Device Input Stack Reference > Functions +function winuser.h GetPointerDevice +function winuser.h GetPointerDeviceCursors +function winuser.h GetPointerDeviceProperties +function winuser.h GetPointerDeviceRects +function winuser.h GetPointerDevices +function winuser.h GetRawPointerDeviceData +function winuser.h RegisterPointerDeviceNotifications + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Device Input Stack > Pointer Device Input Stack Reference > Structures +struct winuser.h POINTER_DEVICE_CURSOR_INFO +struct winuser.h POINTER_DEVICE_INFO +struct winuser.h POINTER_DEVICE_PROPERTY + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Input Messages and Notifications > Pointer Input Message Reference > Enumerations +enum winuser.h POINTER_BUTTON_CHANGE_TYPE +enum winuser.h POINTER_INPUT_TYPE =tagpointer_input_type + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Input Messages and Notifications > Pointer Input Message Reference > Functions +function winuser.h EnableMouseInPointer +function winuser.h GetPointerCursorId +function winuser.h GetPointerFrameInfo +function winuser.h GetPointerFrameInfoHistory +function winuser.h GetPointerFramePenInfo +function winuser.h GetPointerFramePenInfoHistory +function winuser.h GetPointerFrameTouchInfo +function winuser.h GetPointerFrameTouchInfoHistory +function winuser.h GetPointerInfo +function winuser.h GetPointerInfoHistory +function winuser.h GetPointerInputTransform +function winuser.h GetPointerPenInfo +function winuser.h GetPointerPenInfoHistory +function winuser.h GetPointerTouchInfo +function winuser.h GetPointerTouchInfoHistory +function winuser.h GetPointerType +function winuser.h GetUnpredictedMessagePos +function winuser.h IsMouseInPointerEnabled +function winuser.h SkipPointerFrameMessages + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Input Messages and Notifications > Pointer Input Message Reference > Macros +function winuser.h GET_POINTERID_WPARAM +function winuser.h HAS_POINTER_CONFIDENCE_WPARAM +function winuser.h IS_POINTER_CANCELED_WPARAM +function winuser.h IS_POINTER_FIFTHBUTTON_WPARAM +function winuser.h IS_POINTER_FIRSTBUTTON_WPARAM +function winuser.h IS_POINTER_FLAG_SET_WPARAM +function winuser.h IS_POINTER_FOURTHBUTTON_WPARAM +function winuser.h IS_POINTER_INCONTACT_WPARAM +function winuser.h IS_POINTER_INRANGE_WPARAM +function winuser.h IS_POINTER_NEW_WPARAM +function winuser.h IS_POINTER_SECONDBUTTON_WPARAM +function winuser.h IS_POINTER_THIRDBUTTON_WPARAM + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Pointer Input Messages and Notifications > Pointer Input Message Reference > Structures +struct winuser.h INPUT_TRANSFORM +struct winuser.h POINTER_INFO +struct winuser.h POINTER_PEN_INFO +struct winuser.h POINTER_TOUCH_INFO +struct winuser.h POINTER_TYPE_INFO +struct winuser.h TOUCHPREDICTIONPARAMETERS + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Radial controller input > Radial controller interfaces +interface radialcontrollerinterop.h IRadialControllerConfigurationInterop +interface radialcontrollerinterop.h IRadialControllerInterop + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Text Services Framework > TSF Reference > TSF Interfaces +interface ctffunc.h IEnumTfCandidates +interface ctffunc.h IEnumTfLatticeElements +interface ctffunc.h ITfCandidateList +interface ctffunc.h ITfCandidateString +interface ctffunc.h ITfFnAdviseText +interface ctffunc.h ITfFnBalloon +interface ctffunc.h ITfFnConfigure +interface ctffunc.h ITfFnConfigureRegisterEudc +interface ctffunc.h ITfFnConfigureRegisterWord +interface ctffunc.h ITfFnGetLinguisticAlternates +interface ctffunc.h ITfFnGetPreferredTouchKeyboardLayout +interface ctffunc.h ITfFnGetSAPIObject +interface ctffunc.h ITfFnLangProfileUtil +interface ctffunc.h ITfFnLMInternal +interface ctffunc.h ITfFnLMProcessor +interface ctffunc.h ITfFnPlayBack +interface ctffunc.h ITfFnPropertyUIStatus +interface ctffunc.h ITfFnReconversion +interface ctffunc.h ITfFnSearchCandidateProvider +interface ctffunc.h ITfFnShowHelp +interface ctffunc.h ITfIntegratableCandidateListUIElement +interface ctffunc.h ITfLMLattice +interface ctffunc.h IUIManagerEventSink +interface ctfspui.h ITfSpeechUIServer +interface ctfutb.h IEnumTfLangBarItems +interface ctfutb.h ITfLangBarEventSink +interface ctfutb.h ITfLangBarItem +interface ctfutb.h ITfLangBarItemBalloon +interface ctfutb.h ITfLangBarItemBitmap +interface ctfutb.h ITfLangBarItemBitmapButton +interface ctfutb.h ITfLangBarItemButton +interface ctfutb.h ITfLangBarItemMgr +interface ctfutb.h ITfLangBarItemSink +interface ctfutb.h ITfLangBarMgr +interface ctfutb.h ITfMenu +interface ctfutb.h ITfSystemDeviceTypeLangBarItem +interface ctfutb.h ITfSystemLangBarItem +interface ctfutb.h ITfSystemLangBarItemSink +interface ctfutb.h ITfSystemLangBarItemText +interface inputscope.h ITfInputScope +interface inputscope.h ITfInputScope2 +interface msaatext.h ITfMSAAControl +interface msctf.h IEnumITfCompositionView +interface msctf.h IEnumTfContexts +interface msctf.h IEnumTfContextViews +interface msctf.h IEnumTfDisplayAttributeInfo +interface msctf.h IEnumTfDocumentMgrs +interface msctf.h IEnumTfFunctionProviders +interface msctf.h IEnumTfInputProcessorProfiles +interface msctf.h IEnumTfLanguageProfiles +interface msctf.h IEnumTfProperties +interface msctf.h IEnumTfPropertyValue +interface msctf.h IEnumTfRanges +interface msctf.h IEnumTfUIElements +interface msctf.h ITextStoreACPServices +interface msctf.h ITfActiveLanguageProfileNotifySink +interface msctf.h ITfCandidateListUIElement +interface msctf.h ITfCandidateListUIElementBehavior +interface msctf.h ITfCategoryMgr +interface msctf.h ITfCleanupContextDurationSink +interface msctf.h ITfCleanupContextSink +interface msctf.h ITfClientId +interface msctf.h ITfCompartment +interface msctf.h ITfCompartmentEventSink +interface msctf.h ITfCompartmentMgr +interface msctf.h ITfComposition +interface msctf.h ITfCompositionSink +interface msctf.h ITfCompositionView +interface msctf.h ITfConfigureSystemKeystrokeFeed +interface msctf.h ITfContext +interface msctf.h ITfContextComposition +interface msctf.h ITfContextKeyEventSink +interface msctf.h ITfContextOwner +interface msctf.h ITfContextOwnerCompositionServices +interface msctf.h ITfContextOwnerCompositionSink +interface msctf.h ITfContextOwnerServices +interface msctf.h ITfContextView +interface msctf.h ITfCreatePropertyStore +interface msctf.h ITfDisplayAttributeInfo +interface msctf.h ITfDisplayAttributeMgr +interface msctf.h ITfDisplayAttributeNotifySink +interface msctf.h ITfDisplayAttributeProvider +interface msctf.h ITfDocumentMgr +interface msctf.h ITfEditRecord +interface msctf.h ITfEditSession +interface msctf.h ITfEditTransactionSink +interface msctf.h ITfFunction +interface msctf.h ITfFunctionProvider +interface msctf.h ITfInputProcessorProfileActivationSink +interface msctf.h ITfInputProcessorProfileMgr +interface msctf.h ITfInputProcessorProfiles +interface msctf.h ITfInputProcessorProfilesEx +interface msctf.h ITfInputProcessorProfileSubstituteLayout +interface msctf.h ITfInsertAtSelection +interface msctf.h ITfKeyEventSink +interface msctf.h ITfKeystrokeMgr +interface msctf.h ITfKeyTraceEventSink +interface msctf.h ITfLanguageProfileNotifySink +interface msctf.h ITfMessagePump +interface msctf.h ITfMouseSink +interface msctf.h ITfMouseTracker +interface msctf.h ITfMouseTrackerACP +interface msctf.h ITfPersistentPropertyLoaderACP +interface msctf.h ITfPreservedKeyNotifySink +interface msctf.h ITfProperty +interface msctf.h ITfPropertyStore +interface msctf.h ITfQueryEmbedded +interface msctf.h ITfRange +interface msctf.h ITfRangeACP +interface msctf.h ITfRangeBackup +interface msctf.h ITfReadingInformationUIElement +interface msctf.h ITfReadOnlyProperty +interface msctf.h ITfReverseConversion +interface msctf.h ITfReverseConversionList +interface msctf.h ITfReverseConversionMgr +interface msctf.h ITfSource +interface msctf.h ITfSourceSingle +interface msctf.h ITfStatusSink +interface msctf.h ITfTextEditSink +interface msctf.h ITfTextInputProcessor +interface msctf.h ITfTextInputProcessorEx +interface msctf.h ITfTextLayoutSink +interface msctf.h ITfThreadFocusSink +interface msctf.h ITfThreadMgr +interface msctf.h ITfThreadMgr2 +interface msctf.h ITfThreadMgrEventSink +interface msctf.h ITfThreadMgrEx +interface msctf.h ITfToolTipUIElement +interface msctf.h ITfTransitoryExtensionSink +interface msctf.h ITfTransitoryExtensionUIElement +interface msctf.h ITfUIElement +interface msctf.h ITfUIElementMgr +interface msctf.h ITfUIElementSink +interface textstor.h IAnchor +interface textstor.h ITextStoreACP +interface textstor.h ITextStoreACP2 +interface textstor.h ITextStoreACPSink +interface textstor.h ITextStoreAnchor +interface textstor.h ITextStoreAnchorSink + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Text Services Framework > TSF Reference > TSF Functions +function inputscope.h SetInputScope +function inputscope.h SetInputScopes +function inputscope.h SetInputScopes2 +function inputscope.h SetInputScopeXML +function msctf.h TF_CreateCategoryMgr +function msctf.h TF_CreateDisplayAttributeMgr +function msctf.h TF_CreateInputProcessorProfiles +function msctf.h TF_CreateLangBarItemMgr +function msctf.h TF_CreateLangBarMgr +function msctf.h TF_CreateThreadMgr +function msctf.h TF_GetThreadMgr +function msctfmonitorapi.h InitLocalMsCtfMonitor +function msctfmonitorapi.h UninitLocalMsCtfMonitor + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Text Services Framework > TSF Reference > TSF Structures +struct ctffunc.h TF_LMLATTELEMENT +struct ctfutb.h TF_LANGBARITEMINFO +struct ctfutb.h TF_LBBALLOONINFO +struct msctf.h TF_DA_COLOR +struct msctf.h TF_DISPLAYATTRIBUTE +struct msctf.h TF_HALTCOND +struct msctf.h TF_INPUTPROCESSORPROFILE +struct msctf.h TF_LANGUAGEPROFILE +struct msctf.h TF_PERSISTENT_PROPERTY_HEADER_ACP +struct msctf.h TF_PRESERVEDKEY +struct msctf.h TF_PROPERTYVAL +struct msctf.h TF_SELECTION +struct msctf.h TF_SELECTIONSTYLE +struct textstor.h TF_STATUS =ts_status +struct textstor.h TS_ATTRVAL +struct textstor.h TS_RUNINFO +struct textstor.h TS_SELECTION_ACP +struct textstor.h TS_SELECTION_ANCHOR +struct textstor.h TS_SELECTIONSTYLE +struct textstor.h TS_STATUS +struct textstor.h TS_TEXTCHANGE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Text Services Framework > TSF Reference > TSF Enumerations +enum ctffunc.h TfCandidateResult +enum ctffunc.h TfIntegratableCandidateListSelectionStyle +enum ctffunc.h TfSapiObject +enum ctffunc.h TKBLayoutType +enum ctfutb.h TfLBBalloonStyle +enum ctfutb.h TfLBIClick +enum inputscope.h InputScope +enum msctf.h TF_DA_ATTR_INFO +enum msctf.h TF_DA_COLORTYPE +enum msctf.h TF_DA_LINESTYLE +enum msctf.h TfActiveSelEnd +enum msctf.h TfAnchor +enum msctf.h TfGravity +enum msctf.h TfLayoutCode +enum msctf.h TfShiftDir +enum textstor.h TsActiveSelEnd +enum textstor.h TsGravity +enum textstor.h TsLayoutCode +enum textstor.h TsRunType +enum textstor.h TsShiftDir + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Touch Hit Testing > Touch Hit Testing Reference > Functions +function winuser.h EvaluateProximityToPolygon +function winuser.h EvaluateProximityToRect +function winuser.h PackTouchHitTestingProximityEvaluation +function winuser.h RegisterTouchHitTestingWindow + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Touch Hit Testing > Touch Hit Testing Reference > Structures +struct winuser.h TOUCH_HIT_TESTING_INPUT +struct winuser.h TOUCH_HIT_TESTING_PROXIMITY_EVALUATION + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Touch Injection > Touch Injection Reference > Functions +function winuser.h InitializeTouchInjection +function winuser.h InjectTouchInput + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Boundary Feedback > Functions +function uxtheme.h BeginPanningFeedback +function uxtheme.h EndPanningFeedback +function uxtheme.h UpdatePanningFeedback + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Inertia and Manipulation > Classes and Interfaces +interface manipulations.h _IManipulationEvents +interface manipulations.h IInertiaProcessor +interface manipulations.h IManipulationProcessor + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Inertia and Manipulation > Enumerations +enum manipulations.h MANIPULATION_PROCESSOR_MANIPULATIONS + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Gestures > Functions +function winuser.h CloseGestureInfoHandle +function winuser.h GetGestureConfig +function winuser.h GetGestureExtraArgs +function winuser.h GetGestureInfo +function winuser.h SetGestureConfig + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Gestures > Macros +function winuser.h GID_ROTATE_ANGLE_FROM_ARGUMENT +function winuser.h GID_ROTATE_ANGLE_TO_ARGUMENT + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Gestures > Structures +struct winuser.h GESTURECONFIG +struct winuser.h GESTUREINFO +struct winuser.h GESTURENOTIFYSTRUCT + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Input > Macros +function winuser.h TOUCH_COORD_TO_PIXEL + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Input > Functions +function winuser.h CloseTouchInputHandle +function winuser.h GetTouchInputInfo +function winuser.h IsTouchWindow +function winuser.h RegisterTouchWindow +function winuser.h UnregisterTouchWindow + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Touch Input > Windows Touch Programming Reference > Windows Touch Input > Structures +struct winuser.h TOUCHINPUT + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Keyboard Input > Keyboard Input Reference > Keyboard Input Functions +function winuser.h ActivateKeyboardLayout +function winuser.h BlockInput +function winuser.h EnableWindow +function winuser.h GetActiveWindow +function winuser.h GetAsyncKeyState +function winuser.h GetFocus +function winuser.h GetKBCodePage +function winuser.h GetKeyboardLayout +function winuser.h GetKeyboardLayoutList +function winuser.h GetKeyboardLayoutName =getkeyboardlayoutnamea +function winuser.h GetKeyboardState +function winuser.h GetKeyboardType +function winuser.h GetKeyNameText =getkeynametexta +function winuser.h GetKeyState +function winuser.h GetLastInputInfo +function winuser.h IsWindowEnabled +function winuser.h keybd_event +function winuser.h LoadKeyboardLayout =loadkeyboardlayouta +function winuser.h MapVirtualKey =mapvirtualkeya +function winuser.h MapVirtualKeyEx =mapvirtualkeyexa +function winuser.h OemKeyScan +function winuser.h RegisterHotKey +function winuser.h SendInput +function winuser.h SetActiveWindow +function winuser.h SetFocus +function winuser.h SetKeyboardState +function winuser.h ToAscii +function winuser.h ToAsciiEx +function winuser.h ToUnicode +function winuser.h ToUnicodeEx +function winuser.h UnloadKeyboardLayout +function winuser.h UnregisterHotKey +function winuser.h VkKeyScan =vkkeyscana +function winuser.h VkKeyScanEx =vkkeyscanexa + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Keyboard Input > Keyboard Input Reference > Keyboard Input Structures +struct winuser.h HARDWAREINPUT +struct winuser.h INPUT +struct winuser.h KEYBDINPUT +struct winuser.h LASTINPUTINFO +struct winuser.h MOUSEINPUT + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Mouse Input > Mouse Input Reference > Mouse Input Functions +function commctrl.h _TrackMouseEvent +function winuser.h DragDetect +function winuser.h GetCapture +function winuser.h GetDoubleClickTime +function winuser.h GetMouseMovePointsEx +function winuser.h mouse_event +function winuser.h ReleaseCapture +function winuser.h SetCapture +function winuser.h SetDoubleClickTime +function winuser.h SwapMouseButton +function winuser.h TrackMouseEvent + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Mouse Input > Mouse Input Reference > Mouse Input Macros +function winuser.h GET_APPCOMMAND_LPARAM +function winuser.h GET_DEVICE_LPARAM +function winuser.h GET_FLAGS_LPARAM +function winuser.h GET_KEYSTATE_LPARAM +function winuser.h GET_KEYSTATE_WPARAM +function winuser.h GET_NCHITTEST_WPARAM +function winuser.h GET_WHEEL_DELTA_WPARAM +function winuser.h GET_XBUTTON_WPARAM + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Mouse Input > Mouse Input Reference > Mouse Input Structures +struct winuser.h MOUSEMOVEPOINT +struct winuser.h TRACKMOUSEEVENT + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Raw Input > Raw Input Reference > Raw Input Functions +function winuser.h DefRawInputProc +function winuser.h GetRawInputBuffer +function winuser.h GetRawInputData +function winuser.h GetRawInputDeviceInfo =getrawinputdeviceinfoa +function winuser.h GetRawInputDeviceList +function winuser.h GetRegisteredRawInputDevices +function winuser.h RegisterRawInputDevices + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Raw Input > Raw Input Reference > Raw Input Macros +function winuser.h GET_RAWINPUT_CODE_WPARAM +function winuser.h NEXTRAWINPUTBLOCK + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Keyboard and Mouse Input > Raw Input > Raw Input Reference > Raw Input Structures +struct winuser.h RAWHID +struct winuser.h RAWINPUT +struct winuser.h RAWINPUTDEVICE +struct winuser.h RAWINPUTDEVICELIST +struct winuser.h RAWINPUTHEADER +struct winuser.h RAWKEYBOARD +struct winuser.h RAWMOUSE +struct winuser.h RID_DEVICE_INFO +struct winuser.h RID_DEVICE_INFO_HID +struct winuser.h RID_DEVICE_INFO_KEYBOARD +struct winuser.h RID_DEVICE_INFO_MOUSE + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkCollector Class +function msinkaut.h AutoRedraw Property =iinkcollector-get_autoredraw +function msinkaut.h CollectingInk Property =iinkcollector-get_collectingink +function msinkaut.h CollectionMode Property =iinkcollector-get_collectionmode +function msinkaut.h Cursors Property =iinkcollector-get_cursors +function msinkaut.h DefaultDrawingAttributes Property =iinkcollector-get_defaultdrawingattributes +function msinkaut.h DesiredPacketDescription Property =iinkcollector-get_desiredpacketdescription +function msinkaut.h DynamicRendering Property =iinkcollector-get_dynamicrendering +function msinkaut.h Enabled Property =iinkcollector-get_enabled +function msinkaut.h GetEventInterest Method =iinkcollector-geteventinterest +function msinkaut.h GetGestureStatus Method =iinkcollector-getgesturestatus +function msinkaut.h GetWindowInputRectangle Method =iinkcollector-getwindowinputrectangle +function msinkaut.h hWnd Property =iinkcollector-get_hwnd +function msinkaut.h Ink Property =iinkcollector-get_ink +function msinkaut.h MarginX Property =iinkcollector-get_marginx +function msinkaut.h MarginY Property =iinkcollector-get_marginy +function msinkaut.h MouseIcon Property =iinkcollector-get_mouseicon +function msinkaut.h MousePointer Property =iinkcollector-get_mousepointer +function msinkaut.h Renderer Property =iinkcollector-get_renderer +function msinkaut.h SetAllTabletsMode Method =iinkcollector-setalltabletsmode +function msinkaut.h SetEventInterest Method =iinkcollector-seteventinterest +function msinkaut.h SetGestureStatus Method =iinkcollector-setgesturestatus +function msinkaut.h SetSingleTabletIntegratedMode Method =iinkcollector-setsingletabletintegratedmode +function msinkaut.h SetWindowInputRectangle Method =iinkcollector-setwindowinputrectangle +function msinkaut.h SupportHighContrastInk Property =iinkcollector-get_supporthighcontrastink +function msinkaut.h Tablet Property =iinkcollector-get_tablet + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkDisp Class +function msinkaut.h AddStrokesAtRectangle Method =iinkdisp-addstrokesatrectangle +function msinkaut.h CanPaste Method =iinkdisp-canpaste +function msinkaut.h Clip Method =iinkdisp-clip +function msinkaut.h ClipboardCopy Method =iinkdisp-clipboardcopy +function msinkaut.h ClipboardCopyWithRectangle Method =iinkdisp-clipboardcopywithrectangle +function msinkaut.h ClipboardPaste Method =iinkdisp-clipboardpaste +function msinkaut.h Clone Method =iinkdisp-clone +function msinkaut.h CreateStroke Method =iinkdisp-createstroke +function msinkaut.h CreateStrokes Method =iinkdisp-createstrokes +function msinkaut.h CustomStrokes Property =iinkdisp-get_customstrokes +function msinkaut.h DeleteStroke Method =iinkdisp-deletestroke +function msinkaut.h DeleteStrokes Method =iinkdisp-deletestrokes +function msinkaut.h Dirty Property =iinkdisp-get_dirty +function msinkaut.h ExtendedProperties Property =iinkdisp-get_extendedproperties +function msinkaut.h ExtractStrokes Method =iinkdisp-extractstrokes +function msinkaut.h ExtractWithRectangle Method =iinkdisp-extractwithrectangle +function msinkaut.h GetBoundingBox Method =iinkdisp-getboundingbox +function msinkaut.h HitTestCircle Method =iinkdisp-hittestcircle +function msinkaut.h HitTestWithLasso Method =iinkdisp-hittestwithlasso +function msinkaut.h HitTestWithRectangle Method =iinkdisp-hittestwithrectangle +function msinkaut.h Load Method =iinkdisp-load +function msinkaut.h NearestPoint Method =iinkdisp-nearestpoint +function msinkaut.h Save Method =iinkdisp-save +function msinkaut.h Strokes Property =iinkdisp-get_strokes + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkDivider Class +function msinkaut15.h Divide Method =iinkdivider-divide +function msinkaut15.h LineHeight Property =iinkdivider-get_lineheight +function msinkaut15.h RecognizerContext Property =iinkdivider-get_recognizercontext +function msinkaut15.h Strokes Property =iinkdivider-get_strokes + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkDrawingAttributes Class +function msinkaut.h AntiAliased Property =iinkdrawingattributes-get_antialiased +function msinkaut.h Clone Method =iinkdrawingattributes-clone +function msinkaut.h Color Property =iinkdrawingattributes-get_color +function msinkaut.h ExtendedProperties Property =iinkdrawingattributes-get_extendedproperties +function msinkaut.h FitToCurve Property =iinkdrawingattributes-get_fittocurve +function msinkaut.h Height Property =iinkdrawingattributes-get_height +function msinkaut.h IgnorePressure Property =iinkdrawingattributes-get_ignorepressure +function msinkaut.h PenTip Property =iinkdrawingattributes-get_pentip +function msinkaut.h RasterOperation Property =iinkdrawingattributes-get_rasteroperation +function msinkaut.h Transparency Property =iinkdrawingattributes-get_transparency +function msinkaut.h Width Property =iinkdrawingattributes-get_width + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkOverlay Class +function msinkaut.h AttachMode Property =iinkoverlay-get_attachmode +function msinkaut.h AutoRedraw Property =iinkoverlay-get_autoredraw +function msinkaut.h CollectingInk Property =iinkoverlay-get_collectingink +function msinkaut.h CollectionMode Property =iinkoverlay-get_collectionmode +function msinkaut.h Cursors Property =iinkoverlay-get_cursors +function msinkaut.h DefaultDrawingAttributes Property =iinkoverlay-get_defaultdrawingattributes +function msinkaut.h DesiredPacketDescription Property =iinkoverlay-get_desiredpacketdescription +function msinkaut.h Draw Method =iinkoverlay-draw +function msinkaut.h DynamicRendering Property =iinkoverlay-get_dynamicrendering +function msinkaut.h EditingMode Property =iinkoverlay-get_editingmode +function msinkaut.h Enabled Property =iinkoverlay-get_enabled +function msinkaut.h EraserMode Property =iinkoverlay-get_erasermode +function msinkaut.h EraserWidth Property =iinkoverlay-get_eraserwidth +function msinkaut.h GetEventInterest Method =iinkoverlay-geteventinterest +function msinkaut.h GetGestureStatus Method =iinkoverlay-getgesturestatus +function msinkaut.h GetWindowInputRectangle Method =iinkoverlay-getwindowinputrectangle +function msinkaut.h HitTestSelection Method =iinkoverlay-hittestselection +function msinkaut.h hWnd Property =iinkoverlay-get_hwnd +function msinkaut.h Ink Property =iinkoverlay-get_ink +function msinkaut.h MarginX Property =iinkoverlay-get_marginx +function msinkaut.h MarginY Property =iinkoverlay-get_marginy +function msinkaut.h MouseIcon Property =iinkoverlay-get_mouseicon +function msinkaut.h MousePointer Property =iinkoverlay-get_mousepointer +function msinkaut.h Renderer Property =iinkoverlay-get_renderer +function msinkaut.h Selection Property =iinkoverlay-get_selection +function msinkaut.h SetAllTabletsMode Method =iinkoverlay-setalltabletsmode +function msinkaut.h SetEventInterest Method =iinkoverlay-seteventinterest +function msinkaut.h SetGestureStatus Method =iinkoverlay-setgesturestatus +function msinkaut.h SetSingleTabletIntegratedMode Method =iinkoverlay-setsingletabletintegratedmode +function msinkaut.h SetWindowInputRectangle Method =iinkoverlay-setwindowinputrectangle +function msinkaut.h SupportHighContrastInk Property =iinkoverlay-get_supporthighcontrastink +function msinkaut.h SupportHighContrastSelectionUI Property =iinkoverlay-get_supporthighcontrastselectionui +function msinkaut.h Tablet Property =iinkoverlay-get_tablet + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkRecognizerContext Class +function msinkaut.h BackgroundRecognize Method =iinkrecognizercontext-backgroundrecognize +function msinkaut.h BackgroundRecognizeWithAlternates Method =iinkrecognizercontext-backgroundrecognizewithalternates +function msinkaut.h CharacterAutoCompletion Property =iinkrecognizercontext-get_characterautocompletionmode +function msinkaut.h Clone Method =iinkrecognizercontext-clone +function msinkaut.h EndInkInput Method =iinkrecognizercontext-endinkinput +function msinkaut.h Factoid Property =iinkrecognizercontext-get_factoid +function msinkaut.h Guide Property =iinkrecognizercontext-get_guide +function msinkaut.h IsStringSupported Method =iinkrecognizercontext-isstringsupported +function msinkaut.h PrefixText Property =iinkrecognizercontext-get_prefixtext +function msinkaut.h RecognitionFlags Property =iinkrecognizercontext-get_recognitionflags +function msinkaut.h Recognize Method =iinkrecognizercontext-recognize +function msinkaut.h Recognizer Property =iinkrecognizercontext-get_recognizer +function msinkaut.h StopBackgroundRecognition Method =iinkrecognizercontext-stopbackgroundrecognition +function msinkaut.h Strokes Property =iinkrecognizercontext-get_strokes +function msinkaut.h SuffixText Property =iinkrecognizercontext-get_suffixtext +function msinkaut.h WordList Property =iinkrecognizercontext-get_wordlist + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkRecognizerGuide Class +function msinkaut.h Columns Property =iinkrecognizerguide-get_columns +function msinkaut.h DrawnBox Property =iinkrecognizerguide-get_drawnbox +function msinkaut.h GuideData Property =iinkrecognizerguide-get_guidedata +function msinkaut.h Midline Property =iinkrecognizerguide-get_midline +function msinkaut.h Rows Property =iinkrecognizerguide-get_rows +function msinkaut.h WritingBox Property =iinkrecognizerguide-get_writingbox + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkRecognizers Collection +function msinkaut.h Count Property =iinkrecognizers-get_count +function msinkaut.h GetDefaultRecognizer Method =iinkrecognizers-getdefaultrecognizer +function msinkaut.h Item Method =iinkrecognizers-item + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkRectangle Class +function msinkaut.h Bottom Property =iinkrectangle-get_bottom +function msinkaut.h Data Property =iinkrectangle-get_data +function msinkaut.h GetRectangle Method =iinkrectangle-getrectangle +function msinkaut.h Left Property =iinkrectangle-get_left +function msinkaut.h Right Property =iinkrectangle-get_right +function msinkaut.h SetRectangle Method =iinkrectangle-setrectangle +function msinkaut.h Top Property =iinkrectangle-get_top + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkRenderer Class +function msinkaut.h Draw Method =iinkrenderer-draw +function msinkaut.h DrawStroke Method =iinkrenderer-drawstroke +function msinkaut.h GetObjectTransform Method =iinkrenderer-getobjecttransform +function msinkaut.h GetViewTransform Method =iinkrenderer-getviewtransform +function msinkaut.h InkSpaceToPixel Method =iinkrenderer-inkspacetopixel +function msinkaut.h InkSpaceToPixelFromPoints Method =iinkrenderer-inkspacetopixelfrompoints +function msinkaut.h Measure Method =iinkrenderer-measure +function msinkaut.h MeasureStroke Method =iinkrenderer-measurestroke +function msinkaut.h Move Method =iinkrenderer-move +function msinkaut.h PixelToInkSpace Method =iinkrenderer-pixeltoinkspace +function msinkaut.h PixelToInkSpaceFromPoints Method =iinkrenderer-pixeltoinkspacefrompoints +function msinkaut.h Rotate Method =iinkrenderer-rotate +function msinkaut.h ScaleTransform Method =iinkrenderer-scaletransform +function msinkaut.h SetObjectTransform Method =iinkrenderer-setobjecttransform +function msinkaut.h SetViewTransform Method =iinkrenderer-setviewtransform + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkStrokes Collection +function msinkaut.h Add Method =iinkstrokes-add +function msinkaut.h AddStrokes Method =iinkstrokes-addstrokes +function msinkaut.h Clip Method =iinkstrokes-clip +function msinkaut.h Count Property =iinkstrokes-get_count +function msinkaut.h GetBoundingBox Method =iinkstrokes-getboundingbox +function msinkaut.h Ink Property =iinkstrokes-get_ink +function msinkaut.h Item Method =iinkstrokes-item +function msinkaut.h ModifyDrawingAttributes Method =iinkstrokes-modifydrawingattributes +function msinkaut.h Move Method =iinkstrokes-move +function msinkaut.h RecognitionResult Property =iinkstrokes-get_recognitionresult +function msinkaut.h Remove Method =iinkstrokes-remove +function msinkaut.h RemoveRecognitionResult Method =iinkstrokes-removerecognitionresult +function msinkaut.h RemoveStrokes Method =iinkstrokes-removestrokes +function msinkaut.h Rotate Method =iinkstrokes-rotate +function msinkaut.h ScaleToRectangle Method =iinkstrokes-scaletorectangle +function msinkaut.h ScaleTransform Method =iinkstrokes-scaletransform +function msinkaut.h Shear Method =iinkstrokes-shear +function msinkaut.h ToString Method =iinkstrokes-tostring +function msinkaut.h Transform Method =iinkstrokes-transform + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkTablets Collection +function msinkaut.h Count Property =iinktablets-get_count +function msinkaut.h DefaultTablet Property =iinktablets-get_defaulttablet +function msinkaut.h IsPacketPropertySupported Method =iinktablets-ispacketpropertysupported +function msinkaut.h Item Method =iinktablets-item + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkTransform Class +function msinkaut.h Data Property =iinktransform-get_data +function msinkaut.h eDx Property =iinktransform-get_edx +function msinkaut.h eDy Property =iinktransform-get_edy +function msinkaut.h eM11 Property =iinktransform-get_em11 +function msinkaut.h eM12 Property =iinktransform-get_em12 +function msinkaut.h eM21 Property =iinktransform-get_em21 +function msinkaut.h eM22 Property =iinktransform-get_em22 +function msinkaut.h GetTransform Method =iinktransform-gettransform +function msinkaut.h Reflect Method =iinktransform-reflect +function msinkaut.h Reset Method =iinktransform-reset +function msinkaut.h Rotate Method =iinktransform-rotate +function msinkaut.h ScaleTransform Method =iinktransform-scaletransform +function msinkaut.h SetTransform Method =iinktransform-settransform +function msinkaut.h Shear Method =iinktransform-shear +function msinkaut.h Translate Method =iinktransform-translate + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > InkWordList Class +function msinkaut.h AddWord Method =iinkwordlist-addword +function msinkaut.h Merge Method =iinkwordlist-merge +function msinkaut.h RemoveWord Method =iinkwordlist-removeword + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces > PenInputPanel Class +function peninputpanel.h AttachedEditWindow Property =ipeninputpanel-get_attachededitwindow +function peninputpanel.h AutoShow Property =ipeninputpanel-get_autoshow +function peninputpanel.h Busy Property =ipeninputpanel-get_busy +function peninputpanel.h CommitPendingInput Method =ipeninputpanel-commitpendinginput +function peninputpanel.h CurrentPanel Property =ipeninputpanel-get_currentpanel +function peninputpanel.h DefaultPanel Property =ipeninputpanel-get_defaultpanel +function peninputpanel.h EnableTsf Method =ipeninputpanel-enabletsf +function peninputpanel.h Factoid Property =ipeninputpanel-get_factoid +function peninputpanel.h Height Property =ipeninputpanel-get_height +function peninputpanel.h HorizontalOffset Property =ipeninputpanel-get_horizontaloffset +function peninputpanel.h Left Property =ipeninputpanel-get_left +function peninputpanel.h MoveTo Method =ipeninputpanel-moveto +function peninputpanel.h Refresh Method =ipeninputpanel-refresh +function peninputpanel.h Top Property =ipeninputpanel-get_top +function peninputpanel.h VerticalOffset Property =ipeninputpanel-get_verticaloffset +function peninputpanel.h Visible Property =ipeninputpanel-get_visible +function peninputpanel.h Width Property =ipeninputpanel-get_width + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Classes and Interfaces +interface msinkaut.h IInkCursor Interface =iinkcursor +interface msinkaut.h IInkCursorButton Interface =iinkcursorbutton +interface msinkaut.h IInkCursorButtons Interface =iinkcursorbuttons +interface msinkaut.h IInkCursors Interface =iinkcursors +interface msinkaut.h IInkCustomStrokes Interface =iinkcustomstrokes +interface msinkaut.h IInkExtendedProperties Interface =iinkextendedproperties +interface msinkaut.h IInkExtendedProperty Interface =iinkextendedproperty +interface msinkaut.h IInkGesture Interface =iinkgesture +interface msinkaut.h IInkRecognitionAlternate Interface =iinkrecognitionalternate +interface msinkaut.h IInkRecognitionAlternates Interface =iinkrecognitionalternates +interface msinkaut.h IInkRecognitionResult Interface =iinkrecognitionresult +interface msinkaut.h IInkRecognizer Interface =iinkrecognizer +interface msinkaut.h IInkRecognizer2 Interface =iinkrecognizer2 +interface msinkaut.h IInkRecognizerContext2 Interface =iinkrecognizercontext2 +interface msinkaut.h IInkStrokeDisp Interface =iinkstrokedisp +interface msinkaut.h IInkTablet Interface =iinktablet +interface msinkaut.h IInkTablet2 Interface =iinktablet2 +interface msinkaut.h IInkTablet3 Interface =iinktablet3 +interface msinkaut.h IInkWordList2 Interface =iinkwordlist2 +interface msinkaut15.h IInkDivisionResult Interface =iinkdivisionresult +interface msinkaut15.h IInkDivisionUnit Interface =iinkdivisionunit +interface msinkaut15.h IInkDivisionUnits Interface =iinkdivisionunits + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Core Tablet PC Reference > Core Tablet PC Enumerations +enum msinkaut.h InkApplicationGesture Enumeration =inkapplicationgesture +enum msinkaut.h InkBoundingBoxMode Enumeration =inkboundingboxmode +enum msinkaut.h InkClipboardFormats Enumeration =inkclipboardformats +enum msinkaut.h InkClipboardModes Enumeration =inkclipboardmodes +enum msinkaut.h InkCollectionMode Enumeration =inkcollectionmode +enum msinkaut.h InkCollectorEventInterest Enumeration =inkcollectoreventinterest +enum msinkaut.h InkCursorButtonState Enumeration =inkcursorbuttonstate +enum msinkaut.h InkExtractFlags Enumeration =inkextractflags +enum msinkaut.h InkMouseButton Enumeration =inkmousebutton +enum msinkaut.h InkMousePointer Enumeration =inkmousepointer +enum msinkaut.h InkOverlayAttachMode Enumeration =inkoverlayattachmode +enum msinkaut.h InkOverlayEditingMode Enumeration =inkoverlayeditingmode +enum msinkaut.h InkOverlayEraserMode Enumeration =inkoverlayerasermode +enum msinkaut.h InkPenTip Enumeration =inkpentip +enum msinkaut.h InkPersistenceCompressionMode Enumeration =inkpersistencecompressionmode +enum msinkaut.h InkPersistenceFormat Enumeration =inkpersistenceformat +enum msinkaut.h InkRasterOperation Enumeration =inkrasteroperation +enum msinkaut.h InkRecognitionAlternatesSelection Enumeration =inkrecognitionalternatesselection +enum msinkaut.h InkRecognitionConfidence Enumeration =inkrecognitionconfidence +enum msinkaut.h InkRecognitionModes Enumeration =inkrecognitionmodes +enum msinkaut.h InkRecognitionStatus Enumeration =inkrecognitionstatus +enum msinkaut.h InkRecognizerCapabilities Enumeration =inkrecognizercapabilities +enum msinkaut.h InkRecognizerCharacterAutoCompletionMode Enumeration =inkrecognizercharacterautocompletionmode +enum msinkaut.h InkShiftKeyModifierFlags Enumeration =inkshiftkeymodifierflags +enum msinkaut.h InkSystemGesture Enumeration =inksystemgesture +enum msinkaut.h ItemSelectionConstants Enumeration =inkselectionconstants +enum msinkaut.h SelectionHitResult Enumeration =selectionhitresult +enum msinkaut.h TabletHardwareCapabilities Enumeration =tablethardwarecapabilities +enum msinkaut.h TabletPropertyMetricUnit Enumeration =tabletpropertymetricunit +enum msinkaut15.h InkDivisionType Enumeration =inkdivisiontype +enum peninputpanel.h PanelType Enumeration =paneltype +struct msinkaut.h InkRecoGuide Enumeration =inkrecoguide + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Flicks Reference > Flicks Enumerations +enum tabflicks.h FLICKACTION_COMMANDMODE Enumeration =flickaction_commandcode +enum tabflicks.h FLICKDIRECTION Enumeration =flickdirection +enum tabflicks.h FLICKMODE Enumeration =flickmode +enum tabflicks.h KEYMODIFIER Enumeration =keymodifier +enum tabflicks.h SCROLLDIRECTION Enumeration =scrolldirection + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Flicks Reference > Flicks Structures +struct tabflicks.h FLICK_DATA Structure =flick_data +struct tabflicks.h FLICK_POINT Structure =flick_point + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkEdit Control Reference > InkEdit Events +function inked.h Appearance Property =iinkedit-get_appearance +function inked.h BackColor Property =iinkedit-get_backcolor +function inked.h BorderStyle Property =iinkedit-get_borderstyle +function inked.h DisableNoScroll Property =iinkedit-get_disablenoscroll +function inked.h DrawingAttributes Property [InkEdit Control =iinkedit-get_drawingattributes +function inked.h Enabled Property =iinkedit-get_enabled +function inked.h Factoid Property [InkEdit Control =iinkedit-get_factoid +function inked.h Font Property =iinkedit-get_font +function inked.h GetGestureStatus Method [InkEdit Control =iinkedit-getgesturestatus +function inked.h hWnd Property =iinkedit-get_hwnd +function inked.h InkInsertMode Property =iinkedit-get_inkinsertmode +function inked.h InkMode Property =iinkedit-get_inkmode +function inked.h Locked Property =iinkedit-get_locked +function inked.h MaxLength Property =iinkedit-get_maxlength +function inked.h MouseIcon Property =iinkedit-get_mouseicon +function inked.h MousePointer Property =iinkedit-get_mousepointer +function inked.h MultiLine Property =iinkedit-get_multiline +function inked.h Recognize Method [InkEdit Control =iinkedit-recognize +function inked.h Recognizer Property [InkEdit Control =iinkedit-get_recognizer +function inked.h RecoTimeout Property =iinkedit-get_recognitiontimeout +function inked.h Refresh Method [InkEdit Control =iinkedit-refresh +function inked.h ScrollBars Property =iinkedit-get_scrollbars +function inked.h SelAlignment Property =iinkedit-get_selalignment +function inked.h SelBold Property =iinkedit-get_selbold +function inked.h SelCharOffset Property =iinkedit-get_selcharoffset +function inked.h SelColor Property =iinkedit-get_selcolor +function inked.h SelFontName Property =iinkedit-get_selfontname +function inked.h SelFontSize Property =iinkedit-get_selfontsize +function inked.h SelInks Property =iinkedit-get_selinks +function inked.h SelInksDisplayMode Property =iinkedit-get_selinksdisplaymode +function inked.h SelItalic Property =iinkedit-get_selitalic +function inked.h SelLength Property =iinkedit-get_sellength +function inked.h SelRTF Property =iinkedit-get_selrtf +function inked.h SelStart Property =iinkedit-get_selstart +function inked.h SelText Property =iinkedit-get_seltext +function inked.h SelUnderline Property =iinkedit-get_selunderline +function inked.h SetGestureStatus Method [InkEdit Control =iinkedit-setgesturestatus +function inked.h Status Property =iinkedit-get_status +function inked.h Text Property =iinkedit-get_text +function inked.h TextRTF Property =iinkedit-get_textrtf +function inked.h UseMouseForInput Property =iinkedit-get_usemouseforinput + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkEdit Control Reference > InkEdit Enumerations +enum inked.h AppearanceConstants Enumeration =appearanceconstants +enum inked.h BorderStyleConstants Enumeration =borderstyleconstants +enum inked.h InkDisplayMode Enumeration =inkdisplaymode +enum inked.h InkEditStatus Enumeration =inkeditstatus +enum inked.h InkInsertMode Enumeration =inkinsertmode +enum inked.h InkMode Enumeration =inkmode +enum inked.h MouseButton Enumeration =mousebutton +enum inked.h ScrollBarsConstants Enumeration =scrollbarsconstants +enum inked.h SelAlignmentConstants Enumeration =selalignmentconstants + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkEdit Control Reference > InkEdit Structures +struct inked.h IEC_GESTUREINFO Structure (Win32 Only) =iec_gestureinfo +struct inked.h IEC_RECOGNITIONRESULTINFO Structure (Win32 Only) =iec_recognitionresultinfo +struct inked.h IEC_STROKEINFO Structure (Win32 Only) =iec_strokeinfo + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkPicture Control Reference > InkPicture Enumerations +enum msinkaut.h InkPictureSizeMode Enumeration =inkpicturesizemode + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkPicture Control Reference > InkPicture Methods +function msinkaut.h GetEventInterest Method =iinkpicture-geteventinterest +function msinkaut.h GetGestureStatus Method =iinkpicture-getgesturestatus +function msinkaut.h GetWindowInputRectangle Method =iinkpicture-getwindowinputrectangle +function msinkaut.h HitTestSelection Method =iinkpicture-hittestselection +function msinkaut.h SetAllTabletsMode Method =iinkpicture-setalltabletsmode +function msinkaut.h SetEventInterest Method =iinkpicture-seteventinterest +function msinkaut.h SetGestureStatus Method =iinkpicture-setgesturestatus +function msinkaut.h SetSingleTabletIntegratedMode Method =iinkpicture-setsingletabletintegratedmode +function msinkaut.h SetWindowInputRectangle Method =iinkpicture-setwindowinputrectangle + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > InkPicture Control Reference > InkPicture Properties +function msinkaut.h AutoRedraw Property =iinkpicture-get_autoredraw +function msinkaut.h BackColor Property =iinkpicture-get_backcolor +function msinkaut.h CollectingInk Property =iinkpicture-get_collectingink +function msinkaut.h CollectionMode Property [InkPicture Control =iinkpicture-get_collectionmode +function msinkaut.h Cursors Property =iinkpicture-get_cursors +function msinkaut.h DefaultDrawingAttributes Property =iinkpicture-get_defaultdrawingattributes +function msinkaut.h DesiredPacketDescription Property =iinkpicture-get_desiredpacketdescription +function msinkaut.h DynamicRendering Property =iinkpicture-get_dynamicrendering +function msinkaut.h EditingMode Property [InkPicture Control =iinkpicture-get_editingmode +function msinkaut.h Enabled Property [InkPicture Control =iinkpicture-get_enabled +function msinkaut.h EraserMode Property [InkPicture Control =iinkpicture-get_erasermode +function msinkaut.h EraserWidth Property [InkPicture Control =iinkpicture-get_eraserwidth +function msinkaut.h hWnd Property =iinkpicture-get_hwnd +function msinkaut.h Ink Property [InkPicture Control =iinkpicture-get_ink +function msinkaut.h InkEnabled Property =iinkpicture-get_inkenabled +function msinkaut.h MarginX Property =iinkpicture-get_marginx +function msinkaut.h MarginY Property =iinkpicture-get_marginy +function msinkaut.h MouseIcon Property =iinkpicture-get_mouseicon +function msinkaut.h MousePointer Property =iinkpicture-get_mousepointer +function msinkaut.h Picture Property =iinkpicture-get_picture +function msinkaut.h Renderer Property =iinkpicture-get_renderer +function msinkaut.h Selection Property [InkPicture Control =iinkpicture-get_selection +function msinkaut.h SizeMode Property =iinkpicture-get_sizemode +function msinkaut.h SupportHighContrastInk Property =iinkpicture-get_supporthighcontrastink +function msinkaut.h SupportHighContrastSelectionUI Property [InkPicture Control =iinkpicture-get_supporthighcontrastselectionui +function msinkaut.h Tablet Property =iinkpicture-get_tablet + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Math Input Control Reference > Math Input Classes and Interfaces +interface micaut.h _IMathInputControlEvents +interface micaut.h IMathInputControl + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Math Input Control Reference > Math Input Enumerations +enum micaut.h MICUIELEMENT Enumeration =micuielement +enum micaut.h MICUIELEMENTSTATE Enumeration =micuielementstate + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > RealTimeStylus Reference > RealTimeStylus Classes and Interfaces +interface rtscom.h IDynamicRenderer Interface =idynamicrenderer +interface rtscom.h IGestureRecognizer Interface =igesturerecognizer +interface rtscom.h IRealTimeStylus Interface =irealtimestylus +interface rtscom.h IRealTimeStylus2 Interface =irealtimestylus2 +interface rtscom.h IRealTimeStylus3 +interface rtscom.h IRealTimeStylusSynchronization Interface =irealtimestylussynchronization +interface rtscom.h IStrokeBuilder Interface =istrokebuilder +interface rtscom.h IStylusAsyncPlugin Interface =istylusasyncplugin +interface rtscom.h IStylusPlugin Interface =istylusplugin +interface rtscom.h IStylusSyncPlugin Interface =istylussyncplugin + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > RealTimeStylus Reference > RealTimeStylus Enumerations +enum rtscom.h RealTimeStylusDataInterest Enumerations =realtimestylusdatainterest +enum rtscom.h RealTimeStylusLockType Enumerations =realtimestyluslocktype +enum rtscom.h StylusQueue Enumerations =stylusqueue + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > RealTimeStylus Reference > RealTimeStylus Structures +struct rtscom.h StylusInfo Structure =stylusinfo +struct tpcshrd.h SYSTEM_EVENT_DATA Structure =system_event_data + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > HRECOALT Handle +function recapis.h DestroyAlternate Function =destroyalternate + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > HRECOCONTEXT Handle +function recapis.h AddStroke Function =addstroke +function recapis.h AdviseInkChange Function =adviseinkchange +function recapis.h CloneContext Function =clonecontext +function recapis.h EndInkInput Function =endinkinput +function recapis.h GetBestResultString Function =getbestresultstring +function recapis.h GetContextPreferenceFlags +function recapis.h GetContextPropertyList Function =getcontextpropertylist +function recapis.h GetContextPropertyValue Function =getcontextpropertyvalue +function recapis.h GetEnabledUnicodeRanges Function =getenabledunicoderanges +function recapis.h GetGuide Function =getguide +function recapis.h GetLatticePtr Function =getlatticeptr +function recapis.h GetLeftSeparator +function recapis.h GetRightSeparator +function recapis.h IsStringSupported Function =isstringsupported +function recapis.h Process Function =process +function recapis.h ResetContext Function =resetcontext +function recapis.h SetCACMode Function =setcacmode +function recapis.h SetContextPropertyValue Function =setcontextpropertyvalue +function recapis.h SetEnabledUnicodeRanges Function =setenabledunicoderanges +function recapis.h SetFactoid Function =setfactoid +function recapis.h SetFlags Function =setflags +function recapis.h SetGuide Function =setguide +function recapis.h SetTextContext Function =settextcontext +function recapis.h SetWordList Function =setwordlist + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > HRECOGNIZER Handle +function recapis.h CreateContext Function =createcontext +function recapis.h CreateRecognizer Function =createrecognizer +function recapis.h DestroyContext Function =destroycontext +function recapis.h DestroyRecognizer Function =destroyrecognizer +function recapis.h GetAllRecognizers +function recapis.h GetPreferredPacketDescription Function =getpreferredpacketdescription +function recapis.h GetRecoAttributes Function =getrecoattributes +function recapis.h GetResultPropertyList Function =getresultpropertylist +function recapis.h GetUnicodeRanges Function =getunicoderanges +function recapis.h LoadCachedAttributes + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > HRECOWORDLIST Handle +function recapis.h AddWordsToWordList Function =addwordstowordlist +function recapis.h DestroyWordList Function =destroywordlist +function recapis.h MakeWordList Function =makewordlist + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > Recognizer Enumerations +enum rectypes.h ALT_BREAKS Enumeration =alt_breaks +enum rectypes.h CONFIDENCE_LEVEL Enumeration =confidence_level +enum rectypes.h LINE_METRICS Enumeration =line_metrics +enum tpcshrd.h PROPERTY_UNITS Enumeration =property_units + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Recognizer Reference > Recognizer Structures +struct rectypes.h CHARACTER_RANGE Structure =character_range +struct rectypes.h LATTICE_METRICS Structure =lattice_metrics +struct rectypes.h LINE_SEGMENT Structure =line_segment +struct rectypes.h RECO_ATTRS Structure =reco_attrs +struct rectypes.h RECO_GUIDE Structure =reco_guide +struct rectypes.h RECO_LATTICE Structure =reco_lattice +struct rectypes.h RECO_LATTICE_COLUMN Structure =reco_lattice_column +struct rectypes.h RECO_LATTICE_ELEMENT Structure =reco_lattice_element +struct rectypes.h RECO_LATTICE_PROPERTIES Structure =reco_lattice_properties +struct rectypes.h RECO_LATTICE_PROPERTY Structure =reco_lattice_property +struct rectypes.h RECO_RANGE Structure =reco_range +struct tpcshrd.h PACKET_DESCRIPTION Structure =packet_description +struct tpcshrd.h PACKET_PROPERTY Structure =packet_property +struct tpcshrd.h PROPERTY_METRICS Structure =property_metrics +struct tpcshrd.h STROKE_RANGE Structure =stroke_range + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Text Input Panel Reference > Text Input Panel Enumerations +enum peninputpanel.h CorrectionMode Enumeration =correctionmode +enum peninputpanel.h CorrectionPosition Enumeration =correctionposition +enum peninputpanel.h EventMask Enumeration =eventmask +enum peninputpanel.h InPlaceDirection Enumeration =inplacedirection +enum peninputpanel.h InPlaceState Enumeration =inplacestate +enum peninputpanel.h InteractionMode Enumeration =interactionmode +enum peninputpanel.h PanelInputArea Enumeration =panelinputarea + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC API Reference > Text Input Panel Reference > Text Input Panel Interfaces +interface peninputpanel.h IHandWrittenTextInsertion Interface =ihandwrittentextinsertion +interface peninputpanel.h ITextInputPanel Interface =itextinputpanel +interface peninputpanel.h ITextInputPanelEventSink Interface =itextinputpaneleventsink +interface peninputpanel.h ITextInputPanelRunInfo Interface =itextinputpanelruninfo + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC Appendices > Working with the Text Ink Object > Text Ink Reference +interface msinkaut.h IInkLineInfo Interface =iinklineinfo + +# Desktop App Technologies > Desktop App User Interface > User Interaction > Legacy User Interaction Features > Tablet PC > Tablet PC Appendices > Working with the Text Ink Object +struct msinkaut.h INKMETRIC Structure =inkmetric + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Windows > Window Reference > Window Functions +callback winuser.h WNDPROC +function winbase.h WinMain +function winuser.h AdjustWindowRect +function winuser.h AdjustWindowRectEx +function winuser.h AllowSetForegroundWindow +function winuser.h AnimateWindow +function winuser.h AnyPopup +function winuser.h ArrangeIconicWindows +function winuser.h BeginDeferWindowPos +function winuser.h BringWindowToTop +function winuser.h CalculatePopupWindowPosition +function winuser.h CascadeWindows +function winuser.h ChangeWindowMessageFilter +function winuser.h ChangeWindowMessageFilterEx +function winuser.h ChildWindowFromPoint +function winuser.h ChildWindowFromPointEx +function winuser.h CloseWindow +function winuser.h CreateWindow =createwindowa +function winuser.h CreateWindowEx =createwindowexa +function winuser.h DeferWindowPos +function winuser.h DeregisterShellHookWindow +function winuser.h DestroyWindow +function winuser.h EndDeferWindowPos +function winuser.h EndTask +function winuser.h EnumChildWindows +function winuser.h EnumThreadWindows +function winuser.h EnumWindows +function winuser.h FindWindow =findwindowa +function winuser.h FindWindowEx =findwindowexa +function winuser.h GetAltTabInfo =getalttabinfoa +function winuser.h GetAncestor +function winuser.h GetClientRect +function winuser.h GetDesktopWindow +function winuser.h GetForegroundWindow +function winuser.h GetGUIThreadInfo +function winuser.h GetLastActivePopup +function winuser.h GetLayeredWindowAttributes +function winuser.h GetNextWindow +function winuser.h GetParent +function winuser.h GetProcessDefaultLayout +function winuser.h GetShellWindow +function winuser.h GetSysColor +function winuser.h GetTitleBarInfo +function winuser.h GetTopWindow +function winuser.h GetWindow +function winuser.h GetWindowDisplayAffinity +function winuser.h GetWindowInfo +function winuser.h GetWindowModuleFileName =getwindowmodulefilenamea +function winuser.h GetWindowPlacement +function winuser.h GetWindowRect +function winuser.h GetWindowText =getwindowtexta +function winuser.h GetWindowTextLength =getwindowtextlengtha +function winuser.h GetWindowThreadProcessId +function winuser.h InternalGetWindowText +function winuser.h IsChild +function winuser.h IsGUIThread +function winuser.h IsHungAppWindow +function winuser.h IsIconic +function winuser.h IsProcessDPIAware +function winuser.h IsWindow +function winuser.h IsWindowUnicode +function winuser.h IsWindowVisible +function winuser.h IsZoomed +function winuser.h LockSetForegroundWindow +function winuser.h LogicalToPhysicalPoint +function winuser.h MoveWindow +function winuser.h OpenIcon +function winuser.h PhysicalToLogicalPoint +function winuser.h RealChildWindowFromPoint +function winuser.h RealGetWindowClass =realgetwindowclassw +function winuser.h RegisterShellHookWindow +function winuser.h SetForegroundWindow +function winuser.h SetLayeredWindowAttributes +function winuser.h SetParent +function winuser.h SetProcessDefaultLayout +function winuser.h SetProcessDPIAware +function winuser.h SetSysColors +function winuser.h SetWindowDisplayAffinity +function winuser.h SetWindowPlacement +function winuser.h SetWindowPos +function winuser.h SetWindowText =setwindowtexta +function winuser.h ShowOwnedPopups +function winuser.h ShowWindow +function winuser.h ShowWindowAsync +function winuser.h SoundSentry +function winuser.h SwitchToThisWindow +function winuser.h TileWindows +function winuser.h UpdateLayeredWindow +function winuser.h WindowFromPhysicalPoint +function winuser.h WindowFromPoint + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Windows > Window Reference > Window Macros +function windowsx.h GET_X_LPARAM +function windowsx.h GET_Y_LPARAM +function winuser.h MAKELPARAM +function winuser.h MAKELRESULT +function winuser.h MAKEWPARAM + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Windows > Window Reference > Window Structures +struct winuser.h ALTTABINFO +struct winuser.h CHANGEFILTERSTRUCT +struct winuser.h CLIENTCREATESTRUCT +struct winuser.h CREATESTRUCT =createstructa +struct winuser.h GUITHREADINFO +struct winuser.h MINMAXINFO +struct winuser.h NCCALCSIZE_PARAMS +struct winuser.h STYLESTRUCT +struct winuser.h TITLEBARINFO +struct winuser.h TITLEBARINFOEX +struct winuser.h UPDATELAYEREDWINDOWINFO +struct winuser.h WINDOWINFO +struct winuser.h WINDOWPLACEMENT +struct winuser.h WINDOWPOS + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Window Classes > Window Class Reference > Window Class Functions +function winuser.h GetClassInfo =getclassinfoa +function winuser.h GetClassInfoEx =getclassinfoexa +function winuser.h GetClassLong =getclasslonga +function winuser.h GetClassLongPtr =getclasslongptra +function winuser.h GetClassName +function winuser.h GetClassWord +function winuser.h GetWindowLong =getwindowlonga +function winuser.h GetWindowLongPtr =getwindowlongptra +function winuser.h RegisterClass =registerclassa +function winuser.h RegisterClassEx =registerclassexa +function winuser.h SetClassLong =setclasslonga +function winuser.h SetClassLongPtr =setclasslongptra +function winuser.h SetClassWord +function winuser.h SetWindowLong =setwindowlonga +function winuser.h SetWindowLongPtr =setwindowlongptra +function winuser.h UnregisterClass =unregisterclassa + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Window Classes > Window Class Reference > Window Class Structures +struct winuser.h WNDCLASS =wndclassa +struct winuser.h WNDCLASSEX =wndclassexa + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Window Procedures > Window Procedure Reference > Window Procedure Functions +callback winuser.h WindowProc =wndproc +function winuser.h CallWindowProc =callwindowproca +function winuser.h DefWindowProc =defwindowproca + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Messages and Message Queues > Message Reference > Message Functions +callback winuser.h SendAsyncProc +function winuser.h BroadcastSystemMessage +function winuser.h BroadcastSystemMessageEx =broadcastsystemmessageexa +function winuser.h DispatchMessage +function winuser.h GetInputState +function winuser.h GetMessage +function winuser.h GetMessageExtraInfo +function winuser.h GetMessagePos +function winuser.h GetMessageTime +function winuser.h GetQueueStatus +function winuser.h InSendMessage +function winuser.h InSendMessageEx +function winuser.h PeekMessage =peekmessagea +function winuser.h PostMessage =postmessagea +function winuser.h PostQuitMessage +function winuser.h PostThreadMessage =postthreadmessagea +function winuser.h RegisterWindowMessage =registerwindowmessagea +function winuser.h ReplyMessage +function winuser.h SendMessage +function winuser.h SendMessageCallback =sendmessagecallbacka +function winuser.h SendMessageTimeout =sendmessagetimeouta +function winuser.h SendNotifyMessage =sendnotifymessagea +function winuser.h SetMessageExtraInfo +function winuser.h TranslateMessage +function winuser.h WaitMessage + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Messages and Message Queues > Message Reference > Message Structures +struct winuser.h BSMINFO +struct winuser.h MSG + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Timers > Timer Reference > Timer Functions +callback winuser.h TimerProc +function winuser.h KillTimer +function winuser.h SetCoalescableTimer +function winuser.h SetTimer + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Window Properties > Window Property Reference > Window Property Functions +callback winuser.h PropEnumProc =propenumproca +callback winuser.h PropEnumProcEx =propenumprocexa +function winuser.h EnumProps =enumpropsa +function winuser.h EnumPropsEx =enumpropsexa +function winuser.h GetProp =getpropa +function winuser.h RemoveProp =removepropa +function winuser.h SetProp =setpropa + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Configuration > Configuration Reference +function winuser.h GetSystemMetrics +function winuser.h SystemParametersInfo =systemparametersinfoa + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Hooks > Hook Reference > Hook Functions +callback winuser.h CallWndRetProc =hookproc +function winuser.h CallMsgFilter =callmsgfiltera +function winuser.h CallNextHookEx +function winuser.h SetWindowsHookEx =setwindowshookexa +function winuser.h UnhookWindowsHookEx + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Hooks > Hook Reference > Hook Structures +struct winuser.h CBT_CREATEWND =cbt_createwnda +struct winuser.h CBTACTIVATESTRUCT +struct winuser.h CWPRETSTRUCT +struct winuser.h CWPSTRUCT +struct winuser.h DEBUGHOOKINFO +struct winuser.h EVENTMSG +struct winuser.h KBDLLHOOKSTRUCT +struct winuser.h MOUSEHOOKSTRUCT +struct winuser.h MOUSEHOOKSTRUCTEX +struct winuser.h MSLLHOOKSTRUCT + +# Desktop App Technologies > Desktop App User Interface > Windows and Messages > Multiple Document Interface (MDI) > MDI Reference > MDI Functions +function winuser.h CreateMDIWindow =createmdiwindowa +function winuser.h DefFrameProc =defframeproca +function winuser.h DefMDIChildProc =defmdichildproca +function winuser.h TranslateMDISysAccel + +# Desktop App Technologies > Desktop App User Interface > Desktop Window Manager (DWM) > DWM Reference > DWM Constants and Enumerations +enum dwmapi.h DWM_SOURCE_FRAME_SAMPLING +enum dwmapi.h DWM_SYSTEMBACKDROP_TYPE +enum dwmapi.h DWM_TAB_WINDOW_REQUIREMENTS +enum dwmapi.h DWMFLIP3DWINDOWPOLICY +enum dwmapi.h DWMNCRENDERINGPOLICY +enum dwmapi.h DWMTRANSITION_OWNEDWINDOW_TARGET +enum dwmapi.h DWMWINDOWATTRIBUTE +enum dwmapi.h GESTURE_TYPE + +# Desktop App Technologies > Desktop App User Interface > Desktop Window Manager (DWM) > DWM Reference > DWM Functions +function dwmapi.h DwmAttachMilContent +function dwmapi.h DwmDefWindowProc +function dwmapi.h DwmDetachMilContent +function dwmapi.h DwmEnableBlurBehindWindow +function dwmapi.h DwmEnableComposition +function dwmapi.h DwmEnableMMCSS +function dwmapi.h DwmExtendFrameIntoClientArea +function dwmapi.h DwmFlush +function dwmapi.h DwmGetColorizationColor +function dwmapi.h DwmGetCompositionTimingInfo +function dwmapi.h DwmGetGraphicsStreamClient +function dwmapi.h DwmGetGraphicsStreamTransformHint +function dwmapi.h DwmGetTransportAttributes +function dwmapi.h DwmGetUnmetTabRequirements +function dwmapi.h DwmGetWindowAttribute +function dwmapi.h DwmInvalidateIconicBitmaps +function dwmapi.h DwmIsCompositionEnabled +function dwmapi.h DwmModifyPreviousDxFrameDuration +function dwmapi.h DwmQueryThumbnailSourceSize +function dwmapi.h DwmRegisterThumbnail +function dwmapi.h DwmRenderGesture +function dwmapi.h DwmSetDxFrameDuration +function dwmapi.h DwmSetIconicLivePreviewBitmap +function dwmapi.h DwmSetIconicThumbnail +function dwmapi.h DwmSetPresentParameters +function dwmapi.h DwmSetWindowAttribute +function dwmapi.h DwmShowContact +function dwmapi.h DwmTetherContact +function dwmapi.h DwmTransitionOwnedWindow +function dwmapi.h DwmUnregisterThumbnail +function dwmapi.h DwmUpdateThumbnailProperties + +# Desktop App Technologies > Desktop App User Interface > Desktop Window Manager (DWM) > DWM Reference > DWM Structures +struct dwmapi.h DWM_BLURBEHIND +struct dwmapi.h DWM_PRESENT_PARAMETERS +struct dwmapi.h DWM_THUMBNAIL_PROPERTIES +struct dwmapi.h DWM_TIMING_INFO +struct dwmapi.h MilMatrix3x2D +struct dwmapi.h UNSIGNED_RATIO + +# Desktop App Technologies > Desktop App User Interface > Dialog Boxes > Dialog Box Reference > Dialog Box Functions +callback winuser.h DLGPROC +callback winuser.h MSGBOXCALLBACK +function winuser.h CreateDialog =createdialoga +function winuser.h CreateDialogIndirect =createdialogindirecta +function winuser.h CreateDialogIndirectParam =createdialogindirectparama +function winuser.h CreateDialogParam =createdialogparama +function winuser.h DefDlgProc =defdlgprocw +function winuser.h DialogBox =dialogboxa +function winuser.h DialogBoxIndirect =dialogboxindirecta +function winuser.h DialogBoxIndirectParam =dialogboxindirectparama +function winuser.h DialogBoxParam =dialogboxparama +function winuser.h EndDialog +function winuser.h GetDialogBaseUnits +function winuser.h GetDlgCtrlID +function winuser.h GetDlgItem +function winuser.h GetDlgItemInt +function winuser.h GetDlgItemText =getdlgitemtexta +function winuser.h GetNextDlgGroupItem +function winuser.h GetNextDlgTabItem +function winuser.h IsDialogMessage =isdialogmessagea +function winuser.h MapDialogRect +function winuser.h MessageBox +function winuser.h MessageBoxEx =messageboxexa +function winuser.h MessageBoxIndirect =messageboxindirecta +function winuser.h SendDlgItemMessage =senddlgitemmessagea +function winuser.h SetDlgItemInt +function winuser.h SetDlgItemText =setdlgitemtexta + +# Desktop App Technologies > Desktop App User Interface > Dialog Boxes > Dialog Box Reference > Dialog Box Structures +struct winuser.h DLGITEMTEMPLATE +struct winuser.h DLGTEMPLATE +struct winuser.h MSGBOXPARAMS =msgboxparamsa + +# Desktop App Technologies > Desktop App User Interface > Dialog Boxes > Common Dialog Box Library > Common Dialog Box Reference > Common Dialog Box Functions +callback commdlg.h CCHookProc =lpcchookproc +callback commdlg.h LPCFHOOKPROC +callback commdlg.h LPFRHOOKPROC +callback commdlg.h LPOFNHOOKPROC +callback commdlg.h LPPAGEPAINTHOOK +callback commdlg.h LPPAGESETUPHOOK +callback commdlg.h LPPRINTHOOKPROC +callback commdlg.h LPSETUPHOOKPROC +function commdlg.h CommDlgExtendedError +function commdlg.h FindText =findtexta +function commdlg.h GetFileTitle =getfiletitlea +function commdlg.h GetOpenFileName =getopenfilenamea +function commdlg.h GetSaveFileName =getsavefilenamea +function commdlg.h ReplaceText =replacetexta +struct commdlg.h ChooseFont =choosefonta + +# Desktop App Technologies > Desktop App User Interface > Dialog Boxes > Common Dialog Box Library > Common Dialog Box Reference > Common Dialog Box Interfaces +interface commdlg.h IPrintDialogCallback +interface commdlg.h IPrintDialogServices + +# Desktop App Technologies > Desktop App User Interface > Dialog Boxes > Common Dialog Box Library > Common Dialog Box Reference > Common Dialog Box Structures +struct commdlg.h CHOOSECOLOR =choosecolora-r1 +struct commdlg.h CHOOSEFONT =choosefonta +struct commdlg.h DEVNAMES +struct commdlg.h FINDREPLACE =findreplacea +struct commdlg.h OFNOTIFY =ofnotifya +struct commdlg.h OFNOTIFYEX =ofnotifyexa +struct commdlg.h OPENFILENAME =openfilenamea +struct commdlg.h OPENFILENAME_NT4 =openfilename_nt4a +struct commdlg.h PAGESETUPDLG =pagesetupdlga +struct commdlg.h PRINTDLG =printdlga +struct commdlg.h PRINTDLGEX =printdlgexa +struct commdlg.h PRINTPAGERANGE + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Introduction to Resources > Resource Reference > Resource Functions +callback libloaderapi.h EnumResNameProc =enumresnameprocw +callback libloaderapi.h EnumResTypeProc =enumrestypeprocw +function libloaderapi.h EnumResourceLanguagesEx =enumresourcelanguagesexw +function libloaderapi.h EnumResourceNames =enumresourcenamesa +function libloaderapi.h EnumResourceNamesEx =enumresourcenamesexw +function libloaderapi.h EnumResourceTypesEx =enumresourcetypesexw +function libloaderapi.h FreeResource +function libloaderapi.h LoadResource +function libloaderapi.h LockResource +function libloaderapi.h SizeofResource +function resourceindexer.h CreateResourceIndexer +function resourceindexer.h DestroyIndexedResults +function resourceindexer.h DestroyResourceIndexer +function resourceindexer.h IndexFilePath +function winbase.h BeginUpdateResource =beginupdateresourcea +function winbase.h EndUpdateResource =endupdateresourcea +function winbase.h EnumResourceLanguages =enumresourcelanguagesa +function winbase.h EnumResourceTypes =enumresourcetypesa +function winbase.h FindResource =findresourcea +function winbase.h FindResourceEx =findresourceexa +function winbase.h UpdateResource =updateresourcea +function winuser.h CopyImage +function winuser.h LoadImage =loadimagea + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Introduction to Resources > Resource Reference > Resource Macros +function winuser.h IS_INTRESOURCE +function winuser.h MAKEINTRESOURCE =makeintresourcea + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Introduction to Resources > Resource Reference > Resource Structures +struct resourceindexer.h IndexedResourceQualifier +struct winnt.h MESSAGE_RESOURCE_BLOCK +struct winnt.h MESSAGE_RESOURCE_DATA +struct winnt.h MESSAGE_RESOURCE_ENTRY +struct winuser.h CURSORSHAPE + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Carets > Caret Reference > Caret Functions +function winuser.h CreateCaret +function winuser.h DestroyCaret +function winuser.h GetCaretBlinkTime +function winuser.h GetCaretPos +function winuser.h HideCaret +function winuser.h SetCaretBlinkTime +function winuser.h SetCaretPos +function winuser.h ShowCaret + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Cursors > Cursor Reference > Cursor Functions +function winuser.h ClipCursor +function winuser.h CopyCursor +function winuser.h CreateCursor +function winuser.h DestroyCursor +function winuser.h GetClipCursor +function winuser.h GetCursor +function winuser.h GetCursorInfo +function winuser.h GetCursorPos +function winuser.h GetPhysicalCursorPos +function winuser.h LoadCursor =loadcursora +function winuser.h LoadCursorFromFile =loadcursorfromfilea +function winuser.h SetCursor +function winuser.h SetCursorPos +function winuser.h SetPhysicalCursorPos +function winuser.h SetSystemCursor +function winuser.h ShowCursor + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Cursors > Cursor Reference > Cursor Structures +struct winuser.h CURSORINFO + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Icons > Icon Reference > Icon Functions +function shellapi.h DuplicateIcon +function shellapi.h ExtractAssociatedIcon =extractassociatedicona +function shellapi.h ExtractIcon =extracticona +function shellapi.h ExtractIconEx =extracticonexa +function winuser.h CopyIcon +function winuser.h CreateIcon +function winuser.h CreateIconFromResource +function winuser.h CreateIconFromResourceEx +function winuser.h CreateIconIndirect +function winuser.h DestroyIcon +function winuser.h DrawIcon +function winuser.h DrawIconEx +function winuser.h GetIconInfo +function winuser.h GetIconInfoEx =geticoninfoexa +function winuser.h LoadIcon =loadicona +function winuser.h LookupIconIdFromDirectory +function winuser.h LookupIconIdFromDirectoryEx +function winuser.h PrivateExtractIcons =privateextracticonsa + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Icons > Icon Reference > Icon Structures +struct winuser.h ICONINFO +struct winuser.h ICONINFOEX =iconinfoexa +struct winuser.h ICONMETRICS =iconmetricsa + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Keyboard Accelerators > Keyboard Accelerator Reference > Keyboard Accelerator Functions +function winuser.h CopyAcceleratorTable =copyacceleratortablea +function winuser.h CreateAcceleratorTable =createacceleratortablea +function winuser.h DestroyAcceleratorTable +function winuser.h LoadAccelerators =loadacceleratorsa +function winuser.h TranslateAccelerator =translateacceleratora + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Keyboard Accelerators > Keyboard Accelerator Reference > Keyboard Accelerator Structures +struct winuser.h ACCEL + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Menus > Menu Reference > Menu Functions +function winuser.h AppendMenu =appendmenua +function winuser.h CheckMenuItem +function winuser.h CheckMenuRadioItem +function winuser.h CreateMenu +function winuser.h CreatePopupMenu +function winuser.h DeleteMenu +function winuser.h DestroyMenu +function winuser.h DrawMenuBar +function winuser.h EnableMenuItem +function winuser.h EndMenu +function winuser.h GetMenu +function winuser.h GetMenuBarInfo +function winuser.h GetMenuCheckMarkDimensions +function winuser.h GetMenuDefaultItem +function winuser.h GetMenuInfo +function winuser.h GetMenuItemCount +function winuser.h GetMenuItemID +function winuser.h GetMenuItemInfo =getmenuiteminfoa +function winuser.h GetMenuItemRect +function winuser.h GetMenuState +function winuser.h GetMenuString =getmenustringa +function winuser.h GetSubMenu +function winuser.h GetSystemMenu +function winuser.h HiliteMenuItem +function winuser.h InsertMenu =insertmenua +function winuser.h InsertMenuItem =insertmenuitema +function winuser.h IsMenu +function winuser.h LoadMenu =loadmenua +function winuser.h LoadMenuIndirect =loadmenuindirecta +function winuser.h MenuItemFromPoint +function winuser.h ModifyMenu =modifymenua +function winuser.h RemoveMenu +function winuser.h SetMenu +function winuser.h SetMenuDefaultItem +function winuser.h SetMenuInfo +function winuser.h SetMenuItemBitmaps +function winuser.h SetMenuItemInfo =setmenuiteminfoa +function winuser.h TrackPopupMenu +function winuser.h TrackPopupMenuEx + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Menus > Menu Reference > Menu Structures +struct winuser.h MDINEXTMENU +struct winuser.h MENUBARINFO +struct winuser.h MENUGETOBJECTINFO +struct winuser.h MENUINFO +struct winuser.h MENUITEMINFO =menuiteminfoa +struct winuser.h MENUITEMTEMPLATE +struct winuser.h MENUITEMTEMPLATEHEADER +struct winuser.h TPMPARAMS + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Strings > String Reference > String Functions +function winbase.h lstrcat =lstrcata +function winbase.h lstrcmp =lstrcmpa +function winbase.h lstrcmpi =lstrcmpia +function winbase.h lstrcpy =lstrcpya +function winbase.h lstrcpyn =lstrcpyna +function winbase.h lstrlen =lstrlena +function winuser.h CharLower =charlowera +function winuser.h CharLowerBuff =charlowerbuffa +function winuser.h CharNext =charnexta +function winuser.h CharNextExA +function winuser.h CharPrev =charpreva +function winuser.h CharPrevExA +function winuser.h CharToOem =chartooema +function winuser.h CharToOemBuff =chartooembuffa +function winuser.h CharUpper =charuppera +function winuser.h CharUpperBuff =charupperbuffa +function winuser.h IsCharAlpha =ischaralphaa +function winuser.h IsCharAlphaNumeric =ischaralphanumerica +function winuser.h IsCharLower =ischarlowera +function winuser.h IsCharUpper =ischaruppera +function winuser.h LoadString =loadstringa +function winuser.h OemToChar =oemtochara +function winuser.h OemToCharBuff =oemtocharbuffa +function winuser.h wsprintf =wsprintfa +function winuser.h wvsprintf =wvsprintfa + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Strings > String Reference > Strsafe Functions +function strsafe.h StringCbCat =stringcbcata +function strsafe.h StringCbCatEx =stringcbcatexa +function strsafe.h StringCbCatN =stringcbcatna +function strsafe.h StringCbCatNEx =stringcbcatnexa +function strsafe.h StringCbCopy =stringcbcopya +function strsafe.h StringCbCopyEx =stringcbcopyexa +function strsafe.h StringCbCopyN =stringcbcopyna +function strsafe.h StringCbCopyNEx =stringcbcopynexa +function strsafe.h StringCbGets =stringcbgetsa +function strsafe.h StringCbGetsEx =stringcbgetsexa +function strsafe.h StringCbLength =stringcblengtha +function strsafe.h StringCbPrintf =stringcbprintfa +function strsafe.h StringCbPrintf_l =stringcbprintf_la +function strsafe.h StringCbPrintf_lEx =stringcbprintf_lexa +function strsafe.h StringCbPrintfEx =stringcbprintfexa +function strsafe.h StringCbVPrintf =stringcbvprintfa +function strsafe.h StringCbVPrintf_l =stringcbvprintf_la +function strsafe.h StringCbVPrintf_lEx =stringcbvprintf_lexa +function strsafe.h StringCbVPrintfEx =stringcbvprintfexa +function strsafe.h StringCchCat =stringcchcata +function strsafe.h StringCchCatEx =stringcchcatexa +function strsafe.h StringCchCatN =stringcchcatna +function strsafe.h StringCchCatNEx =stringcchcatnexa +function strsafe.h StringCchCopy =stringcchcopya +function strsafe.h StringCchCopyEx =stringcchcopyexa +function strsafe.h StringCchCopyN =stringcchcopyna +function strsafe.h StringCchCopyNEx =stringcchcopynexa +function strsafe.h StringCchGets =stringcchgetsa +function strsafe.h StringCchGetsEx =stringcchgetsexa +function strsafe.h StringCchLength =stringcchlengtha +function strsafe.h StringCchPrintf =stringcchprintfa +function strsafe.h StringCchPrintf_l =stringcchprintf_la +function strsafe.h StringCchPrintf_lEx =stringcchprintf_lexa +function strsafe.h StringCchPrintfEx =stringcchprintfexa +function strsafe.h StringCchVPrintf =stringcchvprintfa +function strsafe.h StringCchVPrintf_l =stringcchvprintf_la +function strsafe.h StringCchVPrintf_lEx =stringcchvprintf_lexa +function strsafe.h StringCchVPrintfEx =stringcchvprintfexa + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Version Information > Version Information Reference > Version Information Functions +function winver.h GetFileVersionInfo =getfileversioninfoa +function winver.h GetFileVersionInfoEx =getfileversioninfoexa +function winver.h GetFileVersionInfoSize =getfileversioninfosizea +function winver.h GetFileVersionInfoSizeEx =getfileversioninfosizeexa +function winver.h VerFindFile =verfindfilea +function winver.h VerInstallFile =verinstallfilea +function winver.h VerLanguageName =verlanguagenamea +function winver.h VerQueryValue =verqueryvaluea + +# Desktop App Technologies > Desktop App User Interface > Menus and Other Resources > Version Information > Version Information Reference > Version Information Structures +struct verrsrc.h VS_FIXEDFILEINFO + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Clipboard > Clipboard Reference > Clipboard Functions +function winuser.h AddClipboardFormatListener +function winuser.h ChangeClipboardChain +function winuser.h CloseClipboard +function winuser.h CountClipboardFormats +function winuser.h EmptyClipboard +function winuser.h EnumClipboardFormats +function winuser.h GetClipboardData +function winuser.h GetClipboardFormatName =getclipboardformatnamea +function winuser.h GetClipboardOwner +function winuser.h GetClipboardSequenceNumber +function winuser.h GetClipboardViewer +function winuser.h GetOpenClipboardWindow +function winuser.h GetPriorityClipboardFormat +function winuser.h GetUpdatedClipboardFormats +function winuser.h IsClipboardFormatAvailable +function winuser.h OpenClipboard +function winuser.h RegisterClipboardFormat =registerclipboardformata +function winuser.h RemoveClipboardFormatListener +function winuser.h SetClipboardData +function winuser.h SetClipboardViewer + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Clipboard > Clipboard Reference > Clipboard Structures +struct wingdi.h METAFILEPICT + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Data Copy > Data Copy Reference +struct winuser.h COPYDATASTRUCT + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange (DDE) > DDE Reference > DDE Functions +function dde.h DdeSetQualityOfService +function dde.h FreeDDElParam +function dde.h ImpersonateDdeClientWindow +function dde.h PackDDElParam +function dde.h ReuseDDElParam +function dde.h UnpackDDElParam + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange (DDE) > DDE Reference > DDE Structures +struct dde.h DDEACK +struct dde.h DDEADVISE +struct dde.h DDEDATA +struct dde.h DDEPOKE +struct ddeml.h HSZPAIR + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange (DDE) > Atom Reference > Atom Functions +function winbase.h AddAtom =addatomw +function winbase.h DeleteAtom +function winbase.h FindAtom =findatoma +function winbase.h GetAtomName =getatomnamea +function winbase.h GlobalAddAtom =globaladdatoma +function winbase.h GlobalAddAtomEx =globaladdatomexa +function winbase.h GlobalDeleteAtom +function winbase.h GlobalFindAtom =globalfindatoma +function winbase.h GlobalGetAtomName =globalgetatomnamea +function winbase.h InitAtomTable + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange (DDE) > Atom Reference > Atom Macros +function winbase.h MAKEINTATOM + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange Management Library (DDEML) > DDEML Reference > DDEML Functions +callback ddeml.h DdeCallback =pfncallback +function ddeml.h DdeAbandonTransaction +function ddeml.h DdeAccessData +function ddeml.h DdeAddData +function ddeml.h DdeClientTransaction +function ddeml.h DdeCmpStringHandles +function ddeml.h DdeConnect +function ddeml.h DdeConnectList +function ddeml.h DdeCreateDataHandle +function ddeml.h DdeCreateStringHandle =ddecreatestringhandlea +function ddeml.h DdeDisconnect +function ddeml.h DdeDisconnectList +function ddeml.h DdeEnableCallback +function ddeml.h DdeFreeDataHandle +function ddeml.h DdeFreeStringHandle +function ddeml.h DdeGetData +function ddeml.h DdeGetLastError +function ddeml.h DdeImpersonateClient +function ddeml.h DdeInitialize =ddeinitializea +function ddeml.h DdeKeepStringHandle +function ddeml.h DdeNameService +function ddeml.h DdePostAdvise +function ddeml.h DdeQueryConvInfo +function ddeml.h DdeQueryNextServer +function ddeml.h DdeQueryString =ddequerystringa +function ddeml.h DdeReconnect +function ddeml.h DdeSetUserHandle +function ddeml.h DdeUnaccessData +function ddeml.h DdeUninitialize + +# Desktop App Technologies > Desktop App User Interface > Data Exchange > Dynamic Data Exchange Management Library (DDEML) > DDEML Reference > DDEML Structures +struct ddeml.h CONVCONTEXT +struct ddeml.h CONVINFO +struct ddeml.h DDEML_MSG_HOOK_DATA +struct ddeml.h MONCBSTRUCT +struct ddeml.h MONCONVSTRUCT +struct ddeml.h MONERRSTRUCT +struct ddeml.h MONHSZSTRUCT =monhszstructa +struct ddeml.h MONLINKSTRUCT +struct ddeml.h MONMSGSTRUCT + +# Desktop App Technologies > Desktop App User Interface > High DPI > High DPI Reference +enum shellscalingapi.h MONITOR_DPI_TYPE +enum shellscalingapi.h PROCESS_DPI_AWARENESS +enum windef.h _DPI_HOSTING_BEHAVIOR +enum windef.h DPI_AWARENESS +enum winuser.h DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS +enum winuser.h DIALOG_DPI_CHANGE_BEHAVIORS +function shellscalingapi.h GetDpiForMonitor +function shellscalingapi.h GetProcessDpiAwareness +function shellscalingapi.h SetProcessDpiAwareness +function uxtheme.h OpenThemeDataForDpi +function winuser.h AdjustWindowRectExForDpi +function winuser.h AreDpiAwarenessContextsEqual +function winuser.h EnableNonClientDpiScaling +function winuser.h GetAwarenessFromDpiAwarenessContext +function winuser.h GetDialogControlDpiChangeBehavior +function winuser.h GetDialogDpiChangeBehavior +function winuser.h GetDpiForSystem +function winuser.h GetDpiForWindow +function winuser.h GetDpiFromDpiAwarenessContext +function winuser.h GetSystemDpiForProcess +function winuser.h GetSystemMetricsForDpi +function winuser.h GetThreadDpiAwarenessContext +function winuser.h GetThreadDpiHostingBehavior +function winuser.h GetWindowDpiAwarenessContext +function winuser.h GetWindowDpiHostingBehavior +function winuser.h IsValidDpiAwarenessContext +function winuser.h LogicalToPhysicalPointForPerMonitorDPI +function winuser.h PhysicalToLogicalPointForPerMonitorDPI +function winuser.h SetDialogControlDpiChangeBehavior +function winuser.h SetDialogDpiChangeBehavior +function winuser.h SetProcessDpiAwarenessContext +function winuser.h SetThreadCursorCreationScaling +function winuser.h SetThreadDpiAwarenessContext +function winuser.h SetThreadDpiHostingBehavior +function winuser.h SystemParametersInfoForDpi + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Data Types +struct uianimation.h UI_ANIMATION_KEYFRAME =__midl___midl_itf_uianimation_0000_0002_0003 + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Enumerations +enum uianimation.h UI_ANIMATION_DEPENDENCIES +enum uianimation.h UI_ANIMATION_IDLE_BEHAVIOR +enum uianimation.h UI_ANIMATION_MANAGER_STATUS +enum uianimation.h UI_ANIMATION_MODE +enum uianimation.h UI_ANIMATION_PRIORITY_EFFECT +enum uianimation.h UI_ANIMATION_REPEAT_MODE +enum uianimation.h UI_ANIMATION_ROUNDING_MODE +enum uianimation.h UI_ANIMATION_SCHEDULING_RESULT +enum uianimation.h UI_ANIMATION_SLOPE +enum uianimation.h UI_ANIMATION_STORYBOARD_STATUS +enum uianimation.h UI_ANIMATION_TIMER_CLIENT_STATUS +enum uianimation.h UI_ANIMATION_UPDATE_RESULT + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Interfaces > Animation Management Interfaces +interface uianimation.h IUIAnimationManager +interface uianimation.h IUIAnimationManager2 +interface uianimation.h IUIAnimationManagerEventHandler +interface uianimation.h IUIAnimationManagerEventHandler2 + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Interfaces > Storyboard Interfaces +interface uianimation.h IUIAnimationLoopIterationChangeHandler2 +interface uianimation.h IUIAnimationPriorityComparison +interface uianimation.h IUIAnimationPriorityComparison2 +interface uianimation.h IUIAnimationStoryboard +interface uianimation.h IUIAnimationStoryboard2 +interface uianimation.h IUIAnimationStoryboardEventHandler +interface uianimation.h IUIAnimationStoryboardEventHandler2 + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Interfaces > Transition Interfaces +interface uianimation.h IUIAnimationInterpolator +interface uianimation.h IUIAnimationInterpolator2 +interface uianimation.h IUIAnimationPrimitiveInterpolation +interface uianimation.h IUIAnimationTransition +interface uianimation.h IUIAnimationTransition2 +interface uianimation.h IUIAnimationTransitionFactory +interface uianimation.h IUIAnimationTransitionFactory2 +interface uianimation.h IUIAnimationTransitionLibrary +interface uianimation.h IUIAnimationTransitionLibrary2 +interface uianimation.h IUIAnimationVariable +interface uianimation.h IUIAnimationVariable2 +interface uianimation.h IUIAnimationVariableChangeHandler +interface uianimation.h IUIAnimationVariableChangeHandler2 +interface uianimation.h IUIAnimationVariableCurveChangeHandler2 +interface uianimation.h IUIAnimationVariableIntegerChangeHandler +interface uianimation.h IUIAnimationVariableIntegerChangeHandler2 + +# Desktop App Technologies > Desktop App User Interface > Windows Animation Manager > Windows Animation Reference > Interfaces > Animation Timer Interfaces +interface uianimation.h IUIAnimationTimer +interface uianimation.h IUIAnimationTimerClientEventHandler +interface uianimation.h IUIAnimationTimerEventHandler +interface uianimation.h IUIAnimationTimerUpdateHandler + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Custom Draw > Custom Draw Reference > Custom Draw Structures +struct commctrl.h NMCUSTOMDRAW + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Animation > Animation Control Reference > Animation Control Macros +function commctrl.h Animate_Close +function commctrl.h Animate_Create +function commctrl.h Animate_IsPlaying +function commctrl.h Animate_Open +function commctrl.h Animate_OpenEx +function commctrl.h Animate_Play +function commctrl.h Animate_Seek +function commctrl.h Animate_Stop + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Button > Button Control Reference > Button Control Functions +function winuser.h CheckDlgButton +function winuser.h CheckRadioButton +function winuser.h IsDlgButtonChecked + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Button > Button Control Reference > Button Control Macros +function commctrl.h Button_GetIdealSize +function commctrl.h Button_GetImageList +function commctrl.h Button_GetNote +function commctrl.h Button_GetNoteLength +function commctrl.h Button_GetSplitInfo +function commctrl.h Button_GetTextMargin +function commctrl.h Button_SetDropDownState +function commctrl.h Button_SetElevationRequiredState +function commctrl.h Button_SetImageList +function commctrl.h Button_SetNote +function commctrl.h Button_SetSplitInfo +function commctrl.h Button_SetTextMargin +function windowsx.h Button_Enable +function windowsx.h Button_GetCheck +function windowsx.h Button_GetState +function windowsx.h Button_GetText +function windowsx.h Button_GetTextLength +function windowsx.h Button_SetCheck +function windowsx.h Button_SetState +function windowsx.h Button_SetStyle +function windowsx.h Button_SetText + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Button > Button Control Reference > Button Control Structures +struct commctrl.h BUTTON_IMAGELIST +struct commctrl.h BUTTON_SPLITINFO +struct commctrl.h NMBCDROPDOWN +struct commctrl.h NMBCHOTITEM + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > ComboBox > ComboBox Control Reference > ComboBox Control Functions +function winuser.h DlgDirListComboBox =dlgdirlistcomboboxa +function winuser.h DlgDirSelectComboBoxEx =dlgdirselectcomboboxexa +function winuser.h GetComboBoxInfo + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > ComboBox > ComboBox Control Reference > ComboBox Control Macros +function commctrl.h ComboBox_GetCueBannerText +function commctrl.h ComboBox_GetMinVisible +function commctrl.h ComboBox_SetCueBannerText +function commctrl.h ComboBox_SetMinVisible +function windowsx.h ComboBox_AddItemData +function windowsx.h ComboBox_AddString +function windowsx.h ComboBox_DeleteString +function windowsx.h ComboBox_Dir +function windowsx.h ComboBox_Enable +function windowsx.h ComboBox_FindItemData +function windowsx.h ComboBox_FindString +function windowsx.h ComboBox_FindStringExact +function windowsx.h ComboBox_GetCount +function windowsx.h ComboBox_GetCurSel +function windowsx.h ComboBox_GetDroppedControlRect +function windowsx.h ComboBox_GetDroppedState +function windowsx.h ComboBox_GetExtendedUI +function windowsx.h ComboBox_GetItemData +function windowsx.h ComboBox_GetItemHeight +function windowsx.h ComboBox_GetLBText +function windowsx.h ComboBox_GetLBTextLen +function windowsx.h ComboBox_GetText +function windowsx.h ComboBox_GetTextLength +function windowsx.h ComboBox_InsertItemData +function windowsx.h ComboBox_InsertString +function windowsx.h ComboBox_LimitText +function windowsx.h ComboBox_ResetContent +function windowsx.h ComboBox_SelectItemData +function windowsx.h ComboBox_SelectString +function windowsx.h ComboBox_SetCurSel +function windowsx.h ComboBox_SetExtendedUI +function windowsx.h ComboBox_SetItemData +function windowsx.h ComboBox_SetItemHeight +function windowsx.h ComboBox_SetText +function windowsx.h ComboBox_ShowDropdown + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > ComboBox > ComboBox Control Reference > ComboBox Control Structures +struct winuser.h COMBOBOXINFO +struct winuser.h COMPAREITEMSTRUCT +struct winuser.h DRAWITEMSTRUCT +struct winuser.h MEASUREITEMSTRUCT + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > ComboBoxEx > ComboBoxEx Control Reference > ComboBoxEx Structures +struct commctrl.h COMBOBOXEXITEM =comboboxexitema +struct commctrl.h NMCBEDRAGBEGIN =nmcbedragbegina +struct commctrl.h NMCBEENDEDIT =nmcbeendedita +struct commctrl.h NMCOMBOBOXEX =nmcomboboxexa + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Date and Time Picker > Date and Time Picker Control Reference > Date and Time Picker Macros +function commctrl.h DateTime_CloseMonthCal +function commctrl.h DateTime_GetDateTimePickerInfo +function commctrl.h DateTime_GetIdealSize +function commctrl.h DateTime_GetMonthCal +function commctrl.h DateTime_GetMonthCalColor +function commctrl.h DateTime_GetMonthCalFont +function commctrl.h DateTime_GetMonthCalStyle +function commctrl.h DateTime_GetRange +function commctrl.h DateTime_GetSystemtime +function commctrl.h DateTime_SetFormat +function commctrl.h DateTime_SetMonthCalColor +function commctrl.h DateTime_SetMonthCalFont +function commctrl.h DateTime_SetMonthCalStyle +function commctrl.h DateTime_SetRange +function commctrl.h DateTime_SetSystemtime + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Date and Time Picker > Date and Time Picker Control Reference > Date and Time Picker Structures +struct commctrl.h DATETIMEPICKERINFO +struct commctrl.h NMDATETIMECHANGE +struct commctrl.h NMDATETIMEFORMAT =nmdatetimeformata +struct commctrl.h NMDATETIMEFORMATQUERY =nmdatetimeformatquerya +struct commctrl.h NMDATETIMESTRING =nmdatetimestringa +struct commctrl.h NMDATETIMEWMKEYDOWN =nmdatetimewmkeydowna + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Edit Control > Edit Control Reference > Edit Control Functions +callback winuser.h EditWordBreakProc =editwordbreakproca + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Edit Control > Edit Control Reference > Edit Control Macros +function commctrl.h Edit_GetCueBannerText +function commctrl.h Edit_GetHilite +function commctrl.h Edit_HideBalloonTip +function commctrl.h Edit_NoSetFocus +function commctrl.h Edit_SetCueBannerText +function commctrl.h Edit_SetCueBannerTextFocused +function commctrl.h Edit_SetHilite +function commctrl.h Edit_ShowBalloonTip +function commctrl.h Edit_TakeFocus +function windowsx.h Edit_CanUndo +function windowsx.h Edit_EmptyUndoBuffer +function windowsx.h Edit_Enable +function windowsx.h Edit_FmtLines +function windowsx.h Edit_GetFirstVisibleLine +function windowsx.h Edit_GetHandle +function windowsx.h Edit_GetLine +function windowsx.h Edit_GetLineCount +function windowsx.h Edit_GetModify +function windowsx.h Edit_GetPasswordChar +function windowsx.h Edit_GetRect +function windowsx.h Edit_GetSel +function windowsx.h Edit_GetText +function windowsx.h Edit_GetTextLength +function windowsx.h Edit_GetWordBreakProc +function windowsx.h Edit_LimitText +function windowsx.h Edit_LineFromChar +function windowsx.h Edit_LineIndex +function windowsx.h Edit_LineLength +function windowsx.h Edit_ReplaceSel +function windowsx.h Edit_Scroll +function windowsx.h Edit_ScrollCaret +function windowsx.h Edit_SetHandle +function windowsx.h Edit_SetModify +function windowsx.h Edit_SetPasswordChar +function windowsx.h Edit_SetReadOnly +function windowsx.h Edit_SetRect +function windowsx.h Edit_SetRectNoPaint +function windowsx.h Edit_SetSel +function windowsx.h Edit_SetTabStops +function windowsx.h Edit_SetText +function windowsx.h Edit_SetWordBreakProc +function windowsx.h Edit_Undo + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Edit Control > Edit Control Reference > Edit Control Structures +struct commctrl.h EDITBALLOONTIP + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Flat Scroll Bar > Flat Scroll Bar Reference > Flat Scroll Bar Functions +function commctrl.h FlatSB_EnableScrollBar +function commctrl.h FlatSB_GetScrollInfo +function commctrl.h FlatSB_GetScrollPos +function commctrl.h FlatSB_GetScrollProp +function commctrl.h FlatSB_GetScrollPropPtr +function commctrl.h FlatSB_GetScrollRange +function commctrl.h FlatSB_SetScrollInfo +function commctrl.h FlatSB_SetScrollPos +function commctrl.h FlatSB_SetScrollProp +function commctrl.h FlatSB_SetScrollRange +function commctrl.h FlatSB_ShowScrollBar +function commctrl.h InitializeFlatSB +function commctrl.h UninitializeFlatSB + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Header Control > Header Control Reference > Header Control Macros +function commctrl.h Header_ClearAllFilters +function commctrl.h Header_ClearFilter +function commctrl.h Header_CreateDragImage +function commctrl.h Header_DeleteItem +function commctrl.h Header_EditFilter +function commctrl.h Header_GetBitmapMargin +function commctrl.h Header_GetFocusedItem +function commctrl.h Header_GetImageList +function commctrl.h Header_GetItem +function commctrl.h Header_GetItemCount +function commctrl.h Header_GetItemDropDownRect +function commctrl.h Header_GetItemRect +function commctrl.h Header_GetOrderArray +function commctrl.h Header_GetOverflowRect +function commctrl.h Header_GetStateImageList +function commctrl.h Header_GetUnicodeFormat +function commctrl.h Header_InsertItem +function commctrl.h Header_Layout +function commctrl.h Header_OrderToIndex +function commctrl.h Header_SetBitmapMargin +function commctrl.h Header_SetFilterChangeTimeout +function commctrl.h Header_SetFocusedItem +function commctrl.h Header_SetHotDivider +function commctrl.h Header_SetImageList +function commctrl.h Header_SetItem +function commctrl.h Header_SetOrderArray +function commctrl.h Header_SetStateImageList +function commctrl.h Header_SetUnicodeFormat + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Header Control > Header Control Reference > Header Control Structures +struct commctrl.h HDHITTESTINFO +struct commctrl.h HDITEM =hditema +struct commctrl.h HDLAYOUT +struct commctrl.h HDTEXTFILTER =hd_textfiltera +struct commctrl.h NMHDDISPINFO =nmhddispinfoa +struct commctrl.h NMHDFILTERBTNCLICK +struct commctrl.h NMHEADER =nmheadera + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Image Lists > Image Lists Reference > Image List Functions +function commctrl.h HIMAGELIST_QueryInterface +function commctrl.h ImageList_Add +function commctrl.h ImageList_AddMasked +function commctrl.h ImageList_BeginDrag +function commctrl.h ImageList_Copy +function commctrl.h ImageList_Create +function commctrl.h ImageList_Destroy +function commctrl.h ImageList_DragEnter +function commctrl.h ImageList_DragLeave +function commctrl.h ImageList_DragMove +function commctrl.h ImageList_DragShowNolock +function commctrl.h ImageList_Draw +function commctrl.h ImageList_DrawEx +function commctrl.h ImageList_DrawIndirect +function commctrl.h ImageList_Duplicate +function commctrl.h ImageList_EndDrag +function commctrl.h ImageList_GetBkColor +function commctrl.h ImageList_GetDragImage +function commctrl.h ImageList_GetIcon +function commctrl.h ImageList_GetIconSize +function commctrl.h ImageList_GetImageCount +function commctrl.h ImageList_GetImageInfo +function commctrl.h ImageList_LoadImage =imagelist_loadimagea +function commctrl.h ImageList_Merge +function commctrl.h ImageList_Read +function commctrl.h ImageList_ReadEx +function commctrl.h ImageList_Remove +function commctrl.h ImageList_Replace +function commctrl.h ImageList_ReplaceIcon +function commctrl.h ImageList_SetBkColor +function commctrl.h ImageList_SetDragCursorImage +function commctrl.h ImageList_SetIconSize +function commctrl.h ImageList_SetImageCount +function commctrl.h ImageList_SetOverlayImage +function commctrl.h ImageList_Write +function commctrl.h ImageList_WriteEx +function commoncontrols.h ImageList_CoCreateInstance + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Image Lists > Image Lists Reference > Image List Interfaces +interface commoncontrols.h IImageList +interface commoncontrols.h IImageList2 + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Image Lists > Image Lists Reference > Image List Macros +function commctrl.h ImageList_AddIcon +function commctrl.h ImageList_ExtractIcon +function commctrl.h ImageList_LoadBitmap +function commctrl.h ImageList_RemoveAll +function commctrl.h INDEXTOOVERLAYMASK + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Image Lists > Image Lists Reference > Image List Structures +struct commoncontrols.h IMAGEINFO +struct commoncontrols.h IMAGELISTDRAWPARAMS +struct commoncontrols.h IMAGELISTSTATS + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > IP Address Control > IP Address Control Reference > IP Address Macros +function commctrl.h FIRST_IPADDRESS +function commctrl.h FOURTH_IPADDRESS +function commctrl.h MAKEIPADDRESS +function commctrl.h MAKEIPRANGE +function commctrl.h SECOND_IPADDRESS +function commctrl.h THIRD_IPADDRESS + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > IP Address Control > IP Address Control Reference > IP Address Structures +struct commctrl.h NMIPADDRESS + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List Box > List Box Control Reference > List Box Functions +function commctrl.h DrawInsert +function commctrl.h LBItemFromPt +function commctrl.h MakeDragList +function winuser.h DlgDirList =dlgdirlista +function winuser.h DlgDirSelectEx =dlgdirselectexa +function winuser.h GetListBoxInfo + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List Box > List Box Control Reference > List Box Macros +function windowsx.h ListBox_AddItemData +function windowsx.h ListBox_AddString +function windowsx.h ListBox_DeleteString +function windowsx.h ListBox_Dir +function windowsx.h ListBox_Enable +function windowsx.h ListBox_FindItemData +function windowsx.h ListBox_FindString +function windowsx.h ListBox_FindStringExact +function windowsx.h ListBox_GetCaretIndex +function windowsx.h ListBox_GetCount +function windowsx.h ListBox_GetCurSel +function windowsx.h ListBox_GetHorizontalExtent +function windowsx.h ListBox_GetItemData +function windowsx.h ListBox_GetItemHeight +function windowsx.h ListBox_GetItemRect +function windowsx.h ListBox_GetSel +function windowsx.h ListBox_GetSelCount +function windowsx.h ListBox_GetSelItems +function windowsx.h ListBox_GetText +function windowsx.h ListBox_GetTextLen +function windowsx.h ListBox_GetTopIndex +function windowsx.h ListBox_InsertItemData +function windowsx.h ListBox_InsertString +function windowsx.h ListBox_ResetContent +function windowsx.h ListBox_SelectItemData +function windowsx.h ListBox_SelectString +function windowsx.h ListBox_SelItemRange +function windowsx.h ListBox_SetCaretIndex +function windowsx.h ListBox_SetColumnWidth +function windowsx.h ListBox_SetCurSel +function windowsx.h ListBox_SetHorizontalExtent +function windowsx.h ListBox_SetItemData +function windowsx.h ListBox_SetItemHeight +function windowsx.h ListBox_SetSel +function windowsx.h ListBox_SetTabStops +function windowsx.h ListBox_SetTopIndex + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List Box > List Box Control Reference > List Box Structures +struct commctrl.h DRAGLISTINFO +struct winuser.h DELETEITEMSTRUCT + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List View > List-View Control Reference > List View Functions +callback commctrl.h LVGroupCompare =pfnlvgroupcompare + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List View > List-View Control Reference > List View Macros +function commctrl.h ListView_ApproximateViewRect +function commctrl.h ListView_Arrange +function commctrl.h ListView_CancelEditLabel +function commctrl.h ListView_CreateDragImage +function commctrl.h ListView_DeleteAllItems +function commctrl.h ListView_DeleteColumn +function commctrl.h ListView_DeleteItem +function commctrl.h ListView_EditLabel +function commctrl.h ListView_EnableGroupView +function commctrl.h ListView_EnsureVisible +function commctrl.h ListView_FindItem +function commctrl.h ListView_GetBkColor +function commctrl.h ListView_GetBkImage +function commctrl.h ListView_GetCallbackMask +function commctrl.h ListView_GetCheckState +function commctrl.h ListView_GetColumn +function commctrl.h ListView_GetColumnOrderArray +function commctrl.h ListView_GetColumnWidth +function commctrl.h ListView_GetCountPerPage +function commctrl.h ListView_GetEditControl +function commctrl.h ListView_GetEmptyText +function commctrl.h ListView_GetExtendedListViewStyle +function commctrl.h ListView_GetFocusedGroup +function commctrl.h ListView_GetFooterInfo +function commctrl.h ListView_GetFooterItem +function commctrl.h ListView_GetFooterItemRect +function commctrl.h ListView_GetFooterRect +function commctrl.h ListView_GetGroupCount +function commctrl.h ListView_GetGroupHeaderImageList +function commctrl.h ListView_GetGroupInfo +function commctrl.h ListView_GetGroupInfoByIndex +function commctrl.h ListView_GetGroupMetrics +function commctrl.h ListView_GetGroupRect +function commctrl.h ListView_GetGroupState +function commctrl.h ListView_GetHeader +function commctrl.h ListView_GetHotCursor +function commctrl.h ListView_GetHotItem +function commctrl.h ListView_GetHoverTime +function commctrl.h ListView_GetImageList +function commctrl.h ListView_GetInsertMark +function commctrl.h ListView_GetInsertMarkColor +function commctrl.h ListView_GetInsertMarkRect +function commctrl.h ListView_GetISearchString +function commctrl.h ListView_GetItem +function commctrl.h ListView_GetItemCount +function commctrl.h ListView_GetItemIndexRect +function commctrl.h ListView_GetItemPosition +function commctrl.h ListView_GetItemRect +function commctrl.h ListView_GetItemSpacing +function commctrl.h ListView_GetItemState +function commctrl.h ListView_GetItemText +function commctrl.h ListView_GetNextItem +function commctrl.h ListView_GetNextItemIndex +function commctrl.h ListView_GetNumberOfWorkAreas +function commctrl.h ListView_GetOrigin +function commctrl.h ListView_GetOutlineColor +function commctrl.h ListView_GetSelectedColumn +function commctrl.h ListView_GetSelectedCount +function commctrl.h ListView_GetSelectionMark +function commctrl.h ListView_GetStringWidth +function commctrl.h ListView_GetSubItemRect +function commctrl.h ListView_GetTextBkColor +function commctrl.h ListView_GetTextColor +function commctrl.h ListView_GetTileInfo +function commctrl.h ListView_GetTileViewInfo +function commctrl.h ListView_GetToolTips +function commctrl.h ListView_GetTopIndex +function commctrl.h ListView_GetUnicodeFormat +function commctrl.h ListView_GetView +function commctrl.h ListView_GetViewRect +function commctrl.h ListView_GetWorkAreas +function commctrl.h ListView_HasGroup +function commctrl.h ListView_HitTest +function commctrl.h ListView_HitTestEx +function commctrl.h ListView_InsertColumn +function commctrl.h ListView_InsertGroup +function commctrl.h ListView_InsertGroupSorted +function commctrl.h ListView_InsertItem +function commctrl.h ListView_InsertMarkHitTest +function commctrl.h ListView_IsGroupViewEnabled +function commctrl.h ListView_IsItemVisible +function commctrl.h ListView_MapIDToIndex +function commctrl.h ListView_MapIndexToID +function commctrl.h ListView_MoveGroup +function commctrl.h ListView_MoveItemToGroup +function commctrl.h ListView_RedrawItems +function commctrl.h ListView_RemoveAllGroups +function commctrl.h ListView_RemoveGroup +function commctrl.h ListView_Scroll +function commctrl.h ListView_SetBkColor +function commctrl.h ListView_SetBkImage +function commctrl.h ListView_SetCallbackMask +function commctrl.h ListView_SetCheckState +function commctrl.h ListView_SetColumn +function commctrl.h ListView_SetColumnOrderArray +function commctrl.h ListView_SetColumnWidth +function commctrl.h ListView_SetExtendedListViewStyle +function commctrl.h ListView_SetExtendedListViewStyleEx +function commctrl.h ListView_SetGroupHeaderImageList +function commctrl.h ListView_SetGroupInfo +function commctrl.h ListView_SetGroupMetrics +function commctrl.h ListView_SetGroupState +function commctrl.h ListView_SetHotCursor +function commctrl.h ListView_SetHotItem +function commctrl.h ListView_SetHoverTime +function commctrl.h ListView_SetIconSpacing +function commctrl.h ListView_SetImageList +function commctrl.h ListView_SetInfoTip +function commctrl.h ListView_SetInsertMark +function commctrl.h ListView_SetInsertMarkColor +function commctrl.h ListView_SetItem +function commctrl.h ListView_SetItemCount +function commctrl.h ListView_SetItemCountEx +function commctrl.h ListView_SetItemIndexState +function commctrl.h ListView_SetItemPosition +function commctrl.h ListView_SetItemPosition32 +function commctrl.h ListView_SetItemState +function commctrl.h ListView_SetItemText +function commctrl.h ListView_SetOutlineColor +function commctrl.h ListView_SetSelectedColumn +function commctrl.h ListView_SetSelectionMark +function commctrl.h ListView_SetTextBkColor +function commctrl.h ListView_SetTextColor +function commctrl.h ListView_SetTileInfo +function commctrl.h ListView_SetTileViewInfo +function commctrl.h ListView_SetToolTips +function commctrl.h ListView_SetUnicodeFormat +function commctrl.h ListView_SetView +function commctrl.h ListView_SetWorkAreas +function commctrl.h ListView_SortGroups +function commctrl.h ListView_SortItems +function commctrl.h ListView_SortItemsEx +function commctrl.h ListView_SubItemHitTest +function commctrl.h ListView_SubItemHitTestEx +function commctrl.h ListView_Update + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > List View > List-View Control Reference > List View Structures +struct commctrl.h LVBKIMAGE =lvbkimagea +struct commctrl.h LVCOLUMN =lvcolumna +struct commctrl.h LVFINDINFO =lvfindinfoa +struct commctrl.h LVFOOTERINFO +struct commctrl.h LVFOOTERITEM +struct commctrl.h LVGROUP +struct commctrl.h LVGROUPMETRICS +struct commctrl.h LVHITTESTINFO +struct commctrl.h LVINSERTGROUPSORTED +struct commctrl.h LVINSERTMARK +struct commctrl.h LVITEM =lvitema +struct commctrl.h LVITEMINDEX +struct commctrl.h LVSETINFOTIP +struct commctrl.h LVTILEINFO +struct commctrl.h LVTILEVIEWINFO +struct commctrl.h NMITEMACTIVATE +struct commctrl.h NMLISTVIEW +struct commctrl.h NMLVCACHEHINT +struct commctrl.h NMLVCUSTOMDRAW +struct commctrl.h NMLVDISPINFO =nmlvdispinfoa +struct commctrl.h NMLVEMPTYMARKUP +struct commctrl.h NMLVFINDITEM =nmlvfinditema +struct commctrl.h NMLVGETINFOTIP =nmlvgetinfotipa +struct commctrl.h NMLVKEYDOWN +struct commctrl.h NMLVLINK +struct commctrl.h NMLVODSTATECHANGE +struct commctrl.h NMLVSCROLL + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Month Calendar > Month Calendar Control Reference > Month Calendar Macros +function commctrl.h MonthCal_GetCalendarBorder +function commctrl.h MonthCal_GetCalendarCount +function commctrl.h MonthCal_GetCalendarGridInfo +function commctrl.h MonthCal_GetCALID +function commctrl.h MonthCal_GetColor +function commctrl.h MonthCal_GetCurrentView +function commctrl.h MonthCal_GetCurSel +function commctrl.h MonthCal_GetFirstDayOfWeek +function commctrl.h MonthCal_GetMaxSelCount +function commctrl.h MonthCal_GetMaxTodayWidth +function commctrl.h MonthCal_GetMinReqRect +function commctrl.h MonthCal_GetMonthDelta +function commctrl.h MonthCal_GetMonthRange +function commctrl.h MonthCal_GetRange +function commctrl.h MonthCal_GetSelRange +function commctrl.h MonthCal_GetToday +function commctrl.h MonthCal_GetUnicodeFormat +function commctrl.h MonthCal_HitTest +function commctrl.h MonthCal_SetCalendarBorder +function commctrl.h MonthCal_SetCALID +function commctrl.h MonthCal_SetColor +function commctrl.h MonthCal_SetCurrentView +function commctrl.h MonthCal_SetCurSel +function commctrl.h MonthCal_SetDayState +function commctrl.h MonthCal_SetFirstDayOfWeek +function commctrl.h MonthCal_SetMaxSelCount +function commctrl.h MonthCal_SetMonthDelta +function commctrl.h MonthCal_SetRange +function commctrl.h MonthCal_SetSelRange +function commctrl.h MonthCal_SetToday +function commctrl.h MonthCal_SetUnicodeFormat +function commctrl.h MonthCal_SizeRectToMin + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Month Calendar > Month Calendar Control Reference > Month Calendar Structures +struct commctrl.h MCGRIDINFO +struct commctrl.h MCHITTESTINFO +struct commctrl.h NMDAYSTATE +struct commctrl.h NMSELCHANGE +struct commctrl.h NMVIEWCHANGE + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Pager > Pager Control Reference > Pager Control Macros +function commctrl.h Pager_ForwardMouse +function commctrl.h Pager_GetBkColor +function commctrl.h Pager_GetBorder +function commctrl.h Pager_GetButtonSize +function commctrl.h Pager_GetButtonState +function commctrl.h Pager_GetDropTarget +function commctrl.h Pager_GetPos +function commctrl.h Pager_RecalcSize +function commctrl.h Pager_SetBkColor +function commctrl.h Pager_SetBorder +function commctrl.h Pager_SetButtonSize +function commctrl.h Pager_SetChild +function commctrl.h Pager_SetPos +function commctrl.h Pager_SetScrollInfo + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Pager > Pager Control Reference > Pager Control Structures +struct commctrl.h NMPGCALCSIZE +struct commctrl.h NMPGHOTITEM +struct commctrl.h NMPGSCROLL + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Progress Bar > Progress Bar Control Reference > Progress Bar Structures +struct commctrl.h PBRANGE + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Property Sheet > Property Sheet Reference > Property Sheet Functions +callback prsht.h AddPropSheetPageProc =lpfnaddpropsheetpage +callback prsht.h LPFNPSPCALLBACK =lpfnpspcallbacka +callback prsht.h PropSheetProc =pfnpropsheetcallback +function prsht.h CreatePropertySheetPage =createpropertysheetpagea +function prsht.h DestroyPropertySheetPage +function prsht.h PropertySheet =propertysheeta + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Property Sheet > Property Sheet Reference > Property Sheet Macros +function prsht.h PropSheet_AddPage +function prsht.h PropSheet_Apply +function prsht.h PropSheet_CancelToClose +function prsht.h PropSheet_Changed +function prsht.h PropSheet_EnableWizButtons +function prsht.h PropSheet_GetCurrentPageHwnd +function prsht.h PropSheet_GetResult +function prsht.h PropSheet_GetTabControl +function prsht.h PropSheet_HwndToIndex +function prsht.h PropSheet_IdToIndex +function prsht.h PropSheet_IndexToHwnd +function prsht.h PropSheet_IndexToId +function prsht.h PropSheet_IndexToPage +function prsht.h PropSheet_InsertPage +function prsht.h PropSheet_IsDialogMessage +function prsht.h PropSheet_PageToIndex +function prsht.h PropSheet_PressButton +function prsht.h PropSheet_QuerySiblings +function prsht.h PropSheet_RebootSystem +function prsht.h PropSheet_RecalcPageSizes +function prsht.h PropSheet_RemovePage +function prsht.h PropSheet_RestartWindows +function prsht.h PropSheet_SetButtonText +function prsht.h PropSheet_SetCurSel +function prsht.h PropSheet_SetCurSelByID +function prsht.h PropSheet_SetFinishText +function prsht.h PropSheet_SetHeaderSubTitle +function prsht.h PropSheet_SetHeaderTitle +function prsht.h PropSheet_SetNextText +function prsht.h PropSheet_SetTitle +function prsht.h PropSheet_SetWizButtons +function prsht.h PropSheet_ShowWizButtons +function prsht.h PropSheet_UnChanged + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Property Sheet > Property Sheet Reference > Property Sheet Structures +struct prsht.h PSHNOTIFY + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rebar > Rebar Control Reference > Rebar Control Structures +struct commctrl.h NMRBAUTOSIZE +struct commctrl.h NMREBAR +struct commctrl.h NMREBARAUTOBREAK +struct commctrl.h NMREBARCHEVRON +struct commctrl.h NMREBARCHILDSIZE +struct commctrl.h NMREBARSPLITTER +struct commctrl.h RBHITTESTINFO +struct commctrl.h REBARBANDINFO =rebarbandinfoa +struct commctrl.h REBARINFO + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Rich Edit Control Reference > Rich Edit Functions +callback richedit.h AutoCorrectProc +callback richedit.h EditStreamCallback +callback richedit.h EditWordBreakProcEx +function richedit.h HyphenateProc + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Rich Edit Control Reference > Rich Edit Interfaces +interface richole.h IRichEditOle +interface richole.h IRichEditOleCallback + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Rich Edit Control Reference > Rich Edit Structures +struct richedit.h BIDIOPTIONS +struct richedit.h CHARFORMAT =charformata +struct richedit.h CHARFORMAT2 =charformat2a +struct richedit.h CHARRANGE +struct richedit.h CLIPBOARDFORMAT +struct richedit.h COMPCOLOR +struct richedit.h EDITSTREAM +struct richedit.h ENCORRECTTEXT +struct richedit.h ENDCOMPOSITIONNOTIFY +struct richedit.h ENDROPFILES +struct richedit.h ENLINK +struct richedit.h ENLOWFIRTF +struct richedit.h ENOLEOPFAILED +struct richedit.h ENPROTECTED +struct richedit.h ENSAVECLIPBOARD +struct richedit.h FINDTEXT =findtexta +struct richedit.h FINDTEXTEX =findtextexa +struct richedit.h FORMATRANGE +struct richedit.h GETCONTEXTMENUEX +struct richedit.h GETTEXTEX +struct richedit.h GETTEXTLENGTHEX +struct richedit.h HYPHENATEINFO +struct richedit.h HYPHRESULT +struct richedit.h IMECOMPTEXT +struct richedit.h MSGFILTER +struct richedit.h OBJECTPOSITIONS +struct richedit.h PARAFORMAT +struct richedit.h PARAFORMAT2 +struct richedit.h PUNCTUATION +struct richedit.h REPASTESPECIAL +struct richedit.h REQRESIZE +struct richedit.h RICHEDIT_IMAGE_PARAMETERS +struct richedit.h SELCHANGE +struct richedit.h SETTEXTEX +struct richedit.h TABLECELLPARMS +struct richedit.h TABLEROWPARMS +struct richedit.h TEXTRANGE =textrangea +struct richole.h REOBJECT +struct textserv.h CHANGENOTIFY + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Rich Edit Control Reference > Rich Edit Enumeration Types +enum richedit.h KHYPH +enum richedit.h TEXTMODE +enum richedit.h UNDONAMEID + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Text Object Model > Text Object Model Reference > Text Object Model Interfaces +interface tom.h ITextDisplays +interface tom.h ITextDocument +interface tom.h ITextDocument2 +interface tom.h ITextFont +interface tom.h ITextFont2 +interface tom.h ITextPara +interface tom.h ITextPara2 +interface tom.h ITextRange +interface tom.h ITextRange2 +interface tom.h ITextRow +interface tom.h ITextSelection +interface tom.h ITextSelection2 +interface tom.h ITextStory +interface tom.h ITextStoryRanges +interface tom.h ITextStoryRanges2 +interface tom.h ITextStrings + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Text Object Model > Text Object Model Reference > Text Object Model Enumeration Types +enum tom.h MANCODE +enum tom.h OBJECTTYPE +enum tom.h tomConstants + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Windowless Rich Edit > Windowless Rich Edit Control Reference > Windowless Rich Edit Functions +function textserv.h CreateTextServices +function textserv.h ShutdownTextServices + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Rich Edit > Windowless Rich Edit > Windowless Rich Edit Control Reference > Windowless Rich Edit Interfaces +class textserv.h ITextHost +class textserv.h ITextHost2 +class textserv.h ITextServices +class textserv.h ITextServices2 +interface textserv.h IRichEditUiaInformation +interface textserv.h IRicheditUiaOverrides +interface textserv.h IRicheditWindowlessAccessibility + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Scroll Bar > Scroll Bar Reference > Scroll Bar Functions +function winuser.h EnableScrollBar +function winuser.h GetScrollBarInfo +function winuser.h GetScrollInfo +function winuser.h GetScrollPos +function winuser.h GetScrollRange +function winuser.h ScrollDC +function winuser.h ScrollWindow +function winuser.h ScrollWindowEx +function winuser.h SetScrollInfo +function winuser.h SetScrollPos +function winuser.h SetScrollRange +function winuser.h ShowScrollBar + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Scroll Bar > Scroll Bar Reference > Scroll Bar Macros +function windowsx.h ScrollBar_Enable +function windowsx.h ScrollBar_GetPos +function windowsx.h ScrollBar_GetRange +function windowsx.h ScrollBar_SetPos +function windowsx.h ScrollBar_SetRange +function windowsx.h ScrollBar_Show + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Scroll Bar > Scroll Bar Reference > Scroll Bar Structures +struct winuser.h SCROLLBARINFO +struct winuser.h SCROLLINFO + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Static Control > Static Control Reference > Static Control Macros +function windowsx.h Static_Enable +function windowsx.h Static_GetIcon +function windowsx.h Static_GetText +function windowsx.h Static_GetTextLength +function windowsx.h Static_SetIcon +function windowsx.h Static_SetText + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Status Bar > Status Bar Reference > Status Bar Functions +function commctrl.h CreateStatusWindow =createstatuswindowa +function commctrl.h DrawStatusText =drawstatustexta +function commctrl.h MenuHelp + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > SysLink > SysLink Control Reference > SysLink Control Structures +struct commctrl.h LHITTESTINFO +struct commctrl.h LITEM +struct commctrl.h NMLINK + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Tab > Tab Control Reference > Tab Control Macros +function commctrl.h TabCtrl_AdjustRect +function commctrl.h TabCtrl_DeleteAllItems +function commctrl.h TabCtrl_DeleteItem +function commctrl.h TabCtrl_DeselectAll +function commctrl.h TabCtrl_GetCurFocus +function commctrl.h TabCtrl_GetCurSel +function commctrl.h TabCtrl_GetExtendedStyle +function commctrl.h TabCtrl_GetImageList +function commctrl.h TabCtrl_GetItem +function commctrl.h TabCtrl_GetItemCount +function commctrl.h TabCtrl_GetItemRect +function commctrl.h TabCtrl_GetRowCount +function commctrl.h TabCtrl_GetToolTips +function commctrl.h TabCtrl_GetUnicodeFormat +function commctrl.h TabCtrl_HighlightItem +function commctrl.h TabCtrl_HitTest +function commctrl.h TabCtrl_InsertItem +function commctrl.h TabCtrl_RemoveImage +function commctrl.h TabCtrl_SetCurFocus +function commctrl.h TabCtrl_SetCurSel +function commctrl.h TabCtrl_SetExtendedStyle +function commctrl.h TabCtrl_SetImageList +function commctrl.h TabCtrl_SetItem +function commctrl.h TabCtrl_SetItemExtra +function commctrl.h TabCtrl_SetItemSize +function commctrl.h TabCtrl_SetMinTabWidth +function commctrl.h TabCtrl_SetPadding +function commctrl.h TabCtrl_SetToolTips +function commctrl.h TabCtrl_SetUnicodeFormat + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Tab > Tab Control Reference > Tab Control Structures +struct commctrl.h NMTCKEYDOWN +struct commctrl.h TCHITTESTINFO +struct commctrl.h TCITEM =tcitema +struct commctrl.h TCITEMHEADER =tcitemheadera + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Task Dialog > Task Dialog Reference > Task Dialog Functions +callback commctrl.h PFTASKDIALOGCALLBACK +function commctrl.h TaskDialog +function commctrl.h TaskDialogIndirect + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Task Dialog > Task Dialog Reference > Task Dialog Structures +struct commctrl.h TASKDIALOG_BUTTON +struct commctrl.h TASKDIALOGCONFIG + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Toolbar > Toolbar Control Reference > Toolbar Control Functions +function commctrl.h CreateMappedBitmap +function commctrl.h CreateToolbarEx + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Toolbar > Toolbar Control Reference > Toolbar Control Structures +struct commctrl.h COLORMAP +struct commctrl.h NMTBCUSTOMDRAW +struct commctrl.h NMTBDISPINFO =nmtbdispinfoa +struct commctrl.h NMTBGETINFOTIP =nmtbgetinfotipa +struct commctrl.h NMTBHOTITEM +struct commctrl.h NMTBRESTORE +struct commctrl.h NMTBSAVE +struct commctrl.h NMTOOLBAR =nmtoolbara +struct commctrl.h TBADDBITMAP +struct commctrl.h TBBUTTON +struct commctrl.h TBBUTTONINFO =tbbuttoninfoa +struct commctrl.h TBINSERTMARK +struct commctrl.h TBMETRICS +struct commctrl.h TBREPLACEBITMAP +struct commctrl.h TBSAVEPARAMS =tbsaveparamsa + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Tooltip > Tooltip Control Reference > Tooltip Control Structures +struct commctrl.h NMTTCUSTOMDRAW +struct commctrl.h NMTTDISPINFO =nmttdispinfoa +struct commctrl.h TOOLINFO =tttoolinfoa +struct commctrl.h TTGETTITLE +struct commctrl.h TTHITTESTINFO =tthittestinfoa + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Trackbar > Trackbar Control Reference > Trackbar Control Structures +struct commctrl.h NMTRBTHUMBPOSCHANGING Structure =nmtrbthumbposchanging + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Tree View > Tree-View Control Reference > Tree View Macros +function commctrl.h TreeView_CreateDragImage +function commctrl.h TreeView_DeleteAllItems +function commctrl.h TreeView_DeleteItem +function commctrl.h TreeView_EditLabel +function commctrl.h TreeView_EndEditLabelNow +function commctrl.h TreeView_EnsureVisible +function commctrl.h TreeView_Expand +function commctrl.h TreeView_GetBkColor +function commctrl.h TreeView_GetCheckState +function commctrl.h TreeView_GetChild +function commctrl.h TreeView_GetCount +function commctrl.h TreeView_GetDropHilight +function commctrl.h TreeView_GetEditControl +function commctrl.h TreeView_GetExtendedStyle +function commctrl.h TreeView_GetFirstVisible +function commctrl.h TreeView_GetImageList +function commctrl.h TreeView_GetIndent +function commctrl.h TreeView_GetInsertMarkColor +function commctrl.h TreeView_GetISearchString +function commctrl.h TreeView_GetItem +function commctrl.h TreeView_GetItemHeight +function commctrl.h TreeView_GetItemPartRect +function commctrl.h TreeView_GetItemRect +function commctrl.h TreeView_GetItemState +function commctrl.h TreeView_GetLastVisible +function commctrl.h TreeView_GetLineColor +function commctrl.h TreeView_GetNextItem +function commctrl.h TreeView_GetNextSelected +function commctrl.h TreeView_GetNextSibling +function commctrl.h TreeView_GetNextVisible +function commctrl.h TreeView_GetParent +function commctrl.h TreeView_GetPrevSibling +function commctrl.h TreeView_GetPrevVisible +function commctrl.h TreeView_GetRoot +function commctrl.h TreeView_GetScrollTime +function commctrl.h TreeView_GetSelectedCount +function commctrl.h TreeView_GetSelection +function commctrl.h TreeView_GetTextColor +function commctrl.h TreeView_GetToolTips +function commctrl.h TreeView_GetUnicodeFormat +function commctrl.h TreeView_GetVisibleCount +function commctrl.h TreeView_HitTest +function commctrl.h TreeView_InsertItem +function commctrl.h TreeView_MapAccIDToHTREEITEM +function commctrl.h TreeView_MapHTREEITEMtoAccID +function commctrl.h TreeView_Select +function commctrl.h TreeView_SelectDropTarget +function commctrl.h TreeView_SelectItem +function commctrl.h TreeView_SelectSetFirstVisible +function commctrl.h TreeView_SetAutoScrollInfo +function commctrl.h TreeView_SetBkColor +function commctrl.h TreeView_SetBorder +function commctrl.h TreeView_SetCheckState +function commctrl.h TreeView_SetExtendedStyle +function commctrl.h TreeView_SetHot +function commctrl.h TreeView_SetImageList +function commctrl.h TreeView_SetIndent +function commctrl.h TreeView_SetInsertMark +function commctrl.h TreeView_SetInsertMarkColor +function commctrl.h TreeView_SetItem +function commctrl.h TreeView_SetItemHeight +function commctrl.h TreeView_SetItemState +function commctrl.h TreeView_SetLineColor +function commctrl.h TreeView_SetScrollTime +function commctrl.h TreeView_SetTextColor +function commctrl.h TreeView_SetToolTips +function commctrl.h TreeView_SetUnicodeFormat +function commctrl.h TreeView_ShowInfoTip +function commctrl.h TreeView_SortChildren +function commctrl.h TreeView_SortChildrenCB + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Tree View > Tree-View Control Reference > Tree View Structures +struct commctrl.h NMTREEVIEW =nmtreeviewa +struct commctrl.h NMTVASYNCDRAW +struct commctrl.h NMTVCUSTOMDRAW +struct commctrl.h NMTVDISPINFO =nmtvdispinfoa +struct commctrl.h NMTVDISPINFOEX =nmtvdispinfoexa +struct commctrl.h NMTVGETINFOTIP =nmtvgetinfotipa +struct commctrl.h NMTVITEMCHANGE +struct commctrl.h NMTVKEYDOWN +struct commctrl.h NMTVSTATEIMAGECHANGING +struct commctrl.h TVGETITEMPARTRECTINFO +struct commctrl.h TVHITTESTINFO +struct commctrl.h TVINSERTSTRUCT =tvinsertstructa +struct commctrl.h TVITEM =tvitema +struct commctrl.h TVITEMEX =tvitemexa +struct commctrl.h TVSORTCB + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Up-Down Control > Up-Down Control Reference > Up-Down Functions +function commctrl.h CreateUpDownControl + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Control Library > Up-Down Control > Up-Down Control Reference > Up-Down Structures +struct commctrl.h NMUPDOWN +struct commctrl.h UDACCEL + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > General Control Reference > Control Functions +callback dpa_dsa.h PFNDACOMPARE +callback dpa_dsa.h PFNDACOMPARECONST +callback dpa_dsa.h PFNDAENUMCALLBACK +callback dpa_dsa.h PFNDAENUMCALLBACKCONST +callback dpa_dsa.h PFNDPAMERGE +callback dpa_dsa.h PFNDPAMERGECONST +callback dpa_dsa.h PFNDPASTREAM +function commctrl.h DrawShadowText +function commctrl.h GetEffectiveClientRect +function commctrl.h GetMUILanguage +function commctrl.h InitCommonControls +function commctrl.h InitCommonControlsEx +function commctrl.h InitMUILanguage +function commctrl.h LoadIconMetric +function commctrl.h LoadIconWithScaleDown +function commctrl.h ShowHideMenuCtl +function dpa_dsa.h DPA_Clone +function dpa_dsa.h DPA_Create +function dpa_dsa.h DPA_CreateEx +function dpa_dsa.h DPA_DeleteAllPtrs +function dpa_dsa.h DPA_DeletePtr +function dpa_dsa.h DPA_Destroy +function dpa_dsa.h DPA_DestroyCallback +function dpa_dsa.h DPA_EnumCallback +function dpa_dsa.h DPA_GetPtr +function dpa_dsa.h DPA_GetPtrIndex +function dpa_dsa.h DPA_GetSize +function dpa_dsa.h DPA_Grow +function dpa_dsa.h DPA_InsertPtr +function dpa_dsa.h DPA_LoadStream +function dpa_dsa.h DPA_Merge +function dpa_dsa.h DPA_SaveStream +function dpa_dsa.h DPA_Search +function dpa_dsa.h DPA_SetPtr +function dpa_dsa.h DPA_Sort +function dpa_dsa.h DSA_Clone +function dpa_dsa.h DSA_Create +function dpa_dsa.h DSA_DeleteAllItems +function dpa_dsa.h DSA_DeleteItem +function dpa_dsa.h DSA_Destroy +function dpa_dsa.h DSA_DestroyCallback +function dpa_dsa.h DSA_EnumCallback +function dpa_dsa.h DSA_GetItem +function dpa_dsa.h DSA_GetItemPtr +function dpa_dsa.h DSA_GetSize +function dpa_dsa.h DSA_InsertItem +function dpa_dsa.h DSA_SetItem +function dpa_dsa.h DSA_Sort +function dpa_dsa.h Str_SetPtr =str_setptrw + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > General Control Reference > Control Macros +function commctrl.h FORWARD_WM_NOTIFY +function commctrl.h HANDLE_WM_NOTIFY +function commctrl.h INDEXTOSTATEIMAGEMASK +function dpa_dsa.h DPA_AppendPtr +function dpa_dsa.h DPA_FastDeleteLastPtr +function dpa_dsa.h DPA_FastGetPtr +function dpa_dsa.h DPA_GetPtrCount +function dpa_dsa.h DPA_GetPtrPtr +function dpa_dsa.h DPA_SetPtrCount +function dpa_dsa.h DPA_SortedInsertPtr +function dpa_dsa.h DSA_AppendItem +function dpa_dsa.h DSA_GetItemCount + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > General Control Reference > Control Structures +struct commctrl.h COLORSCHEME +struct commctrl.h INITCOMMONCONTROLSEX +struct commctrl.h NMCHAR +struct commctrl.h NMCUSTOMSPLITRECTINFO +struct commctrl.h NMCUSTOMTEXT +struct commctrl.h NMKEY +struct commctrl.h NMMOUSE +struct commctrl.h NMOBJECTNOTIFY +struct commctrl.h NMTOOLTIPSCREATED +struct dpa_dsa.h DPASTREAMINFO +struct richedit.h NMHDR + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Visual Styles > Visual Styles Reference > Functions +function uxtheme.h BeginBufferedAnimation +function uxtheme.h BeginBufferedPaint +function uxtheme.h BeginPanningFeedback +function uxtheme.h BufferedPaintClear +function uxtheme.h BufferedPaintInit +function uxtheme.h BufferedPaintRenderAnimation +function uxtheme.h BufferedPaintSetAlpha +function uxtheme.h BufferedPaintStopAllAnimations +function uxtheme.h BufferedPaintUnInit +function uxtheme.h CloseThemeData +function uxtheme.h DrawThemeBackground +function uxtheme.h DrawThemeBackgroundEx +function uxtheme.h DrawThemeEdge +function uxtheme.h DrawThemeIcon +function uxtheme.h DrawThemeParentBackground +function uxtheme.h DrawThemeParentBackgroundEx +function uxtheme.h DrawThemeText +function uxtheme.h DrawThemeTextEx +function uxtheme.h EnableThemeDialogTexture +function uxtheme.h EnableTheming +function uxtheme.h EndBufferedAnimation +function uxtheme.h EndBufferedPaint +function uxtheme.h EndPanningFeedback +function uxtheme.h GetBufferedPaintBits +function uxtheme.h GetBufferedPaintDC +function uxtheme.h GetBufferedPaintTargetDC +function uxtheme.h GetBufferedPaintTargetRect +function uxtheme.h GetCurrentThemeName +function uxtheme.h GetThemeAnimationProperty +function uxtheme.h GetThemeAnimationTransform +function uxtheme.h GetThemeAppProperties +function uxtheme.h GetThemeBackgroundContentRect +function uxtheme.h GetThemeBackgroundExtent +function uxtheme.h GetThemeBackgroundRegion +function uxtheme.h GetThemeBitmap +function uxtheme.h GetThemeBool +function uxtheme.h GetThemeColor +function uxtheme.h GetThemeDocumentationProperty +function uxtheme.h GetThemeEnumValue +function uxtheme.h GetThemeFilename +function uxtheme.h GetThemeFont +function uxtheme.h GetThemeInt +function uxtheme.h GetThemeIntList +function uxtheme.h GetThemeMargins +function uxtheme.h GetThemeMetric +function uxtheme.h GetThemePartSize +function uxtheme.h GetThemePosition +function uxtheme.h GetThemePropertyOrigin +function uxtheme.h GetThemeRect +function uxtheme.h GetThemeStream +function uxtheme.h GetThemeString +function uxtheme.h GetThemeSysBool +function uxtheme.h GetThemeSysColor +function uxtheme.h GetThemeSysColorBrush +function uxtheme.h GetThemeSysFont +function uxtheme.h GetThemeSysInt +function uxtheme.h GetThemeSysSize +function uxtheme.h GetThemeSysString +function uxtheme.h GetThemeTextExtent +function uxtheme.h GetThemeTextMetrics +function uxtheme.h GetThemeTimingFunction +function uxtheme.h GetThemeTransitionDuration +function uxtheme.h GetWindowTheme +function uxtheme.h HitTestThemeBackground +function uxtheme.h IsAppThemed +function uxtheme.h IsCompositionActive +function uxtheme.h IsThemeActive +function uxtheme.h IsThemeBackgroundPartiallyTransparent +function uxtheme.h IsThemeDialogTextureEnabled +function uxtheme.h IsThemePartDefined +function uxtheme.h OpenThemeData +function uxtheme.h OpenThemeDataEx +function uxtheme.h SetThemeAppProperties +function uxtheme.h SetWindowTheme +function uxtheme.h SetWindowThemeAttribute +function uxtheme.h SetWindowThemeNonClientAttributes +function uxtheme.h UpdatePanningFeedback + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Visual Styles > Visual Styles Reference > Structures +struct uxtheme.h BP_ANIMATIONPARAMS +struct uxtheme.h BP_PAINTPARAMS +struct uxtheme.h DTBGOPTS +struct uxtheme.h DTTOPTS +struct uxtheme.h INTLIST +struct uxtheme.h MARGINS +struct uxtheme.h WTA_OPTIONS + +# Desktop App Technologies > Desktop App User Interface > Windows Controls > Visual Styles > Visual Styles Reference > Enumerated Types +enum uxtheme.h BP_ANIMATIONSTYLE +enum uxtheme.h BP_BUFFERFORMAT +enum uxtheme.h PROPERTYORIGIN +enum uxtheme.h THEMESIZE +enum uxtheme.h WINDOWTHEMEATTRIBUTETYPE + +# Desktop App Technologies > Desktop App User Interface > Windows Ribbon Framework > Ribbon Framework Reference > Constants and Enumerations +enum uiribbon.h UI_COLLECTIONCHANGE +enum uiribbon.h UI_COMMANDTYPE +enum uiribbon.h UI_CONTEXTAVAILABILITY +enum uiribbon.h UI_CONTROLDOCK +enum uiribbon.h UI_EVENTLOCATION +enum uiribbon.h UI_EVENTTYPE +enum uiribbon.h UI_EXECUTIONVERB +enum uiribbon.h UI_FONTDELTASIZE +enum uiribbon.h UI_FONTPROPERTIES +enum uiribbon.h UI_FONTUNDERLINE +enum uiribbon.h UI_FONTVERTICALPOSITION +enum uiribbon.h UI_INVALIDATIONS +enum uiribbon.h UI_OWNERSHIP +enum uiribbon.h UI_SWATCHCOLORMODE +enum uiribbon.h UI_SWATCHCOLORTYPE +enum uiribbon.h UI_VIEWTYPE +enum uiribbon.h UI_VIEWVERB + +# Desktop App Technologies > Desktop App User Interface > Windows Ribbon Framework > Ribbon Framework Reference > Interfaces +interface uiribbon.h IUIApplication +interface uiribbon.h IUICollection +interface uiribbon.h IUICollectionChangedEvent +interface uiribbon.h IUICommandHandler +interface uiribbon.h IUIContextualUI +interface uiribbon.h IUIEventingManager +interface uiribbon.h IUIEventLogger +interface uiribbon.h IUIFramework +interface uiribbon.h IUIImage +interface uiribbon.h IUIImageFromBitmap +interface uiribbon.h IUIRibbon +interface uiribbon.h IUISimplePropertySet + +# Desktop App Technologies > Desktop App User Interface > Windows Ribbon Framework > Ribbon Framework Reference > Structures +struct uiribbon.h UI_EVENTPARAMS +struct uiribbon.h UI_EVENTPARAMS_COMMAND + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Classes +class shidfact.h CItemIDFactory + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Interfaces > IAccessibilityDockingService +function shobjidl.h DockWindow =iaccessibilitydockingservice-dockwindow +function shobjidl.h GetAvailableSize =iaccessibilitydockingservice-getavailablesize +function shobjidl.h UndockWindow =iaccessibilitydockingservice-undockwindow + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Interfaces > IAccessibilityDockingServiceCallback +function shobjidl.h Undocked =iaccessibilitydockingservicecallback-undocked + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Interfaces > IInitializeNetworkFolder +function shobjidl_core.h Initialize =ipersistfolder-initialize + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Interfaces +interface credentialprovider.h IConnectableCredentialProviderCredential +interface credentialprovider.h ICredentialProvider +interface credentialprovider.h ICredentialProviderCredential +interface credentialprovider.h ICredentialProviderCredential2 +interface credentialprovider.h ICredentialProviderCredentialEvents +interface credentialprovider.h ICredentialProviderCredentialEvents2 +interface credentialprovider.h ICredentialProviderCredentialWithFieldOptions +interface credentialprovider.h ICredentialProviderEvents +interface credentialprovider.h ICredentialProviderFilter +interface credentialprovider.h ICredentialProviderSetUserArray +interface credentialprovider.h ICredentialProviderUser +interface credentialprovider.h ICredentialProviderUserArray +interface credentialprovider.h IQueryContinueWithStatus +interface exdisp.h IShellWindows +interface imagetranscode.h ITranscodeImage +interface inputpanelconfiguration.h IInputPanelConfiguration +interface inputpanelconfiguration.h IInputPanelInvocationConfiguration +interface mobsync.h ISyncMgrEnumItems +interface mobsync.h ISyncMgrRegister +interface mobsync.h ISyncMgrSynchronize +interface mobsync.h ISyncMgrSynchronizeCallback +interface mobsync.h ISyncMgrSynchronizeInvoke +interface objectarray.h IObjectArray +interface objectarray.h IObjectCollection +interface propsys.h ICreateObject +interface propsys.h IDelayedPropertyStoreFactory +interface propsys.h IInitializeWithFile +interface propsys.h IInitializeWithStream +interface propsys.h INamedPropertyStore +interface propsys.h IObjectWithPropertyKey +interface propsys.h IPersistSerializedPropStorage +interface propsys.h IPersistSerializedPropStorage2 +interface reconcil.h INotifyReplica +interface shappmgr.h IAppPublisher +interface shappmgr.h IEnumPublishedApps +interface shappmgr.h IPublishedApp +interface shappmgr.h IPublishedApp2 +interface shappmgr.h IShellApp +interface shdeprecated.h ITrackShellMenu +interface shimgdata.h IShellImageDataAbort +interface shimgdata.h IShellImageDataFactory +interface shldisp.h IAutoComplete +interface shldisp.h IAutoComplete2 +interface shldisp.h IDataObjectAsyncCapability +interface shldisp.h IShellFolderViewDual +interface shldisp.h IShellFolderViewDual2 +interface shldisp.h IShellFolderViewDual3 +interface shlobj_core.h IACList +interface shlobj_core.h IACList2 +interface shlobj_core.h IDockingWindowSite +interface shlobj_core.h IExtractIcon =iextracticona +interface shlobj_core.h INamedPropertyBag +interface shlobj_core.h IObjMgr +interface shlobj_core.h IProgressDialog +interface shlobj_core.h IQueryInfo +interface shlobj_core.h ISearchContext +interface shlobj_core.h IShellChangeNotify +interface shlobj_core.h IShellDetails +interface shlobj_core.h IShellFolderViewCB +interface shlobj_core.h IShellIconOverlay +interface shlobj_core.h IURLSearchHook +interface shlobj_core.h IURLSearchHook2 +interface shlobj.h ICurrentWorkingDirectory +interface shlobj.h IDockingWindowFrame +interface shlobj.h IFileViewerSite +interface shlobj.h INewShortcutHook =inewshortcuthooka +interface shlwapi.h IQueryAssociations +interface shobjidl_core.h IActionProgress +interface shobjidl_core.h IActionProgressDialog +interface shobjidl_core.h IApplicationActivationManager +interface shobjidl_core.h IApplicationAssociationRegistration +interface shobjidl_core.h IApplicationDesignModeSettings +interface shobjidl_core.h IApplicationDesignModeSettings2 +interface shobjidl_core.h IApplicationDestinations +interface shobjidl_core.h IApplicationDocumentLists +interface shobjidl_core.h IAppVisibility +interface shobjidl_core.h IAppVisibilityEvents +interface shobjidl_core.h IAssocHandler +interface shobjidl_core.h IAssocHandlerInvoker +interface shobjidl_core.h IAttachmentExecute +interface shobjidl_core.h IBandSite +interface shobjidl_core.h IBrowserFrameOptions +interface shobjidl_core.h ICategorizer +interface shobjidl_core.h ICategoryProvider +interface shobjidl_core.h IColumnManager +interface shobjidl_core.h ICommDlgBrowser +interface shobjidl_core.h ICommDlgBrowser2 +interface shobjidl_core.h IContactManagerInterop +interface shobjidl_core.h IContextMenu +interface shobjidl_core.h IContextMenu2 +interface shobjidl_core.h IContextMenu3 +interface shobjidl_core.h IContextMenuCB +interface shobjidl_core.h ICreateProcessInputs +interface shobjidl_core.h ICreatingProcess +interface shobjidl_core.h ICurrentItem +interface shobjidl_core.h ICustomDestinationList +interface shobjidl_core.h IDataObjectProvider +interface shobjidl_core.h IDataTransferManagerInterop +interface shobjidl_core.h IDefaultExtractIconInit +interface shobjidl_core.h IDefaultFolderMenuInitialize +interface shobjidl_core.h IDelegateFolder +interface shobjidl_core.h IDelegateItem +interface shobjidl_core.h IDesktopWallpaper +interface shobjidl_core.h IDestinationStreamFactory +interface shobjidl_core.h IDisplayItem +interface shobjidl_core.h IDockingWindow +interface shobjidl_core.h IDragSourceHelper +interface shobjidl_core.h IDropTargetHelper +interface shobjidl_core.h IEnumAssocHandlers +interface shobjidl_core.h IEnumExplorerCommand +interface shobjidl_core.h IEnumExtraSearch +interface shobjidl_core.h IEnumFullIDList +interface shobjidl_core.h IEnumIDList +interface shobjidl_core.h IEnumObjects +interface shobjidl_core.h IEnumResources +interface shobjidl_core.h IEnumShellItems +interface shobjidl_core.h IExecuteCommand +interface shobjidl_core.h IExecuteCommandApplicationHostEnvironment +interface shobjidl_core.h IExecuteCommandHost +interface shobjidl_core.h IExplorerBrowser +interface shobjidl_core.h IExplorerBrowserEvents +interface shobjidl_core.h IExplorerCommand +interface shobjidl_core.h IExplorerCommandProvider +interface shobjidl_core.h IExplorerCommandState +interface shobjidl_core.h IExplorerPaneVisibility +interface shobjidl_core.h IExtractImage +interface shobjidl_core.h IExtractImage2 +interface shobjidl_core.h IFileDialog +interface shobjidl_core.h IFileDialogCustomize +interface shobjidl_core.h IFileDialogEvents +interface shobjidl_core.h IFileIsInUse +interface shobjidl_core.h IFileOpenDialog +interface shobjidl_core.h IFileOperation +interface shobjidl_core.h IFileOperationProgressSink +interface shobjidl_core.h IFileSaveDialog +interface shobjidl_core.h IFileSyncMergeHandler +interface shobjidl_core.h IFileSystemBindData +interface shobjidl_core.h IFileSystemBindData2 +interface shobjidl_core.h IFolderFilter +interface shobjidl_core.h IFolderFilterSite +interface shobjidl_core.h IFolderView +interface shobjidl_core.h IFolderView2 +interface shobjidl_core.h IFolderViewSettings +interface shobjidl_core.h IFrameworkInputPane +interface shobjidl_core.h IFrameworkInputPaneHandler +interface shobjidl_core.h IHandlerActivationHost +interface shobjidl_core.h IHandlerInfo +interface shobjidl_core.h IHomeGroup +interface shobjidl_core.h IIdentityName +interface shobjidl_core.h IInitializeCommand +interface shobjidl_core.h IInitializeWithBindCtx +interface shobjidl_core.h IInitializeWithItem +interface shobjidl_core.h IInitializeWithPropertyStore +interface shobjidl_core.h IInitializeWithWindow +interface shobjidl_core.h IInputObject +interface shobjidl_core.h IInputObject2 +interface shobjidl_core.h IInputObjectSite +interface shobjidl_core.h IIOCancelInformation +interface shobjidl_core.h IItemNameLimits +interface shobjidl_core.h IKnownFolder +interface shobjidl_core.h IKnownFolderManager +interface shobjidl_core.h ILaunchSourceAppUserModelId +interface shobjidl_core.h ILaunchSourceViewSizePreference +interface shobjidl_core.h ILaunchTargetMonitor +interface shobjidl_core.h ILaunchTargetViewSizePreference +interface shobjidl_core.h IMenuPopup +interface shobjidl_core.h IModalWindow +interface shobjidl_core.h INameSpaceTreeControl +interface shobjidl_core.h INameSpaceTreeControlFolderCapabilities +interface shobjidl_core.h INamespaceWalk +interface shobjidl_core.h INamespaceWalkCB +interface shobjidl_core.h INamespaceWalkCB2 +interface shobjidl_core.h INewMenuClient +interface shobjidl_core.h INewWindowManager +interface shobjidl_core.h IObjectProvider +interface shobjidl_core.h IObjectWithAppUserModelID +interface shobjidl_core.h IObjectWithBackReferences +interface shobjidl_core.h IObjectWithCancelEvent +interface shobjidl_core.h IObjectWithFolderEnumMode +interface shobjidl_core.h IObjectWithProgID +interface shobjidl_core.h IObjectWithSelection +interface shobjidl_core.h IOpenControlPanel +interface shobjidl_core.h IOpenSearchSource +interface shobjidl_core.h IOperationsProgressDialog +interface shobjidl_core.h IPackageDebugSettings +interface shobjidl_core.h IPackageExecutionStateChangeNotification +interface shobjidl_core.h IParentAndItem +interface shobjidl_core.h IParseAndCreateItem +interface shobjidl_core.h IPersistFolder +interface shobjidl_core.h IPersistFolder2 +interface shobjidl_core.h IPersistFolder3 +interface shobjidl_core.h IPersistIDList +interface shobjidl_core.h IPreviewHandler +interface shobjidl_core.h IPreviewHandlerFrame +interface shobjidl_core.h IPreviewHandlerVisuals +interface shobjidl_core.h IPreviewItem +interface shobjidl_core.h IProfferService +interface shobjidl_core.h IQueryContinue +interface shobjidl_core.h IRelatedItem +interface shobjidl_core.h IRemoteComputer +interface shobjidl_core.h IResolveShellLink +interface shobjidl_core.h IRunnableTask +interface shobjidl_core.h ISearchFolderItemFactory +interface shobjidl_core.h ISharingConfigurationManager +interface shobjidl_core.h IShellBrowser +interface shobjidl_core.h IShellExtInit +interface shobjidl_core.h IShellFolder +interface shobjidl_core.h IShellFolder2 +interface shobjidl_core.h IShellIcon +interface shobjidl_core.h IShellIconOverlayIdentifier +interface shobjidl_core.h IShellItem +interface shobjidl_core.h IShellItem2 +interface shobjidl_core.h IShellItemArray +interface shobjidl_core.h IShellItemFilter +interface shobjidl_core.h IShellItemImageFactory +interface shobjidl_core.h IShellItemResources +interface shobjidl_core.h IShellLibrary +interface shobjidl_core.h IShellLink =ishelllinka +interface shobjidl_core.h IShellLinkDataList +interface shobjidl_core.h IShellMenu +interface shobjidl_core.h IShellMenuCallback +interface shobjidl_core.h IShellPropSheetExt +interface shobjidl_core.h IShellView +interface shobjidl_core.h IShellView2 +interface shobjidl_core.h ISuspensionDependencyManager +interface shobjidl_core.h ITaskbarList +interface shobjidl_core.h ITaskbarList2 +interface shobjidl_core.h ITaskbarList3 +interface shobjidl_core.h ITaskbarList4 +interface shobjidl_core.h IThumbnailHandlerFactory +interface shobjidl_core.h ITransferAdviseSink +interface shobjidl_core.h ITransferDestination +interface shobjidl_core.h ITransferMediumItem +interface shobjidl_core.h ITransferSource +interface shobjidl_core.h IUpdateIDList +interface shobjidl_core.h IUserNotification +interface shobjidl_core.h IViewStateIdentityItem +interface shobjidl_core.h IVirtualDesktopManager +interface shobjidl.h IAccessibleObject +interface shobjidl.h IApplicationAssociationRegistrationUI +interface shobjidl.h IAutoCompleteDropDown +interface shobjidl.h IBandHost +interface shobjidl.h ICDBurn +interface shobjidl.h ICommDlgBrowser3 +interface shobjidl.h IComputerInfoChangeNotify +interface shobjidl.h IDesktopGadget +interface shobjidl.h IDragSourceHelper2 +interface shobjidl.h IDynamicHWHandler +interface shobjidl.h IEnumerableView +interface shobjidl.h IEnumReadyCallback +interface shobjidl.h IFileDialog2 +interface shobjidl.h IFileDialogControlEvents +interface shobjidl.h IFolderViewHost +interface shobjidl.h IFolderViewOptions +interface shobjidl.h IHWEventHandler +interface shobjidl.h IHWEventHandler2 +interface shobjidl.h IImageRecompress +interface shobjidl.h INameSpaceTreeAccessible +interface shobjidl.h INameSpaceTreeControl2 +interface shobjidl.h INameSpaceTreeControlCustomDraw +interface shobjidl.h INameSpaceTreeControlDropHandler +interface shobjidl.h INameSpaceTreeControlEvents +interface shobjidl.h IPreviousVersionsInfo +interface shobjidl.h IPublishingWizard +interface shobjidl.h IQueryCancelAutoPlay +interface shobjidl.h IQueryCodePage +interface shobjidl.h IResultsFolder +interface shobjidl.h ISearchBoxInfo +interface shobjidl.h IShellRunDll +interface shobjidl.h IShellView3 +interface shobjidl.h IStartMenuPinnedList +interface shobjidl.h IStreamAsync +interface shobjidl.h IStreamUnbufferedInfo +interface shobjidl.h ITrayDeskBand +interface shobjidl.h IUserAccountChangeCallback +interface shobjidl.h IUserNotification2 +interface shobjidl.h IUserNotificationCallback +interface shobjidl.h IUseToBrowseItem +interface shobjidl.h IVisualProperties +interface shobjidl.h IWebWizardExtension +interface shobjidl.h IWizardExtension +interface shobjidl.h IWizardSite +interface storageprovider.h IStorageProviderHandler +interface storageprovider.h IStorageProviderPropertyHandler +interface syncmgr.h IEnumSyncMgrConflict +interface syncmgr.h IEnumSyncMgrEvents +interface syncmgr.h IEnumSyncMgrSyncItems +interface syncmgr.h ISyncMgrConflict +interface syncmgr.h ISyncMgrConflictFolder +interface syncmgr.h ISyncMgrConflictItems +interface syncmgr.h ISyncMgrConflictPresenter +interface syncmgr.h ISyncMgrConflictResolutionItems +interface syncmgr.h ISyncMgrConflictResolveInfo +interface syncmgr.h ISyncMgrConflictStore +interface syncmgr.h ISyncMgrControl +interface syncmgr.h ISyncMgrEvent +interface syncmgr.h ISyncMgrEventLinkUIOperation +interface syncmgr.h ISyncMgrEventStore +interface syncmgr.h ISyncMgrHandler +interface syncmgr.h ISyncMgrHandlerCollection +interface syncmgr.h ISyncMgrHandlerInfo +interface syncmgr.h ISyncMgrResolutionHandler +interface syncmgr.h ISyncMgrScheduleWizardUIOperation +interface syncmgr.h ISyncMgrSessionCreator +interface syncmgr.h ISyncMgrSyncCallback +interface syncmgr.h ISyncMgrSyncItem +interface syncmgr.h ISyncMgrSyncItemContainer +interface syncmgr.h ISyncMgrSyncItemInfo +interface syncmgr.h ISyncMgrSyncResult +interface syncmgr.h ISyncMgrUIOperation +interface thumbcache.h ISharedBitmap +interface thumbcache.h IThumbnailCache +interface thumbcache.h IThumbnailCachePrimer +interface thumbcache.h IThumbnailProvider +interface thumbcache.h IThumbnailSettings +interface thumbnailstreamcache.h IThumbnailStreamCache + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Functions > Intsafe.h Functions > Conversion Functions +function intsafe.h ByteToChar +function intsafe.h ByteToInt8 +function intsafe.h Int8ToUChar +function intsafe.h Int8ToUInt +function intsafe.h Int8ToUInt8 +function intsafe.h Int8ToUIntPtr +function intsafe.h Int8ToULong +function intsafe.h Int8ToULongLong +function intsafe.h Int8ToULongPtr +function intsafe.h Int8ToUShort +function intsafe.h IntPtrToChar +function intsafe.h IntPtrToInt +function intsafe.h IntPtrToInt8 +function intsafe.h IntPtrToLong +function intsafe.h IntPtrToLongPtr +function intsafe.h IntPtrToShort +function intsafe.h IntPtrToUChar +function intsafe.h IntPtrToUInt +function intsafe.h IntPtrToUInt8 +function intsafe.h IntPtrToUIntPtr +function intsafe.h IntPtrToULong +function intsafe.h IntPtrToULongLong +function intsafe.h IntPtrToULongPtr +function intsafe.h IntPtrToUShort +function intsafe.h IntToChar +function intsafe.h IntToInt8 +function intsafe.h IntToShort +function intsafe.h IntToUChar +function intsafe.h IntToUInt +function intsafe.h IntToUInt8 +function intsafe.h IntToUIntPtr =inttoulonglong +function intsafe.h IntToULong +function intsafe.h IntToUShort +function intsafe.h LongLongToChar +function intsafe.h LongLongToInt +function intsafe.h LongLongToInt8 +function intsafe.h LongLongToIntPtr +function intsafe.h LongLongToLong +function intsafe.h LongLongToLongPtr +function intsafe.h LongLongToShort +function intsafe.h LongLongToUChar +function intsafe.h LongLongToUInt +function intsafe.h LongLongToUInt8 +function intsafe.h LongLongToULong +function intsafe.h LongLongToULongLong +function intsafe.h LongLongToUShort +function intsafe.h LongPtrToChar +function intsafe.h LongPtrToInt +function intsafe.h LongPtrToInt8 +function intsafe.h LongPtrToIntPtr +function intsafe.h LongPtrToLong +function intsafe.h LongPtrToShort +function intsafe.h LongPtrToUChar +function intsafe.h LongPtrToUInt +function intsafe.h LongPtrToUInt8 +function intsafe.h LongPtrToUIntPtr +function intsafe.h LongPtrToULong +function intsafe.h LongPtrToULongLong +function intsafe.h LongPtrToULongPtr +function intsafe.h LongPtrToUShort +function intsafe.h LongToChar +function intsafe.h LongToInt +function intsafe.h LongToInt8 +function intsafe.h LongToIntPtr +function intsafe.h LongToShort +function intsafe.h LongToUChar +function intsafe.h LongToUInt +function intsafe.h LongToUInt8 +function intsafe.h LongToUIntPtr +function intsafe.h LongToULong +function intsafe.h LongToULongLong +function intsafe.h LongToULongPtr +function intsafe.h LongToUShort +function intsafe.h ShortToChar +function intsafe.h ShortToDWordPtr +function intsafe.h ShortToInt8 +function intsafe.h ShortToUChar +function intsafe.h ShortToUInt +function intsafe.h ShortToUInt8 +function intsafe.h ShortToUIntPtr +function intsafe.h ShortToULong +function intsafe.h ShortToULongLong +function intsafe.h ShortToULongPtr +function intsafe.h ShortToUShort +function intsafe.h SizeTToInt =ulongptrtoint +function intsafe.h SIZETToInt_1 =uintptrtoint +function intsafe.h SIZETToInt64_1 =ulongptrtolonglong +function intsafe.h SizeTToLong =uintptrtolong +function intsafe.h UInt8ToChar +function intsafe.h UInt8ToInt8 +function intsafe.h UIntPtrToChar +function intsafe.h UIntPtrToInt16 +function intsafe.h UIntPtrToInt8 +function intsafe.h UIntPtrToIntPtr +function intsafe.h UIntPtrToLongLong +function intsafe.h UIntPtrToLongPtr +function intsafe.h UIntPtrToShort +function intsafe.h UIntPtrToUChar +function intsafe.h UIntPtrToUInt +function intsafe.h UIntPtrToUInt16 +function intsafe.h UIntPtrToUInt8 +function intsafe.h UIntPtrToULong +function intsafe.h UIntPtrToUShort +function intsafe.h UIntToChar +function intsafe.h UIntToInt +function intsafe.h UIntToInt8 +function intsafe.h UIntToIntPtr +function intsafe.h UIntToLong +function intsafe.h UIntToLongPtr +function intsafe.h UIntToShort +function intsafe.h UIntToUChar +function intsafe.h UIntToUInt8 +function intsafe.h UIntToUShort +function intsafe.h ULongLongToChar +function intsafe.h ULongLongToInt +function intsafe.h ULongLongToInt8 +function intsafe.h ULongLongToIntPtr =ulonglongtolonglong +function intsafe.h ULongLongToLong +function intsafe.h ULongLongToLongPtr +function intsafe.h ULongLongToShort +function intsafe.h ULongLongToUChar +function intsafe.h ULongLongToUInt +function intsafe.h ULongLongToUInt8 +function intsafe.h ULongLongToUIntPtr +function intsafe.h ULongLongToULong +function intsafe.h ULongLongToULongPtr +function intsafe.h ULongLongToUShort +function intsafe.h ULongPtrToChar +function intsafe.h ULongPtrToInt8 +function intsafe.h ULongPtrToIntPtr +function intsafe.h ULongPtrToLong +function intsafe.h ULongPtrToLongPtr +function intsafe.h ULongPtrToShort +function intsafe.h ULongPtrToUChar +function intsafe.h ULongPtrToUInt +function intsafe.h ULongPtrToUInt8 +function intsafe.h ULongPtrToUIntPtr +function intsafe.h ULongPtrToULong +function intsafe.h ULongPtrToUShort +function intsafe.h ULongToChar +function intsafe.h ULongToInt +function intsafe.h ULongToInt8 +function intsafe.h ULongToIntPtr +function intsafe.h ULongToLong +function intsafe.h ULongToLongPtr +function intsafe.h ULongToShort +function intsafe.h ULongToUChar +function intsafe.h ULongToUInt +function intsafe.h ULongToUInt8 +function intsafe.h ULongToUIntPtr +function intsafe.h ULongToUShort +function intsafe.h UShortToChar +function intsafe.h UShortToInt8 +function intsafe.h UShortToShort +function intsafe.h UShortToUChar +function intsafe.h UShortToUInt8 + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Functions > Intsafe.h Functions > Math Functions +function intsafe.h DWordPtrAdd +function intsafe.h DWordPtrMult +function intsafe.h DWordPtrSub +function intsafe.h Int8Add +function intsafe.h Int8Mult +function intsafe.h Int8Sub +function intsafe.h IntAdd +function intsafe.h IntMult +function intsafe.h IntPtrAdd +function intsafe.h IntPtrMult +function intsafe.h IntPtrSub +function intsafe.h IntSub +function intsafe.h LongAdd +function intsafe.h LongLongAdd +function intsafe.h LongLongMult +function intsafe.h LongLongSub +function intsafe.h LongMult +function intsafe.h LongPtrAdd +function intsafe.h LongPtrMult +function intsafe.h LongPtrSub +function intsafe.h LongSub +function intsafe.h PtrdiffTAdd +function intsafe.h PtrdiffTMult +function intsafe.h PtrdiffTSub +function intsafe.h ShortAdd +function intsafe.h ShortMult +function intsafe.h ShortSub +function intsafe.h SizeTAdd +function intsafe.h SIZETAdd_1 =ulonglongadd +function intsafe.h SizeTMult +function intsafe.h SizeTSub +function intsafe.h SIZETSub_1 =ulonglongsub +function intsafe.h SSIZETAdd +function intsafe.h SSIZETMult +function intsafe.h SSIZETSub +function intsafe.h UInt8Add +function intsafe.h UInt8Mult +function intsafe.h UInt8Sub +function intsafe.h UIntAdd +function intsafe.h UIntMult +function intsafe.h UIntPtrAdd +function intsafe.h UIntPtrMult +function intsafe.h UIntPtrSub +function intsafe.h UIntSub +function intsafe.h ULongAdd +function intsafe.h ULongMult +function intsafe.h ULongPtrAdd +function intsafe.h ULongPtrMult +function intsafe.h ULongPtrSub +function intsafe.h ULongSub +function intsafe.h UShortAdd +function intsafe.h UShortMult +function intsafe.h UShortSub + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Functions > Library Functions +function shobjidl_core.h SHAddFolderPathToLibrary +function shobjidl_core.h SHCreateLibrary +function shobjidl_core.h SHLoadLibraryFromItem +function shobjidl_core.h SHLoadLibraryFromKnownFolder +function shobjidl_core.h SHLoadLibraryFromParsingName +function shobjidl_core.h SHRemoveFolderPathFromLibrary +function shobjidl_core.h SHResolveLibrary +function shobjidl_core.h SHSaveLibraryInFolderPath +function shobjidl_core.h SHShowManageLibraryUI +function shobjidl.h SHResolveFolderPathInLibrary + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Functions > Path Functions +function pathcch.h PathAllocCanonicalize +function pathcch.h PathAllocCombine +function pathcch.h PathCchAddBackslash +function pathcch.h PathCchAddBackslashEx +function pathcch.h PathCchAddExtension +function pathcch.h PathCchAppend +function pathcch.h PathCchAppendEx +function pathcch.h PathCchCanonicalize +function pathcch.h PathCchCanonicalizeEx +function pathcch.h PathCchCombine +function pathcch.h PathCchCombineEx +function pathcch.h PathCchFindExtension +function pathcch.h PathCchIsRoot +function pathcch.h PathCchRemoveBackslash +function pathcch.h PathCchRemoveBackslashEx +function pathcch.h PathCchRemoveExtension +function pathcch.h PathCchRemoveFileSpec +function pathcch.h PathCchRenameExtension +function pathcch.h PathCchSkipRoot +function pathcch.h PathCchStripPrefix +function pathcch.h PathCchStripToRoot +function pathcch.h PathIsUNCEx + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Functions +callback cpl.h APPLET_PROC +callback shlwapi.h DLLGETVERSIONPROC +function appnotify.h RegisterAppStateChangeNotification +function appnotify.h UnregisterAppStateChangeNotification +function commctrl.h DefSubclassProc +function commctrl.h GetWindowSubclass +function commctrl.h RemoveWindowSubclass +function commctrl.h SetWindowSubclass +function intshcut.h InetIsOffline +function intshcut.h MIMEAssociationDialog =mimeassociationdialoga +function intshcut.h TranslateURL =translateurla +function intshcut.h URLAssociationDialog =urlassociationdialoga +function scrnsave.h DefScreenSaverProc +function scrnsave.h RegisterDialogClasses +function scrnsave.h ScreenSaverConfigureDialog +function scrnsave.h ScreenSaverProc +function shellapi.h AssocCreateForClasses +function shellapi.h CommandLineToArgvW +function shellapi.h DragAcceptFiles +function shellapi.h DragFinish +function shellapi.h DragQueryFile =dragqueryfilea +function shellapi.h DragQueryPoint +function shellapi.h DuplicateIcon +function shellapi.h ExtractAssociatedIcon =extractassociatedicona +function shellapi.h ExtractIcon =extracticona +function shellapi.h ExtractIconEx =extracticonexa +function shellapi.h FindExecutable =findexecutablea +function shellapi.h InitNetworkAddressControl +function shellapi.h SHAppBarMessage +function shellapi.h Shell_NotifyIcon =shell_notifyicona +function shellapi.h Shell_NotifyIconGetRect +function shellapi.h ShellAbout =shellabouta +function shellapi.h ShellExecute =shellexecutea +function shellapi.h ShellExecuteEx =shellexecuteexa +function shellapi.h SHEmptyRecycleBin =shemptyrecyclebina +function shellapi.h SHEnumerateUnreadMailAccounts =shenumerateunreadmailaccountsa +function shellapi.h SHEvaluateSystemCommandTemplate +function shellapi.h SHFileOperation =shfileoperationa +function shellapi.h SHFreeNameMappings +function shellapi.h SHGetDiskFreeSpaceEx =shgetdiskfreespaceexa +function shellapi.h SHGetDriveMedia +function shellapi.h SHGetFileInfo =shgetfileinfoa +function shellapi.h SHGetImageList +function shellapi.h SHGetLocalizedName +function shellapi.h SHGetNewLinkInfo =shgetnewlinkinfoa +function shellapi.h SHGetStockIconInfo +function shellapi.h SHGetUnreadMailCount =shgetunreadmailcountw +function shellapi.h SHIsFileAvailableOffline +function shellapi.h SHLoadNonloadedIconOverlayIdentifiers +function shellapi.h SHQueryRecycleBin =shqueryrecyclebina +function shellapi.h SHQueryUserNotificationState +function shellapi.h SHRemoveLocalizedName +function shellapi.h SHSetLocalizedName +function shellapi.h SHSetUnreadMailCount =shsetunreadmailcountw +function shellapi.h SHTestTokenMembership +function shellscalingapi.h GetDpiForShellUiComponent +function shellscalingapi.h GetScaleFactorForDevice +function shellscalingapi.h GetScaleFactorForMonitor +function shellscalingapi.h RegisterScaleChangeEvent +function shellscalingapi.h RegisterScaleChangeNotifications +function shellscalingapi.h RevokeScaleChangeNotifications +function shellscalingapi.h UnregisterScaleChangeEvent +function shlobj_core.h AssocGetDetailsOfPropKey +function shlobj_core.h CDefFolderMenu_Create2 +function shlobj_core.h IDListContainerIsConsistent +function shlobj_core.h ILAppendID +function shlobj_core.h ILClone +function shlobj_core.h ILCloneChild +function shlobj_core.h ILCloneFirst +function shlobj_core.h ILCloneFull +function shlobj_core.h ILCombine +function shlobj_core.h ILCreateFromPath +function shlobj_core.h ILFindChild +function shlobj_core.h ILFindLastID +function shlobj_core.h ILFree +function shlobj_core.h ILGetNext +function shlobj_core.h ILGetSize +function shlobj_core.h ILIsAligned +function shlobj_core.h ILIsChild +function shlobj_core.h ILIsEmpty +function shlobj_core.h ILIsEqual +function shlobj_core.h ILIsParent +function shlobj_core.h ILNext(PCUIDLIST_RELATIVE) =ilnext +function shlobj_core.h ILRemoveLastID +function shlobj_core.h ILSaveToStream +function shlobj_core.h ILSkip(PCUIDLIST_RELATIVE, UINT) =ilskip +function shlobj_core.h PathMakeUniqueName +function shlobj_core.h PathYetAnotherMakeUniqueName +function shlobj_core.h SHAddToRecentDocs +function shlobj_core.h SHBindToFolderIDListParent +function shlobj_core.h SHBindToFolderIDListParentEx +function shlobj_core.h SHBindToObject +function shlobj_core.h SHBindToParent +function shlobj_core.h SHBrowseForFolder =shbrowseforfoldera +function shlobj_core.h SHChangeNotification_Lock +function shlobj_core.h SHChangeNotification_Unlock +function shlobj_core.h SHChangeNotify +function shlobj_core.h SHChangeNotifyDeregister +function shlobj_core.h SHChangeNotifyRegister +function shlobj_core.h SHCreateDataObject +function shlobj_core.h SHCreateDefaultContextMenu +function shlobj_core.h SHCreateShellFolderView +function shlobj_core.h SHCreateShellFolderViewEx +function shlobj_core.h SHCreateShellItem +function shlobj_core.h SHDefExtractIcon =shdefextracticona +function shlobj_core.h SHDoDragDrop +function shlobj_core.h SHGetDataFromIDList =shgetdatafromidlista +function shlobj_core.h SHGetDesktopFolder +function shlobj_core.h SHGetIconOverlayIndex =shgeticonoverlayindexa +function shlobj_core.h SHGetInstanceExplorer +function shlobj_core.h SHGetKnownFolderIDList +function shlobj_core.h SHGetKnownFolderItem +function shlobj_core.h SHGetKnownFolderPath +function shlobj_core.h SHGetPathFromIDList =shgetpathfromidlista +function shlobj_core.h SHGetPathFromIDListEx +function shlobj_core.h SHGetSettings +function shlobj_core.h SHLoadInProc +function shlobj_core.h SHOpenFolderAndSelectItems +function shlobj_core.h SHOpenWithDialog +function shlobj_core.h SHParseDisplayName +function shlobj_core.h SHPathPrepareForWrite =shpathprepareforwritea +function shlobj_core.h SHSetInstanceExplorer +function shlobj_core.h SHSetKnownFolderPath +function shlobj_core.h SHUpdateImage =shupdateimagea +function shlobj_core.h StgMakeUniqueName +function shlobj.h SHChangeNotifyRegisterThread +function shlobj.h SHMultiFileProperties +function shlobj.h SHRunControlPanel +function shlobj.h SoftwareUpdateMessageBox +function shlwapi.h SHLocalStrDup =shlocalstrdupa +function shlwapi.h StrStrNIW +function shlwapi.h StrStrNW +function shobjidl_core.h FreeIDListArray +function shobjidl_core.h FreeIDListArrayChild +function shobjidl_core.h FreeIDListArrayFull +function shobjidl_core.h FreeKnownFolderDefinitionFields +function shobjidl_core.h GetCurrentProcessExplicitAppUserModelID +function shobjidl_core.h SetContractDelegateWindow +function shobjidl_core.h SetCurrentProcessExplicitAppUserModelID +function shobjidl_core.h SHAssocEnumHandlers +function shobjidl_core.h SHAssocEnumHandlersForProtocolByApplication +function shobjidl_core.h SHCreateAssociationRegistration +function shobjidl_core.h SHCreateDefaultExtractIcon +function shobjidl_core.h SHCreateItemFromIDList +function shobjidl_core.h SHCreateItemFromParsingName +function shobjidl_core.h SHCreateItemFromRelativeName +function shobjidl_core.h SHCreateItemInKnownFolder +function shobjidl_core.h SHCreateItemWithParent +function shobjidl_core.h SHCreateShellItemArray +function shobjidl_core.h SHCreateShellItemArrayFromDataObject +function shobjidl_core.h SHCreateShellItemArrayFromIDLists +function shobjidl_core.h SHCreateShellItemArrayFromShellItem +function shobjidl_core.h SHGetIDListFromObject +function shobjidl_core.h SHGetItemFromDataObject +function shobjidl_core.h SHGetItemFromObject +function shobjidl_core.h SHGetNameFromIDList +function shobjidl_core.h SHGetTemporaryPropertyForItem +function shobjidl_core.h SHSetTemporaryPropertyForItem +function shobjidl.h SHCreateDefaultPropertiesOp +function shobjidl.h SHSetDefaultProperties +function syncmgr.h FreeConfirmConflictItem +function userenv.h CreateAppContainerProfile +function userenv.h CreateEnvironmentBlock +function userenv.h CreateProfile +function userenv.h DeleteAppContainerProfile +function userenv.h DeleteProfile =deleteprofilea +function userenv.h DeriveAppContainerSidFromAppContainerName +function userenv.h DeriveRestrictedAppContainerSidFromAppContainerSidAndRestrictedName +function userenv.h DestroyEnvironmentBlock +function userenv.h ExpandEnvironmentStringsForUser =expandenvironmentstringsforusera +function userenv.h GetAllUsersProfileDirectory =getallusersprofiledirectorya +function userenv.h GetAppContainerFolderPath +function userenv.h GetAppContainerRegistryLocation +function userenv.h GetDefaultUserProfileDirectory =getdefaultuserprofiledirectorya +function userenv.h GetProfilesDirectory =getprofilesdirectorya +function userenv.h GetProfileType +function userenv.h GetUserProfileDirectory =getuserprofiledirectorya +function userenv.h LoadUserProfile =loaduserprofilea +function userenv.h UnloadUserProfile +function winuser.h GetMenuContextHelpId +function winuser.h GetWindowContextHelpId +function winuser.h SetMenuContextHelpId +function winuser.h SetWindowContextHelpId +function winuser.h WinHelp =winhelpa + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Callback Functions +callback appnotify.h PAPPSTATE_CHANGE_ROUTINE +callback commctrl.h SUBCLASSPROC + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Constants, Enumerations, and Flags +enum credentialprovider.h CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS +enum credentialprovider.h CREDENTIAL_PROVIDER_CREDENTIAL_FIELD_OPTIONS +enum credentialprovider.h CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE +enum credentialprovider.h CREDENTIAL_PROVIDER_FIELD_STATE +enum credentialprovider.h CREDENTIAL_PROVIDER_FIELD_TYPE +enum credentialprovider.h CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE +enum credentialprovider.h CREDENTIAL_PROVIDER_STATUS_ICON +enum credentialprovider.h CREDENTIAL_PROVIDER_USAGE_SCENARIO +enum exdisp.h ShellWindowFindWindowOptions +enum exdisp.h ShellWindowTypeConstants +enum intshcut.h MIMEASSOCIATIONDIALOG_IN_FLAGS +enum intshcut.h TRANSLATEURL_IN_FLAGS +enum intshcut.h URLASSOCIATIONDIALOG_IN_FLAGS +enum mobsync.h SYNCMGRFLAG +enum mobsync.h SYNCMGRHANDLERFLAGS +enum mobsync.h SYNCMGRINVOKEFLAGS +enum mobsync.h SYNCMGRITEMFLAGS +enum mobsync.h SYNCMGRLOGLEVEL +enum mobsync.h SYNCMGRREGISTERFLAGS +enum mobsync.h SYNCMGRSTATUS +enum shappmgr.h APPACTIONFLAGS +enum shappmgr.h APPINFODATAFLAGS +enum shappmgr.h PUBAPPINFOFLAGS +enum shdeprecated.h BNSTATE +enum shellapi.h QUERY_USER_NOTIFICATION_STATE +enum shellapi.h SHSTOCKICONID +enum shellscalingapi.h DISPLAY_DEVICE_TYPE +enum shellscalingapi.h SCALE_CHANGE_FLAGS +enum shellscalingapi.h SHELL_UI_COMPONENT +enum shldisp.h AUTOCOMPLETEOPTIONS +enum shldisp.h ShellFolderViewOptions +enum shldisp.h ShellSpecialFolderConstants +enum shlobj_core.h AUTOCOMPLETELISTOPTIONS +enum shlobj_core.h DROPIMAGETYPE +enum shlobj_core.h IESHORTCUTFLAGS +enum shlobj_core.h KNOWN_FOLDER_FLAG +enum shlobj_core.h RESTRICTIONS +enum shlobj_core.h SCNRT_STATUS +enum shlobj_core.h SHARD +enum shlobj_core.h SHELL_LINK_DATA_FLAGS +enum shlwapi.h ASSOCDATA +enum shlwapi.h ASSOCKEY +enum shlwapi.h ASSOCSTR +enum shlwapi.h FILETYPEATTRIBUTEFLAGS +enum shlwapi.h SFBS_FLAGS =tagsfbs_flags +enum shlwapi.h SHGLOBALCOUNTER +enum shlwapi.h SHREGDEL_FLAGS +enum shlwapi.h SHREGENUM_FLAGS +enum shlwapi.h URL_SCHEME +enum shobjidl_core.h _SVGIO +enum shobjidl_core.h _SVSIF +enum shobjidl_core.h APPLICATION_VIEW_ORIENTATION +enum shobjidl_core.h APPLICATION_VIEW_SIZE_PREFERENCE +enum shobjidl_core.h APPLICATION_VIEW_STATE +enum shobjidl_core.h ASSOCIATIONLEVEL +enum shobjidl_core.h ASSOCIATIONTYPE +enum shobjidl_core.h ATTACHMENT_ACTION +enum shobjidl_core.h ATTACHMENT_PROMPT +enum shobjidl_core.h BROWSERFRAMEOPTIONS +enum shobjidl_core.h CATEGORYINFO_FLAGS +enum shobjidl_core.h CATSORT_FLAGS +enum shobjidl_core.h CM_ENUM_FLAGS +enum shobjidl_core.h CM_MASK +enum shobjidl_core.h CM_SET_WIDTH_VALUE +enum shobjidl_core.h CM_STATE +enum shobjidl_core.h DATAOBJ_GET_ITEM_FLAGS +enum shobjidl_core.h DBID Command Flags =tagdeskbandcid +enum shobjidl_core.h DEF_SHARE_ID +enum shobjidl_core.h DEFAULT_FOLDER_MENU_RESTRICTIONS +enum shobjidl_core.h DEFAULTSAVEFOLDERTYPE +enum shobjidl_core.h DESKTOP_WALLPAPER_POSITION +enum shobjidl_core.h EXPCMDSTATE +enum shobjidl_core.h EXPLORER_BROWSER_FILL_FLAGS +enum shobjidl_core.h EXPLORER_BROWSER_OPTIONS +enum shobjidl_core.h EXPLORERPANESTATE +enum shobjidl_core.h FDAP +enum shobjidl_core.h FDE_OVERWRITE_RESPONSE +enum shobjidl_core.h FDE_SHAREVIOLATION_RESPONSE +enum shobjidl_core.h FFFP_MODE +enum shobjidl_core.h FILE_USAGE_TYPE +enum shobjidl_core.h FILEOPENDIALOGOPTIONS +enum shobjidl_core.h FOLDER_ENUM_MODE +enum shobjidl_core.h FOLDERFLAGS +enum shobjidl_core.h FOLDERLOGICALVIEWMODE +enum shobjidl_core.h FOLDERVIEWMODE +enum shobjidl_core.h KF_CATEGORY +enum shobjidl_core.h KF_DEFINITION_FLAGS +enum shobjidl_core.h KF_REDIRECT_FLAGS +enum shobjidl_core.h KF_REDIRECTION_CAPABILITIES +enum shobjidl_core.h LIBRARYFOLDERFILTER +enum shobjidl_core.h LIBRARYMANAGEDIALOGOPTIONS +enum shobjidl_core.h LIBRARYOPTIONFLAGS +enum shobjidl_core.h LIBRARYSAVEFLAGS +enum shobjidl_core.h MONITOR_APP_VISIBILITY +enum shobjidl_core.h NSTCFOLDERCAPABILITIES +enum shobjidl_core.h NSTCITEMSTATE +enum shobjidl_core.h NSTCSTYLE +enum shobjidl_core.h NWMF +enum shobjidl_core.h PACKAGE_EXECUTION_STATE +enum shobjidl_core.h SHARE_ROLE +enum shobjidl_core.h SHCONTF +enum shobjidl_core.h SHGDNF +enum shobjidl_core.h SICHINTF +enum shobjidl_core.h SIGDN +enum shobjidl_core.h SPACTION +enum shobjidl_core.h SPBEGINF +enum shobjidl_core.h SPTEXT +enum shobjidl_core.h STPFLAG +enum shobjidl_core.h SVUIA_STATUS +enum shobjidl_core.h THUMBBUTTONFLAGS +enum shobjidl_core.h THUMBBUTTONMASK +enum shobjidl_core.h TRANSFER_SOURCE_FLAGS +enum shobjidl.h FOLDERVIEWOPTIONS +enum shobjidl.h NSTCSTYLE2 +enum shobjidl.h UNDOCK_REASON +enum shobjidl.h VPCOLORFLAGS +enum shobjidl.h VPWATERMARKFLAGS +enum shtypes.h DEVICE_SCALE_FACTOR +enum shtypes.h PERCEIVED +enum shtypes.h SHCOLSTATE +enum syncmgr.h SYNCMGR_CANCEL_REQUEST +enum syncmgr.h SYNCMGR_CONFLICT_ITEM_TYPE +enum syncmgr.h SYNCMGR_CONTROL_FLAGS +enum syncmgr.h SYNCMGR_EVENT_FLAGS +enum syncmgr.h SYNCMGR_EVENT_LEVEL +enum syncmgr.h SYNCMGR_HANDLER_CAPABILITIES +enum syncmgr.h SYNCMGR_HANDLER_POLICIES +enum syncmgr.h SYNCMGR_HANDLER_TYPE +enum syncmgr.h SYNCMGR_ITEM_CAPABILITIES +enum syncmgr.h SYNCMGR_ITEM_POLICIES +enum syncmgr.h SYNCMGR_PRESENTER_CHOICE +enum syncmgr.h SYNCMGR_PRESENTER_NEXT_STEP +enum syncmgr.h SYNCMGR_PROGRESS_STATUS +enum syncmgr.h SYNCMGR_RESOLUTION_ABILITIES +enum syncmgr.h SYNCMGR_RESOLUTION_FEEDBACK +enum syncmgr.h SYNCMGR_SYNC_CONTROL_FLAGS +enum thumbnailstreamcache.h ThumbnailStreamCacheOptions + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Lightweight Utility Functions > String Functions +function shlwapi.h ChrCmpI =chrcmpia +function shlwapi.h GetAcceptLanguages =getacceptlanguagesa +function shlwapi.h IntlStrEqN =intlstreqna +function shlwapi.h IntlStrEqNI =intlstreqnia +function shlwapi.h IntlStrEqWorker =intlstreqworkera +function shlwapi.h IsCharSpace =ischarspacea +function shlwapi.h SHLoadIndirectString +function shlwapi.h SHStrDup =shstrdupa +function shlwapi.h StrCat =strcatw +function shlwapi.h StrCatBuff =strcatbuffa +function shlwapi.h StrCatChainW +function shlwapi.h StrChr =strchra +function shlwapi.h StrChrI =strchria +function shlwapi.h StrChrNIW +function shlwapi.h StrChrNW +function shlwapi.h StrCmp =strcmpw +function shlwapi.h StrCmpC =strcmpca +function shlwapi.h StrCmpI =strcmpiw +function shlwapi.h StrCmpIC =strcmpica +function shlwapi.h StrCmpLogicalW +function shlwapi.h StrCmpN =strcmpna +function shlwapi.h StrCmpNC =strcmpnca +function shlwapi.h StrCmpNI =strcmpnia +function shlwapi.h StrCmpNIC =strcmpnica +function shlwapi.h StrCpy =strcpyw +function shlwapi.h StrCpyN =strcpynw +function shlwapi.h StrCSpn =strcspna +function shlwapi.h StrCSpnI =strcspnia +function shlwapi.h StrDup =strdupa +function shlwapi.h StrFormatByteSize64 =strformatbytesize64a +function shlwapi.h StrFormatByteSizeA +function shlwapi.h StrFormatByteSizeEx +function shlwapi.h StrFormatByteSizeW +function shlwapi.h StrFormatKBSize =strformatkbsizea +function shlwapi.h StrFromTimeInterval =strfromtimeintervala +function shlwapi.h StrIsIntlEqual =strisintlequala +function shlwapi.h StrNCat =strncata +function shlwapi.h StrPBrk =strpbrka +function shlwapi.h StrRChr =strrchra +function shlwapi.h StrRChrI =strrchria +function shlwapi.h StrRetToBSTR +function shlwapi.h StrRetToBuf =strrettobufa +function shlwapi.h StrRetToStr =strrettostra +function shlwapi.h StrRStrI =strrstria +function shlwapi.h StrSpn =strspna +function shlwapi.h StrStr =strstra +function shlwapi.h StrStrI =strstria +function shlwapi.h StrToInt =strtointa +function shlwapi.h StrToInt64Ex =strtoint64exa +function shlwapi.h StrToIntEx =strtointexa +function shlwapi.h StrTrim =strtrima +function shlwapi.h wnsprintf =wnsprintfa +function shlwapi.h wvnsprintf =wvnsprintfa + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Lightweight Utility Functions > Path Functions +function shlwapi.h PathAddBackslash =pathaddbackslasha +function shlwapi.h PathAddExtension =pathaddextensiona +function shlwapi.h PathAppend =pathappenda +function shlwapi.h PathBuildRoot =pathbuildroota +function shlwapi.h PathCanonicalize =pathcanonicalizea +function shlwapi.h PathCombine =pathcombinea +function shlwapi.h PathCommonPrefix =pathcommonprefixa +function shlwapi.h PathCompactPath =pathcompactpatha +function shlwapi.h PathCompactPathEx =pathcompactpathexa +function shlwapi.h PathCreateFromUrl =pathcreatefromurla +function shlwapi.h PathCreateFromUrlAlloc +function shlwapi.h PathFileExists =pathfileexistsa +function shlwapi.h PathFindExtension =pathfindextensiona +function shlwapi.h PathFindFileName =pathfindfilenamea +function shlwapi.h PathFindNextComponent =pathfindnextcomponenta +function shlwapi.h PathFindOnPath =pathfindonpatha +function shlwapi.h PathFindSuffixArray =pathfindsuffixarraya +function shlwapi.h PathGetArgs =pathgetargsa +function shlwapi.h PathGetCharType =pathgetchartypea +function shlwapi.h PathGetDriveNumber =pathgetdrivenumbera +function shlwapi.h PathIsContentType =pathiscontenttypea +function shlwapi.h PathIsDirectory =pathisdirectorya +function shlwapi.h PathIsDirectoryEmpty =pathisdirectoryemptya +function shlwapi.h PathIsFileSpec =pathisfilespeca +function shlwapi.h PathIsHTMLFile =pathishtmlfilea +function shlwapi.h PathIsLFNFileSpec =pathislfnfilespeca +function shlwapi.h PathIsNetworkPath =pathisnetworkpatha +function shlwapi.h PathIsPrefix =pathisprefixa +function shlwapi.h PathIsRelative =pathisrelativea +function shlwapi.h PathIsRoot =pathisroota +function shlwapi.h PathIsSameRoot =pathissameroota +function shlwapi.h PathIsSystemFolder =pathissystemfoldera +function shlwapi.h PathIsUNC =pathisunca +function shlwapi.h PathIsUNCServer =pathisuncservera +function shlwapi.h PathIsUNCServerShare =pathisuncserversharea +function shlwapi.h PathIsURL =pathisurla +function shlwapi.h PathMakePretty =pathmakeprettya +function shlwapi.h PathMakeSystemFolder =pathmakesystemfoldera +function shlwapi.h PathMatchSpec =pathmatchspeca +function shlwapi.h PathMatchSpecEx =pathmatchspecexa +function shlwapi.h PathParseIconLocation =pathparseiconlocationa +function shlwapi.h PathQuoteSpaces =pathquotespacesa +function shlwapi.h PathRelativePathTo =pathrelativepathtoa +function shlwapi.h PathRemoveArgs =pathremoveargsa +function shlwapi.h PathRemoveBackslash =pathremovebackslasha +function shlwapi.h PathRemoveBlanks =pathremoveblanksa +function shlwapi.h PathRemoveExtension =pathremoveextensiona +function shlwapi.h PathRemoveFileSpec =pathremovefilespeca +function shlwapi.h PathRenameExtension =pathrenameextensiona +function shlwapi.h PathSearchAndQualify =pathsearchandqualifya +function shlwapi.h PathSetDlgItemPath =pathsetdlgitempatha +function shlwapi.h PathSkipRoot =pathskiproota +function shlwapi.h PathStripPath =pathstrippatha +function shlwapi.h PathStripToRoot =pathstriptoroota +function shlwapi.h PathUndecorate =pathundecoratea +function shlwapi.h PathUnExpandEnvStrings =pathunexpandenvstringsa +function shlwapi.h PathUnmakeSystemFolder =pathunmakesystemfoldera +function shlwapi.h PathUnquoteSpaces =pathunquotespacesa +function shlwapi.h SHSkipJunction +function shlwapi.h UrlApplyScheme =urlapplyschemea +function shlwapi.h UrlCanonicalize =urlcanonicalizea +function shlwapi.h UrlCombine =urlcombinea +function shlwapi.h UrlCompare =urlcomparea +function shlwapi.h UrlCreateFromPath =urlcreatefrompatha +function shlwapi.h UrlEscape =urlescapea +function shlwapi.h UrlEscapeSpaces +function shlwapi.h UrlGetLocation =urlgetlocationa +function shlwapi.h UrlGetPart =urlgetparta +function shlwapi.h UrlHash =urlhasha +function shlwapi.h UrlIs =urlisa +function shlwapi.h UrlIsFileUrl =urlisfileurla +function shlwapi.h UrlIsNoHistory =urlisnohistorya +function shlwapi.h UrlIsOpaque =urlisopaquea +function shlwapi.h UrlUnescape =urlunescapea +function shlwapi.h UrlUnescapeInPlace + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Lightweight Utility Functions > Registry Functions +function shlwapi.h AssocCreate +function shlwapi.h AssocGetPerceivedType +function shlwapi.h AssocIsDangerous +function shlwapi.h AssocQueryKey =assocquerykeya +function shlwapi.h AssocQueryString =assocquerystringa +function shlwapi.h AssocQueryStringByKey =assocquerystringbykeya +function shlwapi.h SHCopyKey =shcopykeya +function shlwapi.h SHDeleteEmptyKey =shdeleteemptykeya +function shlwapi.h SHDeleteKey =shdeletekeya +function shlwapi.h SHDeleteValue =shdeletevaluea +function shlwapi.h SHEnumKeyEx =shenumkeyexa +function shlwapi.h SHEnumValue =shenumvaluea +function shlwapi.h SHGetAssocKeys +function shlwapi.h SHGetValue =shgetvaluea +function shlwapi.h SHOpenRegStream2 =shopenregstream2a +function shlwapi.h SHQueryInfoKey =shqueryinfokeya +function shlwapi.h SHQueryValueEx =shqueryvalueexa +function shlwapi.h SHRegCloseUSKey +function shlwapi.h SHRegCreateUSKey =shregcreateuskeya +function shlwapi.h SHRegDeleteEmptyUSKey =shregdeleteemptyuskeya +function shlwapi.h SHRegDeleteUSValue =shregdeleteusvaluea +function shlwapi.h SHRegDuplicateHKey +function shlwapi.h SHRegEnumUSKey =shregenumuskeya +function shlwapi.h SHRegEnumUSValue =shregenumusvaluea +function shlwapi.h SHRegGetBoolUSValue =shreggetboolusvaluea +function shlwapi.h SHRegGetIntW +function shlwapi.h SHRegGetPath =shreggetpatha +function shlwapi.h SHRegGetUSValue =shreggetusvaluea +function shlwapi.h SHRegOpenUSKey =shregopenuskeya +function shlwapi.h SHRegQueryInfoUSKey =shregqueryinfouskeya +function shlwapi.h SHRegQueryUSValue =shregqueryusvaluea +function shlwapi.h SHRegSetPath =shregsetpatha +function shlwapi.h SHRegSetUSValue =shregsetusvaluea +function shlwapi.h SHRegSetValue +function shlwapi.h SHRegWriteUSValue =shregwriteusvaluea +function shlwapi.h SHSetValue =shsetvaluea + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Lightweight Utility Functions > Color Palette Functions +function shlwapi.h ColorAdjustLuma +function shlwapi.h ColorHLSToRGB +function shlwapi.h ColorRGBToHLS +function shlwapi.h SHCreateShellPalette + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Lightweight Utility Functions > Miscellaneous +function shlwapi.h DllInstall +function shlwapi.h GetProcessReference +function shlwapi.h HashData +function shlwapi.h IsInternetESCEnabled +function shlwapi.h IsOS +function shlwapi.h IStream_Copy +function shlwapi.h IStream_Read +function shlwapi.h IStream_ReadPidl +function shlwapi.h IStream_ReadStr +function shlwapi.h IStream_Reset +function shlwapi.h IStream_Size +function shlwapi.h IStream_Write +function shlwapi.h IStream_WritePidl +function shlwapi.h IStream_WriteStr +function shlwapi.h IUnknown_AtomicRelease +function shlwapi.h IUnknown_GetSite +function shlwapi.h IUnknown_GetWindow +function shlwapi.h IUnknown_QueryService +function shlwapi.h IUnknown_Set +function shlwapi.h IUnknown_SetSite +function shlwapi.h ParseURL =parseurla +function shlwapi.h QISearch +function shlwapi.h SetProcessReference +function shlwapi.h SHAutoComplete +function shlwapi.h SHCreateMemStream +function shlwapi.h SHCreateStreamOnFileEx +function shlwapi.h SHCreateThread +function shlwapi.h SHCreateThreadRef +function shlwapi.h SHCreateThreadWithHandle +function shlwapi.h SHGetThreadRef +function shlwapi.h SHGlobalCounterDecrement +function shlwapi.h SHGlobalCounterGetValue +function shlwapi.h SHGlobalCounterIncrement +function shlwapi.h SHIsLowMemoryMachine +function shlwapi.h SHReleaseThreadRef +function shlwapi.h SHSetThreadRef + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Macros +function combaseapi.h IID_PPV_ARGS +function propkeydef.h DEFINE_PROPERTYKEY +function propkeydef.h IsEqualPropertyKey +function shellapi.h NetAddr_DisplayErrorTip +function shellapi.h NetAddr_GetAddress +function shellapi.h NetAddr_GetAllowType +function shellapi.h NetAddr_SetAllowType +function shlwapi.h MAKEDLLVERULL + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Shell Reference > Shell Structures +enum thumbcache.h WTS_CONTEXTFLAGS +enum thumbcache.h WTS_FLAGS +struct appmgmt.h APPCATEGORYINFO +struct appmgmt.h APPCATEGORYINFOLIST +struct cpl.h CPLINFO +struct cpl.h NEWCPLINFO =newcplinfoa +struct credentialprovider.h CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION +struct credentialprovider.h CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR +struct dimm.h LOGFONT =logfonta +struct mobsync.h SYNCMGRHANDLERINFO +struct mobsync.h SYNCMGRITEM +struct mobsync.h SYNCMGRLOGERRORINFO +struct mobsync.h SYNCMGRPROGRESSITEM +struct profinfo.h PROFILEINFO =profileinfoa +struct propidl.h SERIALIZEDPROPERTYVALUE +struct shappmgr.h APPINFODATA +struct shappmgr.h PUBAPPINFO +struct shappmgr.h SLOWAPPINFO +struct shdeprecated.h BASEBROWSERDATA =basebrowserdatalh +struct shellapi.h APPBARDATA +struct shellapi.h ASSOCIATIONELEMENT +struct shellapi.h NC_ADDRESS +struct shellapi.h NOTIFYICONDATA =notifyicondataa +struct shellapi.h NOTIFYICONIDENTIFIER +struct shellapi.h OPEN_PRINTER_PROPS_INFO =open_printer_props_infoa +struct shellapi.h SHELLEXECUTEINFO =shellexecuteinfoa +struct shellapi.h SHFILEINFO =shfileinfoa +struct shellapi.h SHFILEOPSTRUCT =shfileopstructa +struct shellapi.h SHNAMEMAPPING =shnamemappinga +struct shellapi.h SHQUERYRBINFO +struct shellapi.h SHSTOCKICONINFO +struct shlobj_core.h BROWSEINFO =browseinfoa +struct shlobj_core.h CIDA +struct shlobj_core.h COMPONENT +struct shlobj_core.h COMPONENTSOPT +struct shlobj_core.h COMPPOS +struct shlobj_core.h COMPSTATEINFO +struct shlobj_core.h CSFV +struct shlobj_core.h DATABLOCK_HEADER +struct shlobj_core.h DEFCONTEXTMENU +struct shlobj_core.h DETAILSINFO +struct shlobj_core.h DFMICS +struct shlobj_core.h DROPDESCRIPTION +struct shlobj_core.h DROPFILES +struct shlobj_core.h EXP_DARWIN_LINK +struct shlobj_core.h EXP_PROPERTYSTORAGE +struct shlobj_core.h EXP_SPECIAL_FOLDER +struct shlobj_core.h EXP_SZ_LINK +struct shlobj_core.h FILE_ATTRIBUTES_ARRAY +struct shlobj_core.h FILEDESCRIPTOR =filedescriptora +struct shlobj_core.h FILEGROUPDESCRIPTOR =filegroupdescriptora +struct shlobj_core.h IE4COMPONENT +struct shlobj_core.h ITEMSPACING +struct shlobj_core.h NRESARRAY +struct shlobj_core.h NT_CONSOLE_PROPS +struct shlobj_core.h NT_FE_CONSOLE_PROPS +struct shlobj_core.h OPENASINFO +struct shlobj_core.h QCMINFO +struct shlobj_core.h SFV_CREATE +struct shlobj_core.h SFVM_HELPTOPIC_DATA +struct shlobj_core.h SFVM_PROPPAGE_DATA +struct shlobj_core.h SHARDAPPIDINFO +struct shlobj_core.h SHARDAPPIDINFOIDLIST +struct shlobj_core.h SHARDAPPIDINFOLINK +struct shlobj_core.h SHChangeNotifyEntry +struct shlobj_core.h SHDESCRIPTIONID +struct shlobj_core.h SHELLFLAGSTATE +struct shlobj_core.h SHELLSTATE =shellstatea +struct shlobj_core.h SHFOLDERCUSTOMSETTINGS +struct shlobj_core.h WALLPAPEROPT +struct shlobj.h AASHELLMENUFILENAME +struct shlobj.h AASHELLMENUITEM +struct shlobj.h BANDINFOSFB +struct shlobj.h FVSHOWINFO +struct shlobj.h SFV_SETITEMPOS +struct shlobj.h SHCOLUMNDATA +struct shlobj.h SHCOLUMNINFO +struct shlobj.h SHCOLUMNINIT +struct shlobj.h TBINFO +struct shlwapi.h DLLVERSIONINFO +struct shlwapi.h DLLVERSIONINFO2 +struct shlwapi.h PARSEDURL =parsedurla +struct shlwapi.h QITAB +struct shobjidl_core.h BANDSITEINFO +struct shobjidl_core.h CATEGORY_INFO +struct shobjidl_core.h CM_COLUMNINFO +struct shobjidl_core.h CMINVOKECOMMANDINFO +struct shobjidl_core.h CMINVOKECOMMANDINFOEX +struct shobjidl_core.h DELEGATEITEMID +struct shobjidl_core.h EXTRASEARCH +struct shobjidl_core.h FOLDERSETTINGS +struct shobjidl_core.h KNOWNFOLDER_DEFINITION +struct shobjidl_core.h PERSIST_FOLDER_TARGET_INFO +struct shobjidl_core.h PREVIEWHANDLERFRAMEINFO +struct shobjidl_core.h SHDRAGIMAGE +struct shobjidl_core.h SHELL_ITEM_RESOURCE +struct shobjidl_core.h SMCSHCHANGENOTIFYSTRUCT +struct shobjidl_core.h SMDATA +struct shobjidl_core.h SMINFO +struct shobjidl_core.h SORTCOLUMN +struct shobjidl_core.h SV2CVW2_PARAMS +struct shobjidl_core.h THUMBBUTTON +struct shobjidl.h NSTCCUSTOMDRAW +struct shobjidl.h OVERLAPPED +struct shtypes.h COMDLG_FILTERSPEC +struct shtypes.h ITEMIDLIST +struct shtypes.h SHELLDETAILS +struct shtypes.h SHITEMID +struct shtypes.h STRRET +struct syncmgr.h CONFIRM_CONFLICT_ITEM +struct syncmgr.h CONFIRM_CONFLICT_RESULT_INFO +struct syncmgr.h SYNCMGR_CONFLICT_ID_INFO +struct thumbcache.h WTS_THUMBNAILID +struct tlogstg.h WINDOWDATA +struct urlmon.h SOFTDISTINFO +struct winuser.h HELPINFO +struct winuser.h HELPWININFO =helpwininfoa +struct winuser.h MULTIKEYHELP =multikeyhelpa + +# Desktop App Technologies > Desktop Environment > The Windows Shell > Deprecated API +callback shlobj_core.h LPFNDFMCALLBACK +callback shlobj_core.h LPFNVIEWCALLBACK +enum shdeprecated.h SECURELOCK =securelockcode +function shellapi.h DoEnvironmentSubst =doenvironmentsubsta +function shellapi.h ExtractAssociatedIconEx =extractassociatediconexa +function shellapi.h SHCreateProcessAsUserW +function shellapi.h ShellMessageBox =shellmessageboxa +function shellapi.h SHInvokePrinterCommand =shinvokeprintercommanda +function shlobj_core.h CIDLData_CreateFromIDArray +function shlobj_core.h DAD_AutoScroll +function shlobj_core.h DAD_DragEnterEx +function shlobj_core.h DAD_DragEnterEx2 +function shlobj_core.h DAD_DragLeave +function shlobj_core.h DAD_DragMove +function shlobj_core.h DAD_SetDragImage +function shlobj_core.h DAD_ShowDragImage +function shlobj_core.h ILLoadFromStream +function shlobj_core.h ILLoadFromStreamEx(IStream*, PIDLIST_ABSOLUTE*) =illoadfromstreamex +function shlobj_core.h IsNetDrive +function shlobj_core.h IsUserAnAdmin +function shlobj_core.h OpenRegStream +function shlobj_core.h PathCleanupSpec +function shlobj_core.h PathGetShortPath +function shlobj_core.h PathIsExe +function shlobj_core.h PathResolve +function shlobj_core.h PickIconDlg +function shlobj_core.h ReadCabinetState +function shlobj_core.h RealDriveType +function shlobj_core.h RestartDialog +function shlobj_core.h RestartDialogEx +function shlobj_core.h SHAddFromPropSheetExtArray +function shlobj_core.h SHAlloc +function shlobj_core.h SHCloneSpecialIDList +function shlobj_core.h SHCLSIDFromString +function shlobj_core.h SHCoCreateInstance +function shlobj_core.h SHCreateDirectory +function shlobj_core.h SHCreateDirectoryEx =shcreatedirectoryexa +function shlobj_core.h SHCreateFileExtractIcon =shcreatefileextracticonw +function shlobj_core.h SHCreateStdEnumFmtEtc +function shlobj_core.h SHDestroyPropSheetExtArray +function shlobj_core.h Shell_GetCachedImageIndex +function shlobj_core.h Shell_GetImageLists +function shlobj_core.h Shell_MergeMenus +function shlobj_core.h SHFind_InitMenuPopup +function shlobj_core.h SHFindFiles +function shlobj_core.h SHFlushSFCache +function shlobj_core.h SHFormatDrive +function shlobj_core.h SHFree +function shlobj_core.h SHGetAttributesFromDataObject +function shlobj_core.h SHGetFolderLocation +function shlobj_core.h SHGetFolderPath =shgetfolderpatha +function shlobj_core.h SHGetFolderPathAndSubDir =shgetfolderpathandsubdira +function shlobj_core.h SHGetMalloc +function shlobj_core.h SHGetRealIDL +function shlobj_core.h SHGetSetFolderCustomSettings +function shlobj_core.h SHGetSetSettings +function shlobj_core.h SHGetSpecialFolderLocation +function shlobj_core.h SHGetSpecialFolderPath =shgetspecialfolderpatha +function shlobj_core.h SHHandleUpdateImage +function shlobj_core.h SHILCreateFromPath +function shlobj_core.h SHLimitInputEdit +function shlobj_core.h SHMapPIDLToSystemImageListIndex +function shlobj_core.h SHObjectProperties +function shlobj_core.h SHReplaceFromPropSheetExtArray +function shlobj_core.h SHRestricted +function shlobj_core.h SHSetFolderPath =shsetfolderpatha +function shlobj_core.h SHShellFolderView_Message +function shlobj_core.h SHStartNetConnectionDialog =shstartnetconnectiondialoga +function shlobj_core.h SHValidateUNC +function shlobj_core.h SignalFileOpen +function shlobj_core.h Win32DeleteFile +function shlobj_core.h WriteCabinetState +function shlobj.h DriveType +function shlobj.h GetFileNameFromBrowse +function shlobj.h PathIsSlow =pathisslowa +function shlobj.h PathProcessCommand +function shlobj.h PerUserInit +function shlobj.h SHCreatePropSheetExtArray +function shlobj.h SHCreateQueryCancelAutoPlayMoniker +function shlobj.h SHLoadOLE +function shlobj.h SHOpenPropSheet =shopenpropsheeta +function shlobj.h UpdateAllDesktopSubscriptions =iadesktopp2-updatealldesktopsubscriptions +function shlwapi.h ConnectToConnectionPoint +function shlwapi.h GetMenuPosFromID +function shlwapi.h SHAllocShared +function shlwapi.h SHAnsiToAnsi +function shlwapi.h SHAnsiToUnicode +function shlwapi.h SHCreateStreamOnFile =shcreatestreamonfilea +function shlwapi.h SHFormatDateTime =shformatdatetimea +function shlwapi.h SHFreeShared +function shlwapi.h SHGetInverseCMAP +function shlwapi.h SHGetViewStatePropertyBag +function shlwapi.h SHLockShared +function shlwapi.h SHMessageBoxCheck =shmessageboxchecka +function shlwapi.h SHOpenRegStream =shopenregstreama +function shlwapi.h SHRegGetBoolValueFromHKCUHKLM +function shlwapi.h SHRegGetValue =shreggetvaluea +function shlwapi.h SHRegGetValueFromHKCUHKLM +function shlwapi.h SHSendMessageBroadcast =shsendmessagebroadcasta +function shlwapi.h SHStripMneumonic =shstripmneumonica +function shlwapi.h SHUnicodeToAnsi +function shlwapi.h SHUnicodeToUnicode +function shlwapi.h SHUnlockShared +function shlwapi.h StopWatchFlush +function shlwapi.h StopWatchMode +function shlwapi.h UrlFixupW +function shlwapi.h WhichPlatform +function shobjidl_core.h SHSimpleIDListFromPath +interface shdeprecated.h IBrowserService +interface shdeprecated.h IBrowserService2 +interface shdeprecated.h IBrowserService3 +interface shdeprecated.h IBrowserService4 +interface shdeprecated.h IExpDispSupport +interface shdeprecated.h IExpDispSupportXP +interface shdeprecated.h IShellService +interface shdeprecated.h ITravelEntry +interface shdeprecated.h ITravelLog +interface shimgdata.h IShellImageData +interface shlobj_core.h IShellFolderView +interface shlobj_core.h IShellIconOverlayManager +interface shlobj.h IColumnProvider +interface shlobj.h IDefViewFrame +interface shlobj.h IDocViewSite +interface shlobj.h IShellFolderBand +interface shlobj.h IShellImageStore +interface shlobj.h IThumbnailCapture +interface shobjidl_core.h IContextMenuSite +interface shobjidl_core.h IDeskBand +interface shobjidl_core.h IDeskBandInfo +interface shobjidl_core.h IDeskBar +interface shobjidl_core.h IMenuBand +interface shobjidl_core.h IRegTreeItem +interface shobjidl_core.h IShellTaskScheduler +interface shobjidl.h ICDBurnExt +interface shobjidl.h IDeskBand2 +interface shobjidl.h IFolderBandPriv +interface shobjidl.h IInsertItem +struct shdeprecated.h FOLDERSETDATA +struct shdeprecated.h TOOLBARITEM +struct shellapi.h SHCREATEPROCESSINFOW +struct shlobj_core.h AUTO_SCROLL_DATA +struct shlobj_core.h CABINETSTATE +struct shlobj_core.h SHChangeDWORDAsIDList +struct shlobj_core.h SHChangeUpdateImageIDList +struct shlobj.h SHChangeProductKeyAsIDList +struct shobjidl_core.h DESKBANDINFO + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Interfaces +interface propsys.h IPropertyChange +interface propsys.h IPropertyChangeArray +interface propsys.h IPropertyDescription +interface propsys.h IPropertyDescription2 +interface propsys.h IPropertyDescriptionAliasInfo +interface propsys.h IPropertyDescriptionList +interface propsys.h IPropertyDescriptionRelatedPropertyInfo +interface propsys.h IPropertyDescriptionSearchInfo +interface propsys.h IPropertyEnumType +interface propsys.h IPropertyEnumType2 +interface propsys.h IPropertyEnumTypeList +interface propsys.h IPropertyStore +interface propsys.h IPropertyStoreCache +interface propsys.h IPropertyStoreCapabilities +interface propsys.h IPropertyStoreFactory +interface propsys.h IPropertySystem +interface shobjidl_core.h IPropertyUI + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Functions > Property Bag Functions +function propsys.h PSPropertyBag_Delete +function propsys.h PSPropertyBag_ReadBOOL +function propsys.h PSPropertyBag_ReadBSTR +function propsys.h PSPropertyBag_ReadDWORD +function propsys.h PSPropertyBag_ReadGUID +function propsys.h PSPropertyBag_ReadInt +function propsys.h PSPropertyBag_ReadLONG +function propsys.h PSPropertyBag_ReadPOINTL +function propsys.h PSPropertyBag_ReadPOINTS +function propsys.h PSPropertyBag_ReadPropertyKey +function propsys.h PSPropertyBag_ReadRECTL +function propsys.h PSPropertyBag_ReadSHORT +function propsys.h PSPropertyBag_ReadStr +function propsys.h PSPropertyBag_ReadStrAlloc +function propsys.h PSPropertyBag_ReadStream +function propsys.h PSPropertyBag_ReadType +function propsys.h PSPropertyBag_ReadULONGLONG +function propsys.h PSPropertyBag_ReadUnknown +function propsys.h PSPropertyBag_WriteBOOL +function propsys.h PSPropertyBag_WriteBSTR +function propsys.h PSPropertyBag_WriteDWORD +function propsys.h PSPropertyBag_WriteGUID +function propsys.h PSPropertyBag_WriteInt +function propsys.h PSPropertyBag_WriteLONG +function propsys.h PSPropertyBag_WritePOINTL +function propsys.h PSPropertyBag_WritePOINTS +function propsys.h PSPropertyBag_WritePropertyKey +function propsys.h PSPropertyBag_WriteRECTL +function propsys.h PSPropertyBag_WriteSHORT +function propsys.h PSPropertyBag_WriteStr +function propsys.h PSPropertyBag_WriteStream +function propsys.h PSPropertyBag_WriteULONGLONG +function propsys.h PSPropertyBag_WriteUnknown + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Functions > PROPVARIANT and VARIANT Functions +function propidl.h FreePropVariantArray +function propidl.h PropVariantClear +function propidl.h PropVariantCopy +function propsys.h PropVariantToWinRTPropertyValue +function propsys.h WinRTPropertyValueToPropVariant +function propvarutil.h ClearPropVariantArray +function propvarutil.h ClearVariantArray +function propvarutil.h InitPropVariantFromBoolean +function propvarutil.h InitPropVariantFromBooleanVector +function propvarutil.h InitPropVariantFromBuffer +function propvarutil.h InitPropVariantFromCLSID +function propvarutil.h InitPropVariantFromDouble +function propvarutil.h InitPropVariantFromDoubleVector +function propvarutil.h InitPropVariantFromFileTime +function propvarutil.h InitPropVariantFromFileTimeVector +function propvarutil.h InitPropVariantFromGUIDAsBuffer +function propvarutil.h InitPropVariantFromGUIDAsString +function propvarutil.h InitPropVariantFromInt16 +function propvarutil.h InitPropVariantFromInt16Vector +function propvarutil.h InitPropVariantFromInt32 +function propvarutil.h InitPropVariantFromInt32Vector +function propvarutil.h InitPropVariantFromInt64 +function propvarutil.h InitPropVariantFromInt64Vector +function propvarutil.h InitPropVariantFromPropVariantVectorElem +function propvarutil.h InitPropVariantFromResource +function propvarutil.h InitPropVariantFromString +function propvarutil.h InitPropVariantFromStringAsVector +function propvarutil.h InitPropVariantFromStringVector +function propvarutil.h InitPropVariantFromStrRet +function propvarutil.h InitPropVariantFromUInt16 +function propvarutil.h InitPropVariantFromUInt16Vector +function propvarutil.h InitPropVariantFromUInt32 +function propvarutil.h InitPropVariantFromUInt32Vector +function propvarutil.h InitPropVariantFromUInt64 +function propvarutil.h InitPropVariantFromUInt64Vector +function propvarutil.h InitPropVariantVectorFromPropVariant +function propvarutil.h InitVariantFromBoolean +function propvarutil.h InitVariantFromBooleanArray +function propvarutil.h InitVariantFromBuffer +function propvarutil.h InitVariantFromDispatch +function propvarutil.h InitVariantFromDosDateTime +function propvarutil.h InitVariantFromDouble +function propvarutil.h InitVariantFromDoubleArray +function propvarutil.h InitVariantFromFileTime +function propvarutil.h InitVariantFromFileTimeArray +function propvarutil.h InitVariantFromGUIDAsBuffer +function propvarutil.h InitVariantFromGUIDAsString +function propvarutil.h InitVariantFromInt16 +function propvarutil.h InitVariantFromInt16Array +function propvarutil.h InitVariantFromInt32 +function propvarutil.h InitVariantFromInt32Array +function propvarutil.h InitVariantFromInt64 +function propvarutil.h InitVariantFromInt64Array +function propvarutil.h InitVariantFromResource +function propvarutil.h InitVariantFromString +function propvarutil.h InitVariantFromStringArray +function propvarutil.h InitVariantFromStrRet +function propvarutil.h InitVariantFromUInt16 +function propvarutil.h InitVariantFromUInt16Array +function propvarutil.h InitVariantFromUInt32 +function propvarutil.h InitVariantFromUInt32Array +function propvarutil.h InitVariantFromUInt64 +function propvarutil.h InitVariantFromUInt64Array +function propvarutil.h InitVariantFromVariantArrayElem +function propvarutil.h IsPropVariantString +function propvarutil.h IsPropVariantVector +function propvarutil.h IsVariantArray +function propvarutil.h IsVariantString +function propvarutil.h IsVarTypeFloat +function propvarutil.h IsVarTypeInteger +function propvarutil.h IsVarTypeNumber +function propvarutil.h IsVarTypeSignedInteger +function propvarutil.h IsVarTypeUnsignedInteger +function propvarutil.h PropVariantChangeType +function propvarutil.h PropVariantCompare +function propvarutil.h PropVariantCompareEx +function propvarutil.h PropVariantGetBooleanElem +function propvarutil.h PropVariantGetDoubleElem +function propvarutil.h PropVariantGetElem +function propvarutil.h PropVariantGetElementCount +function propvarutil.h PropVariantGetFileTimeElem +function propvarutil.h PropVariantGetInt16Elem +function propvarutil.h PropVariantGetInt32Elem +function propvarutil.h PropVariantGetInt64Elem +function propvarutil.h PropVariantGetStringElem +function propvarutil.h PropVariantGetUInt16Elem +function propvarutil.h PropVariantGetUInt32Elem +function propvarutil.h PropVariantGetUInt64Elem +function propvarutil.h PropVariantToBoolean +function propvarutil.h PropVariantToBooleanVector +function propvarutil.h PropVariantToBooleanVectorAlloc +function propvarutil.h PropVariantToBooleanWithDefault +function propvarutil.h PropVariantToBSTR +function propvarutil.h PropVariantToBuffer +function propvarutil.h PropVariantToCLSID +function propvarutil.h PropVariantToDouble +function propvarutil.h PropVariantToDoubleVector +function propvarutil.h PropVariantToDoubleVectorAlloc +function propvarutil.h PropVariantToDoubleWithDefault +function propvarutil.h PropVariantToFileTime +function propvarutil.h PropVariantToFileTimeVector +function propvarutil.h PropVariantToFileTimeVectorAlloc +function propvarutil.h PropVariantToGUID +function propvarutil.h PropVariantToInt16 +function propvarutil.h PropVariantToInt16Vector +function propvarutil.h PropVariantToInt16VectorAlloc +function propvarutil.h PropVariantToInt16WithDefault +function propvarutil.h PropVariantToInt32 +function propvarutil.h PropVariantToInt32Vector +function propvarutil.h PropVariantToInt32VectorAlloc +function propvarutil.h PropVariantToInt32WithDefault +function propvarutil.h PropVariantToInt64 +function propvarutil.h PropVariantToInt64Vector +function propvarutil.h PropVariantToInt64VectorAlloc +function propvarutil.h PropVariantToInt64WithDefault +function propvarutil.h PropVariantToString +function propvarutil.h PropVariantToStringAlloc +function propvarutil.h PropVariantToStringVector +function propvarutil.h PropVariantToStringVectorAlloc +function propvarutil.h PropVariantToStringWithDefault +function propvarutil.h PropVariantToStrRet +function propvarutil.h PropVariantToUInt16 +function propvarutil.h PropVariantToUInt16Vector +function propvarutil.h PropVariantToUInt16VectorAlloc +function propvarutil.h PropVariantToUInt16WithDefault +function propvarutil.h PropVariantToUInt32 +function propvarutil.h PropVariantToUInt32Vector +function propvarutil.h PropVariantToUInt32VectorAlloc +function propvarutil.h PropVariantToUInt32WithDefault +function propvarutil.h PropVariantToUInt64 +function propvarutil.h PropVariantToUInt64Vector +function propvarutil.h PropVariantToUInt64VectorAlloc +function propvarutil.h PropVariantToUInt64WithDefault +function propvarutil.h PropVariantToVariant +function propvarutil.h StgDeserializePropVariant +function propvarutil.h StgSerializePropVariant +function propvarutil.h VariantCompare +function propvarutil.h VariantGetBooleanElem +function propvarutil.h VariantGetDoubleElem +function propvarutil.h VariantGetElem +function propvarutil.h VariantGetElementCount +function propvarutil.h VariantGetInt16Elem +function propvarutil.h VariantGetInt32Elem +function propvarutil.h VariantGetInt64Elem +function propvarutil.h VariantGetStringElem +function propvarutil.h VariantGetUInt16Elem +function propvarutil.h VariantGetUInt32Elem +function propvarutil.h VariantGetUInt64Elem +function propvarutil.h VariantToBoolean +function propvarutil.h VariantToBooleanArray +function propvarutil.h VariantToBooleanArrayAlloc +function propvarutil.h VariantToBooleanWithDefault +function propvarutil.h VariantToBuffer +function propvarutil.h VariantToDosDateTime +function propvarutil.h VariantToDouble +function propvarutil.h VariantToDoubleArray +function propvarutil.h VariantToDoubleArrayAlloc +function propvarutil.h VariantToDoubleWithDefault +function propvarutil.h VariantToFileTime +function propvarutil.h VariantToGUID +function propvarutil.h VariantToInt16 +function propvarutil.h VariantToInt16Array +function propvarutil.h VariantToInt16ArrayAlloc +function propvarutil.h VariantToInt16WithDefault +function propvarutil.h VariantToInt32 +function propvarutil.h VariantToInt32Array +function propvarutil.h VariantToInt32ArrayAlloc +function propvarutil.h VariantToInt32WithDefault +function propvarutil.h VariantToInt64 +function propvarutil.h VariantToInt64Array +function propvarutil.h VariantToInt64ArrayAlloc +function propvarutil.h VariantToInt64WithDefault +function propvarutil.h VariantToPropVariant +function propvarutil.h VariantToString +function propvarutil.h VariantToStringAlloc +function propvarutil.h VariantToStringArray +function propvarutil.h VariantToStringArrayAlloc +function propvarutil.h VariantToStringWithDefault +function propvarutil.h VariantToStrRet +function propvarutil.h VariantToUInt16 +function propvarutil.h VariantToUInt16Array +function propvarutil.h VariantToUInt16ArrayAlloc +function propvarutil.h VariantToUInt16WithDefault +function propvarutil.h VariantToUInt32 +function propvarutil.h VariantToUInt32Array +function propvarutil.h VariantToUInt32ArrayAlloc +function propvarutil.h VariantToUInt32WithDefault +function propvarutil.h VariantToUInt64 +function propvarutil.h VariantToUInt64Array +function propvarutil.h VariantToUInt64ArrayAlloc +function propvarutil.h VariantToUInt64WithDefault +function shlobj_core.h SHPropStgCreate +function shlobj_core.h SHPropStgReadMultiple +function shlobj_core.h SHPropStgWriteMultiple + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Functions +function propsys.h PSCoerceToCanonicalValue +function propsys.h PSCreateAdapterFromPropertyStore +function propsys.h PSCreateDelayedMultiplexPropertyStore +function propsys.h PSCreateMemoryPropertyStore +function propsys.h PSCreateMultiplexPropertyStore +function propsys.h PSCreatePropertyChangeArray +function propsys.h PSCreatePropertyStoreFromObject +function propsys.h PSCreatePropertyStoreFromPropertySetStorage +function propsys.h PSCreateSimplePropertyChange +function propsys.h PSEnumeratePropertyDescriptions +function propsys.h PSFormatForDisplay +function propsys.h PSFormatForDisplayAlloc +function propsys.h PSFormatPropertyValue +function propsys.h PSGetImageReferenceForValue +function propsys.h PSGetItemPropertyHandler +function propsys.h PSGetItemPropertyHandlerWithCreateObject +function propsys.h PSGetNamedPropertyFromPropertyStorage +function propsys.h PSGetNameFromPropertyKey +function propsys.h PSGetPropertyDescription +function propsys.h PSGetPropertyDescriptionByName +function propsys.h PSGetPropertyDescriptionListFromString +function propsys.h PSGetPropertyFromPropertyStorage +function propsys.h PSGetPropertyKeyFromName +function propsys.h PSGetPropertySystem +function propsys.h PSGetPropertyValue +function propsys.h PSLookupPropertyHandlerCLSID +function propsys.h PSPropertyKeyFromString +function propsys.h PSRefreshPropertySchema +function propsys.h PSRegisterPropertySchema +function propsys.h PSSetPropertyValue +function propsys.h PSStringFromPropertyKey +function propsys.h PSUnregisterPropertySchema +function shellapi.h SHGetPropertyStoreForWindow +function shlobj_core.h PifMgr_CloseProperties +function shlobj_core.h PifMgr_GetProperties +function shlobj_core.h PifMgr_OpenProperties +function shlobj_core.h PifMgr_SetProperties +function shlobj_core.h SHPropStgCreate +function shlobj_core.h SHPropStgReadMultiple +function shlobj_core.h SHPropStgWriteMultiple +function shobjidl_core.h SHGetPropertyStoreFromIDList +function shobjidl_core.h SHGetPropertyStoreFromParsingName +function shobjidl.h SHAddDefaultPropertiesByExt + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Structures +struct shlobj_core.h PROPPRG +struct wtypes.h PROPERTYKEY + +# Desktop App Technologies > Desktop Environment > Windows Property System > Property System Reference > Constants, Enumerations, and Flags +enum propsys.h GETPROPERTYSTOREFLAGS +enum propsys.h PKA_FLAGS +enum propsys.h PROPDESC_AGGREGATION_TYPE +enum propsys.h PROPDESC_COLUMNINDEX_TYPE +enum propsys.h PROPDESC_CONDITION_TYPE +enum propsys.h PROPDESC_ENUMFILTER +enum propsys.h PROPDESC_FORMAT_FLAGS +enum propsys.h PROPDESC_RELATIVEDESCRIPTION_TYPE +enum propsys.h PROPDESC_SEARCHINFO_FLAGS +enum propsys.h PROPDESC_TYPE_FLAGS +enum propsys.h PROPDESC_VIEW_FLAGS +enum propsys.h PSC_STATE +enum propvarutil.h PROPVAR_COMPARE_UNIT +enum shobjidl_core.h PDOPSTATUS +enum shobjidl_core.h PLACEHOLDER_STATES +enum shobjidl_core.h PROPERTYUI_FLAGS +enum shobjidl_core.h SYNC_TRANSFER_STATUS +enum shobjidl.h SYNC_ENGINE_STATE_FLAGS + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Protocol handlers interfaces +interface searchapi.h IOpLockStatus +interface searchapi.h IProtocolHandlerSite +interface searchapi.h ISearchProtocol +interface searchapi.h ISearchProtocol2 +interface searchapi.h ISearchProtocolThreadContext +interface searchapi.h IUrlAccessor +interface searchapi.h IUrlAccessor2 +interface searchapi.h IUrlAccessor3 +interface searchapi.h IUrlAccessor4 + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Querying interfaces +interface searchapi.h IRowsetEvents +interface searchapi.h IRowsetPrioritization +interface searchapi.h ISearchQueryHelper +interface structuredquery.h IConditionFactory +interface structuredquery.h IConditionFactory2 +interface structuredquery.h IConditionGenerator +interface structuredquery.h IEntity +interface structuredquery.h IInterval +interface structuredquery.h IMetaData +interface structuredquery.h INamedEntity +interface structuredquery.h INamedEntityCollector +interface structuredquery.h IQueryParser +interface structuredquery.h IQueryParserManager +interface structuredquery.h IQuerySolution +interface structuredquery.h IRelationship +interface structuredquery.h ISchemaLocalizerSupport +interface structuredquery.h ISchemaProvider +interface structuredquery.h ITokenCollection +interface structuredquerycondition.h ICondition +interface structuredquerycondition.h ICondition2 +interface structuredquerycondition.h IRichChunk + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Crawl scope interfaces +interface searchapi.h IEnumSearchRoots +interface searchapi.h IEnumSearchScopeRules +interface searchapi.h ISearchCrawlScopeManager +interface searchapi.h ISearchCrawlScopeManager2 +interface searchapi.h ISearchRoot +interface searchapi.h ISearchScopeRule + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Data add-in interfaces +interface filter.h IFilter +interface filtereg.h ILoadFilter +interface indexsrv.h IColumnMapper +interface indexsrv.h IColumnMapperCreator +interface indexsrv.h IPhraseSink +interface indexsrv.h ISimpleCommandCreator +interface indexsrv.h IStemmer +interface indexsrv.h IWordBreaker +interface indexsrv.h IWordFormSink +interface searchapi.h ISearchLanguageSupport + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Index management interfaces +interface searchapi.h ISearchCatalogManager +interface searchapi.h ISearchCatalogManager2 +interface searchapi.h ISearchManager +interface searchapi.h ISearchManager2 + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search interfaces > Notifications interfaces +interface searchapi.h ISearchItemsChangedSink +interface searchapi.h ISearchNotifyInlineSite +interface searchapi.h ISearchPersistentItemsChangedSink +interface searchapi.h ISearchViewChangedSink + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search structures +struct filter.h FILTERREGION +struct filter.h FULLPROPSPEC +struct filter.h STAT_CHUNK +struct filtereg.h FILTERED_DATA_SOURCES +struct indexsrv.h TEXT_SOURCE +struct searchapi.h AUTHENTICATION_INFO +struct searchapi.h INCREMENTAL_ACCESS_INFO +struct searchapi.h ITEM_INFO +struct searchapi.h PROXY_INFO +struct searchapi.h SEARCH_COLUMN_PROPERTIES +struct searchapi.h SEARCH_ITEM_CHANGE +struct searchapi.h SEARCH_ITEM_INDEXING_STATUS +struct searchapi.h SEARCH_ITEM_PERSISTENT_CHANGE +struct searchapi.h TIMEOUT_INFO +struct structuredquery.h HITRANGE +struct subsmgr.h ITEMPROP + +# Desktop App Technologies > Desktop Environment > Windows Search > Windows Search reference > Windows Search constants and enumerations +enum filter.h CHUNK_BREAKTYPE +enum filter.h CHUNKSTATE +enum filter.h IFILTER_FLAGS +enum searchapi.h AUTH_TYPE +enum searchapi.h CatalogPausedReason +enum searchapi.h CatalogStatus +enum searchapi.h CLUSION_REASON +enum searchapi.h FOLLOW_FLAGS +enum searchapi.h PRIORITIZE_FLAGS =tagprioritize_flags +enum searchapi.h PRIORITY_LEVEL +enum searchapi.h PROXY_ACCESS +enum searchapi.h ROWSETEVENT_ITEMSTATE +enum searchapi.h ROWSETEVENT_TYPE +enum searchapi.h SEARCH_INDEXING_PHASE +enum searchapi.h SEARCH_KIND_OF_CHANGE +enum searchapi.h SEARCH_NOTIFICATION_PRIORITY +enum searchapi.h SEARCH_QUERY_SYNTAX +enum searchapi.h SEARCH_TERM_EXPANSION +enum structuredquery.h CASE_REQUIREMENT +enum structuredquery.h CONDITION_CREATION_OPTIONS +enum structuredquery.h INTERVAL_LIMIT_KIND +enum structuredquery.h NAMED_ENTITY_CERTAINTY +enum structuredquery.h QUERY_PARSER_MANAGER_OPTION +enum structuredquery.h STRUCTURED_QUERY_MULTIOPTION +enum structuredquery.h STRUCTURED_QUERY_PARSE_ERROR +enum structuredquery.h STRUCTURED_QUERY_RESOLVE_OPTION +enum structuredquery.h STRUCTURED_QUERY_SINGLE_OPTION +enum structuredquery.h STRUCTURED_QUERY_SYNTAX +enum structuredquerycondition.h CONDITION_OPERATION +enum structuredquerycondition.h CONDITION_TYPE + +# Desktop App Technologies > Desktop Environment > Window Stations and Desktops > Window Station and Desktop Reference > Window Station and Desktop Functions +function winuser.h CloseDesktop +function winuser.h CloseWindowStation +function winuser.h CreateDesktop =createdesktopa +function winuser.h CreateDesktopEx =createdesktopexa +function winuser.h CreateWindowStation =createwindowstationa +function winuser.h EnumDesktops =enumdesktopsa +function winuser.h EnumDesktopWindows +function winuser.h EnumWindowStations =enumwindowstationsa +function winuser.h GetProcessWindowStation +function winuser.h GetThreadDesktop +function winuser.h GetUserObjectInformation =getuserobjectinformationa +function winuser.h OpenDesktop =opendesktopa +function winuser.h OpenInputDesktop +function winuser.h OpenWindowStation =openwindowstationa +function winuser.h SetProcessWindowStation +function winuser.h SetThreadDesktop +function winuser.h SetUserObjectInformation =setuserobjectinformationa +function winuser.h SwitchDesktop + +# Desktop App Technologies > Desktop Environment > Window Stations and Desktops > Window Station and Desktop Reference > Window Station and Desktop Structures +struct winuser.h USEROBJECTFLAGS + +# Desktop App Technologies > Application Installation and Servicing > Isolated Applications and Side-by-side Assemblies > Side-by-side Assemblies Reference > Side-by-Side Assembly API > Side-by-Side Assembly Enumerations +enum winsxs.h ASM_CMP_FLAGS +enum winsxs.h ASM_DISPLAY_FLAGS +enum winsxs.h ASM_NAME +enum winsxs.h CREATE_ASM_NAME_OBJ_FLAGS + +# Desktop App Technologies > Application Installation and Servicing > Isolated Applications and Side-by-side Assemblies > Side-by-side Assemblies Reference > Side-by-Side Assembly API > Side-by-Side Assembly Functions +function winsxs.h CreateAssemblyCache +function winsxs.h CreateAssemblyNameObject + +# Desktop App Technologies > Application Installation and Servicing > Isolated Applications and Side-by-side Assemblies > Side-by-side Assemblies Reference > Side-by-Side Assembly API > Side-by-Side Assembly Interfaces +interface winsxs.h IAssemblyCache +interface winsxs.h IAssemblyCacheItem +interface winsxs.h IAssemblyName + +# Desktop App Technologies > Application Installation and Servicing > Isolated Applications and Side-by-side Assemblies > Side-by-side Assemblies Reference > Side-by-Side Assembly API > Side-by-Side Assembly Structures +struct winsxs.h ASSEMBLY_INFO +struct winsxs.h FUSION_INSTALL_REFERENCE + +# Desktop App Technologies > Application Installation and Servicing > Isolated Applications and Side-by-side Assemblies > Side-by-side Assemblies Reference > Activation Context Reference +enum winnt.h ACTCTX_COMPATIBILITY_ELEMENT_TYPE +enum winnt.h ACTCTX_REQUESTED_RUN_LEVEL +function winbase.h ActivateActCtx +function winbase.h AddRefActCtx +function winbase.h CreateActCtx =createactctxa +function winbase.h DeactivateActCtx +function winbase.h FindActCtxSectionGuid +function winbase.h FindActCtxSectionString =findactctxsectionstringa +function winbase.h GetCurrentActCtx +function winbase.h QueryActCtxSettingsW +function winbase.h QueryActCtxW +function winbase.h ReleaseActCtx +function winbase.h ZombifyActCtx +struct winbase.h ACTCTX =actctxa +struct winbase.h ACTCTX_SECTION_KEYED_DATA +struct winnt.h ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION +struct winnt.h ACTIVATION_CONTEXT_COMPATIBILITY_INFORMATION +struct winnt.h ACTIVATION_CONTEXT_DETAILED_INFORMATION +struct winnt.h ACTIVATION_CONTEXT_QUERY_INDEX +struct winnt.h ACTIVATION_CONTEXT_RUN_LEVEL_INFORMATION +struct winnt.h ASSEMBLY_FILE_DETAILED_INFORMATION +struct winnt.h COMPATIBILITY_CONTEXT_ELEMENT + +# Desktop App Technologies > Application Installation and Servicing > Packaging, deployment, and query of Windows apps > API reference > Packaging API +enum appxpackaging.h APPX_BUNDLE_FOOTPRINT_FILE_TYPE +enum appxpackaging.h APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE +enum appxpackaging.h APPX_CAPABILITIES +enum appxpackaging.h APPX_COMPRESSION_OPTION +enum appxpackaging.h APPX_ENCRYPTED_PACKAGE_OPTIONS +enum appxpackaging.h APPX_FOOTPRINT_FILE_TYPE +enum appxpackaging.h APPX_PACKAGE_ARCHITECTURE +enum appxpackaging.h APPX_PACKAGE_ARCHITECTURE2 +enum appxpackaging.h APPX_PACKAGE_EDITOR_UPDATE_PACKAGE_MANIFEST_OPTIONS +enum appxpackaging.h APPX_PACKAGE_EDITOR_UPDATE_PACKAGE_OPTION +interface appxpackaging.h IAppxBlockMapBlock +interface appxpackaging.h IAppxBlockMapBlocksEnumerator +interface appxpackaging.h IAppxBlockMapFile +interface appxpackaging.h IAppxBlockMapFilesEnumerator +interface appxpackaging.h IAppxBlockMapReader +interface appxpackaging.h IAppxBundleFactory +interface appxpackaging.h IAppxBundleManifestOptionalBundleInfo +interface appxpackaging.h IAppxBundleManifestOptionalBundleInfoEnumerator +interface appxpackaging.h IAppxBundleManifestPackageInfo +interface appxpackaging.h IAppxBundleManifestPackageInfo2 +interface appxpackaging.h IAppxBundleManifestPackageInfoEnumerator +interface appxpackaging.h IAppxBundleManifestReader +interface appxpackaging.h IAppxBundleManifestReader2 +interface appxpackaging.h IAppxBundleReader +interface appxpackaging.h IAppxBundleWriter +interface appxpackaging.h IAppxBundleWriter2 +interface appxpackaging.h IAppxBundleWriter3 +interface appxpackaging.h IAppxBundleWriter4 +interface appxpackaging.h IAppxContentGroup +interface appxpackaging.h IAppxContentGroupFilesEnumerator +interface appxpackaging.h IAppxContentGroupMapReader +interface appxpackaging.h IAppxContentGroupMapWriter +interface appxpackaging.h IAppxContentGroupsEnumerator +interface appxpackaging.h IAppxEncryptedBundleWriter +interface appxpackaging.h IAppxEncryptedBundleWriter2 +interface appxpackaging.h IAppxEncryptedBundleWriter3 +interface appxpackaging.h IAppxEncryptedPackageWriter +interface appxpackaging.h IAppxEncryptedPackageWriter2 +interface appxpackaging.h IAppxEncryptionFactory +interface appxpackaging.h IAppxEncryptionFactory2 +interface appxpackaging.h IAppxEncryptionFactory3 +interface appxpackaging.h IAppxEncryptionFactory4 +interface appxpackaging.h IAppxFactory +interface appxpackaging.h IAppxFactory2 +interface appxpackaging.h IAppxFile +interface appxpackaging.h IAppxFilesEnumerator +interface appxpackaging.h IAppxManifestApplication +interface appxpackaging.h IAppxManifestApplicationsEnumerator +interface appxpackaging.h IAppxManifestDeviceCapabilitiesEnumerator +interface appxpackaging.h IAppxManifestMainPackageDependenciesEnumerator +interface appxpackaging.h IAppxManifestMainPackageDependency +interface appxpackaging.h IAppxManifestOptionalPackageInfo +interface appxpackaging.h IAppxManifestPackageDependenciesEnumerator +interface appxpackaging.h IAppxManifestPackageDependency +interface appxpackaging.h IAppxManifestPackageDependency2 +interface appxpackaging.h IAppxManifestPackageId +interface appxpackaging.h IAppxManifestPackageId2 +interface appxpackaging.h IAppxManifestProperties +interface appxpackaging.h IAppxManifestReader +interface appxpackaging.h IAppxManifestReader2 +interface appxpackaging.h IAppxManifestReader5 +interface appxpackaging.h IAppxManifestReader6 +interface appxpackaging.h IAppxManifestResourcesEnumerator +interface appxpackaging.h IAppxManifestTargetDeviceFamily +interface appxpackaging.h IAppxPackageEditor +interface appxpackaging.h IAppxPackageReader +interface appxpackaging.h IAppxPackageWriter +interface appxpackaging.h IAppxPackageWriter2 +interface appxpackaging.h IAppxPackageWriter3 +interface appxpackaging.h IAppxSourceContentGroupMapReader +struct appxpackaging.h APPX_ENCRYPTED_EXEMPTIONS +struct appxpackaging.h APPX_ENCRYPTED_PACKAGE_SETTINGS +struct appxpackaging.h APPX_ENCRYPTED_PACKAGE_SETTINGS2 +struct appxpackaging.h APPX_KEY_INFO +struct appxpackaging.h APPX_PACKAGE_SETTINGS +struct appxpackaging.h APPX_PACKAGE_WRITER_PAYLOAD_STREAM + +# Desktop App Technologies > Application Installation and Servicing > Packaging, deployment, and query of Windows apps > API reference > Query API +enum appmodel.h PackageOrigin +function appmodel.h ClosePackageInfo +function appmodel.h FindPackagesByPackageFamily +function appmodel.h FormatApplicationUserModelId +function appmodel.h GetApplicationUserModelId +function appmodel.h GetApplicationUserModelIdFromToken +function appmodel.h GetCurrentApplicationUserModelId +function appmodel.h GetCurrentPackageFamilyName +function appmodel.h GetCurrentPackageFullName +function appmodel.h GetCurrentPackageId +function appmodel.h GetCurrentPackageInfo +function appmodel.h GetCurrentPackageInfo2 +function appmodel.h GetCurrentPackagePath +function appmodel.h GetCurrentPackagePath2 +function appmodel.h GetPackageApplicationIds +function appmodel.h GetPackageFamilyName +function appmodel.h GetPackageFamilyNameFromToken +function appmodel.h GetPackageFullName +function appmodel.h GetPackageFullNameFromToken +function appmodel.h GetPackageId +function appmodel.h GetPackageInfo +function appmodel.h GetPackageInfo2 +function appmodel.h GetPackagePath +function appmodel.h GetPackagePathByFullName +function appmodel.h GetPackagePathByFullName2 +function appmodel.h GetPackagesByPackageFamily +function appmodel.h GetStagedPackageOrigin +function appmodel.h GetStagedPackagePathByFullName +function appmodel.h GetStagedPackagePathByFullName2 +function appmodel.h OpenPackageInfoByFullName +function appmodel.h PackageFamilyNameFromFullName +function appmodel.h PackageFamilyNameFromId +function appmodel.h PackageFullNameFromId +function appmodel.h PackageIdFromFullName +function appmodel.h PackageNameAndPublisherIdFromFamilyName +function appmodel.h ParseApplicationUserModelId +struct appmodel.h PACKAGE_ID +struct appmodel.h PACKAGE_INFO +struct appmodel.h PACKAGE_VERSION + +# Desktop App Technologies > Application Installation and Servicing > Developer licensing > Developer License APIs +function wsdevlicensing.h AcquireDeveloperLicense +function wsdevlicensing.h CheckDeveloperLicense +function wsdevlicensing.h RemoveDeveloperLicense + +# Desktop App Technologies > Application Installation and Servicing > Restart Manager > Restart Manager Reference > Restart Manager Callback Functions +callback restartmanager.h RM_WRITE_STATUS_CALLBACK + +# Desktop App Technologies > Application Installation and Servicing > Restart Manager > Restart Manager Reference > Restart Manager Enumerations +enum restartmanager.h RM_APP_STATUS +enum restartmanager.h RM_APP_TYPE +enum restartmanager.h RM_FILTER_ACTION +enum restartmanager.h RM_FILTER_TRIGGER +enum restartmanager.h RM_REBOOT_REASON +enum restartmanager.h RM_SHUTDOWN_TYPE + +# Desktop App Technologies > Application Installation and Servicing > Restart Manager > Restart Manager Reference > Restart Manager Functions +function restartmanager.h RmAddFilter +function restartmanager.h RmCancelCurrentTask +function restartmanager.h RmEndSession +function restartmanager.h RmGetFilterList +function restartmanager.h RmGetList +function restartmanager.h RmJoinSession +function restartmanager.h RmRegisterResources +function restartmanager.h RmRemoveFilter +function restartmanager.h RmRestart +function restartmanager.h RmShutdown +function restartmanager.h RmStartSession + +# Desktop App Technologies > Application Installation and Servicing > Restart Manager > Restart Manager Reference > Restart Manager Structures +struct restartmanager.h RM_FILTER_INFO +struct restartmanager.h RM_PROCESS_INFO +struct restartmanager.h RM_UNIQUE_PROCESS + +# Desktop App Technologies > Application Installation and Servicing > Windows Installer > Windows Installer Guide > Package Validation > Validation Automation > EvalCom2 Interfaces +interface evalcom2.h IValidate + +# Desktop App Technologies > Application Installation and Servicing > Windows Installer > Windows Installer Guide > Merge Modules > Merge Module Reference > Merge Module Automation > Mergemod Interfaces +interface mergemod.h IMsmConfigurableItem Interface =imsmconfigurableitem +interface mergemod.h IMsmConfigureModule Interface =imsmconfiguremodule +interface mergemod.h IMsmDependency Interface =imsmdependency +interface mergemod.h IMsmError Interface =imsmerror +interface mergemod.h IMsmGetFiles Interface =imsmgetfiles +interface mergemod.h IMsmMerge Interface =imsmmerge +interface mergemod.h IMsmMerge2 Interface =imsmmerge2 + +# Desktop App Technologies > Application Installation and Servicing > Windows Installer > Windows Installer Reference > Installer Functions > Installer Function Reference +callback msi.h INSTALLUI_HANDLER =installui_handlera +callback msi.h INSTALLUI_HANDLER_RECORD +function msi.h MsiAdvertiseProduct =msiadvertiseproducta +function msi.h MsiAdvertiseProductEx =msiadvertiseproductexa +function msi.h MsiAdvertiseScript =msiadvertisescripta +function msi.h MsiApplyMultiplePatches =msiapplymultiplepatchesa +function msi.h MsiApplyPatch =msiapplypatcha +function msi.h MsiBeginTransaction =msibegintransactiona +function msi.h MsiCloseAllHandles +function msi.h MsiCloseHandle +function msi.h MsiCollectUserInfo =msicollectuserinfoa +function msi.h MsiConfigureFeature =msiconfigurefeaturea +function msi.h MsiConfigureProduct =msiconfigureproducta +function msi.h MsiConfigureProductEx =msiconfigureproductexa +function msi.h MsiDetermineApplicablePatches =msidetermineapplicablepatchesa +function msi.h MsiDeterminePatchSequence =msideterminepatchsequencea +function msi.h MsiEnableLog =msienableloga +function msi.h MsiEndTransaction +function msi.h MsiEnumClients =msienumclientsa +function msi.h MsiEnumClientsEx =msienumclientsexa +function msi.h MsiEnumComponentQualifiers =msienumcomponentqualifiersa +function msi.h MsiEnumComponents =msienumcomponentsa +function msi.h MsiEnumComponentsEx =msienumcomponentsexa +function msi.h MsiEnumFeatures =msienumfeaturesa +function msi.h MsiEnumPatches =msienumpatchesa +function msi.h MsiEnumPatchesEx =msienumpatchesexa +function msi.h MsiEnumProducts =msienumproductsa +function msi.h MsiEnumProductsEx =msienumproductsexa +function msi.h MsiEnumRelatedProducts =msienumrelatedproductsa +function msi.h MsiExtractPatchXMLData =msiextractpatchxmldataa +function msi.h MsiGetComponentPath =msigetcomponentpatha +function msi.h MsiGetComponentPathEx =msigetcomponentpathexa +function msi.h MsiGetFeatureInfo =msigetfeatureinfoa +function msi.h MsiGetFeatureUsage =msigetfeatureusagea +function msi.h MsiGetFileHash =msigetfilehasha +function msi.h MsiGetFileSignatureInformation =msigetfilesignatureinformationa +function msi.h MsiGetFileVersion =msigetfileversiona +function msi.h MsiGetPatchFileList =msigetpatchfilelista +function msi.h MsiGetPatchInfo =msigetpatchinfoa +function msi.h MsiGetPatchInfoEx =msigetpatchinfoexa +function msi.h MsiGetProductCode =msigetproductcodea +function msi.h MsiGetProductInfo =msigetproductinfoa +function msi.h MsiGetProductInfoEx =msigetproductinfoexa +function msi.h MsiGetProductInfoFromScript =msigetproductinfofromscripta +function msi.h MsiGetProductProperty =msigetproductpropertya +function msi.h MsiGetShortcutTarget =msigetshortcuttargeta +function msi.h MsiGetUserInfo =msigetuserinfoa +function msi.h MsiInstallMissingComponent =msiinstallmissingcomponenta +function msi.h MsiInstallMissingFile =msiinstallmissingfilea +function msi.h MsiInstallProduct =msiinstallproducta +function msi.h MsiIsProductElevated =msiisproductelevateda +function msi.h MsiJoinTransaction +function msi.h MsiLocateComponent =msilocatecomponenta +function msi.h MsiNotifySidChange =msinotifysidchangea +function msi.h MsiOpenPackage =msiopenpackagea +function msi.h MsiOpenPackageEx =msiopenpackageexa +function msi.h MsiOpenProduct =msiopenproducta +function msi.h MsiProcessAdvertiseScript =msiprocessadvertisescripta +function msi.h MsiProvideAssembly =msiprovideassemblya +function msi.h MsiProvideComponent =msiprovidecomponenta +function msi.h MsiProvideQualifiedComponent =msiprovidequalifiedcomponenta +function msi.h MsiProvideQualifiedComponentEx =msiprovidequalifiedcomponentexa +function msi.h MsiQueryComponentState =msiquerycomponentstatea +function msi.h MsiQueryFeatureState =msiqueryfeaturestatea +function msi.h MsiQueryFeatureStateEx =msiqueryfeaturestateexa +function msi.h MsiQueryProductState =msiqueryproductstatea +function msi.h MsiReinstallFeature =msireinstallfeaturea +function msi.h MsiReinstallProduct =msireinstallproducta +function msi.h MsiRemovePatches =msiremovepatchesa +function msi.h MsiSetExternalUI =msisetexternaluia +function msi.h MsiSetExternalUIRecord +function msi.h MsiSetInternalUI +function msi.h MsiSourceListAddMediaDisk =msisourcelistaddmediadiska +function msi.h MsiSourceListAddSource =msisourcelistaddsourcea +function msi.h MsiSourceListAddSourceEx =msisourcelistaddsourceexa +function msi.h MsiSourceListClearAll =msisourcelistclearalla +function msi.h MsiSourceListClearAllEx =msisourcelistclearallexa +function msi.h MsiSourceListClearMediaDisk =msisourcelistclearmediadiska +function msi.h MsiSourceListClearSource =msisourcelistclearsourcea +function msi.h MsiSourceListEnumMediaDisks =msisourcelistenummediadisksa +function msi.h MsiSourceListEnumSources =msisourcelistenumsourcesa +function msi.h MsiSourceListForceResolution =msisourcelistforceresolutiona +function msi.h MsiSourceListForceResolutionEx =msisourcelistforceresolutionexa +function msi.h MsiSourceListGetInfo =msisourcelistgetinfoa +function msi.h MsiSourceListSetInfo =msisourcelistsetinfoa +function msi.h MsiUseFeature =msiusefeaturea +function msi.h MsiUseFeatureEx =msiusefeatureexa +function msi.h MsiVerifyPackage =msiverifypackagea + +# Desktop App Technologies > Application Installation and Servicing > Windows Installer > Windows Installer Reference > Installer Structures +struct msi.h MSIFILEHASHINFO +struct msi.h MSIPATCHSEQUENCEINFO =msipatchsequenceinfoa + +# Desktop App Technologies > Application Installation and Servicing > Windows Installer > Windows Installer Reference > Installer Database > Installer Database Reference > Database Functions +function msiquery.h MsiCreateRecord +function msiquery.h MsiCreateTransformSummaryInfo =msicreatetransformsummaryinfoa +function msiquery.h MsiDatabaseApplyTransform =msidatabaseapplytransforma +function msiquery.h MsiDatabaseCommit +function msiquery.h MsiDatabaseExport =msidatabaseexporta +function msiquery.h MsiDatabaseGenerateTransform =msidatabasegeneratetransforma +function msiquery.h MsiDatabaseGetPrimaryKeys =msidatabasegetprimarykeysa +function msiquery.h MsiDatabaseImport =msidatabaseimporta +function msiquery.h MsiDatabaseIsTablePersistent =msidatabaseistablepersistenta +function msiquery.h MsiDatabaseMerge =msidatabasemergea +function msiquery.h MsiDatabaseOpenView =msidatabaseopenviewa +function msiquery.h MsiDoAction =msidoactiona +function msiquery.h MsiEnableUIPreview +function msiquery.h MsiEnumComponentCosts =msienumcomponentcostsa +function msiquery.h MsiEvaluateCondition =msievaluateconditiona +function msiquery.h MsiFormatRecord =msiformatrecorda +function msiquery.h MsiGetActiveDatabase +function msiquery.h MsiGetComponentState =msigetcomponentstatea +function msiquery.h MsiGetDatabaseState +function msiquery.h MsiGetFeatureCost =msigetfeaturecosta +function msiquery.h MsiGetFeatureState =msigetfeaturestatea +function msiquery.h MsiGetFeatureValidStates =msigetfeaturevalidstatesa +function msiquery.h MsiGetLanguage +function msiquery.h MsiGetLastErrorRecord +function msiquery.h MsiGetMode +function msiquery.h MsiGetProperty =msigetpropertya +function msiquery.h MsiGetSourcePath =msigetsourcepatha +function msiquery.h MsiGetSummaryInformation =msigetsummaryinformationa +function msiquery.h MsiGetTargetPath =msigettargetpatha +function msiquery.h MsiOpenDatabase =msiopendatabasea +function msiquery.h MsiPreviewBillboard =msipreviewbillboarda +function msiquery.h MsiPreviewDialog =msipreviewdialoga +function msiquery.h MsiProcessMessage +function msiquery.h MsiRecordClearData +function msiquery.h MsiRecordDataSize +function msiquery.h MsiRecordGetFieldCount +function msiquery.h MsiRecordGetInteger +function msiquery.h MsiRecordGetString =msirecordgetstringa +function msiquery.h MsiRecordIsNull +function msiquery.h MsiRecordReadStream +function msiquery.h MsiRecordSetInteger +function msiquery.h MsiRecordSetStream =msirecordsetstreama +function msiquery.h MsiRecordSetString =msirecordsetstringa +function msiquery.h MsiSequence =msisequencea +function msiquery.h MsiSetComponentState =msisetcomponentstatea +function msiquery.h MsiSetFeatureAttributes =msisetfeatureattributesa +function msiquery.h MsiSetFeatureState =msisetfeaturestatea +function msiquery.h MsiSetInstallLevel +function msiquery.h MsiSetMode +function msiquery.h MsiSetProperty =msisetpropertya +function msiquery.h MsiSetTargetPath =msisettargetpatha +function msiquery.h MsiSummaryInfoGetProperty =msisummaryinfogetpropertya +function msiquery.h MsiSummaryInfoGetPropertyCount +function msiquery.h MsiSummaryInfoPersist +function msiquery.h MsiSummaryInfoSetProperty =msisummaryinfosetpropertya +function msiquery.h MsiVerifyDiskSpace +function msiquery.h MsiViewClose +function msiquery.h MsiViewExecute +function msiquery.h MsiViewFetch +function msiquery.h MsiViewGetColumnInfo +function msiquery.h MsiViewGetError =msiviewgeterrora +function msiquery.h MsiViewModify + +# Desktop App Technologies > Audio and Video > Codec API Reference > Codec API Enumerations +enum codecapi.h eAVAudioChannelConfig +enum codecapi.h eAVDDSurroundMode +enum codecapi.h eAVDecAACDownmixMode +enum codecapi.h eAVDecAudioDualMono +enum codecapi.h eAVDecAudioDualMonoReproMode +enum codecapi.h eAVDecDDOperationalMode +enum codecapi.h eAVDecHEAACDynamicRangeControl +enum codecapi.h eAVDecVideoInputScanType +enum codecapi.h eAVDecVideoSoftwareDeinterlaceMode +enum codecapi.h eAVDecVideoSWPowerLevel +enum codecapi.h eAVDSPLoudnessEqualization +enum codecapi.h eAVDSPSpeakerFill +enum codecapi.h eAVEncAudioDualMono +enum codecapi.h eAVEncAudioInputContent +enum codecapi.h eAVEncCommonRateControlMode +enum codecapi.h eAVEncCommonStreamEndHandling +enum codecapi.h eAVEncDDAtoDConverterType +enum codecapi.h eAVEncDDDynamicRangeCompressionControl +enum codecapi.h eAVEncDDHeadphoneMode +enum codecapi.h eAVEncDDPreferredStereoDownMixMode +enum codecapi.h eAVEncDDProductionRoomType +enum codecapi.h eAVEncDDService +enum codecapi.h eAVEncDDSurroundExMode +enum codecapi.h eAVEncInputVideoSystem +enum codecapi.h eAVEncMPACodingMode +enum codecapi.h eAVEncMPAEmphasisType +enum codecapi.h eAVEncMPALayer +enum codecapi.h eAVEncMPVFrameFieldMode +enum codecapi.h eAVEncMPVIntraVLCTable +enum codecapi.h eAVEncMPVLevel +enum codecapi.h eAVEncMPVProfile +enum codecapi.h eAVEncMPVQScaleType +enum codecapi.h eAVEncMPVScanPattern +enum codecapi.h eAVEncMPVSceneDetection +enum codecapi.h eAVEncMuxOutput +enum codecapi.h eAVEncVideoChromaResolution +enum codecapi.h eAVEncVideoChromaSubsampling +enum codecapi.h eAVEncVideoColorLighting +enum codecapi.h eAVEncVideoColorNominalRange +enum codecapi.h eAVEncVideoColorPrimaries +enum codecapi.h eAVEncVideoColorTransferFunction +enum codecapi.h eAVEncVideoColorTransferMatrix +enum codecapi.h eAVEncVideoFilmContent +enum codecapi.h eAVEncVideoOutputFrameRateConversion +enum codecapi.h eAVEncVideoOutputScanType +enum codecapi.h eAVEncVideoSourceScanType +enum codecapi.h eAVFastDecodeMode + +# Desktop App Technologies > Audio and Video > Codec API Reference > Codec API Interfaces +interface icodecapi.h ICodecAPI + +# Desktop App Technologies > Audio and Video > Codec API Reference > Codec API Structures +struct icodecapi.h CodecAPIEventData + +# Desktop App Technologies > Audio and Video > Core Audio APIs > Programming Reference > Core Audio Interfaces +interface audioclient.h IAcousticEchoCancellationControl +interface audioclient.h IAudioCaptureClient +interface audioclient.h IAudioClient +interface audioclient.h IAudioClient2 +interface audioclient.h IAudioClient3 +interface audioclient.h IAudioClientDuckingControl +interface audioclient.h IAudioClock +interface audioclient.h IAudioClock2 +interface audioclient.h IAudioClockAdjustment +interface audioclient.h IAudioEffectsChangedNotificationClient +interface audioclient.h IAudioEffectsManager +interface audioclient.h IAudioRenderClient +interface audioclient.h IAudioStreamVolume +interface audioclient.h IAudioViewManagerService +interface audioclient.h IChannelAudioVolume +interface audioclient.h ISimpleAudioVolume +interface audioendpoints.h IAudioEndpointFormatControl +interface audioenginebaseapo.h IAudioSystemEffects2 +interface audioengineendpoint.h IAudioEndpointLastBufferControl +interface audioengineendpoint.h IAudioEndpointOffloadStreamMeter +interface audioengineendpoint.h IAudioEndpointOffloadStreamMute +interface audioengineendpoint.h IAudioEndpointOffloadStreamVolume +interface audioengineendpoint.h IAudioLfxControl +interface audioengineendpoint.h IHardwareAudioEngineBase +interface audiopolicy.h IAudioSessionControl +interface audiopolicy.h IAudioSessionControl2 +interface audiopolicy.h IAudioSessionEnumerator +interface audiopolicy.h IAudioSessionEvents +interface audiopolicy.h IAudioSessionManager +interface audiopolicy.h IAudioSessionManager2 +interface audiopolicy.h IAudioSessionNotification +interface audiopolicy.h IAudioVolumeDuckNotification +interface devicetopology.h IAudioAutoGainControl +interface devicetopology.h IAudioBass +interface devicetopology.h IAudioChannelConfig +interface devicetopology.h IAudioInputSelector +interface devicetopology.h IAudioLoudness +interface devicetopology.h IAudioMidrange +interface devicetopology.h IAudioMute +interface devicetopology.h IAudioOutputSelector +interface devicetopology.h IAudioPeakMeter +interface devicetopology.h IAudioTreble +interface devicetopology.h IAudioVolumeLevel +interface devicetopology.h IConnector +interface devicetopology.h IControlChangeNotify +interface devicetopology.h IControlInterface +interface devicetopology.h IDeviceSpecificProperty +interface devicetopology.h IDeviceTopology +interface devicetopology.h IKsFormatSupport +interface devicetopology.h IKsJackDescription +interface devicetopology.h IKsJackDescription2 +interface devicetopology.h IKsJackSinkInformation +interface devicetopology.h IPart +interface devicetopology.h IPartsList +interface devicetopology.h IPerChannelDbLevel +interface devicetopology.h ISubunit +interface endpointvolume.h IAudioEndpointVolume +interface endpointvolume.h IAudioEndpointVolumeCallback +interface endpointvolume.h IAudioEndpointVolumeEx +interface endpointvolume.h IAudioMeterInformation +interface mmdeviceapi.h IActivateAudioInterfaceAsyncOperation +interface mmdeviceapi.h IActivateAudioInterfaceAsyncOperation +interface mmdeviceapi.h IActivateAudioInterfaceCompletionHandler +interface mmdeviceapi.h IActivateAudioInterfaceCompletionHandler +interface mmdeviceapi.h IAudioSystemEffectsPropertyChangeNotificationClient +interface mmdeviceapi.h IAudioSystemEffectsPropertyStore +interface mmdeviceapi.h IMMDevice +interface mmdeviceapi.h IMMDeviceCollection +interface mmdeviceapi.h IMMDeviceEnumerator +interface mmdeviceapi.h IMMEndpoint +interface mmdeviceapi.h IMMNotificationClient +interface spatialaudioclient.h IAudioFormatEnumerator +interface spatialaudioclient.h ISpatialAudioClient +interface spatialaudioclient.h ISpatialAudioClient2 +interface spatialaudioclient.h ISpatialAudioObject +interface spatialaudioclient.h ISpatialAudioObjectBase +interface spatialaudioclient.h ISpatialAudioObjectRenderStream +interface spatialaudioclient.h ISpatialAudioObjectRenderStreamBase +interface spatialaudioclient.h ISpatialAudioObjectRenderStreamNotify +interface spatialaudiohrtf.h ISpatialAudioObjectForHrtf +interface spatialaudiohrtf.h ISpatialAudioRenderStreamForHrtf =ispatialaudioobjectrenderstreamforhrtf +interface spatialaudiometadata.h ISpatialAudioMetadataClient +interface spatialaudiometadata.h ISpatialAudioMetadataCopier +interface spatialaudiometadata.h ISpatialAudioMetadataItems +interface spatialaudiometadata.h ISpatialAudioMetadataItemsBuffer +interface spatialaudiometadata.h ISpatialAudioMetadataReader +interface spatialaudiometadata.h ISpatialAudioMetadataWriter +interface spatialaudiometadata.h ISpatialAudioObjectForMetadataCommands +interface spatialaudiometadata.h ISpatialAudioObjectForMetadataItems +interface spatialaudiometadata.h ISpatialAudioObjectRenderStreamForMetadata + +# Desktop App Technologies > Audio and Video > Core Audio APIs > Programming Reference > Core Audio Structures +struct audioclient.h AUDIO_EFFECT +struct audioclient.h AudioClientProperties =audioclientproperties-r1 +struct devicetopology.h KSJACK_DESCRIPTION +struct devicetopology.h KSJACK_DESCRIPTION2 +struct devicetopology.h KSJACK_SINK_INFORMATION +struct devicetopology.h LUID +struct endpointvolume.h AUDIO_VOLUME_NOTIFICATION_DATA +struct mmdeviceapi.h AudioExtensionParams +struct mmdeviceapi.h DIRECTX_AUDIO_ACTIVATION_PARAMS +struct spatialaudioclient.h SpatialAudioClientActivationParams +struct spatialaudioclient.h SpatialAudioObjectRenderStreamActivationParams +struct spatialaudioclient.h SpatialAudioObjectRenderStreamActivationParams2 +struct spatialaudiohrtf.h SpatialAudioHrtfActivationParams +struct spatialaudiohrtf.h SpatialAudioHrtfActivationParams2 +struct spatialaudiohrtf.h SpatialAudioHrtfDirectivity +struct spatialaudiohrtf.h SpatialAudioHrtfDirectivityCardioid +struct spatialaudiohrtf.h SpatialAudioHrtfDirectivityCone +struct spatialaudiohrtf.h SpatialAudioHrtfDirectivityCone +struct spatialaudiohrtf.h SpatialAudioHrtfDistanceDecay +struct spatialaudiometadata.h SpatialAudioMetadataItemsInfo +struct spatialaudiometadata.h SpatialAudioObjectRenderStreamForMetadataActivationParams + +# Desktop App Technologies > Audio and Video > Core Audio APIs > Programming Reference > Core Audio Unions +struct spatialaudiohrtf.h SpatialAudioHrtfDirectivityUnion + +# Desktop App Technologies > Audio and Video > Core Audio APIs > Programming Reference > Core Audio Enumerations +enum audioclient.h _AUDCLNT_BUFFERFLAGS +enum audioclient.h AUDCLNT_STREAMOPTIONS +enum audioclient.h AUDIO_DUCKING_OPTIONS +enum audioclient.h AUDIO_EFFECT_STATE +enum audiosessiontypes.h AUDCLNT_SHAREMODE +enum audiosessiontypes.h AUDIO_STREAM_CATEGORY +enum audiosessiontypes.h AudioSessionState +enum devicetopology.h ConnectorType +enum devicetopology.h DataFlow +enum devicetopology.h KSJACK_SINK_CONNECTIONTYPE +enum devicetopology.h PartType +enum mmdeviceapi.h AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE +enum mmdeviceapi.h EDataFlow +enum mmdeviceapi.h EndpointFormFactor +enum mmdeviceapi.h ERole +enum spatialaudioclient.h AudioObjectType +enum spatialaudioclient.h SPATIAL_AUDIO_STREAM_OPTIONS +enum spatialaudiohrtf.h SpatialAudioHrtfDirectivityType +enum spatialaudiohrtf.h SpatialAudioHrtfDistanceDecayType +enum spatialaudiohrtf.h SpatialAudioHrtfEnvironmentType +enum spatialaudiometadata.h SpatialAudioMetadataCopyMode +enum spatialaudiometadata.h SpatialAudioMetadataWriterOverflowMode + +# Desktop App Technologies > Audio and Video > Core Audio APIs > Programming Reference > Core Audio Functions +function mmdeviceapi.h ActivateAudioInterfaceAsync + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Interfaces +interface amaudio.h IAMDirectSound +interface amparse.h IAMParse +interface amstream.h IDirectDrawMediaSample +interface amstream.h IDirectDrawMediaSampleAllocator +interface amvideo.h IDirectDrawVideo +interface amvideo.h IFullScreenVideoEx +interface amvideo.h IQualProp +interface amxmlgraphbuilder.h IXMLGraphBuilder +interface bdaiface.h IEnumPIDMap +interface bdaiface.h IMPEG2PIDMap +interface control.h IAMStats +interface control.h IBasicAudio +interface control.h IBasicVideo +interface control.h IBasicVideo2 +interface control.h IDeferredCommand +interface control.h IMediaControl +interface control.h IMediaEvent +interface control.h IMediaEventEx +interface control.h IMediaPosition +interface control.h IQueueCommand +interface control.h IVideoWindow +interface dmodshow.h IDMOWrapperFilter +interface dshowasf.h IAMWMBufferPass +interface dshowasf.h IAMWMBufferPassCallback +interface dshowasf.h IConfigAsfWriter +interface dshowasf.h IConfigAsfWriter2 +interface il21dec.h IAMLine21Decoder +interface iwstdec.h IAMWstDecoder +interface mixerocx.h IMixerOCX +interface mixerocx.h IMixerOCXNotify +interface mpconfig.h IMixerPinConfig +interface mpconfig.h IMixerPinConfig2 +interface mpegtype.h IMpegAudioDecoder +interface qnetwork.h IAMChannelInfo +interface qnetwork.h IAMExtendedErrorInfo +interface qnetwork.h IAMExtendedSeeking +interface qnetwork.h IAMMediaContent +interface qnetwork.h IAMMediaContent2 +interface qnetwork.h IAMNetShowConfig +interface qnetwork.h IAMNetShowExProps +interface qnetwork.h IAMNetShowPreroll +interface qnetwork.h IAMNetworkStatus +interface qnetwork.h IDShowPlugin +interface strmif.h IAMAnalogVideoDecoder +interface strmif.h IAMAsyncReaderTimestampScaling +interface strmif.h IAMAudioInputMixer +interface strmif.h IAMAudioRendererStats +interface strmif.h IAMBufferNegotiation +interface strmif.h IAMCameraControl +interface strmif.h IAMCertifiedOutputProtection +interface strmif.h IAMClockAdjust +interface strmif.h IAMClockSlave +interface strmif.h IAMCopyCaptureFileProgress +interface strmif.h IAMCrossbar +interface strmif.h IAMDecoderCaps +interface strmif.h IAMDeviceRemoval +interface strmif.h IAMDroppedFrames +interface strmif.h IAMExtDevice +interface strmif.h IAMExtTransport +interface strmif.h IAMFilterGraphCallback +interface strmif.h IAMFilterMiscFlags +interface strmif.h IAMGraphBuilderCallback +interface strmif.h IAMGraphStreams +interface strmif.h IAMLatency +interface strmif.h IAMOpenProgress +interface strmif.h IAMOverlayFX +interface strmif.h IAMPluginControl +interface strmif.h IAMPushSource +interface strmif.h IAMResourceControl +interface strmif.h IAMStreamConfig +interface strmif.h IAMStreamControl +interface strmif.h IAMStreamSelect +interface strmif.h IAMTimecodeDisplay +interface strmif.h IAMTimecodeGenerator +interface strmif.h IAMTimecodeReader +interface strmif.h IAMTuner +interface strmif.h IAMTVAudio +interface strmif.h IAMTVTuner +interface strmif.h IAMVfwCaptureDialogs +interface strmif.h IAMVfwCompressDialogs +interface strmif.h IAMVideoCompression +interface strmif.h IAMVideoControl +interface strmif.h IAMVideoDecimationProperties +interface strmif.h IAMVideoProcAmp +interface strmif.h IAsyncReader +interface strmif.h IBaseFilter +interface strmif.h ICaptureGraphBuilder2 +interface strmif.h IConfigAviMux +interface strmif.h IConfigInterleaving +interface strmif.h ICreateDevEnum +interface strmif.h IDDrawExclModeVideo +interface strmif.h IDDrawExclModeVideoCallback +interface strmif.h IDecimateVideoImage +interface strmif.h IDistributorNotify +interface strmif.h IDvdCmd +interface strmif.h IDvdControl2 +interface strmif.h IDvdGraphBuilder +interface strmif.h IDvdInfo2 +interface strmif.h IDvdState +interface strmif.h IDVEnc +interface strmif.h IDVRGB219 +interface strmif.h IDVSplitter +interface strmif.h IEnumFilters +interface strmif.h IEnumMediaTypes +interface strmif.h IEnumPins +interface strmif.h IEnumStreamIdMap +interface strmif.h IFileSinkFilter +interface strmif.h IFileSinkFilter2 +interface strmif.h IFileSourceFilter +interface strmif.h IFilterChain +interface strmif.h IFilterGraph +interface strmif.h IFilterGraph2 +interface strmif.h IFilterGraph3 +interface strmif.h IFilterMapper2 +interface strmif.h IFilterMapper3 +interface strmif.h IGetCapabilitiesKey +interface strmif.h IGraphBuilder +interface strmif.h IGraphConfig +interface strmif.h IGraphConfigCallback +interface strmif.h IGraphVersion +interface strmif.h IIPDVDec +interface strmif.h IMediaEventSink +interface strmif.h IMediaFilter +interface strmif.h IMediaPropertyBag +interface strmif.h IMediaSample +interface strmif.h IMediaSample2 +interface strmif.h IMediaSample2Config +interface strmif.h IMediaSeeking +interface strmif.h IMemAllocator +interface strmif.h IMemAllocatorCallbackTemp +interface strmif.h IMemAllocatorNotifyCallbackTemp +interface strmif.h IMemInputPin +interface strmif.h IMpeg2Demultiplexer +interface strmif.h IMPEG2StreamIdMap +interface strmif.h IOverlay +interface strmif.h IOverlayNotify +interface strmif.h IOverlayNotify2 +interface strmif.h IPersistMediaPropertyBag +interface strmif.h IPin +interface strmif.h IPinConnection +interface strmif.h IPinFlowControl +interface strmif.h IQualityControl +interface strmif.h IReferenceClock +interface strmif.h IReferenceClockTimerControl +interface strmif.h IRegisterServiceProvider +interface strmif.h IResourceConsumer +interface strmif.h IResourceManager +interface strmif.h ISeekingPassThru +interface strmif.h IStreamBuilder +interface strmif.h IVideoFrameStep +interface strmif.h IVMRAspectRatioControl +interface strmif.h IVMRDeinterlaceControl +interface strmif.h IVMRFilterConfig +interface strmif.h IVMRImageCompositor +interface strmif.h IVMRImagePresenter +interface strmif.h IVMRImagePresenterConfig +interface strmif.h IVMRImagePresenterExclModeConfig +interface strmif.h IVMRMixerBitmap +interface strmif.h IVMRMixerControl +interface strmif.h IVMRMonitorConfig +interface strmif.h IVMRSurface +interface strmif.h IVMRSurfaceAllocator +interface strmif.h IVMRSurfaceAllocatorNotify +interface strmif.h IVMRVideoStreamControl +interface strmif.h IVMRWindowlessControl +interface strmif.h IVPManager +interface vidcap.h ICameraControl +interface vidcap.h IKsNodeControl +interface vidcap.h IKsTopologyInfo +interface vidcap.h ISelector +interface vidcap.h IVideoProcAmp +interface videoacc.h IAMVideoAccelerator +interface videoacc.h IAMVideoAcceleratorNotify +interface vmr9.h IVMRAspectRatioControl9 +interface vmr9.h IVMRDeinterlaceControl9 +interface vmr9.h IVMRFilterConfig9 +interface vmr9.h IVMRImageCompositor9 +interface vmr9.h IVMRImagePresenter9 +interface vmr9.h IVMRImagePresenterConfig9 +interface vmr9.h IVMRMixerBitmap9 +interface vmr9.h IVMRMixerControl9 +interface vmr9.h IVMRMonitorConfig9 +interface vmr9.h IVMRSurface9 +interface vmr9.h IVMRSurfaceAllocator9 +interface vmr9.h IVMRSurfaceAllocatorEx9 +interface vmr9.h IVMRSurfaceAllocatorNotify9 +interface vmr9.h IVMRVideoStreamControl9 +interface vmr9.h IVMRWindowlessControl9 +interface vpconfig.h IVPBaseConfig +interface vpconfig.h IVPConfig +interface vpnotify.h IVPBaseNotify +interface vpnotify.h IVPNotify +interface vpnotify.h IVPNotify2 + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > DirectShow Structures +struct amva.h AMVABeginFrameInfo +struct amva.h AMVABUFFERINFO +struct amva.h AMVACompBufferInfo +struct amva.h AMVAEndFrameInfo +struct amva.h AMVAInternalMemInfo +struct amva.h AMVAUncompBufferInfo +struct amva.h AMVAUncompDataInfo +struct amvideo.h ANALOGVIDEOINFO +struct amvideo.h MPEG1VIDEOINFO +struct amvideo.h TRUECOLORINFO +struct amvideo.h VIDEOINFO +struct amvideo.h VIDEOINFOHEADER +struct avifmt.h AVIPALCHANGE +struct avifmt.h AVISTREAMHEADER +struct aviriff.h AVIMAINHEADER +struct aviriff.h AVIMETAINDEX +struct aviriff.h AVIOLDINDEX +struct aviriff.h AVISTDINDEX +struct aviriff.h AVISTDINDEX_ENTRY +struct aviriff.h AVISUPERINDEX +struct dvdmedia.h MPEG2VIDEOINFO +struct dvdmedia.h VIDEOINFOHEADER2 +struct dxva9typ.h DXVA_COPPSetProtectionLevelCmdData +struct dxva9typ.h DXVA_COPPSetSignalingCmdData +struct dxva9typ.h DXVA_COPPStatusData +struct dxva9typ.h DXVA_COPPStatusDisplayData +struct dxva9typ.h DXVA_COPPStatusHDCPKeyData +struct dxva9typ.h DXVA_COPPStatusSignalingCmdData +struct evcode.h AM_WMT_EVENT_DATA +struct iwstdec.h AM_WST_PAGE +struct mmreg.h HEAACWAVEFORMAT +struct mmreg.h HEAACWAVEINFO +struct mmreg.h MPEG1WAVEFORMAT +struct mmreg.h MPEGLAYER3WAVEFORMAT +struct mmreg.h WAVEFORMATEXTENSIBLE +struct mpegtype.h AM_MPEGSTREAMTYPE =am_mpegsystemtype +struct mpegtype.h AM_MPEGSYSTEMTYPE +struct strmif.h ALLOCATOR_PROPERTIES +struct strmif.h AM_DVD_RENDERSTATUS +struct strmif.h AM_MEDIA_TYPE +struct strmif.h AM_SAMPLE2_PROPERTIES +struct strmif.h AM_STREAM_INFO +struct strmif.h AMCOPPCommand +struct strmif.h AMCOPPSignature +struct strmif.h AMCOPPStatusInput +struct strmif.h AMCOPPStatusOutput +struct strmif.h AUDIO_STREAM_CONFIG_CAPS +struct strmif.h CodecAPIEventData +struct strmif.h COLORKEY +struct strmif.h DDCOLORKEY +struct strmif.h DVD_AudioAttributes +struct strmif.h DVD_DECODER_CAPS +struct strmif.h DVD_HMSF_TIMECODE +struct strmif.h DVD_KaraokeAttributes +struct strmif.h DVD_MenuAttributes +struct strmif.h DVD_MUA_Coeff +struct strmif.h DVD_MUA_MixingInfo +struct strmif.h DVD_MultichannelAudioAttributes +struct strmif.h DVD_PLAYBACK_LOCATION +struct strmif.h DVD_PLAYBACK_LOCATION2 +struct strmif.h DVD_SubpictureAttributes +struct strmif.h DVD_TIMECODE +struct strmif.h DVD_TitleAttributes +struct strmif.h DVD_VideoAttributes +struct strmif.h DVINFO +struct strmif.h FILTER_INFO +struct strmif.h NORMALIZEDRECT +struct strmif.h PIN_INFO +struct strmif.h Quality +struct strmif.h REGFILTER2 +struct strmif.h REGFILTERPINS +struct strmif.h REGFILTERPINS2 +struct strmif.h REGPINMEDIUM +struct strmif.h REGPINTYPES +struct strmif.h STREAM_ID_MAP +struct strmif.h TIMECODE +struct strmif.h TIMECODE_SAMPLE +struct strmif.h VIDEO_STREAM_CONFIG_CAPS +struct strmif.h VMRALLOCATIONINFO +struct strmif.h VMRALPHABITMAP +struct strmif.h VMRDeinterlaceCaps +struct strmif.h VMRFrequency +struct strmif.h VMRGUID +struct strmif.h VMRMONITORINFO +struct strmif.h VMRPRESENTATIONINFO +struct strmif.h VMRVideoDesc +struct strmif.h VMRVIDEOSTREAMINFO +struct vmr9.h VMR9AllocationInfo +struct vmr9.h VMR9AlphaBitmap +struct vmr9.h VMR9DeinterlaceCaps +struct vmr9.h VMR9Frequency +struct vmr9.h VMR9MonitorInfo +struct vmr9.h VMR9NormalizedRect +struct vmr9.h VMR9PresentationInfo +struct vmr9.h VMR9ProcAmpControl +struct vmr9.h VMR9ProcAmpControlRange +struct vmr9.h VMR9VideoDesc +struct vmr9.h VMR9VideoStreamInfo +struct vptype.h AMVPDATAINFO +struct vptype.h AMVPDIMINFO +struct vptype.h AMVPSIZE +struct wingdi.h BITMAPINFOHEADER + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > DirectShow Enumerated Types +enum audevcod.h SNDDEV_ERR +enum dshowasf.h _AM_ASFWRITERCONFIG_PARAM +enum dvdevcod.h DVD_ERROR +enum dvdevcod.h DVD_PB_STOPPED +enum dvdevcod.h DVD_WARNING +enum dvdmedia.h AM_MPEG2Level +enum dvdmedia.h AM_MPEG2Profile +enum dvdmedia.h AM_PROPERTY_DVD_RATE_CHANGE +enum dxva9typ.h COPP_ACP_Protection_Level +enum dxva9typ.h COPP_BusType +enum dxva9typ.h COPP_CGMSA_Protection_Level +enum dxva9typ.h COPP_ConnectorType +enum dxva9typ.h COPP_HDCP_Protection_Level +enum dxva9typ.h COPP_ImageAspectRatio_EN300294 +enum dxva9typ.h COPP_StatusFlags +enum dxva9typ.h COPP_StatusHDCPFlags +enum dxva9typ.h COPP_TVProtectionStandard +enum il21dec.h AM_LINE21_CCLEVEL +enum il21dec.h AM_LINE21_CCSERVICE +enum il21dec.h AM_LINE21_CCSTATE +enum il21dec.h AM_LINE21_DRAWBGMODE +enum iwstdec.h AM_WST_DRAWBGMODE +enum iwstdec.h AM_WST_LEVEL +enum iwstdec.h AM_WST_SERVICE +enum iwstdec.h AM_WST_STATE +enum iwstdec.h AM_WST_STYLE +enum mpconfig.h AM_ASPECT_RATIO_MODE +enum qnetwork.h AMExtendedSeekingCapabilities +enum strmif.h _AM_AUDIO_RENDERER_STAT_PARAM +enum strmif.h _AM_FILTER_MISC_FLAGS +enum strmif.h AM_DVD_GRAPH_FLAGS +enum strmif.h AM_DVD_STREAM_FLAGS +enum strmif.h AM_GRAPH_CONFIG_RECONNECT_FLAGS +enum strmif.h AM_INTF_SEARCH_FLAGS +enum strmif.h AM_OVERLAY_NOTIFY_FLAGS +enum strmif.h AM_PIN_CONNECTION_BLOCK_FLAGS =_am_pin_flow_control_block_flags +enum strmif.h AM_PUSHSOURCE_FLAGS +enum strmif.h AM_SAMPLE_PROPERTY_FLAGS =tagam_sample_property_flags +enum strmif.h AM_SEEKING_SEEKING_CAPABILITIES +enum strmif.h AM_STREAM_INFO_FLAGS +enum strmif.h AMOVERLAYFX +enum strmif.h AMRESCTL_RESERVEFLAGS +enum strmif.h AMTunerModeType +enum strmif.h AnalogVideoStandard +enum strmif.h CameraControlFlags +enum strmif.h CameraControlProperty +enum strmif.h CompressionCaps +enum strmif.h DECIMATION_USAGE +enum strmif.h DVD_AUDIO_APPMODE +enum strmif.h DVD_AUDIO_FORMAT +enum strmif.h DVD_AUDIO_LANG_EXT +enum strmif.h DVD_CMD_FLAGS +enum strmif.h DVD_DISC_SIDE +enum strmif.h DVD_DOMAIN +enum strmif.h DVD_FRAMERATE +enum strmif.h DVD_KARAOKE_ASSIGNMENT +enum strmif.h DVD_KARAOKE_CONTENTS +enum strmif.h DVD_KARAOKE_DOWNMIX +enum strmif.h DVD_MENU_ID +enum strmif.h DVD_NavCmdType +enum strmif.h DVD_OPTION_FLAG +enum strmif.h DVD_PARENTAL_LEVEL +enum strmif.h DVD_PREFERRED_DISPLAY_MODE +enum strmif.h DVD_RELATIVE_BUTTON +enum strmif.h DVD_SUBPICTURE_CODING +enum strmif.h DVD_SUBPICTURE_LANG_EXT +enum strmif.h DVD_SUBPICTURE_TYPE +enum strmif.h DVD_TextCharSet +enum strmif.h DVD_TextStringType +enum strmif.h DVD_TIMECODE_FLAGS +enum strmif.h DVD_TITLE_APPMODE +enum strmif.h DVD_VIDEO_COMPRESSION +enum strmif.h DVDECODERRESOLUTION =_dvresolution +enum strmif.h DVENCODERFORMAT +enum strmif.h DVENCODERRESOLUTION +enum strmif.h DVENCODERVIDEOFORMAT +enum strmif.h FILTER_STATE +enum strmif.h InterleavingMode +enum strmif.h PhysicalConnectorType +enum strmif.h PIN_DIRECTION +enum strmif.h QualityMessageType +enum strmif.h REM_FILTER_FLAGS +enum strmif.h TunerInputType +enum strmif.h TVAudioMode +enum strmif.h VALID_UOP_FLAG +enum strmif.h VfwCaptureDialogs +enum strmif.h VfwCompressDialogs +enum strmif.h VideoControlFlags +enum strmif.h VIDEOENCODER_BITRATE_MODE +enum strmif.h VideoProcAmpFlags +enum strmif.h VideoProcAmpProperty +enum strmif.h VMR_ASPECT_RATIO_MODE +enum strmif.h VMRDeinterlacePrefs +enum strmif.h VMRDeinterlaceTech +enum strmif.h VMRMixerPrefs +enum strmif.h VMRMode +enum strmif.h VMRPresentationFlags +enum strmif.h VMRRenderPrefs +enum strmif.h VMRSurfaceAllocationFlags +enum vmr9.h VMR9_SampleFormat +enum vmr9.h VMR9AlphaBitmapFlags +enum vmr9.h VMR9AspectRatioMode +enum vmr9.h VMR9DeinterlacePrefs +enum vmr9.h VMR9DeinterlaceTech +enum vmr9.h VMR9MixerPrefs +enum vmr9.h VMR9Mode +enum vmr9.h VMR9PresentationFlags +enum vmr9.h VMR9ProcAmpControlFlags +enum vmr9.h VMR9RenderPrefs +enum vmr9.h VMR9SurfaceAllocationFlags +enum vptype.h AMVP_MODE +enum vptype.h AMVP_SELECT_FORMAT_BY + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Functions +function errors.h AMGetErrorText =amgeterrortexta + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Property Sets > DVD Copy Protection Property Set +enum dvdmedia.h AM_COPY_MACROVISION_LEVEL +enum dvdmedia.h AM_DVDCOPYSTATE +struct dvdmedia.h AM_DVDCOPY_BUSKEY +struct dvdmedia.h AM_DVDCOPY_CHLGKEY +struct dvdmedia.h AM_DVDCOPY_DISCKEY +struct dvdmedia.h AM_DVDCOPY_SET_COPY_STATE +struct dvdmedia.h AM_DVDCOPY_TITLEKEY +struct dvdmedia.h DVD_REGION + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Property Sets > DVD Karaoke Property Set +struct dvdmedia.h AM_DvdKaraokeData + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Property Sets > DVD Subpicture Property Set +struct dvdmedia.h AM_COLCON +struct dvdmedia.h AM_DVD_YUV +struct dvdmedia.h AM_PROPERTY_SPHLI +struct dvdmedia.h AM_PROPERTY_SPPAL + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Property Sets > Frame Stepping Property Set +struct amvideo.h AM_FRAMESTEP_STEP + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > Property Sets > Rate Change Property Set +struct dvdmedia.h AM_ExactRateChange structure =am_exactratechange +struct dvdmedia.h AM_QueryRate structure =am_queryrate +struct dvdmedia.h AM_SimpleRateChange structure =am_simpleratechange + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow Reference > DirectShow Base Classes > Utility Functions > Video and Image Functions +function amvideo.h BIT_MASKS_MATCH +function amvideo.h BITMASKS +function amvideo.h COLORS +function amvideo.h DIBSIZE +function amvideo.h HEADER +function amvideo.h MPEG1_SEQUENCE_INFO +function amvideo.h PALETTE_ENTRIES +function amvideo.h PALETTISED +function amvideo.h RESET_HEADER +function amvideo.h RESET_MASKS +function amvideo.h RESET_PALETTE +function amvideo.h SIZE_MPEG1VIDEOINFO + +# Desktop App Technologies > Audio and Video > DirectShow > DirectX Media Objects > DMO Reference > DMO Interfaces +interface mediaobj.h IDMOQualityControl +interface mediaobj.h IDMOVideoOutputOptimizations +interface mediaobj.h IEnumDMO +interface mediaobj.h IMediaBuffer +interface mediaobj.h IMediaObject +interface mediaobj.h IMediaObjectInPlace +interface medparam.h IMediaParamInfo +interface medparam.h IMediaParams + +# Desktop App Technologies > Audio and Video > DirectShow > DirectX Media Objects > DMO Reference > DMO Structures +struct dmoreg.h DMO_PARTIAL_MEDIATYPE +struct mediaobj.h DMO_MEDIA_TYPE +struct mediaobj.h DMO_OUTPUT_DATA_BUFFER +struct medparam.h MP_ENVELOPE_SEGMENT +struct medparam.h MP_PARAMINFO + +# Desktop App Technologies > Audio and Video > DirectShow > DirectX Media Objects > DMO Reference > DMO Enumerated Types +enum dmoreg.h DMO_ENUM_FLAGS +enum dmoreg.h DMO_REGISTER_FLAGS +enum mediaobj.h DMO_INPUT_DATA_BUFFER_FLAGS +enum mediaobj.h DMO_INPUT_STREAM_INFO_FLAGS +enum mediaobj.h DMO_OUTPUT_DATA_BUFFER_FLAGS +enum mediaobj.h DMO_OUTPUT_STREAM_INFO_FLAGS +enum mediaobj.h DMO_PROCESS_OUTPUT_FLAGS +enum mediaobj.h DMO_SET_TYPE_FLAGS +enum mediaobj.h DMO_VIDEO_OUTPUT_STREAM_FLAGS +enum medparam.h MP_CURVE_TYPE +enum medparam.h MP_TYPE + +# Desktop App Technologies > Audio and Video > DirectShow > DirectX Media Objects > DMO Reference > DMO Functions +function dmoreg.h DMOEnum +function dmoreg.h DMOGetName +function dmoreg.h DMOGetTypes +function dmoreg.h DMORegister +function dmoreg.h DMOUnregister +function dmort.h MoCopyMediaType +function dmort.h MoCreateMediaType +function dmort.h MoDeleteMediaType +function dmort.h MoDuplicateMediaType +function dmort.h MoFreeMediaType +function dmort.h MoInitMediaType + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow: Appendix > Multimedia Streaming > Multimedia Streaming Interfaces +interface amstream.h IAMMediaStream +interface amstream.h IAMMediaTypeSample +interface amstream.h IAMMediaTypeStream +interface amstream.h IAMMultiMediaStream +interface amstream.h IMediaStreamFilter +interface austream.h IAudioData +interface austream.h IAudioMediaStream +interface austream.h IAudioStreamSample +interface austream.h IMemoryData +interface ddstream.h IDirectDrawMediaStream +interface ddstream.h IDirectDrawStreamSample +interface mmstream.h IMediaStream +interface mmstream.h IMultiMediaStream +interface mmstream.h IStreamSample + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow: Appendix > Multimedia Streaming > Multimedia Streaming Enumeration Types +enum mmstream.h STREAM_STATE +enum mmstream.h STREAM_TYPE + +# Desktop App Technologies > Audio and Video > DirectShow > DirectShow: Appendix > Deprecated Interfaces +interface strmif.h IAMAnalogVideoEncoder +interface strmif.h IAMDevMemoryAllocator +interface strmif.h IAMDevMemoryControl +interface strmif.h IAMovieSetup +interface strmif.h IAMPhysicalPinInfo +interface strmif.h IAMTunerNotification +interface strmif.h IAMTVAudioNotification +interface strmif.h IBPCSatelliteTuner +interface strmif.h ICaptureGraphBuilder +interface strmif.h IDrawVideoImage +interface strmif.h IDvdControl +interface strmif.h IDvdInfo +interface strmif.h IEnumRegFilters +interface strmif.h IFilterMapper +interface strmif.h IVideoEncoder + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 9 Video APIs > Direct3D Video Interfaces +interface d3d9.h IDirect3D9ExOverlayExtension +interface d3d9.h IDirect3DAuthenticatedChannel9 +interface d3d9.h IDirect3DCryptoSession9 +interface d3d9.h IDirect3DDevice9Video +interface dxvahd.h IDXVAHD_Device +interface dxvahd.h IDXVAHD_VideoProcessor + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 9 Video APIs > Direct3D Video Structures +struct d3d9caps.h D3DCONTENTPROTECTIONCAPS +struct d3d9caps.h D3DOVERLAYCAPS +struct dxva9typ.h DXVABufferInfo +struct dxva9typ.h DXVACompBufferInfo +struct dxva9typ.h DXVAUncompDataInfo +struct dxvahd.h DXVAHD_BLT_STATE_ALPHA_FILL_DATA +struct dxvahd.h DXVAHD_BLT_STATE_BACKGROUND_COLOR_DATA +struct dxvahd.h DXVAHD_BLT_STATE_CONSTRICTION_DATA +struct dxvahd.h DXVAHD_BLT_STATE_OUTPUT_COLOR_SPACE_DATA +struct dxvahd.h DXVAHD_BLT_STATE_PRIVATE_DATA +struct dxvahd.h DXVAHD_BLT_STATE_TARGET_RECT_DATA +struct dxvahd.h DXVAHD_COLOR_RGBA +struct dxvahd.h DXVAHD_COLOR_YCbCrA +struct dxvahd.h DXVAHD_CONTENT_DESC +struct dxvahd.h DXVAHD_CUSTOM_RATE_DATA +struct dxvahd.h DXVAHD_FILTER_RANGE_DATA +struct dxvahd.h DXVAHD_RATIONAL +struct dxvahd.h DXVAHD_STREAM_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_ALPHA_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_ASPECT_RATIO_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_D3DFORMAT_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_DESTINATION_RECT_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_FILTER_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_FRAME_FORMAT_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_LUMA_KEY_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_OUTPUT_RATE_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_PALETTE_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_PRIVATE_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_PRIVATE_IVTC_DATA +struct dxvahd.h DXVAHD_STREAM_STATE_SOURCE_RECT_DATA +struct dxvahd.h DXVAHD_VPCAPS +struct dxvahd.h DXVAHD_VPDEVCAPS +struct dxvahd.h DXVAHDSW_CALLBACKS + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 9 Video APIs > Direct3D Video Enumerations +enum dxvahd.h DXVAHD_ALPHA_FILL_MODE +enum dxvahd.h DXVAHD_BLT_STATE +enum dxvahd.h DXVAHD_DEVICE_CAPS +enum dxvahd.h DXVAHD_DEVICE_TYPE +enum dxvahd.h DXVAHD_DEVICE_USAGE +enum dxvahd.h DXVAHD_FEATURE_CAPS +enum dxvahd.h DXVAHD_FILTER +enum dxvahd.h DXVAHD_FILTER_CAPS +enum dxvahd.h DXVAHD_FRAME_FORMAT +enum dxvahd.h DXVAHD_INPUT_FORMAT_CAPS +enum dxvahd.h DXVAHD_ITELECINE_CAPS +enum dxvahd.h DXVAHD_OUTPUT_RATE +enum dxvahd.h DXVAHD_PROCESSOR_CAPS +enum dxvahd.h DXVAHD_STREAM_STATE +enum dxvahd.h DXVAHD_SURFACE_TYPE + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 11 Video APIs > Direct3D 11 Video Interfaces +interface d3d11_1.h ID3D11VideoContext1 +interface d3d11_1.h ID3D11VideoDevice1 +interface d3d11_1.h ID3D11VideoProcessorEnumerator1 +interface d3d11_4.h ID3D11VideoContext2 +interface d3d11_4.h ID3D11VideoContext3 +interface d3d11_4.h ID3D11VideoDevice2 +interface d3d11.h ID3D11AuthenticatedChannel +interface d3d11.h ID3D11CryptoSession +interface d3d11.h ID3D11VideoContext +interface d3d11.h ID3D11VideoDecoder +interface d3d11.h ID3D11VideoDecoderOutputView +interface d3d11.h ID3D11VideoDevice +interface d3d11.h ID3D11VideoProcessor +interface d3d11.h ID3D11VideoProcessorEnumerator +interface d3d11.h ID3D11VideoProcessorInputView +interface d3d11.h ID3D11VideoProcessorOutputView + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 11 Video APIs > Direct3D 11 Video Enumerations +enum d3d11_1.h D3D11_CRYPTO_SESSION_KEY_EXCHANGE_FLAGS =d3d11_4-d3d11_crypto_session_key_exchange_flags +enum d3d11_1.h D3D11_CRYPTO_SESSION_STATUS +enum d3d11_1.h D3D11_VIDEO_DECODER_CAPS +enum d3d11_1.h D3D11_VIDEO_PROCESSOR_BEHAVIOR_HINTS +enum d3d11_4.h D3D11_FEATURE_VIDEO +enum d3d11_4.h D3D11_VIDEO_DECODER_HISTOGRAM_COMPONENT +enum d3d11_4.h D3D11_VIDEO_DECODER_HISTOGRAM_COMPONENT_FLAGS +enum d3d11.h D3D11_AUTHENTICATED_CHANNEL_TYPE +enum d3d11.h D3D11_AUTHENTICATED_PROCESS_IDENTIFIER_TYPE +enum d3d11.h D3D11_BUS_TYPE +enum d3d11.h D3D11_CONTENT_PROTECTION_CAPS +enum d3d11.h D3D11_VDOV_DIMENSION +enum d3d11.h D3D11_VIDEO_DECODER_BUFFER_TYPE +enum d3d11.h D3D11_VIDEO_FRAME_FORMAT +enum d3d11.h D3D11_VIDEO_PROCESSOR_ALPHA_FILL_MODE +enum d3d11.h D3D11_VIDEO_PROCESSOR_AUTO_STREAM_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_DEVICE_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_FEATURE_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_FILTER +enum d3d11.h D3D11_VIDEO_PROCESSOR_FILTER_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_FORMAT_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT +enum d3d11.h D3D11_VIDEO_PROCESSOR_ITELECINE_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_NOMINAL_RANGE +enum d3d11.h D3D11_VIDEO_PROCESSOR_OUTPUT_RATE +enum d3d11.h D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_ROTATION +enum d3d11.h D3D11_VIDEO_PROCESSOR_STEREO_CAPS +enum d3d11.h D3D11_VIDEO_PROCESSOR_STEREO_FLIP_MODE +enum d3d11.h D3D11_VIDEO_PROCESSOR_STEREO_FORMAT +enum d3d11.h D3D11_VIDEO_USAGE +enum d3d11.h D3D11_VPIV_DIMENSION +enum d3d11.h D3D11_VPOV_DIMENSION + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 11 Video APIs > Direct3D 11 Video Structures +struct d3d11_1.h D3D11_KEY_EXCHANGE_HW_PROTECTION_DATA +struct d3d11_1.h D3D11_KEY_EXCHANGE_HW_PROTECTION_INPUT_DATA +struct d3d11_1.h D3D11_KEY_EXCHANGE_HW_PROTECTION_OUTPUT_DATA +struct d3d11_1.h D3D11_VIDEO_DECODER_BEGIN_FRAME_CRYPTO_SESSION +struct d3d11_1.h D3D11_VIDEO_DECODER_BUFFER_DESC1 +struct d3d11_1.h D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK +struct d3d11_1.h D3D11_VIDEO_PROCESSOR_STREAM_BEHAVIOR_HINT +struct d3d11_1.h D3D11_VIDEO_SAMPLE_DESC +struct d3d11_4.h D3D11_FEATURE_DATA_D3D11_OPTIONS4 (d3d11_4.h) =d3d11_feature_data_d3d11_options4 +struct d3d11_4.h D3D11_FEATURE_DATA_VIDEO_DECODER_HISTOGRAM +struct d3d11.h D3D11_AES_CTR_IV +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_ACCESSIBLE_ENCRYPTION_INPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_CRYPTO_SESSION_INPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_INITIALIZE_INPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_INPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_PROTECTION_INPUT +struct d3d11.h D3D11_AUTHENTICATED_CONFIGURE_SHARED_RESOURCE_INPUT +struct d3d11.h D3D11_AUTHENTICATED_PROTECTION_FLAGS +struct d3d11.h D3D11_AUTHENTICATED_QUERY_ACCESSIBILITY_ENCRYPTION_GUID_COUNT_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_ACCESSIBILITY_ENCRYPTION_GUID_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_ACCESSIBILITY_ENCRYPTION_GUID_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_ACCESSIBILITY_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_CHANNEL_TYPE_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_CRYPTO_SESSION_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_CRYPTO_SESSION_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_CURRENT_ACCESSIBILITY_ENCRYPTION_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_DEVICE_HANDLE_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_OUTPUT_ID_COUNT_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_OUTPUT_ID_COUNT_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_OUTPUT_ID_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_OUTPUT_ID_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_PROTECTION_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_RESTRICTED_SHARED_RESOURCE_PROCESS_COUNT_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_RESTRICTED_SHARED_RESOURCE_PROCESS_INPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_RESTRICTED_SHARED_RESOURCE_PROCESS_OUTPUT +struct d3d11.h D3D11_AUTHENTICATED_QUERY_UNRESTRICTED_PROTECTED_SHARED_RESOURCE_COUNT_OUTPUT +struct d3d11.h D3D11_ENCRYPTED_BLOCK_INFO +struct d3d11.h D3D11_OMAC +struct d3d11.h D3D11_TEX2D_ARRAY_VPOV +struct d3d11.h D3D11_TEX2D_VDOV +struct d3d11.h D3D11_TEX2D_VPIV +struct d3d11.h D3D11_TEX2D_VPOV +struct d3d11.h D3D11_VIDEO_COLOR +struct d3d11.h D3D11_VIDEO_COLOR_RGBA +struct d3d11.h D3D11_VIDEO_COLOR_YCbCrA +struct d3d11.h D3D11_VIDEO_CONTENT_PROTECTION_CAPS +struct d3d11.h D3D11_VIDEO_DECODER_BUFFER_DESC +struct d3d11.h D3D11_VIDEO_DECODER_CONFIG +struct d3d11.h D3D11_VIDEO_DECODER_DESC +struct d3d11.h D3D11_VIDEO_DECODER_EXTENSION +struct d3d11.h D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC +struct d3d11.h D3D11_VIDEO_PROCESSOR_CAPS +struct d3d11.h D3D11_VIDEO_PROCESSOR_COLOR_SPACE +struct d3d11.h D3D11_VIDEO_PROCESSOR_CONTENT_DESC +struct d3d11.h D3D11_VIDEO_PROCESSOR_CUSTOM_RATE +struct d3d11.h D3D11_VIDEO_PROCESSOR_FILTER_RANGE +struct d3d11.h D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC +struct d3d11.h D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC +struct d3d11.h D3D11_VIDEO_PROCESSOR_RATE_CONVERSION_CAPS +struct d3d11.h D3D11_VIDEO_PROCESSOR_STREAM + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 12 Video APIs > Direct3D 12 Video Enumerations +enum d3d12video.h D3D12_BITSTREAM_ENCRYPTION_TYPE +enum d3d12video.h D3D12_FEATURE_VIDEO +enum d3d12video.h D3D12_VIDEO_DECODE_ARGUMENT_TYPE +enum d3d12video.h D3D12_VIDEO_DECODE_CONFIGURATION_FLAGS +enum d3d12video.h D3D12_VIDEO_DECODE_CONVERSION_SUPPORT_FLAGS +enum d3d12video.h D3D12_VIDEO_DECODE_HISTOGRAM_COMPONENT +enum d3d12video.h D3D12_VIDEO_DECODE_HISTOGRAM_COMPONENT_FLAGS +enum d3d12video.h D3D12_VIDEO_DECODE_STATUS +enum d3d12video.h D3D12_VIDEO_DECODE_SUPPORT_FLAGS +enum d3d12video.h D3D12_VIDEO_DECODE_TIER +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_DIRECT_MODES +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODES +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE +enum d3d12video.h D3D12_VIDEO_ENCODER_FRAME_TYPE_H264 +enum d3d12video.h D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC +enum d3d12video.h D3D12_VIDEO_ENCODER_HEAP_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE +enum d3d12video.h D3D12_VIDEO_ENCODER_LEVELS_H264 +enum d3d12video.h D3D12_VIDEO_ENCODER_LEVELS_HEVC +enum d3d12video.h D3D12_VIDEO_ENCODER_MOTION_ESTIMATION_PRECISION_MODE +enum d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_HEVC_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_PROFILE_H264 +enum d3d12video.h D3D12_VIDEO_ENCODER_PROFILE_HEVC +enum d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE +enum d3d12video.h D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_SUPPORT_FLAGS +enum d3d12video.h D3D12_VIDEO_ENCODER_TIER_HEVC +enum d3d12video.h D3D12_VIDEO_ENCODER_VALIDATION_FLAGS +enum d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_PARAMETER_FLAGS +enum d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_PARAMETER_STAGE +enum d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_PARAMETER_TYPE +enum d3d12video.h D3D12_VIDEO_FIELD_TYPE +enum d3d12video.h D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE +enum d3d12video.h D3D12_VIDEO_FRAME_STEREO_FORMAT +enum d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_SEARCH_BLOCK_SIZE +enum d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_SEARCH_BLOCK_SIZE_FLAGS +enum d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_VECTOR_PRECISION +enum d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_VECTOR_PRECISION_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_ALPHA_FILL_MODE +enum d3d12video.h D3D12_VIDEO_PROCESS_AUTO_PROCESSING_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_DEINTERLACE_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_FEATURE_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_FILTER_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM_FLAGS +enum d3d12video.h D3D12_VIDEO_PROCESS_ORIENTATION +enum d3d12video.h D3D12_VIDEO_PROCESS_SUPPORT_FLAGS +enum d3d12video.h D3D12_VIDEO_PROTECTED_RESOURCE_SUPPORT_FLAGS +enum d3d12video.h D3D12_VIDEO_SCALE_SUPPORT_FLAGS + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 12 Video APIs > Direct3D 12 Video Interfaces +interface d3d12video.h ID3D12VideoDecodeCommandList +interface d3d12video.h ID3D12VideoDecodeCommandList1 +interface d3d12video.h ID3D12VideoDecodeCommandList2 +interface d3d12video.h ID3D12VideoDecoder +interface d3d12video.h ID3D12VideoDecoder1 +interface d3d12video.h ID3D12VideoDecoderHeap +interface d3d12video.h ID3D12VideoDecoderHeap1 +interface d3d12video.h ID3D12VideoDevice +interface d3d12video.h ID3D12VideoDevice3 +interface d3d12video.h ID3D12VideoEncodeCommandList +interface d3d12video.h ID3D12VideoEncodeCommandList1 +interface d3d12video.h ID3D12VideoEncodeCommandList1 +interface d3d12video.h ID3D12VideoEncodeCommandList2 +interface d3d12video.h ID3D12VideoEncoder +interface d3d12video.h ID3D12VideoEncoderHeap +interface d3d12video.h ID3D12VideoExtensionCommand +interface d3d12video.h ID3D12VideoMotionEstimator +interface d3d12video.h ID3D12VideoMotionVectorHeap +interface d3d12video.h ID3D12VideoProcessCommandList +interface d3d12video.h ID3D12VideoProcessCommandList1 +interface d3d12video.h ID3D12VideoProcessCommandList2 +interface d3d12video.h ID3D12VideoProcessor +interface d3d12video.h ID3D12VideoProcessor1 + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Direct3D 12 Video APIs > Direct3D 12 Video Structures +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODE_CONVERSION_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODE_FORMATS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODE_HISTOGRAM +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILES +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_DECODER_HEAP_SIZE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_HEAP_SIZE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_INTRA_REFRESH_MODE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_OUTPUT_RESOLUTION +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_OUTPUT_RESOLUTION_RATIOS_COUNT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOLUTION_SUPPORT_LIMITS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOURCE_REQUIREMENTS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMAND_COUNT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMAND_PARAMETER_COUNT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMAND_PARAMETERS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMAND_SIZE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMAND_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_EXTENSION_COMMANDS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_MOTION_ESTIMATOR +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_MOTION_ESTIMATOR_PROTECTED_RESOURCES +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_MOTION_ESTIMATOR_SIZE +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_PROCESS_MAX_INPUT_STREAMS +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_PROCESS_REFERENCE_INFO +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT +struct d3d12video.h D3D12_FEATURE_DATA_VIDEO_PROCESSOR_SIZE +struct d3d12video.h D3D12_QUERY_DATA_VIDEO_DECODE_STATISTICS +struct d3d12video.h D3D12_RESOLVE_VIDEO_MOTION_VECTOR_HEAP_INPUT +struct d3d12video.h D3D12_RESOLVE_VIDEO_MOTION_VECTOR_HEAP_OUTPUT +struct d3d12video.h D3D12_RESOURCE_COORDINATE +struct d3d12video.h D3D12_VIDEO_DECODE_COMPRESSED_BITSTREAM +struct d3d12video.h D3D12_VIDEO_DECODE_CONFIGURATION +struct d3d12video.h D3D12_VIDEO_DECODE_CONVERSION_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_DECODE_CONVERSION_ARGUMENTS1 +struct d3d12video.h D3D12_VIDEO_DECODE_FRAME_ARGUMENT +struct d3d12video.h D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_DECODE_OUTPUT_HISTOGRAM +struct d3d12video.h D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS1 +struct d3d12video.h D3D12_VIDEO_DECODE_REFERENCE_FRAMES +struct d3d12video.h D3D12_VIDEO_DECODER_DESC +struct d3d12video.h D3D12_VIDEO_DECODER_HEAP_DESC +struct d3d12video.h D3D12_VIDEO_ENCODE_REFERENCE_FRAMES +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_COMPRESSED_BITSTREAM +struct d3d12video.h D3D12_VIDEO_ENCODER_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_ENCODE_OPERATION_METADATA_BUFFER +struct d3d12video.h D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_ENCODER_ENCODEFRAME_OUTPUT_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA +struct d3d12video.h D3D12_VIDEO_ENCODER_HEAP_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_INTRA_REFRESH +struct d3d12video.h D3D12_VIDEO_ENCODER_LEVEL_SETTING +struct d3d12video.h D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_OUTPUT_METADATA +struct d3d12video.h D3D12_VIDEO_ENCODER_OUTPUT_METADATA_STATISTICS +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_LIST_MODIFICATION_OPERATION +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_MARKING_OPERATION +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_SLICES +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_RATIO_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_PROFILE_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_CBR +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_CONFIGURATION_PARAMS +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_CQP +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_QVBR +struct d3d12video.h D3D12_VIDEO_ENCODER_RATE_CONTROL_VBR +struct d3d12video.h D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE +struct d3d12video.h D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_HEVC +struct d3d12video.h D3D12_VIDEO_ENCODER_RESOLVE_METADATA_INPUT_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_ENCODER_RESOLVE_METADATA_OUTPUT_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_DESC +struct d3d12video.h D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE +struct d3d12video.h D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 +struct d3d12video.h D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_HEVC +struct d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_DESC +struct d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_INFO +struct d3d12video.h D3D12_VIDEO_EXTENSION_COMMAND_PARAMETER_INFO +struct d3d12video.h D3D12_VIDEO_FORMAT +struct d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_DESC +struct d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_INPUT +struct d3d12video.h D3D12_VIDEO_MOTION_ESTIMATOR_OUTPUT +struct d3d12video.h D3D12_VIDEO_MOTION_VECTOR_HEAP_DESC +struct d3d12video.h D3D12_VIDEO_PROCESS_ALPHA_BLENDING +struct d3d12video.h D3D12_VIDEO_PROCESS_FILTER_RANGE +struct d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM +struct d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS1 +struct d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC +struct d3d12video.h D3D12_VIDEO_PROCESS_INPUT_STREAM_RATE +struct d3d12video.h D3D12_VIDEO_PROCESS_LUMA_KEY +struct d3d12video.h D3D12_VIDEO_PROCESS_OUTPUT_STREAM +struct d3d12video.h D3D12_VIDEO_PROCESS_OUTPUT_STREAM_ARGUMENTS +struct d3d12video.h D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC +struct d3d12video.h D3D12_VIDEO_PROCESS_REFERENCE_SET +struct d3d12video.h D3D12_VIDEO_PROCESS_TRANSFORM +struct d3d12video.h D3D12_VIDEO_SAMPLE +struct d3d12video.h D3D12_VIDEO_SCALE_SUPPORT +struct d3d12video.h D3D12_VIDEO_SIZE_RANGE + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Output Protection Manager > OPM Programming Reference > OPM Interfaces +interface opmapi.h IOPMVideoOutput + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Output Protection Manager > OPM Programming Reference > OPM Functions +function opmapi.h OPMGetVideoOutputForTarget +function opmapi.h OPMGetVideoOutputsFromHMONITOR +function opmapi.h OPMGetVideoOutputsFromIDirect3DDevice9Object + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Output Protection Manager > OPM Programming Reference > OPM Structures +struct ksopmapi.h OPM_ENCRYPTED_INITIALIZATION_PARAMETERS +struct ksopmapi.h OPM_GET_CODEC_INFO_INFORMATION +struct ksopmapi.h OPM_GET_CODEC_INFO_PARAMETERS +struct ksopmapi.h OPM_GET_INFO_PARAMETERS +struct ksopmapi.h OPM_OMAC +struct ksopmapi.h OPM_RANDOM_NUMBER +struct ksopmapi.h OPM_REQUESTED_INFORMATION +struct ksopmapi.h OPM_STANDARD_INFORMATION +struct opmapi.h OPM_ACP_AND_CGMSA_SIGNALING +struct opmapi.h OPM_ACTUAL_OUTPUT_FORMAT +struct opmapi.h OPM_CONFIGURE_PARAMETERS +struct opmapi.h OPM_CONNECTED_HDCP_DEVICE_INFORMATION +struct opmapi.h OPM_COPP_COMPATIBLE_GET_INFO_PARAMETERS +struct opmapi.h OPM_HDCP_KEY_SELECTION_VECTOR +struct opmapi.h OPM_OUTPUT_ID_DATA +struct opmapi.h OPM_SET_ACP_AND_CGMSA_SIGNALING_PARAMETERS +struct opmapi.h OPM_SET_HDCP_SRM_PARAMETERS +struct opmapi.h OPM_SET_PROTECTION_LEVEL_PARAMETERS + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Output Protection Manager > OPM Programming Reference > OPM Enumerations +enum opmapi.h OPM_ACP_PROTECTION_LEVEL +enum opmapi.h OPM_DPCP_PROTECTION_LEVEL +enum opmapi.h OPM_HDCP_PROTECTION_LEVEL +enum opmapi.h OPM_IMAGE_ASPECT_RATIO_EN300294 +enum opmapi.h OPM_VIDEO_OUTPUT_SEMANTICS + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Windows Media Codecs > Codec and DSP IPropertyBag Constants > Table of Contents Parser Reference > Table of Contents Parser Interfaces +interface wmcodecdsp.h IToc +interface wmcodecdsp.h ITocCollection +interface wmcodecdsp.h ITocEntry +interface wmcodecdsp.h ITocEntryList +interface wmcodecdsp.h ITocParser + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Windows Media Codecs > Codec and DSP IPropertyBag Constants > Table of Contents Parser Reference > Table of Contents Parser Structures +struct wmcodecdsp.h TOC_DESCRIPTOR +struct wmcodecdsp.h TOC_ENTRY_DESCRIPTOR + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Guide > Windows Media Codecs > Codec and DSP IPropertyBag Constants > Table of Contents Parser Reference > Table of Contents Parser Enumerations +enum wmcodecdsp.h TOC_POS_TYPE + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Enumerations +enum codecapi.h eAVDecDDStereoDownMixMode +enum codecapi.h eAVEncAdaptiveMode +enum codecapi.h eAVEncH264PictureType +enum codecapi.h eAVEncH264PictureType +enum codecapi.h eAVEncH264VLevel +enum codecapi.h eAVEncH264VProfile +enum codecapi.h eAVEncVideoContentType +enum dxva2api.h DXVA2_NominalRange +enum dxva2api.h DXVA2_SampleFormat +enum dxva2api.h DXVA2_SurfaceType +enum dxva2api.h DXVA2_VideoChromaSubSampling +enum dxva2api.h DXVA2_VideoLighting +enum dxva2api.h DXVA2_VideoPrimaries +enum dxva2api.h DXVA2_VideoTransferFunction +enum dxva2api.h DXVA2_VideoTransferMatrix +enum evr.h EVRFilterConfigPrefs +enum evr.h MFVideoAspectRatioMode +enum evr.h MFVideoMixPrefs +enum evr.h MFVideoRenderPrefs +enum evr.h MFVP_MESSAGE_TYPE +enum evr9.h MFVideoAlphaBitmapFlags +enum mfapi.h _MFDepthMeasurement +enum mfapi.h _MFT_ENUM_FLAG +enum mfapi.h MF_TOPOSTATUS +enum mfapi.h MFASYNC_WORKQUEUE_TYPE +enum mfapi.h MFFrameSourceTypes +enum mfapi.h MFSampleEncryptionProtectionScheme +enum mfapi.h MFVideo3DFormat +enum mfapi.h MFVideo3DFormat +enum mfapi.h MFVideo3DSampleFormat +enum mfapi.h MFVideo3DSampleFormat +enum mfapi.h MFVideoDRMFlags +enum mfapi.h MFVideoPadFlags +enum mfapi.h MFVideoRotationFormat +enum mfapi.h MFVideoSrcContentHintFlags +enum mfapi.h MFWaveFormatExConvertFlags +enum mfcaptureengine.h MF_CAPTURE_ENGINE_DEVICE_TYPE Enumeration =mf_capture_engine_device_type +enum mfcaptureengine.h MF_CAPTURE_ENGINE_SINK_TYPE Enumeration =mf_capture_engine_sink_type +enum mfcaptureengine.h MF_CAPTURE_ENGINE_STREAM_CATEGORY Enumeration =mf_capture_engine_stream_category +enum mfidl.h _MF_CROSS_ORIGIN_POLICY +enum mfidl.h MF_CONNECT_METHOD +enum mfidl.h MF_OBJECT_TYPE +enum mfidl.h MF_OPM_ACP_PROTECTION_LEVEL +enum mfidl.h MF_OPM_CGMSA_PROTECTION_LEVEL +enum mfidl.h MF_QUALITY_ADVISE_FLAGS +enum mfidl.h MF_QUALITY_DROP_MODE +enum mfidl.h MF_QUALITY_LEVEL +enum mfidl.h MF_TOPOLOGY_RESOLUTION_STATUS_FLAGS +enum mfidl.h MF_TOPOLOGY_TYPE +enum mfidl.h MF_TOPONODE_DRAIN_MODE +enum mfidl.h MF_TOPONODE_FLUSH_MODE +enum mfidl.h MF_TRANSCODE_ADJUST_PROFILE_FLAGS +enum mfidl.h MF_TRANSCODE_TOPOLOGYMODE_FLAGS +enum mfidl.h MF_URL_TRUST_STATUS +enum mfidl.h MF_VIDEO_PROCESSOR_MIRROR +enum mfidl.h MF_VIDEO_PROCESSOR_ROTATION +enum mfidl.h MFAudioConstriction +enum mfidl.h MFCLOCK_CHARACTERISTICS_FLAGS +enum mfidl.h MFCLOCK_RELATIONAL_FLAGS +enum mfidl.h MFCLOCK_STATE +enum mfidl.h MFMEDIASOURCE_CHARACTERISTICS +enum mfidl.h MFNET_PROXYSETTINGS +enum mfidl.h MFNetAuthenticationFlags +enum mfidl.h MFNetCredentialOptions +enum mfidl.h MFNetCredentialRequirements +enum mfidl.h MFNETSOURCE_CACHE_STATE +enum mfidl.h MFNETSOURCE_PROTOCOL_TYPE +enum mfidl.h MFNETSOURCE_STATISTICS_IDS +enum mfidl.h MFNETSOURCE_TRANSPORT_TYPE +enum mfidl.h MFPMPSESSION_CREATION_FLAGS +enum mfidl.h MFPOLICYMANAGER_ACTION +enum mfidl.h MFRATE_DIRECTION +enum mfidl.h MFSampleAllocatorUsage +enum mfidl.h MFSensorDeviceMode +enum mfidl.h MFSensorDeviceType +enum mfidl.h MFSensorStreamType +enum mfidl.h MFSequencerTopologyFlags +enum mfidl.h MFSESSION_GETFULLTOPOLOGY_FLAGS +enum mfidl.h MFSESSION_SETTOPOLOGY_FLAGS +enum mfidl.h MFSHUTDOWN_STATUS +enum mfidl.h MFSTREAMSINK_MARKER_TYPE +enum mfidl.h MFTIMER_FLAGS +enum mfidl.h MFTOPOLOGY_DXVA_MODE +enum mfidl.h MFTOPOLOGY_HARDWARE_MODE +enum mfidl.h SAMPLE_PROTECTION_VERSION +enum mfmediaengine.h MF_MEDIA_ENGINE_CANPLAY +enum mfmediaengine.h MF_MEDIA_ENGINE_CREATEFLAGS +enum mfmediaengine.h MF_MEDIA_ENGINE_ERR +enum mfmediaengine.h MF_MEDIA_ENGINE_EVENT +enum mfmediaengine.h MF_MEDIA_ENGINE_EXTENSION_TYPE +enum mfmediaengine.h MF_MEDIA_ENGINE_FRAME_PROTECTION_FLAGS +enum mfmediaengine.h MF_MEDIA_ENGINE_NETWORK +enum mfmediaengine.h MF_MEDIA_ENGINE_PRELOAD +enum mfmediaengine.h MF_MEDIA_ENGINE_PROTECTION_FLAGS +enum mfmediaengine.h MF_MEDIA_ENGINE_READY +enum mfmediaengine.h MF_MEDIA_ENGINE_S3D_PACKING_MODE +enum mfmediaengine.h MF_MEDIA_ENGINE_SEEK_MODE +enum mfmediaengine.h MF_MEDIA_ENGINE_STATISTIC +enum mfmediaengine.h MF_MEDIA_ENGINE_STREAMTYPE_FAILED +enum mfmediaengine.h MF_TIMED_TEXT_ALIGNMENT +enum mfmediaengine.h MF_TIMED_TEXT_CUE_EVENT +enum mfmediaengine.h MF_TIMED_TEXT_DECORATION +enum mfmediaengine.h MF_TIMED_TEXT_DISPLAY_ALIGNMENT +enum mfmediaengine.h MF_TIMED_TEXT_ERROR_CODE +enum mfmediaengine.h MF_TIMED_TEXT_FONT_STYLE +enum mfmediaengine.h MF_TIMED_TEXT_SCROLL_MODE +enum mfmediaengine.h MF_TIMED_TEXT_TRACK_KIND +enum mfmediaengine.h MF_TIMED_TEXT_UNIT_TYPE +enum mfmediaengine.h MF_TIMED_TEXT_WRITING_MODE +enum mfobjects.h MF_ATTRIBUTE_SERIALIZE_OPTIONS +enum mfobjects.h MF_ATTRIBUTE_TYPE +enum mfobjects.h MF_ATTRIBUTES_MATCH_TYPE +enum mfobjects.h MF_FILE_ACCESSMODE +enum mfobjects.h MF_FILE_FLAGS +enum mfobjects.h MF_FILE_OPENMODE +enum mfobjects.h MF_PLUGIN_CONTROL_POLICY +enum mfobjects.h MF_Plugin_Type +enum mfobjects.h MF2DBuffer_LockFlags +enum mfobjects.h MFBYTESTREAM_SEEK_ORIGIN +enum mfobjects.h MFNominalRange +enum mfobjects.h MFStandardVideoFormat +enum mfobjects.h MFVideoChromaSubsampling +enum mfobjects.h MFVideoFlags +enum mfobjects.h MFVideoInterlaceMode +enum mfobjects.h MFVideoLighting +enum mfobjects.h MFVideoPrimaries +enum mfobjects.h MFVideoTransferFunction +enum mfobjects.h MFVideoTransferMatrix +enum mfplay.h _MFP_CREATION_OPTIONS +enum mfplay.h _MFP_CREDENTIAL_FLAGS +enum mfplay.h _MFP_MEDIAITEM_CHARACTERISTICS +enum mfplay.h MFP_EVENT_TYPE +enum mfplay.h MFP_MEDIAPLAYER_STATE +enum mfreadwrite.h MF_SOURCE_READER_CONTROL_FLAG +enum mfreadwrite.h MF_SOURCE_READER_FLAG +enum mfsharingengine.h MF_SHARING_ENGINE_EVENT +enum mfsharingengine.h PLAYTO_SOURCE_CREATEFLAGS +enum mftransform.h _MFT_DRAIN_TYPE +enum mftransform.h _MFT_INPUT_DATA_BUFFER_FLAGS +enum mftransform.h _MFT_INPUT_STATUS_FLAGS +enum mftransform.h _MFT_INPUT_STREAM_INFO_FLAGS +enum mftransform.h _MFT_OUTPUT_DATA_BUFFER_FLAGS +enum mftransform.h _MFT_OUTPUT_STATUS_FLAGS +enum mftransform.h _MFT_OUTPUT_STREAM_INFO_FLAGS +enum mftransform.h _MFT_PROCESS_OUTPUT_FLAGS +enum mftransform.h _MFT_PROCESS_OUTPUT_STATUS +enum mftransform.h _MFT_SET_TYPE_FLAGS +enum mftransform.h MF3DVideoOutputType +enum mftransform.h MFT_MESSAGE_TYPE +enum wmcodecdsp.h AEC_SYSTEM_MODE +enum wmcodecdsp.h AEC_VAD_MODE +enum wmcodecdsp.h MFVideoDSPMode +enum wmcodecdsp.h MIC_ARRAY_MODE +enum wmcodecdsp.h WMT_PROP_DATATYPE +enum wmcontainer.h ASF_SELECTION_STATUS +enum wmcontainer.h ASF_STATUSFLAGS +enum wmcontainer.h MFASF_INDEXER_FLAGS +enum wmcontainer.h MFASF_MULTIPLEXERFLAGS +enum wmcontainer.h MFASF_SPLITTERFLAGS +enum wmcontainer.h MFASF_STREAMSELECTOR_FLAGS +enum wmcontainer.h MFSINK_WMDRMACTION + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Functions +callback dxvahd.h PDXVAHDSW_CreateDevice +callback dxvahd.h PDXVAHDSW_CreateVideoProcessor +callback dxvahd.h PDXVAHDSW_DestroyDevice +callback dxvahd.h PDXVAHDSW_DestroyVideoProcessor +callback dxvahd.h PDXVAHDSW_GetVideoProcessBltStatePrivate +callback dxvahd.h PDXVAHDSW_GetVideoProcessorCaps +callback dxvahd.h PDXVAHDSW_GetVideoProcessorCustomRates +callback dxvahd.h PDXVAHDSW_GetVideoProcessorDeviceCaps +callback dxvahd.h PDXVAHDSW_GetVideoProcessorFilterRange +callback dxvahd.h PDXVAHDSW_GetVideoProcessorInputFormats +callback dxvahd.h PDXVAHDSW_GetVideoProcessorOutputFormats +callback dxvahd.h PDXVAHDSW_GetVideoProcessStreamStatePrivate +callback dxvahd.h PDXVAHDSW_Plugin +callback dxvahd.h PDXVAHDSW_ProposeVideoPrivateFormat +callback dxvahd.h PDXVAHDSW_SetVideoProcessBltState +callback dxvahd.h PDXVAHDSW_SetVideoProcessStreamState +callback dxvahd.h PDXVAHDSW_VideoProcessBltHD +callback evr.h MFCreateVideoMixer +callback evr.h MFCreateVideoMixerAndPresenter +callback evr.h MFCreateVideoPresenter +callback evr.h MFCreateVideoRenderer +callback evr.h MFCreateVideoSampleAllocator +callback evr.h MFCreateVideoSampleFromSurface +callback mfapi.h MFPERIODICCALLBACK Callback =mfperiodiccallback +function dxva2api.h DXVA2_Fixed32OpaqueAlpha +function dxva2api.h DXVA2_Fixed32TransparentAlpha +function dxva2api.h DXVA2CreateDirect3DDeviceManager9 +function dxva2api.h DXVA2CreateVideoService +function dxva2api.h DXVA2FixedToFloat +function dxva2api.h DXVA2FloatToFixed +function dxvahd.h DXVAHD_CreateDevice +function mfapi.h MFAddPeriodicCallback +function mfapi.h MFAllocateSerialWorkQueue +function mfapi.h MFAllocateWorkQueue +function mfapi.h MFAllocateWorkQueueEx +function mfapi.h MFAverageTimePerFrameToFrameRate +function mfapi.h MFBeginCreateFile +function mfapi.h MFBeginRegisterWorkQueueWithMMCSS +function mfapi.h MFBeginRegisterWorkQueueWithMMCSSEx +function mfapi.h MFBeginUnregisterWorkQueueWithMMCSS +function mfapi.h MFCalculateBitmapImageSize +function mfapi.h MFCalculateImageSize +function mfapi.h MFCancelCreateFile +function mfapi.h MFCancelWorkItem +function mfapi.h MFCombineSamples +function mfapi.h MFCompareFullToPartialMediaType +function mfapi.h MFConvertColorInfoFromDXVA +function mfapi.h MFConvertColorInfoToDXVA +function mfapi.h MFConvertFromFP16Array +function mfapi.h MFConvertToFP16Array +function mfapi.h MFCopyImage +function mfapi.h MFCreate2DMediaBuffer +function mfapi.h MFCreateAlignedMemoryBuffer +function mfapi.h MFCreateAMMediaTypeFromMFMediaType +function mfapi.h MFCreateAsyncResult +function mfapi.h MFCreateAttributes +function mfapi.h MFCreateAudioMediaType +function mfapi.h MFCreateCollection +function mfapi.h MFCreateDXGIDeviceManager +function mfapi.h MFCreateDXGISurfaceBuffer +function mfapi.h MFCreateDXSurfaceBuffer +function mfapi.h MFCreateEventQueue +function mfapi.h MFCreateFile +function mfapi.h MFCreateLegacyMediaBufferOnMFMediaBuffer +function mfapi.h MFCreateMediaBufferFromMediaType +function mfapi.h MFCreateMediaBufferWrapper +function mfapi.h MFCreateMediaEvent +function mfapi.h MFCreateMediaExtensionActivate +function mfapi.h MFCreateMediaType +function mfapi.h MFCreateMediaTypeFromRepresentation +function mfapi.h MFCreateMemoryBuffer +function mfapi.h MFCreateMFByteStreamWrapper +function mfapi.h MFCreateMFVideoFormatFromMFMediaType +function mfapi.h MFCreateMuxStreamAttributes +function mfapi.h MFCreateMuxStreamMediaType +function mfapi.h MFCreateMuxStreamSample +function mfapi.h MFCreateSample +function mfapi.h MFCreateTempFile +function mfapi.h MFCreateVideoMediaType +function mfapi.h MFCreateVideoMediaTypeFromBitMapInfoHeader +function mfapi.h MFCreateVideoMediaTypeFromBitMapInfoHeaderEx +function mfapi.h MFCreateVideoMediaTypeFromSubtype +function mfapi.h MFCreateVideoMediaTypeFromVideoInfoHeader +function mfapi.h MFCreateVideoMediaTypeFromVideoInfoHeader2 +function mfapi.h MFCreateVideoSampleAllocatorEx +function mfapi.h MFCreateWaveFormatExFromMFMediaType +function mfapi.h MFCreateWICBitmapBuffer +function mfapi.h MFEndCreateFile +function mfapi.h MFEndRegisterWorkQueueWithMMCSS +function mfapi.h MFEndUnregisterWorkQueueWithMMCSS +function mfapi.h MFFrameRateToAverageTimePerFrame +function mfapi.h MFGetAttribute2UINT32asUINT64 +function mfapi.h MFGetAttributeDouble +function mfapi.h MFGetAttributeRatio +function mfapi.h MFGetAttributesAsBlob +function mfapi.h MFGetAttributesAsBlobSize +function mfapi.h MFGetAttributeSize +function mfapi.h MFGetAttributeString +function mfapi.h MFGetAttributeUINT32 +function mfapi.h MFGetAttributeUINT64 +function mfapi.h MFGetContentProtectionSystemCLSID +function mfapi.h MFGetMFTMerit +function mfapi.h MFGetPlaneSize +function mfapi.h MFGetPluginControl +function mfapi.h MFGetStrideForBitmapInfoHeader +function mfapi.h MFGetTimerPeriodicity +function mfapi.h MFGetUncompressedVideoFormat +function mfapi.h MFGetWorkQueueMMCSSClass +function mfapi.h MFGetWorkQueueMMCSSPriority +function mfapi.h MFGetWorkQueueMMCSSTaskId +function mfapi.h MFHeapAlloc +function mfapi.h MFHeapFree +function mfapi.h MFInitAMMediaTypeFromMFMediaType +function mfapi.h MFInitAttributesFromBlob +function mfapi.h MFInitMediaTypeFromAMMediaType +function mfapi.h MFInitMediaTypeFromMFVideoFormat +function mfapi.h MFInitMediaTypeFromMPEG1VideoInfo +function mfapi.h MFInitMediaTypeFromMPEG2VideoInfo +function mfapi.h MFInitMediaTypeFromVideoInfoHeader +function mfapi.h MFInitMediaTypeFromVideoInfoHeader2 +function mfapi.h MFInitMediaTypeFromWaveFormatEx +function mfapi.h MFInitVideoFormat +function mfapi.h MFInitVideoFormat_RGB +function mfapi.h MFInvokeCallback +function mfapi.h MFIsFormatYUV +function mfapi.h MFllMulDiv +function mfapi.h MFLockDXGIDeviceManager +function mfapi.h MFLockPlatform +function mfapi.h MFLockSharedWorkQueue +function mfapi.h MFLockWorkQueue +function mfapi.h MFMapDX9FormatToDXGIFormat +function mfapi.h MFMapDXGIFormatToDX9Format +function mfapi.h MFPutWaitingWorkItem +function mfapi.h MFPutWorkItem +function mfapi.h MFPutWorkItem2 +function mfapi.h MFPutWorkItemEx +function mfapi.h MFPutWorkItemEx2 +function mfapi.h MFRegisterLocalByteStreamHandler +function mfapi.h MFRegisterLocalSchemeHandler +function mfapi.h MFRegisterPlatformWithMMCSS +function mfapi.h MFRemovePeriodicCallback +function mfapi.h MFScheduleWorkItem +function mfapi.h MFScheduleWorkItemEx +function mfapi.h MFSetAttribute2UINT32asUINT64 +function mfapi.h MFSetAttributeRatio +function mfapi.h MFSetAttributeSize +function mfapi.h MFShutdown +function mfapi.h MFSplitSample +function mfapi.h MFStartup +function mfapi.h MFTEnum +function mfapi.h MFTEnum2 +function mfapi.h MFTEnumEx +function mfapi.h MFTGetInfo +function mfapi.h MFTRegister +function mfapi.h MFTRegisterLocal +function mfapi.h MFTRegisterLocalByCLSID +function mfapi.h MFTUnregister +function mfapi.h MFTUnregisterLocal +function mfapi.h MFTUnregisterLocalByCLSID +function mfapi.h MFUnlockDXGIDeviceManager +function mfapi.h MFUnlockPlatform +function mfapi.h MFUnlockWorkQueue +function mfapi.h MFUnregisterPlatformFromMMCSS +function mfapi.h MFUnwrapMediaType +function mfapi.h MFValidateMediaTypeSize +function mfapi.h MFWrapMediaType +function mfapi.h Pack2UINT32AsUINT64 +function mfapi.h PackRatio +function mfapi.h PackSize +function mfapi.h Unpack2UINT32AsUINT64 +function mfapi.h UnpackRatio +function mfapi.h UnpackSize +function mfcontentdecryptionmodule.h MFCreateEncryptedMediaExtensionsStoreActivate +function mfidl.h CreateNamedPropertyStore +function mfidl.h CreatePropertyStore +function mfidl.h MFCreate3GPMediaSink +function mfidl.h MFCreateAC3MediaSink +function mfidl.h MFCreateADTSMediaSink +function mfidl.h MFCreateAggregateSource +function mfidl.h MFCreateAudioRenderer +function mfidl.h MFCreateAudioRendererActivate +function mfidl.h MFCreateAVIMediaSink +function mfidl.h MFCreateContentDecryptorContext +function mfidl.h MFCreateContentProtectionDevice +function mfidl.h MFCreateCredentialCache +function mfidl.h MFCreateDeviceSource +function mfidl.h MFCreateDeviceSourceActivate +function mfidl.h MFCreateFMPEG4MediaSink +function mfidl.h MFCreateMediaSession +function mfidl.h MFCreateMediaTypeFromProperties +function mfidl.h MFCreateMFByteStreamOnStream +function mfidl.h MFCreateMFByteStreamOnStreamEx +function mfidl.h MFCreateMP3MediaSink +function mfidl.h MFCreateMPEG4MediaSink +function mfidl.h MFCreateMuxSink +function mfidl.h MFCreateNetSchemePlugin +function mfidl.h MFCreatePMPMediaSession +function mfidl.h MFCreatePMPServer +function mfidl.h MFCreatePresentationClock +function mfidl.h MFCreatePresentationDescriptor +function mfidl.h MFCreatePropertiesFromMediaType +function mfidl.h MFCreateProtectedEnvironmentAccess +function mfidl.h MFCreateProxyLocator +function mfidl.h MFCreateRemoteDesktopPlugin +function mfidl.h MFCreateSampleCopierMFT +function mfidl.h MFCreateSampleGrabberSinkActivate +function mfidl.h MFCreateSensorActivityMonitor +function mfidl.h MFCreateSensorGroup +function mfidl.h MFCreateSensorProfile +function mfidl.h MFCreateSensorProfileCollection +function mfidl.h MFCreateSensorStream +function mfidl.h MFCreateSequencerSegmentOffset +function mfidl.h MFCreateSequencerSource +function mfidl.h MFCreateSimpleTypeHandler +function mfidl.h MFCreateSourceResolver +function mfidl.h MFCreateStandardQualityManager +function mfidl.h MFCreateStreamDescriptor +function mfidl.h MFCreateStreamOnMFByteStream +function mfidl.h MFCreateStreamOnMFByteStreamEx +function mfidl.h MFCreateSystemTimeSource +function mfidl.h MFCreateTopoLoader +function mfidl.h MFCreateTopology +function mfidl.h MFCreateTopologyNode +function mfidl.h MFCreateTrackedSample +function mfidl.h MFCreateTranscodeProfile +function mfidl.h MFCreateTranscodeSinkActivate +function mfidl.h MFCreateTranscodeTopology +function mfidl.h MFCreateTranscodeTopologyFromByteStream +function mfidl.h MFCreateVideoRendererActivate +function mfidl.h MFCreateWAVEMediaSink +function mfidl.h MFDeserializePresentationDescriptor +function mfidl.h MFEnumDeviceSources +function mfidl.h MFGetLocalId +function mfidl.h MFGetService +function mfidl.h MFGetSupportedMimeTypes +function mfidl.h MFGetSupportedSchemes +function mfidl.h MFGetSystemId +function mfidl.h MFGetSystemTime +function mfidl.h MFGetTopoNodeCurrentType +function mfidl.h MFIsContentProtectionDeviceSupported +function mfidl.h MFLoadSignedLibrary +function mfidl.h MFRequireProtectedEnvironment +function mfidl.h MFSerializePresentationDescriptor +function mfidl.h MFShutdownObject +function mfidl.h MFTranscodeGetAudioOutputAvailableTypes +function mfobjects.h MFDeserializeAttributesFromStream +function mfobjects.h MFSerializeAttributesToStream +function mfplay.h MFPCreateMediaPlayer +function mfreadwrite.h MFCreateSinkWriterFromMediaSink +function mfreadwrite.h MFCreateSinkWriterFromURL +function mfreadwrite.h MFCreateSourceReaderFromByteStream +function mfreadwrite.h MFCreateSourceReaderFromMediaSource +function mfreadwrite.h MFCreateSourceReaderFromURL +function mftransform.h MFCreateTransformActivate +function wmcontainer.h MFCreateASFContentInfo +function wmcontainer.h MFCreateASFIndexer +function wmcontainer.h MFCreateASFIndexerByteStream +function wmcontainer.h MFCreateASFMediaSink +function wmcontainer.h MFCreateASFMediaSinkActivate +function wmcontainer.h MFCreateASFMultiplexer +function wmcontainer.h MFCreateASFProfile +function wmcontainer.h MFCreateASFProfileFromPresentationDescriptor +function wmcontainer.h MFCreateASFSplitter +function wmcontainer.h MFCreateASFStreamingMediaSink +function wmcontainer.h MFCreateASFStreamingMediaSinkActivate +function wmcontainer.h MFCreateASFStreamSelector +function wmcontainer.h MFCreatePresentationDescriptorFromASFProfile +function wmcontainer.h MFCreateWMAEncoderActivate +function wmcontainer.h MFCreateWMVEncoderActivate + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Interfaces +interface dxva2api.h IDirect3DDeviceManager9 +interface dxva2api.h IDirectXVideoAccelerationService +interface dxva2api.h IDirectXVideoDecoder +interface dxva2api.h IDirectXVideoDecoderService +interface dxva2api.h IDirectXVideoMemoryConfiguration +interface dxva2api.h IDirectXVideoProcessor +interface dxva2api.h IDirectXVideoProcessorService +interface evr.h IEVRFilterConfig +interface evr.h IEVRFilterConfigEx +interface evr.h IEVRTrustedVideoPlugin +interface evr.h IMFDesiredSample +interface evr.h IMFTopologyServiceLookup +interface evr.h IMFTopologyServiceLookupClient +interface evr.h IMFVideoDeviceID +interface evr.h IMFVideoDisplayControl +interface evr.h IMFVideoMixerControl +interface evr.h IMFVideoMixerControl2 +interface evr.h IMFVideoPositionMapper +interface evr.h IMFVideoPresenter +interface evr.h IMFVideoRenderer +interface evr9.h IEVRVideoStreamControl +interface evr9.h IMFVideoMixerBitmap +interface evr9.h IMFVideoProcessor +interface mfcaptureengine.h IMFCaptureEngine +interface mfcaptureengine.h IMFCaptureEngineClassFactory +interface mfcaptureengine.h IMFCaptureEngineOnEventCallback +interface mfcaptureengine.h IMFCaptureEngineOnSampleCallback +interface mfcaptureengine.h IMFCaptureEngineOnSampleCallback2 +interface mfcaptureengine.h IMFCapturePhotoSink +interface mfcaptureengine.h IMFCapturePreviewSink +interface mfcaptureengine.h IMFCaptureRecordSink +interface mfcaptureengine.h IMFCaptureSink +interface mfcaptureengine.h IMFCaptureSink2 +interface mfcaptureengine.h IMFCaptureSource +interface mfcontentdecryptionmodule.h IMFContentDecryptionModule +interface mfcontentdecryptionmodule.h IMFContentDecryptionModuleAccess +interface mfcontentdecryptionmodule.h IMFContentDecryptionModuleFactory +interface mfcontentdecryptionmodule.h IMFContentDecryptionModuleSession +interface mfcontentdecryptionmodule.h IMFContentDecryptionModuleSessionCallbacks +interface mfidl.h IMFAudioPolicy +interface mfidl.h IMFAudioStreamVolume +interface mfidl.h IMFByteStreamBuffering +interface mfidl.h IMFByteStreamCacheControl +interface mfidl.h IMFByteStreamCacheControl2 +interface mfidl.h IMFByteStreamHandler +interface mfidl.h IMFByteStreamTimeSeek +interface mfidl.h IMFClock +interface mfidl.h IMFClockConsumer +interface mfidl.h IMFClockStateSink +interface mfidl.h IMFContentDecryptorContext +interface mfidl.h IMFContentEnabler +interface mfidl.h IMFContentProtectionDevice +interface mfidl.h IMFContentProtectionManager +interface mfidl.h IMFFieldOfUseMFTUnlock +interface mfidl.h IMFFinalizableMediaSink +interface mfidl.h IMFGetService +interface mfidl.h IMFHttpDownloadRequest +interface mfidl.h IMFHttpDownloadSession +interface mfidl.h IMFHttpDownloadSessionProvider +interface mfidl.h IMFInputTrustAuthority +interface mfidl.h IMFLocalMFTRegistration +interface mfidl.h IMFMediaSession +interface mfidl.h IMFMediaSink +interface mfidl.h IMFMediaSinkPreroll +interface mfidl.h IMFMediaSource +interface mfidl.h IMFMediaSourceEx +interface mfidl.h IMFMediaSourcePresentationProvider +interface mfidl.h IMFMediaSourceTopologyProvider +interface mfidl.h IMFMediaStream +interface mfidl.h IMFMediaTypeHandler +interface mfidl.h IMFMetadata +interface mfidl.h IMFMetadataProvider +interface mfidl.h IMFNetCredential +interface mfidl.h IMFNetCredentialCache +interface mfidl.h IMFNetCredentialManager +interface mfidl.h IMFNetCrossOriginSupport +interface mfidl.h IMFNetProxyLocator +interface mfidl.h IMFNetProxyLocatorFactory +interface mfidl.h IMFNetResourceFilter +interface mfidl.h IMFNetSchemeHandlerConfig +interface mfidl.h IMFObjectReferenceStream +interface mfidl.h IMFOutputPolicy +interface mfidl.h IMFOutputSchema +interface mfidl.h IMFOutputTrustAuthority +interface mfidl.h IMFPMPClient +interface mfidl.h IMFPMPClientApp +interface mfidl.h IMFPMPHost +interface mfidl.h IMFPMPHostApp +interface mfidl.h IMFPMPServer +interface mfidl.h IMFPresentationClock +interface mfidl.h IMFPresentationDescriptor +interface mfidl.h IMFPresentationTimeSource +interface mfidl.h IMFProtectedEnvironmentAccess +interface mfidl.h IMFQualityAdvise +interface mfidl.h IMFQualityAdvise2 +interface mfidl.h IMFQualityAdviseLimits +interface mfidl.h IMFQualityManager +interface mfidl.h IMFRateControl +interface mfidl.h IMFRateSupport +interface mfidl.h IMFRealTimeClient +interface mfidl.h IMFRealTimeClientEx +interface mfidl.h IMFRemoteDesktopPlugin +interface mfidl.h IMFRemoteProxy +interface mfidl.h IMFSAMIStyle +interface mfidl.h IMFSampleAllocatorControl +interface mfidl.h IMFSampleGrabberSinkCallback +interface mfidl.h IMFSampleGrabberSinkCallback2 +interface mfidl.h IMFSampleProtection +interface mfidl.h IMFSaveJob +interface mfidl.h IMFSchemeHandler +interface mfidl.h IMFSecureChannel +interface mfidl.h IMFSeekInfo +interface mfidl.h IMFSensorActivitiesReport +interface mfidl.h IMFSensorActivitiesReportCallback +interface mfidl.h IMFSensorActivityMonitor +interface mfidl.h IMFSensorActivityReport +interface mfidl.h IMFSensorDevice +interface mfidl.h IMFSensorGroup +interface mfidl.h IMFSensorProcessActivity +interface mfidl.h IMFSensorProfile +interface mfidl.h IMFSensorProfileCollection +interface mfidl.h IMFSensorStream +interface mfidl.h IMFSensorTransformFactory +interface mfidl.h IMFSequencerSource +interface mfidl.h IMFShutdown +interface mfidl.h IMFSignedLibrary +interface mfidl.h IMFSimpleAudioVolume +interface mfidl.h IMFSourceOpenMonitor +interface mfidl.h IMFSourceResolver +interface mfidl.h IMFSSLCertificateManager +interface mfidl.h IMFStreamDescriptor +interface mfidl.h IMFStreamingSinkConfig +interface mfidl.h IMFStreamSink +interface mfidl.h IMFSystemId +interface mfidl.h IMFTimecodeTranslate +interface mfidl.h IMFTimer +interface mfidl.h IMFTopoLoader +interface mfidl.h IMFTopology +interface mfidl.h IMFTopologyNode +interface mfidl.h IMFTopologyNodeAttributeEditor +interface mfidl.h IMFTrackedSample +interface mfidl.h IMFTranscodeProfile +interface mfidl.h IMFTranscodeSinkInfoProvider +interface mfidl.h IMFTrustedInput +interface mfidl.h IMFTrustedOutput +interface mfidl.h IMFVideoCaptureSampleAllocator +interface mfidl.h IMFVideoProcessorControl +interface mfidl.h IMFVideoProcessorControl2 +interface mfidl.h IMFVideoSampleAllocator +interface mfidl.h IMFVideoSampleAllocatorCallback +interface mfidl.h IMFVideoSampleAllocatorEx +interface mfidl.h IMFVideoSampleAllocatorNotify +interface mfidl.h IMFVideoSampleAllocatorNotifyEx +interface mfidl.h IMFWorkQueueServices +interface mfidl.h IMFWorkQueueServicesEx +interface mfmediacapture.h IAdvancedMediaCapture +interface mfmediacapture.h IAdvancedMediaCaptureInitializationSettings +interface mfmediacapture.h IAdvancedMediaCaptureSettings +interface mfmediaengine.h IMFBufferListNotify +interface mfmediaengine.h IMFCdmSuspendNotify +interface mfmediaengine.h IMFMediaEngine +interface mfmediaengine.h IMFMediaEngineClassFactory +interface mfmediaengine.h IMFMediaEngineClassFactory2 +interface mfmediaengine.h IMFMediaEngineClassFactoryEx +interface mfmediaengine.h IMFMediaEngineEME +interface mfmediaengine.h IMFMediaEngineEx +interface mfmediaengine.h IMFMediaEngineExtension +interface mfmediaengine.h IMFMediaEngineNeedKeyNotify +interface mfmediaengine.h IMFMediaEngineNotify +interface mfmediaengine.h IMFMediaEngineOPMInfo +interface mfmediaengine.h IMFMediaEngineProtectedContent +interface mfmediaengine.h IMFMediaEngineSrcElements +interface mfmediaengine.h IMFMediaEngineSrcElementsEx +interface mfmediaengine.h IMFMediaEngineSupportsSourceTransfer +interface mfmediaengine.h IMFMediaEngineWebSupport +interface mfmediaengine.h IMFMediaError +interface mfmediaengine.h IMFMediaKeys +interface mfmediaengine.h IMFMediaKeySession +interface mfmediaengine.h IMFMediaKeySessionNotify +interface mfmediaengine.h IMFMediaSourceExtension +interface mfmediaengine.h IMFMediaSourceExtensionNotify +interface mfmediaengine.h IMFMediaTimeRange +interface mfmediaengine.h IMFSourceBuffer +interface mfmediaengine.h IMFSourceBufferList +interface mfmediaengine.h IMFSourceBufferNotify +interface mfmediaengine.h IMFTimedText +interface mfmediaengine.h IMFTimedTextBinary +interface mfmediaengine.h IMFTimedTextCue +interface mfmediaengine.h IMFTimedTextFormattedText +interface mfmediaengine.h IMFTimedTextNotify +interface mfmediaengine.h IMFTimedTextRegion +interface mfmediaengine.h IMFTimedTextStyle +interface mfmediaengine.h IMFTimedTextTrack +interface mfmediaengine.h IMFTimedTextTrackList +interface mfmp2dlna.h IMFDLNASinkInit +interface mfobjects.h IMF2DBuffer +interface mfobjects.h IMF2DBuffer2 +interface mfobjects.h IMFActivate +interface mfobjects.h IMFAsyncCallback +interface mfobjects.h IMFAsyncCallbackLogging +interface mfobjects.h IMFAsyncResult +interface mfobjects.h IMFAttributes +interface mfobjects.h IMFAudioMediaType +interface mfobjects.h IMFByteStream +interface mfobjects.h IMFByteStreamProxyClassFactory +interface mfobjects.h IMFCollection +interface mfobjects.h IMFDXGIBuffer +interface mfobjects.h IMFDXGIDeviceManager +interface mfobjects.h IMFMediaBuffer +interface mfobjects.h IMFMediaEvent +interface mfobjects.h IMFMediaEventGenerator +interface mfobjects.h IMFMediaEventQueue +interface mfobjects.h IMFMediaType +interface mfobjects.h IMFMuxStreamAttributesManager +interface mfobjects.h IMFMuxStreamMediaTypeManager +interface mfobjects.h IMFMuxStreamSampleManager +interface mfobjects.h IMFPluginControl +interface mfobjects.h IMFPluginControl2 +interface mfobjects.h IMFRemoteAsyncCallback +interface mfobjects.h IMFSample +interface mfobjects.h IMFSampleOutputStream +interface mfobjects.h IMFVideoMediaType +interface mfplay.h IMFPMediaItem +interface mfplay.h IMFPMediaPlayer +interface mfplay.h IMFPMediaPlayerCallback +interface mfreadwrite.h IMFReadWriteClassFactory +interface mfreadwrite.h IMFSinkWriter +interface mfreadwrite.h IMFSinkWriterCallback +interface mfreadwrite.h IMFSinkWriterCallback2 +interface mfreadwrite.h IMFSinkWriterEncoderConfig +interface mfreadwrite.h IMFSinkWriterEx +interface mfreadwrite.h IMFSourceReader +interface mfreadwrite.h IMFSourceReaderCallback +interface mfreadwrite.h IMFSourceReaderCallback2 +interface mfreadwrite.h IMFSourceReaderEx +interface mfsharingengine.h IMFImageSharingEngine +interface mfsharingengine.h IMFImageSharingEngineClassFactory +interface mfsharingengine.h IMFMediaSharingEngine +interface mfsharingengine.h IMFMediaSharingEngineClassFactory +interface mfsharingengine.h IMFSharingEngineClassFactory +interface mfsharingengine.h IPlayToControl +interface mfsharingengine.h IPlayToControlWithCapabilities +interface mfsharingengine.h IPlayToSourceClassFactory +interface mfspatialaudio.h IMFSpatialAudioObjectBuffer +interface mfspatialaudio.h IMFSpatialAudioSample +interface mftransform.h IMFTransform +interface wmcodecdsp.h IWMCodecLeakyBucket +interface wmcodecdsp.h IWMCodecOutputTimestamp +interface wmcodecdsp.h IWMCodecPrivateData +interface wmcodecdsp.h IWMCodecProps +interface wmcodecdsp.h IWMCodecStrings +interface wmcodecdsp.h IWMColorConvProps +interface wmcodecdsp.h IWMResamplerProps +interface wmcodecdsp.h IWMResizerProps +interface wmcodecdsp.h IWMSampleExtensionSupport +interface wmcodecdsp.h IWMVideoDecoderHurryup +interface wmcodecdsp.h IWMVideoDecoderReconBuffer +interface wmcodecdsp.h IWMVideoForceKeyFrame +interface wmcontainer.h IMFASFContentInfo +interface wmcontainer.h IMFASFIndexer +interface wmcontainer.h IMFASFMultiplexer +interface wmcontainer.h IMFASFMutualExclusion +interface wmcontainer.h IMFASFProfile +interface wmcontainer.h IMFASFSplitter +interface wmcontainer.h IMFASFStreamConfig +interface wmcontainer.h IMFASFStreamPrioritization +interface wmcontainer.h IMFASFStreamSelector +interface wmcontainer.h IMFDRMNetHelper + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Macros +function mfapi.h DEFINE_MEDIATYPE_GUID +function mfplay.h MFP_GET_ACQUIRE_USER_CREDENTIAL_EVENT +function mfplay.h MFP_GET_ERROR_EVENT +function mfplay.h MFP_GET_FRAME_STEP_EVENT +function mfplay.h MFP_GET_MEDIAITEM_CLEARED_EVENT +function mfplay.h MFP_GET_MEDIAITEM_CREATED_EVENT +function mfplay.h MFP_GET_MEDIAITEM_SET_EVENT +function mfplay.h MFP_GET_MF_EVENT +function mfplay.h MFP_GET_PAUSE_EVENT +function mfplay.h MFP_GET_PLAY_EVENT +function mfplay.h MFP_GET_PLAYBACK_ENDED_EVENT +function mfplay.h MFP_GET_POSITION_SET_EVENT +function mfplay.h MFP_GET_RATE_SET_EVENT +function mfplay.h MFP_GET_STOP_EVENT + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Structures +struct dxva2api.h DXVA2_AES_CTR_IV +struct dxva2api.h DXVA2_AYUVSample16 +struct dxva2api.h DXVA2_AYUVSample8 +struct dxva2api.h DXVA2_ConfigPictureDecode +struct dxva2api.h DXVA2_DecodeBufferDesc +struct dxva2api.h DXVA2_DecodeExecuteParams +struct dxva2api.h DXVA2_DecodeExtensionData +struct dxva2api.h DXVA2_ExtendedFormat +struct dxva2api.h DXVA2_FilterValues +struct dxva2api.h DXVA2_Fixed32 +struct dxva2api.h DXVA2_Frequency +struct dxva2api.h DXVA2_ProcAmpValues +struct dxva2api.h DXVA2_ValueRange +struct dxva2api.h DXVA2_VideoDesc +struct dxva2api.h DXVA2_VideoProcessBltParams +struct dxva2api.h DXVA2_VideoProcessorCaps +struct dxva2api.h DXVA2_VideoSample +struct evr.h MFVideoNormalizedRect +struct evr9.h MFVideoAlphaBitmap +struct evr9.h MFVideoAlphaBitmapParams +struct mfapi.h DigitalWindowSetting +struct mfapi.h MF_FLOAT2 +struct mfapi.h MF_FLOAT3 +struct mfapi.h MF_QUATERNION +struct mfapi.h MFASYNCRESULT +struct mfapi.h MFCameraExtrinsic_CalibratedTransform +struct mfapi.h MFCameraExtrinsics +struct mfapi.h MFCameraIntrinsic_DistortionModel +struct mfapi.h MFCameraIntrinsic_PinholeCameraModel +struct mfapi.h MFFOLDDOWN_MATRIX +struct mfapi.h MFPinholeCameraIntrinsic_IntrinsicModel +struct mfapi.h MFPinholeCameraIntrinsics +struct mfapi.h MT_ARBITRARY_HEADER +struct mfapi.h MT_CUSTOM_VIDEO_PRIMARIES +struct mfapi.h ROI_AREA +struct mfidl.h ASF_FLAT_PICTURE +struct mfidl.h ASF_FLAT_SYNCHRONISED_LYRICS +struct mfidl.h MF_BYTE_STREAM_CACHE_RANGE +struct mfidl.h MF_LEAKY_BUCKET_PAIR +struct mfidl.h MF_TRANSCODE_SINK_INFO +struct mfidl.h MFBYTESTREAM_BUFFERING_PARAMS +struct mfidl.h MFCLOCK_PROPERTIES +struct mfidl.h MFCONTENTPROTECTIONDEVICE_INPUT_DATA +struct mfidl.h MFCONTENTPROTECTIONDEVICE_OUTPUT_DATA +struct mfidl.h MFCONTENTPROTECTIONDEVICE_REALTIMECLIENT_DATA +struct mfidl.h MFINPUTTRUSTAUTHORITY_ACCESS_ACTION +struct mfidl.h MFINPUTTRUSTAUTHORITY_ACCESS_PARAMS +struct mfidl.h MFNetCredentialManagerGetParam +struct mfidl.h MFRR_COMPONENT_HASH_INFO +struct mfidl.h MFRR_COMPONENTS +struct mfidl.h MFT_REGISTRATION_INFO +struct mfidl.h MFTOPONODE_ATTRIBUTE_UPDATE +struct mfidl.h SENSORPROFILEID +struct mfmp2dlna.h MFMPEG2DLNASINKSTATS +struct mfobjects.h MFARGB +struct mfobjects.h MFAYUVSample +struct mfobjects.h MFOffset +struct mfobjects.h MFRatio +struct mfobjects.h MFT_REGISTER_TYPE_INFO +struct mfobjects.h MFVideoArea +struct mfobjects.h MFVideoCompressedInfo +struct mfobjects.h MFVIDEOFORMAT +struct mfobjects.h MFVideoInfo +struct mfobjects.h MFVideoSurfaceInfo +struct mfplay.h MFP_ACQUIRE_USER_CREDENTIAL_EVENT +struct mfplay.h MFP_ERROR_EVENT +struct mfplay.h MFP_EVENT_HEADER +struct mfplay.h MFP_FRAME_STEP_EVENT +struct mfplay.h MFP_MEDIAITEM_CLEARED_EVENT +struct mfplay.h MFP_MEDIAITEM_CREATED_EVENT +struct mfplay.h MFP_MEDIAITEM_SET_EVENT +struct mfplay.h MFP_MF_EVENT +struct mfplay.h MFP_PAUSE_EVENT +struct mfplay.h MFP_PLAY_EVENT +struct mfplay.h MFP_PLAYBACK_ENDED_EVENT +struct mfplay.h MFP_POSITION_SET_EVENT +struct mfplay.h MFP_RATE_SET_EVENT +struct mfplay.h MFP_STOP_EVENT +struct mfreadwrite.h MF_SINK_WRITER_STATISTICS +struct mfsharingengine.h DEVICE_INFO +struct mftransform.h MFT_INPUT_STREAM_INFO +struct mftransform.h MFT_OUTPUT_DATA_BUFFER +struct mftransform.h MFT_OUTPUT_STREAM_INFO +struct mftransform.h STREAM_MEDIUM +struct wmcodecdsp.h AecQualityMetrics_Struct +struct wmcontainer.h ASF_INDEX_DESCRIPTOR +struct wmcontainer.h ASF_INDEX_IDENTIFIER +struct wmcontainer.h ASF_MUX_STATISTICS + +# Desktop App Technologies > Audio and Video > Microsoft Media Foundation > Media Foundation Programming Reference > Media Foundation Unions +struct dxvahd.h DXVAHD_COLOR =dxvahd_color_rgba +struct mfobjects.h MFPaletteEntry Union =mfpaletteentry + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming guide > DirectShow and Windows Media > DirectShow QASF reference +struct evcode.h Am_wmt_event_data structure =am_wmt_event_data + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming reference > Functions +function wmsdkidl.h wmcreatebackuprestorer +function wmsdkidl.h wmcreatedeviceregistration +function wmsdkidl.h wmcreatedrmtranscryptionmanager +function wmsdkidl.h wmcreatedrmtranscryptor +function wmsdkidl.h wmcreateeditor +function wmsdkidl.h wmcreateindexer +function wmsdkidl.h wmcreatelicenserevocationagent +function wmsdkidl.h wmcreateprofilemanager +function wmsdkidl.h wmcreatereader +function wmsdkidl.h wmcreatesyncreader +function wmsdkidl.h wmcreatewriter +function wmsdkidl.h wmcreatewriterfilesink +function wmsdkidl.h wmcreatewriternetworksink +function wmsdkidl.h wmcreatewriterpushsink +function wmsdkidl.h Wmiscontentprotected +function wmsdkvalidate.h Wmcheckurlextension +function wmsdkvalidate.h Wmcheckurlscheme +function wmsdkvalidate.h Wmisavailableoffline +function wmsdkvalidate.h Wmvalidatedata +function wmsecure.h wmcreatesecurechannel +function wmsecure.h wmcreatesecurechannel_certified +function wmsecure.h wmcreatesecurechannel_certified_des +function wmsecure.h wmcreatesecurechannel_des + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming reference > Interfaces > iwmprofile interface +function wmsdkidl.h iwmprofile::addmutualexclusion +function wmsdkidl.h iwmprofile::addstream +function wmsdkidl.h iwmprofile::createnewmutualexclusion +function wmsdkidl.h iwmprofile::createnewstream +function wmsdkidl.h iwmprofile::getdescription +function wmsdkidl.h iwmprofile::getmutualexclusion +function wmsdkidl.h iwmprofile::getmutualexclusioncount +function wmsdkidl.h iwmprofile::getname +function wmsdkidl.h iwmprofile::getstream +function wmsdkidl.h iwmprofile::getstreambynumber +function wmsdkidl.h iwmprofile::getstreamcount +function wmsdkidl.h iwmprofile::getversion +function wmsdkidl.h iwmprofile::reconfigstream +function wmsdkidl.h iwmprofile::removemutualexclusion +function wmsdkidl.h iwmprofile::removestream +function wmsdkidl.h iwmprofile::removestreambynumber +function wmsdkidl.h iwmprofile::setdescription +function wmsdkidl.h iwmprofile::setname + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming reference > Interfaces +interface wmdxva.h iwmcodecamvideoaccelerator interface =iwmcodecamvideoaccelerator +interface wmdxva.h iwmplayertimestamphook interface =iwmplayertimestamphook +interface wmnetsourcecreator.h Insnetsourcecreator interface =insnetsourcecreator +interface wmsbuffer.h Inssbuffer interface =inssbuffer +interface wmsbuffer.h Inssbuffer2 interface =inssbuffer2 +interface wmsbuffer.h Inssbuffer3 interface =inssbuffer3 +interface wmsbuffer.h Inssbuffer4 interface =inssbuffer4 +interface wmsbuffer.h iwmsbufferallocator interface =iwmsbufferallocator +interface wmsdkidl.h iwmaddressaccess interface =iwmaddressaccess +interface wmsdkidl.h iwmaddressaccess2 interface =iwmaddressaccess2 +interface wmsdkidl.h iwmbackuprestoreprops interface =iwmbackuprestoreprops +interface wmsdkidl.h iwmbandwidthsharing interface =iwmbandwidthsharing +interface wmsdkidl.h iwmclientconnections interface =iwmclientconnections +interface wmsdkidl.h iwmclientconnections2 interface =iwmclientconnections2 +interface wmsdkidl.h iwmcodecinfo interface =iwmcodecinfo +interface wmsdkidl.h iwmcodecinfo2 interface =iwmcodecinfo2 +interface wmsdkidl.h iwmcodecinfo3 interface =iwmcodecinfo3 +interface wmsdkidl.h iwmcredentialcallback interface =iwmcredentialcallback +interface wmsdkidl.h iwmdeviceregistration interface =iwmdeviceregistration +interface wmsdkidl.h iwmdrmeditor interface =iwmdrmeditor +interface wmsdkidl.h iwmdrmmessageparser interface =iwmdrmmessageparser +interface wmsdkidl.h iwmdrmreader interface =iwmdrmreader +interface wmsdkidl.h iwmdrmreader2 interface =iwmdrmreader2 +interface wmsdkidl.h iwmdrmreader3 interface =iwmdrmreader3 +interface wmsdkidl.h iwmdrmtranscryptor interface =iwmdrmtranscryptor +interface wmsdkidl.h iwmdrmwriter interface =iwmdrmwriter +interface wmsdkidl.h iwmdrmwriter2 interface =iwmdrmwriter2 +interface wmsdkidl.h iwmdrmwriter3 interface =iwmdrmwriter3 +interface wmsdkidl.h iwmheaderinfo interface =iwmheaderinfo +interface wmsdkidl.h iwmheaderinfo2 interface =iwmheaderinfo2 +interface wmsdkidl.h iwmheaderinfo3 interface =iwmheaderinfo3 +interface wmsdkidl.h iwmimageinfo interface =iwmimageinfo +interface wmsdkidl.h iwmindexer interface =iwmindexer +interface wmsdkidl.h iwmindexer2 interface =iwmindexer2 +interface wmsdkidl.h iwminputmediaprops interface =iwminputmediaprops +interface wmsdkidl.h iwmistreamprops interface =iwmistreamprops +interface wmsdkidl.h iwmlanguagelist interface =iwmlanguagelist +interface wmsdkidl.h iwmlicensebackup interface =iwmlicensebackup +interface wmsdkidl.h iwmlicenserestore interface =iwmlicenserestore +interface wmsdkidl.h iwmlicenserevocationagent interface =iwmlicenserevocationagent +interface wmsdkidl.h iwmmediaprops interface =iwmmediaprops +interface wmsdkidl.h iwmmetadataeditor interface =iwmmetadataeditor +interface wmsdkidl.h iwmmetadataeditor2 interface =iwmmetadataeditor2 +interface wmsdkidl.h iwmmutualexclusion interface =iwmmutualexclusion +interface wmsdkidl.h iwmmutualexclusion2 interface =iwmmutualexclusion2 +interface wmsdkidl.h iwmoutputmediaprops interface =iwmoutputmediaprops +interface wmsdkidl.h iwmpacketsize interface =iwmpacketsize +interface wmsdkidl.h iwmpacketsize2 interface =iwmpacketsize2 +interface wmsdkidl.h iwmplayerhook interface =iwmplayerhook +interface wmsdkidl.h iwmprofile2 interface =iwmprofile2 +interface wmsdkidl.h iwmprofile3 interface =iwmprofile3 +interface wmsdkidl.h iwmprofilemanager interface =iwmprofilemanager +interface wmsdkidl.h iwmprofilemanager2 interface =iwmprofilemanager2 +interface wmsdkidl.h iwmprofilemanagerlanguage interface =iwmprofilemanagerlanguage +interface wmsdkidl.h iwmpropertyvault interface =iwmpropertyvault +interface wmsdkidl.h iwmproximitydetection interface =iwmproximitydetection +interface wmsdkidl.h iwmreader interface =iwmreader +interface wmsdkidl.h iwmreaderaccelerator interface =iwmreaderaccelerator +interface wmsdkidl.h iwmreaderadvanced interface =iwmreaderadvanced +interface wmsdkidl.h iwmreaderadvanced2 interface =iwmreaderadvanced2 +interface wmsdkidl.h iwmreaderadvanced3 interface =iwmreaderadvanced3 +interface wmsdkidl.h iwmreaderadvanced4 interface =iwmreaderadvanced4 +interface wmsdkidl.h iwmreaderadvanced5 interface =iwmreaderadvanced5 +interface wmsdkidl.h iwmreaderadvanced6 interface =iwmreaderadvanced6 +interface wmsdkidl.h iwmreaderallocatorex interface =iwmreaderallocatorex +interface wmsdkidl.h iwmreadercallback interface =iwmreadercallback +interface wmsdkidl.h iwmreadercallbackadvanced interface =iwmreadercallbackadvanced +interface wmsdkidl.h iwmreadernetworkconfig interface =iwmreadernetworkconfig +interface wmsdkidl.h iwmreadernetworkconfig2 interface =iwmreadernetworkconfig2 +interface wmsdkidl.h iwmreaderplaylistburn interface =iwmreaderplaylistburn +interface wmsdkidl.h iwmreaderstreamclock interface =iwmreaderstreamclock +interface wmsdkidl.h iwmreadertimecode interface =iwmreadertimecode +interface wmsdkidl.h iwmreadertypenegotiation interface =iwmreadertypenegotiation +interface wmsdkidl.h iwmregistercallback interface =iwmregistercallback +interface wmsdkidl.h iwmregistereddevice interface =iwmregistereddevice +interface wmsdkidl.h iwmstatuscallback interface =iwmstatuscallback +interface wmsdkidl.h iwmstreamconfig interface =iwmstreamconfig +interface wmsdkidl.h iwmstreamconfig2 interface =iwmstreamconfig2 +interface wmsdkidl.h iwmstreamconfig3 interface =iwmstreamconfig3 +interface wmsdkidl.h iwmstreamlist interface =iwmstreamlist +interface wmsdkidl.h iwmstreamprioritization interface =iwmstreamprioritization +interface wmsdkidl.h iwmsyncreader interface =iwmsyncreader +interface wmsdkidl.h iwmsyncreader2 interface =iwmsyncreader2 +interface wmsdkidl.h iwmvideomediaprops interface =iwmvideomediaprops +interface wmsdkidl.h iwmwatermarkinfo interface =iwmwatermarkinfo +interface wmsdkidl.h iwmwriter interface =iwmwriter +interface wmsdkidl.h iwmwriteradvanced interface =iwmwriteradvanced +interface wmsdkidl.h iwmwriteradvanced2 interface =iwmwriteradvanced2 +interface wmsdkidl.h iwmwriteradvanced3 interface =iwmwriteradvanced3 +interface wmsdkidl.h iwmwriterfilesink interface =iwmwriterfilesink +interface wmsdkidl.h iwmwriterfilesink2 interface =iwmwriterfilesink2 +interface wmsdkidl.h iwmwriterfilesink3 interface =iwmwriterfilesink3 +interface wmsdkidl.h iwmwriternetworksink interface =iwmwriternetworksink +interface wmsdkidl.h iwmwriterpostview interface =iwmwriterpostview +interface wmsdkidl.h iwmwriterpostviewcallback interface =iwmwriterpostviewcallback +interface wmsdkidl.h iwmwriterpreprocess interface =iwmwriterpreprocess +interface wmsdkidl.h iwmwriterpushsink interface =iwmwriterpushsink +interface wmsdkidl.h iwmwritersink interface =iwmwritersink +interface wmsecure.h iwmauthorizer interface =iwmauthorizer +interface wmsecure.h iwmgetsecurechannel interface =iwmgetsecurechannel +interface wmsecure.h iwmsecurechannel interface =iwmsecurechannel +interface wmsinternaladminnetsource.h iwmsinternaladminnetsource interface =iwmsinternaladminnetsource +interface wmsinternaladminnetsource.h iwmsinternaladminnetsource2 interface =iwmsinternaladminnetsource2 +interface wmsinternaladminnetsource.h iwmsinternaladminnetsource3 interface =iwmsinternaladminnetsource3 + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming reference > Structures +struct wmsdkidl.h Drm_copy_opl +struct wmsdkidl.h Drm_minimum_output_protection_levels +struct wmsdkidl.h Drm_opl_output_ids +struct wmsdkidl.h Drm_play_opl +struct wmsdkidl.h Drm_val16 +struct wmsdkidl.h Drm_video_output_protection =drm_output_protection +struct wmsdkidl.h Drm_video_output_protection_ids +struct wmsdkidl.h wm_address_accessentry +struct wmsdkidl.h wm_client_properties +struct wmsdkidl.h wm_client_properties_ex +struct wmsdkidl.h wm_leaky_bucket_pair +struct wmsdkidl.h wm_media_type +struct wmsdkidl.h wm_picture +struct wmsdkidl.h wm_port_number_range +struct wmsdkidl.h wm_reader_clientinfo +struct wmsdkidl.h wm_reader_statistics +struct wmsdkidl.h wm_stream_priority_record +struct wmsdkidl.h wm_stream_type_info +struct wmsdkidl.h wm_synchronised_lyrics +struct wmsdkidl.h wm_user_text +struct wmsdkidl.h wm_user_web_url +struct wmsdkidl.h wm_writer_statistics +struct wmsdkidl.h wm_writer_statistics_ex +struct wmsdkidl.h wmdrm_import_init_struct +struct wmsdkidl.h Wmmpeg2videoinfo +struct wmsdkidl.h Wmscriptformat +struct wmsdkidl.h wmt_buffer_segment +struct wmsdkidl.h wmt_colorspaceinfo_extension_data +struct wmsdkidl.h wmt_filesink_data_unit +struct wmsdkidl.h wmt_payload_fragment +struct wmsdkidl.h wmt_timecode_extension_data +struct wmsdkidl.h wmt_videoimage_sample +struct wmsdkidl.h wmt_videoimage_sample2 +struct wmsdkidl.h wmt_watermark_entry +struct wmsdkidl.h wmt_webstream_format +struct wmsdkidl.h wmt_webstream_sample_header +struct wmsdkidl.h Wmvideoinfoheader +struct wmsdkidl.h Wmvideoinfoheader2 + +# Desktop App Technologies > Audio and Video > Windows Media Format 11 SDK > Programming reference > Enumeration types +enum wmsdkidl.h wm_aetype +enum wmsdkidl.h wmt_attr_datatype +enum wmsdkidl.h wmt_attr_imagetype +enum wmsdkidl.h wmt_codec_info_type +enum wmsdkidl.h wmt_credential_flags +enum wmsdkidl.h wmt_drmla_trust +enum wmsdkidl.h wmt_filesink_mode +enum wmsdkidl.h wmt_image_type +enum wmsdkidl.h wmt_index_type +enum wmsdkidl.h wmt_indexer_type +enum wmsdkidl.h wmt_musicspeech_class_mode +enum wmsdkidl.h wmt_net_protocol +enum wmsdkidl.h wmt_offset_format +enum wmsdkidl.h wmt_play_mode +enum wmsdkidl.h wmt_proxy_settings +enum wmsdkidl.h wmt_rights +enum wmsdkidl.h wmt_status +enum wmsdkidl.h wmt_storage_format +enum wmsdkidl.h wmt_stream_selection +enum wmsdkidl.h wmt_transport_type +enum wmsdkidl.h wmt_version +enum wmsdkidl.h wmt_watermark_entry_type +enum wmsinternaladminnetsource.h Netsource_urlcredpolicy_settings + +# Desktop App Technologies > Audio and Video > Media Streaming API > Interfaces > IMediaRenderer +function windows.media.streaming.h add_RenderingParametersUpdate Method =windows-media-streaming-imediarenderer-add_renderingparametersupdate +function windows.media.streaming.h add_TransportParametersUpdate Method =windows-media-streaming-imediarenderer-add_transportparametersupdate + +# Desktop App Technologies > Audio and Video > Media Streaming API > Interfaces +interface windows.media.streaming.h IActiveBasicDevice =windows-media-streaming-iactivebasicdevice +interface windows.media.streaming.h IActiveBasicDeviceStatics =windows-media-streaming-iactivebasicdevicestatics +interface windows.media.streaming.h IDevicePair =windows-media-streaming-idevicepair + +# Desktop App Technologies > Audio and Video > Windows Multimedia > Multimedia Reference > Multimedia Functions +callback msacm.h acmDriverProc +callback msacm.h acmFilterChooseHookProc +callback msacm.h ACMFILTERENUMCB +callback msacm.h ACMFILTERTAGENUMCB +callback msacm.h acmFormatChooseHookProc +callback msacm.h ACMFORMATENUMCB +callback msacm.h ACMFORMATTAGENUMCB +callback vfw.h capControlCallback +callback vfw.h capErrorCallback =caperrorcallbacka +callback vfw.h capStatusCallback =capstatuscallbacka +callback vfw.h capVideoStreamCallback =capvideocallback +callback vfw.h capWaveStreamCallback =capwavecallback +callback vfw.h capYieldCallback +function joystickapi.h joyConfigChanged +function mmddk.h mmGetCurrentTask +function mmddk.h mmTaskBlock +function mmddk.h mmTaskCreate +function mmddk.h mmTaskSignal +function mmddk.h mmTaskYield +function mmiscapi.h sndOpenSound +function msacm.h acmDriverAdd +function msacm.h acmDriverClose +function msacm.h acmDriverDetails +function msacm.h acmDriverEnum +function msacm.h acmDriverID +function msacm.h acmDriverMessage +function msacm.h acmDriverOpen +function msacm.h acmDriverPriority +function msacm.h acmDriverRemove +function msacm.h acmFilterChoose +function msacm.h acmFilterDetails +function msacm.h acmFilterEnum +function msacm.h acmFilterTagDetails +function msacm.h acmFilterTagEnum +function msacm.h acmFormatChoose +function msacm.h acmFormatDetails +function msacm.h acmFormatEnum +function msacm.h acmFormatSuggest +function msacm.h acmFormatTagDetails +function msacm.h acmFormatTagEnum +function msacm.h acmGetVersion +function msacm.h acmMetrics +function msacm.h acmStreamClose +function msacm.h acmStreamConvert +function msacm.h acmStreamMessage +function msacm.h acmStreamOpen +function msacm.h acmStreamPrepareHeader +function msacm.h acmStreamReset +function msacm.h acmStreamSize +function msacm.h acmStreamUnprepareHeader +function timeapi.h timeBeginPeriod +function timeapi.h timeEndPeriod +function timeapi.h timeGetDevCaps +function timeapi.h timeGetSystemTime +function timeapi.h timeGetTime +function vfw.h AVIBuildFilter =avibuildfiltera +function vfw.h AVIClearClipboard +function vfw.h AVIFileAddRef +function vfw.h AVIFileCreateStream +function vfw.h AVIFileEndRecord +function vfw.h AVIFileExit +function vfw.h AVIFileGetStream +function vfw.h AVIFileInfo +function vfw.h AVIFileInit +function vfw.h AVIFileOpen +function vfw.h AVIFileReadData +function vfw.h AVIFileRelease +function vfw.h AVIFileWriteData +function vfw.h AVIGetFromClipboard +function vfw.h AVIMakeCompressedStream +function vfw.h AVIMakeFileFromStreams +function vfw.h AVIMakeStreamFromClipboard +function vfw.h AVIPutFileOnClipboard +function vfw.h AVISave =avisavea +function vfw.h AVISaveOptions +function vfw.h AVISaveOptionsFree +function vfw.h AVISaveV =avisaveva +function vfw.h AVIStreamAddRef +function vfw.h AVIStreamBeginStreaming +function vfw.h AVIStreamCreate +function vfw.h AVIStreamEndStreaming +function vfw.h AVIStreamFindSample +function vfw.h AVIStreamGetFrame +function vfw.h AVIStreamGetFrameClose +function vfw.h AVIStreamGetFrameOpen +function vfw.h AVIStreamInfo =avistreaminfoa +function vfw.h AVIStreamLength +function vfw.h AVIStreamOpenFromFile =avistreamopenfromfilea +function vfw.h AVIStreamRead +function vfw.h AVIStreamReadData +function vfw.h AVIStreamReadFormat +function vfw.h AVIStreamRelease +function vfw.h AVIStreamSampleToTime +function vfw.h AVIStreamSetFormat +function vfw.h AVIStreamStart +function vfw.h AVIStreamTimeToSample +function vfw.h AVIStreamWrite +function vfw.h AVIStreamWriteData +function vfw.h capCreateCaptureWindow =capcreatecapturewindowa +function vfw.h capGetDriverDescription =capgetdriverdescriptiona +function vfw.h CreateEditableStream +function vfw.h DrawDibBegin +function vfw.h DrawDibChangePalette +function vfw.h DrawDibClose +function vfw.h DrawDibDraw +function vfw.h DrawDibEnd +function vfw.h DrawDibGetBuffer +function vfw.h DrawDibGetPalette +function vfw.h DrawDibOpen +function vfw.h DrawDibProfileDisplay +function vfw.h DrawDibRealize +function vfw.h DrawDibSetPalette +function vfw.h DrawDibStart +function vfw.h DrawDibStop +function vfw.h DrawDibTime +function vfw.h EditStreamClone +function vfw.h EditStreamCopy +function vfw.h EditStreamCut +function vfw.h EditStreamPaste +function vfw.h EditStreamSetInfo =editstreamsetinfoa +function vfw.h EditStreamSetName =editstreamsetnamea +function vfw.h GetOpenFileNamePreview =getopenfilenamepreviewa +function vfw.h GetSaveFileNamePreview =getsavefilenamepreviewa +function vfw.h ICClose +function vfw.h ICCompress +function vfw.h ICCompressorChoose +function vfw.h ICCompressorFree +function vfw.h ICDecompress +function vfw.h ICDecompressEx +function vfw.h ICDecompressExBegin +function vfw.h ICDecompressExQuery +function vfw.h ICDraw +function vfw.h ICDrawBegin +function vfw.h ICDrawSuggestFormat +function vfw.h ICGetDisplayFormat +function vfw.h ICGetInfo +function vfw.h ICImageCompress +function vfw.h ICImageDecompress +function vfw.h ICInfo +function vfw.h ICInstall +function vfw.h ICLocate +function vfw.h ICOpen +function vfw.h ICOpenFunction +function vfw.h ICRemove +function vfw.h ICSendMessage +function vfw.h ICSeqCompressFrame +function vfw.h ICSeqCompressFrameEnd +function vfw.h ICSeqCompressFrameStart +function vfw.h ICSetStatusProc +function vfw.h MCIWndCreate =mciwndcreatea +function vfw.h MCIWndRegisterClass +function vfw.h StretchDIB + +# Desktop App Technologies > Audio and Video > Windows Multimedia > Multimedia Reference > Multimedia Structures +struct digitalv.h MCI_DGV_CAPTURE_PARMS =mci_dgv_capture_parmsa +struct digitalv.h MCI_DGV_COPY_PARMS +struct digitalv.h MCI_DGV_CUE_PARMS +struct digitalv.h MCI_DGV_CUT_PARMS +struct digitalv.h MCI_DGV_DELETE_PARMS +struct digitalv.h MCI_DGV_FREEZE_PARMS =mci_dgv_rect_parms +struct digitalv.h MCI_DGV_INFO_PARMS =mci_dgv_info_parmsa +struct digitalv.h MCI_DGV_LIST_PARMS =mci_dgv_list_parmsa +struct digitalv.h MCI_DGV_MONITOR_PARMS +struct digitalv.h MCI_DGV_OPEN_PARMS =mci_dgv_open_parmsa +struct digitalv.h MCI_DGV_PASTE_PARMS +struct digitalv.h MCI_DGV_QUALITY_PARMS =mci_dgv_quality_parmsa +struct digitalv.h MCI_DGV_RECORD_PARMS +struct digitalv.h MCI_DGV_RECT_PARMS +struct digitalv.h MCI_DGV_RESERVE_PARMS =mci_dgv_reserve_parmsa +struct digitalv.h MCI_DGV_RESTORE_PARMS =mci_dgv_restore_parmsa +struct digitalv.h MCI_DGV_SAVE_PARMS =mci_dgv_save_parmsa +struct digitalv.h MCI_DGV_SET_PARMS +struct digitalv.h MCI_DGV_SETAUDIO_PARMS =mci_dgv_setaudio_parmsa +struct digitalv.h MCI_DGV_SETVIDEO_PARMS =mci_dgv_setvideo_parmsa +struct digitalv.h MCI_DGV_SIGNAL_PARMS +struct digitalv.h MCI_DGV_STATUS_PARMS =mci_dgv_status_parmsa +struct digitalv.h MCI_DGV_STEP_PARMS +struct digitalv.h MCI_DGV_UPDATE_PARMS +struct digitalv.h MCI_DGV_WINDOW_PARMS =mci_dgv_window_parmsa +struct mmeapi.h MIXERCAPS =mixercapsa +struct mmeapi.h MIXERCONTROL =mixercontrola +struct mmeapi.h MIXERCONTROLDETAILS =mixercontroldetails_listtexta +struct mmeapi.h MIXERLINE =mixerlinea +struct mmeapi.h MIXERLINECONTROLS =mixerlinecontrolsa +struct mmeapi.h WAVEFORMAT +struct mmreg.h WAVEFILTER +struct mmreg.h WAVEFORMATEXTENSIBLE +struct msacm.h ACMDRIVERDETAILS +struct msacm.h ACMFILTERCHOOSE +struct msacm.h ACMFILTERDETAILS +struct msacm.h ACMFILTERTAGDETAILS +struct msacm.h ACMFORMATCHOOSE +struct msacm.h ACMFORMATDETAILS +struct msacm.h ACMFORMATTAGDETAILS +struct msacm.h ACMSTREAMHEADER +struct timeapi.h TIMECAPS +struct vfw.h AVICOMPRESSOPTIONS +struct vfw.h AVIFILEINFO =avifileinfoa +struct vfw.h AVISTREAMINFO =avistreaminfoa +struct vfw.h CAPDRIVERCAPS +struct vfw.h CAPINFOCHUNK +struct vfw.h CAPSTATUS +struct vfw.h CAPTUREPARMS +struct vfw.h COMPVARS +struct vfw.h DRAWDIBTIME +struct vfw.h ICCOMPRESS +struct vfw.h ICCOMPRESSFRAMES +struct vfw.h ICDECOMPRESS +struct vfw.h ICDECOMPRESSEX +struct vfw.h ICDRAW +struct vfw.h ICDRAWBEGIN +struct vfw.h ICDRAWSUGGEST +struct vfw.h ICINFO +struct vfw.h ICOPEN +struct vfw.h ICSETSTATUSPROC +struct vfw.h VIDEOHDR + +# Desktop App Technologies > Audio and Video > Windows Multimedia > Multimedia Reference > Multimedia Macros +function vfw.h AVIStreamDataSize +function vfw.h AVIStreamEnd +function vfw.h AVIStreamEndTime +function vfw.h AVIStreamFormatSize +function vfw.h AVIStreamIsKeyFrame +function vfw.h AVIStreamLengthTime +function vfw.h AVIStreamNearestKeyFrame +function vfw.h AVIStreamNearestKeyFrameTime +function vfw.h AVIStreamNearestSample +function vfw.h AVIStreamNearestSampleTime +function vfw.h AVIStreamNextKeyFrame +function vfw.h AVIStreamNextKeyFrameTime +function vfw.h AVIStreamNextSample +function vfw.h AVIStreamNextSampleTime +function vfw.h AVIStreamPrevKeyFrame +function vfw.h AVIStreamPrevKeyFrameTime +function vfw.h AVIStreamPrevSample +function vfw.h AVIStreamPrevSampleTime +function vfw.h AVIStreamSampleSize +function vfw.h AVIStreamSampleToSample +function vfw.h AVIStreamStartTime +function vfw.h capCaptureAbort +function vfw.h capCaptureGetSetup +function vfw.h capCaptureSequence +function vfw.h capCaptureSequenceNoFile +function vfw.h capCaptureSetSetup +function vfw.h capCaptureSingleFrame +function vfw.h capCaptureSingleFrameClose +function vfw.h capCaptureSingleFrameOpen +function vfw.h capCaptureStop +function vfw.h capDlgVideoCompression +function vfw.h capDlgVideoDisplay +function vfw.h capDlgVideoFormat +function vfw.h capDlgVideoSource +function vfw.h capDriverConnect +function vfw.h capDriverDisconnect +function vfw.h capDriverGetCaps +function vfw.h capDriverGetName +function vfw.h capDriverGetVersion +function vfw.h capEditCopy +function vfw.h capFileAlloc +function vfw.h capFileGetCaptureFile +function vfw.h capFileSaveAs +function vfw.h capFileSaveDIB +function vfw.h capFileSetCaptureFile +function vfw.h capFileSetInfoChunk +function vfw.h capGetAudioFormat +function vfw.h capGetAudioFormatSize +function vfw.h capGetMCIDeviceName +function vfw.h capGetStatus +function vfw.h capGetUserData +function vfw.h capGetVideoFormat +function vfw.h capGetVideoFormatSize +function vfw.h capGrabFrame +function vfw.h capGrabFrameNoStop +function vfw.h capOverlay +function vfw.h capPaletteAuto +function vfw.h capPaletteManual +function vfw.h capPaletteOpen +function vfw.h capPalettePaste +function vfw.h capPaletteSave +function vfw.h capPreview +function vfw.h capPreviewRate +function vfw.h capPreviewScale +function vfw.h capSetAudioFormat +function vfw.h capSetCallbackOnCapControl +function vfw.h capSetCallbackOnError +function vfw.h capSetCallbackOnFrame +function vfw.h capSetCallbackOnStatus +function vfw.h capSetCallbackOnVideoStream +function vfw.h capSetCallbackOnWaveStream +function vfw.h capSetCallbackOnYield +function vfw.h capSetMCIDeviceName +function vfw.h capSetScrollPos +function vfw.h capSetUserData +function vfw.h capSetVideoFormat +function vfw.h DrawDibUpdate +function vfw.h ICAbout +function vfw.h ICCompressBegin +function vfw.h ICCompressEnd +function vfw.h ICCompressGetFormat +function vfw.h ICCompressGetFormatSize +function vfw.h ICCompressGetSize +function vfw.h ICCompressQuery +function vfw.h ICConfigure +function vfw.h ICDecompressBegin +function vfw.h ICDecompressEnd +function vfw.h ICDecompressExEnd +function vfw.h ICDecompressGetFormat +function vfw.h ICDecompressGetFormatSize +function vfw.h ICDecompressGetPalette +function vfw.h ICDecompressOpen +function vfw.h ICDecompressQuery +function vfw.h ICDecompressSetPalette +function vfw.h ICDrawChangePalette +function vfw.h ICDrawEnd +function vfw.h ICDrawFlush +function vfw.h ICDrawGetTime +function vfw.h ICDrawOpen +function vfw.h ICDrawQuery +function vfw.h ICDrawRealize +function vfw.h ICDrawRenderBuffer +function vfw.h ICDrawSetTime +function vfw.h ICDrawStart +function vfw.h ICDrawStartPlay +function vfw.h ICDrawStop +function vfw.h ICDrawStopPlay +function vfw.h ICDrawWindow +function vfw.h ICGetBuffersWanted +function vfw.h ICGetDefaultKeyFrameRate +function vfw.h ICGetDefaultQuality +function vfw.h ICGetState +function vfw.h ICGetStateSize +function vfw.h ICQueryAbout +function vfw.h ICQueryConfigure +function vfw.h ICSetState +function vfw.h MCIWndCanConfig +function vfw.h MCIWndCanEject +function vfw.h MCIWndCanPlay +function vfw.h MCIWndCanRecord +function vfw.h MCIWndCanSave +function vfw.h MCIWndCanWindow +function vfw.h MCIWndChangeStyles +function vfw.h MCIWndClose +function vfw.h MCIWndDestroy +function vfw.h MCIWndEject +function vfw.h MCIWndEnd +function vfw.h MCIWndGetActiveTimer +function vfw.h MCIWndGetAlias +function vfw.h MCIWndGetDest +function vfw.h MCIWndGetDevice +function vfw.h MCIWndGetDeviceID +function vfw.h MCIWndGetEnd +function vfw.h MCIWndGetError +function vfw.h MCIWndGetFileName +function vfw.h MCIWndGetInactiveTimer +function vfw.h MCIWndGetLength +function vfw.h MCIWndGetMode +function vfw.h MCIWndGetPalette +function vfw.h MCIWndGetPosition +function vfw.h MCIWndGetPositionString +function vfw.h MCIWndGetRepeat +function vfw.h MCIWndGetSource +function vfw.h MCIWndGetSpeed +function vfw.h MCIWndGetStart +function vfw.h MCIWndGetStyles +function vfw.h MCIWndGetTimeFormat +function vfw.h MCIWndGetVolume +function vfw.h MCIWndGetZoom +function vfw.h MCIWndHome +function vfw.h MCIWndNew +function vfw.h MCIWndOpen +function vfw.h MCIWndOpenDialog +function vfw.h MCIWndOpenInterface +function vfw.h MCIWndPause +function vfw.h MCIWndPlay +function vfw.h MCIWndPlayFrom +function vfw.h MCIWndPlayFromTo +function vfw.h MCIWndPlayReverse +function vfw.h MCIWndPlayTo +function vfw.h MCIWndPutDest +function vfw.h MCIWndPutSource +function vfw.h MCIWndRealize +function vfw.h MCIWndRecord +function vfw.h MCIWndResume +function vfw.h MCIWndReturnString +function vfw.h MCIWndSave +function vfw.h MCIWndSaveDialog +function vfw.h MCIWndSeek +function vfw.h MCIWndSendString +function vfw.h MCIWndSetActiveTimer +function vfw.h MCIWndSetInactiveTimer +function vfw.h MCIWndSetOwner +function vfw.h MCIWndSetPalette +function vfw.h MCIWndSetRepeat +function vfw.h MCIWndSetSpeed +function vfw.h MCIWndSetTimeFormat +function vfw.h MCIWndSetTimers +function vfw.h MCIWndSetVolume +function vfw.h MCIWndSetZoom +function vfw.h MCIWndStep +function vfw.h MCIWndStop +function vfw.h MCIWndUseFrames +function vfw.h MCIWndUseTime +function vfw.h MCIWndValidateMedia +function vfw.h mmioFOURCC + +# Desktop App Technologies > Audio and Video > Windows Multimedia > Multimedia Reference > Multimedia Interfaces +interface vfw.h IAVIEditStream +interface vfw.h IAVIFile +interface vfw.h IAVIStream +interface vfw.h IAVIStreaming +interface vfw.h IGetFrame + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > BITS enumerations +enum bits.h BG_ERROR_CONTEXT +enum bits.h BG_JOB_PRIORITY +enum bits.h BG_JOB_PROXY_USAGE +enum bits.h BG_JOB_STATE +enum bits.h BG_JOB_TYPE +enum bits1_5.h BG_AUTH_SCHEME +enum bits1_5.h BG_AUTH_TARGET +enum bits2_5.h BG_CERT_STORE_LOCATION +enum bits5_0.h BITS_FILE_PROPERTY_ID +enum bits5_0.h BITS_JOB_PROPERTY_ID +enum bits5_0.h BITS_JOB_TRANSFER_POLICY + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > BITS Interfaces +interface bits.h IBackgroundCopyCallback +interface bits.h IBackgroundCopyError +interface bits.h IBackgroundCopyFile +interface bits.h IBackgroundCopyJob +interface bits.h IBackgroundCopyManager +interface bits.h IEnumBackgroundCopyFiles +interface bits.h IEnumBackgroundCopyJobs +interface bits1_5.h IBackgroundCopyJob2 +interface bits10_1.h IBackgroundCopyCallback3 +interface bits10_1.h IBackgroundCopyFile6 +interface bits10_2.h IBackgroundCopyJobHttpOptions2 +interface bits10_3.h IBackgroundCopyJobHttpOptions3 +interface bits10_3.h IBackgroundCopyServerCertificateValidationCallback +interface bits2_0.h IBackgroundCopyFile2 +interface bits2_0.h IBackgroundCopyJob3 +interface bits2_5.h IBackgroundCopyJobHttpOptions +interface bits3_0.h IBackgroundCopyCallback2 +interface bits3_0.h IBackgroundCopyFile3 +interface bits3_0.h IBackgroundCopyJob4 +interface bits3_0.h IBitsPeer +interface bits3_0.h IBitsPeerCacheAdministration +interface bits3_0.h IBitsPeerCacheRecord +interface bits3_0.h IEnumBitsPeerCacheRecords +interface bits3_0.h IEnumBitsPeers +interface bits4_0.h IBackgroundCopyFile4 +interface bits4_0.h IBitsTokenOptions +interface bits5_0.h IBackgroundCopyFile5 +interface bits5_0.h IBackgroundCopyJob5 + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > BITS Server Settings for Upload Jobs > BITS ADSI Extension +interface bitscfg.h IBITSExtensionSetup +interface bitscfg.h IBITSExtensionSetupFactory + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > BITS structures and unions +struct bits.h BG_FILE_INFO +struct bits.h BG_FILE_PROGRESS +struct bits.h BG_JOB_PROGRESS +struct bits.h BG_JOB_TIMES +struct bits1_5.h BG_AUTH_CREDENTIALS +struct bits1_5.h BG_AUTH_CREDENTIALS_UNION +struct bits1_5.h BG_BASIC_CREDENTIALS =bg_auth_credentials +struct bits1_5.h BG_JOB_REPLY_PROGRESS +struct bits2_0.h BG_FILE_RANGE +struct bits5_0.h BITS_FILE_PROPERTY_VALUE +struct bits5_0.h BITS_JOB_PROPERTY_VALUE + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > Queue Manager > QMGR Enumerations +enum qmgr.h GROUPPROP + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > Queue Manager > QMGR Interfaces +interface qmgr.h IBackgroundCopyCallback1 +interface qmgr.h IBackgroundCopyGroup +interface qmgr.h IBackgroundCopyJob1 +interface qmgr.h IBackgroundCopyQMgr +interface qmgr.h IEnumBackgroundCopyGroups +interface qmgr.h IEnumBackgroundCopyJobs1 + +# Desktop App Technologies > Data Access and Storage > Background Intelligent Transfer Service > BITS Reference > Queue Manager > QMGR Structures +struct qmgr.h FILESETINFO + +# Desktop App Technologies > Data Access and Storage > Backup > Backup Reference > Backup Functions +function winbase.h BackupRead +function winbase.h BackupSeek +function winbase.h BackupWrite +function winbase.h CreateTapePartition +function winbase.h EraseTape +function winbase.h GetTapeParameters +function winbase.h GetTapePosition +function winbase.h GetTapeStatus +function winbase.h PrepareTape +function winbase.h SetTapeParameters +function winbase.h SetTapePosition +function winbase.h WriteTapemark + +# Desktop App Technologies > Data Access and Storage > Backup > Backup Reference > Backup Structures +struct winbase.h WIN32_STREAM_ID +struct winnt.h PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY +struct winnt.h TAPE_ERASE +struct winnt.h TAPE_GET_DRIVE_PARAMETERS +struct winnt.h TAPE_GET_MEDIA_PARAMETERS +struct winnt.h TAPE_GET_POSITION +struct winnt.h TAPE_PREPARE +struct winnt.h TAPE_SET_DRIVE_PARAMETERS +struct winnt.h TAPE_SET_MEDIA_PARAMETERS +struct winnt.h TAPE_SET_POSITION +struct winnt.h TAPE_WRITE_MARKS + +# Desktop App Technologies > Data Access and Storage > Cloud Sync Engines > Cloud Filter Reference > Cloud Filter Enumerations +enum cfapi.h CF_CALLBACK_CANCEL_FLAGS +enum cfapi.h CF_CALLBACK_CLOSE_COMPLETION_FLAGS +enum cfapi.h CF_CALLBACK_DEHYDRATE_COMPLETION_FLAGS +enum cfapi.h CF_CALLBACK_DEHYDRATE_FLAGS +enum cfapi.h CF_CALLBACK_DELETE_COMPLETION_FLAGS +enum cfapi.h CF_CALLBACK_DELETE_FLAGS +enum cfapi.h CF_CALLBACK_FETCH_DATA_FLAGS +enum cfapi.h CF_CALLBACK_FETCH_PLACEHOLDERS_FLAGS +enum cfapi.h CF_CALLBACK_OPEN_COMPLETION_FLAGS +enum cfapi.h CF_CALLBACK_RENAME_COMPLETION_FLAGS +enum cfapi.h CF_CALLBACK_RENAME_FLAGS +enum cfapi.h CF_CALLBACK_TYPE +enum cfapi.h CF_CALLBACK_VALIDATE_DATA_FLAGS +enum cfapi.h CF_CONNECT_FLAGS +enum cfapi.h CF_CONVERT_FLAGS +enum cfapi.h CF_CREATE_FLAGS +enum cfapi.h CF_DEHYDRATE_FLAGS +enum cfapi.h CF_HARDLINK_POLICY +enum cfapi.h CF_HYDRATE_FLAGS +enum cfapi.h CF_HYDRATION_POLICY_MODIFIER +enum cfapi.h CF_HYDRATION_POLICY_PRIMARY +enum cfapi.h CF_IN_SYNC_STATE +enum cfapi.h CF_INSYNC_POLICY +enum cfapi.h CF_OPEN_FILE_FLAGS +enum cfapi.h CF_OPERATION_ACK_DATA_FLAGS +enum cfapi.h CF_OPERATION_ACK_DEHYDRATE_FLAGS +enum cfapi.h CF_OPERATION_ACK_DELETE_FLAGS +enum cfapi.h CF_OPERATION_ACK_RENAME_FLAGS +enum cfapi.h CF_OPERATION_RESTART_HYDRATION_FLAGS +enum cfapi.h CF_OPERATION_RETRIEVE_DATA_FLAGS +enum cfapi.h CF_OPERATION_TRANSFER_DATA_FLAGS +enum cfapi.h CF_OPERATION_TRANSFER_PLACEHOLDERS_FLAGS +enum cfapi.h CF_OPERATION_TYPE +enum cfapi.h CF_PIN_STATE +enum cfapi.h CF_PLACEHOLDER_CREATE_FLAGS +enum cfapi.h CF_PLACEHOLDER_INFO_CLASS +enum cfapi.h CF_PLACEHOLDER_RANGE_INFO_CLASS +enum cfapi.h CF_PLACEHOLDER_STATE +enum cfapi.h CF_POPULATION_POLICY_MODIFIER +enum cfapi.h CF_POPULATION_POLICY_PRIMARY +enum cfapi.h CF_REGISTER_FLAGS +enum cfapi.h CF_REVERT_FLAGS +enum cfapi.h CF_SET_IN_SYNC_FLAGS +enum cfapi.h CF_SET_PIN_FLAGS +enum cfapi.h CF_SYNC_PROVIDER_STATUS +enum cfapi.h CF_SYNC_ROOT_INFO_CLASS +enum cfapi.h CF_UPDATE_FLAGS + +# Desktop App Technologies > Data Access and Storage > Cloud Sync Engines > Cloud Filter Reference > Cloud Filter Functions +function cfapi.h CfCloseHandle +function cfapi.h CfConnectSyncRoot +function cfapi.h CfConvertToPlaceholder +function cfapi.h CfCreatePlaceholders +function cfapi.h CfDisconnectSyncRoot +function cfapi.h CfExecute +function cfapi.h CfGetCorrelationVector +function cfapi.h CfGetPlaceholderInfo +function cfapi.h CfGetPlaceholderRangeInfo +function cfapi.h CfGetPlaceholderRangeInfoForHydration +function cfapi.h CfGetPlaceholderStateFromAttributeTag +function cfapi.h CfGetPlaceholderStateFromFileInfo +function cfapi.h CfGetPlaceholderStateFromFindData +function cfapi.h CfGetPlatformInfo +function cfapi.h CfGetSyncRootInfoByHandle +function cfapi.h CfGetSyncRootInfoByPath +function cfapi.h CfGetTransferKey +function cfapi.h CfGetWin32HandleFromProtectedHandle +function cfapi.h CfHydratePlaceholder +function cfapi.h CfOpenFileWithOplock +function cfapi.h CfQuerySyncProviderStatus +function cfapi.h CfReferenceProtectedHandle +function cfapi.h CfRegisterSyncRoot +function cfapi.h CfReleaseProtectedHandle +function cfapi.h CfReleaseTransferKey +function cfapi.h CfReportProviderProgress +function cfapi.h CfReportSyncStatus +function cfapi.h CfRevertPlaceholder +function cfapi.h CfSetCorrelationVector +function cfapi.h CfSetInSyncState +function cfapi.h CfSetPinState +function cfapi.h CfUnregisterSyncRoot +function cfapi.h CfUpdatePlaceholder +function cfapi.h CfUpdateSyncProviderStatus + +# Desktop App Technologies > Data Access and Storage > Cloud Sync Engines > Cloud Filter Reference > Cloud Filter Structures +struct cfapi.h CF_CALLBACK_INFO +struct cfapi.h CF_CALLBACK_PARAMETERS +struct cfapi.h CF_CALLBACK_REGISTRATION +struct cfapi.h CF_FILE_RANGE +struct cfapi.h CF_FS_METADATA +struct cfapi.h CF_HYDRATION_POLICY +struct cfapi.h CF_OPERATION_INFO +struct cfapi.h CF_OPERATION_PARAMETERS +struct cfapi.h CF_PLACEHOLDER_BASIC_INFO +struct cfapi.h CF_PLACEHOLDER_CREATE_INFO +struct cfapi.h CF_PLACEHOLDER_STANDARD_INFO +struct cfapi.h CF_PLATFORM_INFO +struct cfapi.h CF_POPULATION_POLICY +struct cfapi.h CF_PROCESS_INFO +struct cfapi.h CF_SYNC_POLICIES +struct cfapi.h CF_SYNC_REGISTRATION +struct cfapi.h CF_SYNC_ROOT_BASIC_INFO +struct cfapi.h CF_SYNC_ROOT_PROVIDER_INFO +struct cfapi.h CF_SYNC_ROOT_STANDARD_INFO +struct cfapi.h CF_SYNC_STATUS + +# Desktop App Technologies > Data Access and Storage > Image Mastering API > IMAPI Reference > IMAPI Interfaces +interface imapi2.h DDiscFormat2DataEvents +interface imapi2.h DDiscFormat2EraseEvents +interface imapi2.h DDiscFormat2RawCDEvents +interface imapi2.h DDiscFormat2TrackAtOnceEvents +interface imapi2.h DDiscMaster2Events +interface imapi2.h DWriteEngine2Events +interface imapi2.h IBlockRange +interface imapi2.h IBlockRangeList +interface imapi2.h IBurnVerification +interface imapi2.h IDiscFormat2 +interface imapi2.h IDiscFormat2Data +interface imapi2.h IDiscFormat2DataEventArgs +interface imapi2.h IDiscFormat2Erase +interface imapi2.h IDiscFormat2RawCD +interface imapi2.h IDiscFormat2RawCDEventArgs +interface imapi2.h IDiscFormat2TrackAtOnce +interface imapi2.h IDiscFormat2TrackAtOnceEventArgs +interface imapi2.h IDiscMaster2 +interface imapi2.h IDiscRecorder2 +interface imapi2.h IDiscRecorder2Ex +interface imapi2.h IMultisession +interface imapi2.h IMultisessionRandomWrite +interface imapi2.h IMultisessionSequential +interface imapi2.h IMultisessionSequential2 +interface imapi2.h IRawCDImageCreator +interface imapi2.h IRawCDImageTrackInfo +interface imapi2.h IStreamConcatenate +interface imapi2.h IStreamInterleave +interface imapi2.h IStreamPseudoRandomBased +interface imapi2.h IWriteEngine2 +interface imapi2.h IWriteEngine2EventArgs +interface imapi2.h IWriteSpeedDescriptor +interface imapi2fs.h DFileSystemImageEvents +interface imapi2fs.h DFileSystemImageImportEvents +interface imapi2fs.h IBootOptions +interface imapi2fs.h IEnumFsiItems +interface imapi2fs.h IEnumProgressItems +interface imapi2fs.h IFileSystemImage +interface imapi2fs.h IFileSystemImage2 +interface imapi2fs.h IFileSystemImage3 +interface imapi2fs.h IFileSystemImageResult +interface imapi2fs.h IFileSystemImageResult2 +interface imapi2fs.h IFsiDirectoryItem +interface imapi2fs.h IFsiDirectoryItem2 +interface imapi2fs.h IFsiFileItem +interface imapi2fs.h IFsiFileItem2 +interface imapi2fs.h IFsiItem +interface imapi2fs.h IFsiNamedStreams +interface imapi2fs.h IIsoImageManager +interface imapi2fs.h IProgressItem +interface imapi2fs.h IProgressItems + +# Desktop App Technologies > Data Access and Storage > Image Mastering API > IMAPI Reference > IMAPI Enumerations +enum imapi2.h IMAPI_BURN_VERIFICATION_LEVEL +enum imapi2.h IMAPI_CD_SECTOR_TYPE +enum imapi2.h IMAPI_CD_TRACK_DIGITAL_COPY_SETTING +enum imapi2.h IMAPI_FEATURE_PAGE_TYPE +enum imapi2.h IMAPI_FORMAT2_DATA_MEDIA_STATE +enum imapi2.h IMAPI_FORMAT2_DATA_WRITE_ACTION +enum imapi2.h IMAPI_FORMAT2_RAW_CD_DATA_SECTOR_TYPE +enum imapi2.h IMAPI_FORMAT2_RAW_CD_WRITE_ACTION +enum imapi2.h IMAPI_FORMAT2_TAO_WRITE_ACTION +enum imapi2.h IMAPI_MEDIA_PHYSICAL_TYPE +enum imapi2.h IMAPI_MEDIA_WRITE_PROTECT_STATE +enum imapi2.h IMAPI_MODE_PAGE_REQUEST_TYPE +enum imapi2.h IMAPI_MODE_PAGE_TYPE +enum imapi2.h IMAPI_PROFILE_TYPE +enum imapi2.h IMAPI_READ_TRACK_ADDRESS_TYPE +enum imapi2fs.h EmulationType +enum imapi2fs.h FsiFileSystems +enum imapi2fs.h FsiItemType +enum imapi2fs.h PlatformId + +# Desktop App Technologies > Data Access and Storage > Image Mastering API > IMAPIv1 Support > IMAPIv1 Reference > IMAPIv1 Interfaces +interface imapi.h IDiscMaster +interface imapi.h IDiscMasterProgressEvents +interface imapi.h IDiscRecorder +interface imapi.h IJolietDiscMaster +interface imapi.h IRedbookDiscMaster + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Directory Management > Directory Management Reference > Directory Management Control Codes +control-code winioctl.h FSCTL_DELETE_REPARSE_POINT +control-code winioctl.h FSCTL_GET_REPARSE_POINT +control-code winioctl.h FSCTL_SET_REPARSE_POINT + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Directory Management > Directory Management Reference > Directory Management Enumerations +enum minwinbase.h READ_DIRECTORY_NOTIFY_INFORMATION_CLASS + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Directory Management > Directory Management Reference > Directory Management Functions +function fileapi.h CreateDirectory =createdirectorya +function fileapi.h CreateDirectory2A +function fileapi.h CreateDirectory2W +function fileapi.h FindCloseChangeNotification +function fileapi.h FindFirstChangeNotification =findfirstchangenotificationa +function fileapi.h FindNextChangeNotification +function fileapi.h RemoveDirectory =removedirectorya +function fileapi.h RemoveDirectory2A +function fileapi.h RemoveDirectory2W +function winbase.h CreateDirectoryEx =createdirectoryexa +function winbase.h CreateDirectoryTransacted =createdirectorytransacteda +function winbase.h GetCurrentDirectory +function winbase.h ReadDirectoryChangesExW +function winbase.h ReadDirectoryChangesW +function winbase.h RemoveDirectoryTransacted =removedirectorytransacteda +function winbase.h SetCurrentDirectory + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Directory Management > Directory Management Reference > Directory Management Macros +function winnt.h IsReparseTagMicrosoft +function winnt.h IsReparseTagNameSurrogate + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Directory Management > Directory Management Reference > Directory Management Structures +struct winnt.h FILE_NOTIFY_EXTENDED_INFORMATION +struct winnt.h FILE_NOTIFY_INFORMATION +struct winnt.h REPARSE_GUID_DATA_BUFFER + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Disk Management > Disk Management Reference > Disk Management Control Codes +control-code winioctl.h IOCTL_DISK_CREATE_DISK +control-code winioctl.h IOCTL_DISK_DELETE_DRIVE_LAYOUT +control-code winioctl.h IOCTL_DISK_FORMAT_TRACKS +control-code winioctl.h IOCTL_DISK_FORMAT_TRACKS_EX +control-code winioctl.h IOCTL_DISK_GET_CACHE_INFORMATION +control-code winioctl.h IOCTL_DISK_GET_DISK_ATTRIBUTES +control-code winioctl.h IOCTL_DISK_GET_DRIVE_GEOMETRY +control-code winioctl.h IOCTL_DISK_GET_DRIVE_GEOMETRY_EX +control-code winioctl.h IOCTL_DISK_GET_DRIVE_LAYOUT +control-code winioctl.h IOCTL_DISK_GET_DRIVE_LAYOUT_EX +control-code winioctl.h IOCTL_DISK_GET_LENGTH_INFO +control-code winioctl.h IOCTL_DISK_GET_PARTITION_INFO +control-code winioctl.h IOCTL_DISK_GET_PARTITION_INFO_EX +control-code winioctl.h IOCTL_DISK_GROW_PARTITION +control-code winioctl.h IOCTL_DISK_IS_WRITABLE +control-code winioctl.h IOCTL_DISK_PERFORMANCE +control-code winioctl.h IOCTL_DISK_PERFORMANCE_OFF +control-code winioctl.h IOCTL_DISK_REASSIGN_BLOCKS +control-code winioctl.h IOCTL_DISK_REASSIGN_BLOCKS_EX +control-code winioctl.h IOCTL_DISK_RESET_SNAPSHOT_INFO +control-code winioctl.h IOCTL_DISK_SET_CACHE_INFORMATION +control-code winioctl.h IOCTL_DISK_SET_DISK_ATTRIBUTES +control-code winioctl.h IOCTL_DISK_SET_DRIVE_LAYOUT +control-code winioctl.h IOCTL_DISK_SET_DRIVE_LAYOUT_EX +control-code winioctl.h IOCTL_DISK_SET_PARTITION_INFO +control-code winioctl.h IOCTL_DISK_SET_PARTITION_INFO_EX +control-code winioctl.h IOCTL_DISK_UPDATE_PROPERTIES +control-code winioctl.h IOCTL_DISK_VERIFY +control-code winioctl.h IOCTL_STORAGE_DEVICE_POWER_CAP +control-code winioctl.h IOCTL_STORAGE_FIRMWARE_ACTIVATE +control-code winioctl.h IOCTL_STORAGE_FIRMWARE_DOWNLOAD +control-code winioctl.h IOCTL_STORAGE_FIRMWARE_GET_INFO +control-code winioctl.h IOCTL_STORAGE_PROTOCOL_COMMAND +control-code winioctl.h IOCTL_STORAGE_QUERY_PROPERTY +control-code winioctl.h IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Disk Management > Disk Management Reference > Disk Management Enumeration Types +enum winioctl.h MEDIA_TYPE +enum winioctl.h PARTITION_STYLE +enum winioctl.h STORAGE_BUS_TYPE +enum winioctl.h STORAGE_COMPONENT_HEALTH_STATUS +enum winioctl.h STORAGE_DEVICE_FORM_FACTOR +enum winioctl.h STORAGE_DEVICE_POWER_CAP_UNITS +enum winioctl.h STORAGE_PORT_CODE_SET +enum winioctl.h STORAGE_PROPERTY_ID +enum winioctl.h STORAGE_PROTOCOL_TYPE +enum winioctl.h STORAGE_QUERY_TYPE +enum winioctl.h WRITE_CACHE_CHANGE +enum winioctl.h WRITE_CACHE_ENABLE +enum winioctl.h WRITE_CACHE_TYPE +enum winioctl.h WRITE_THROUGH + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Disk Management > Disk Management Reference > Disk Management Functions +function fileapi.h GetDiskFreeSpace =getdiskfreespacea +function fileapi.h GetDiskFreeSpaceEx =getdiskfreespaceexa + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Disk Management > Disk Management Reference > Disk Management Interfaces +interface dskquota.h IDiskQuotaControl +interface dskquota.h IDiskQuotaEvents +interface dskquota.h IDiskQuotaUser +interface dskquota.h IDiskQuotaUserBatch +interface dskquota.h IEnumDiskQuotaUsers + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Disk Management > Disk Management Reference > Disk Management Structures +enum winioctl.h STORAGE_PROTOCOL_ATA_DATA_TYPE +enum winioctl.h STORAGE_PROTOCOL_NVME_DATA_TYPE +struct dskquota.h DISKQUOTA_USER_INFORMATION +struct winioctl.h CREATE_DISK +struct winioctl.h CREATE_DISK_GPT +struct winioctl.h CREATE_DISK_MBR +struct winioctl.h DEVICE_COPY_OFFLOAD_DESCRIPTOR +struct winioctl.h DEVICE_LB_PROVISIONING_DESCRIPTOR +struct winioctl.h DEVICE_POWER_DESCRIPTOR +struct winioctl.h DEVICE_SEEK_PENALTY_DESCRIPTOR +struct winioctl.h DEVICE_TRIM_DESCRIPTOR +struct winioctl.h DEVICE_WRITE_AGGREGATION_DESCRIPTOR +struct winioctl.h DISK_CACHE_INFORMATION +struct winioctl.h DISK_DETECTION_INFO +struct winioctl.h DISK_EX_INT13_INFO +struct winioctl.h DISK_EXTENT +struct winioctl.h DISK_GEOMETRY +struct winioctl.h DISK_GEOMETRY_EX +struct winioctl.h DISK_GROW_PARTITION +struct winioctl.h DISK_INT13_INFO +struct winioctl.h DISK_PARTITION_INFO +struct winioctl.h DISK_PERFORMANCE +struct winioctl.h DRIVE_LAYOUT_INFORMATION +struct winioctl.h DRIVE_LAYOUT_INFORMATION_EX +struct winioctl.h DRIVE_LAYOUT_INFORMATION_GPT +struct winioctl.h DRIVE_LAYOUT_INFORMATION_MBR +struct winioctl.h FORMAT_EX_PARAMETERS +struct winioctl.h FORMAT_PARAMETERS +struct winioctl.h GET_DISK_ATTRIBUTES +struct winioctl.h GET_LENGTH_INFORMATION +struct winioctl.h PARTITION_INFORMATION +struct winioctl.h PARTITION_INFORMATION_EX +struct winioctl.h PARTITION_INFORMATION_GPT +struct winioctl.h PARTITION_INFORMATION_MBR +struct winioctl.h REASSIGN_BLOCKS +struct winioctl.h REASSIGN_BLOCKS_EX +struct winioctl.h SET_DISK_ATTRIBUTES +struct winioctl.h SET_PARTITION_INFORMATION +struct winioctl.h STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR +struct winioctl.h STORAGE_ADAPTER_DESCRIPTOR +struct winioctl.h STORAGE_DESCRIPTOR_HEADER +struct winioctl.h STORAGE_DEVICE_ATTRIBUTES_DESCRIPTOR +struct winioctl.h STORAGE_DEVICE_DESCRIPTOR +struct winioctl.h STORAGE_DEVICE_ID_DESCRIPTOR +struct winioctl.h STORAGE_DEVICE_IO_CAPABILITY_DESCRIPTOR +struct winioctl.h STORAGE_DEVICE_POWER_CAP +struct winioctl.h STORAGE_DEVICE_RESILIENCY_DESCRIPTOR +struct winioctl.h STORAGE_HW_FIRMWARE_ACTIVATE +struct winioctl.h STORAGE_HW_FIRMWARE_DOWNLOAD +struct winioctl.h STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR +struct winioctl.h STORAGE_MINIPORT_DESCRIPTOR +struct winioctl.h STORAGE_PHYSICAL_ADAPTER_DATA +struct winioctl.h STORAGE_PHYSICAL_DEVICE_DATA +struct winioctl.h STORAGE_PHYSICAL_NODE_DATA +struct winioctl.h STORAGE_PHYSICAL_TOPOLOGY_DESCRIPTOR +struct winioctl.h STORAGE_PROPERTY_QUERY +struct winioctl.h STORAGE_PROTOCOL_COMMAND +struct winioctl.h STORAGE_PROTOCOL_DATA_DESCRIPTOR +struct winioctl.h STORAGE_PROTOCOL_SPECIFIC_DATA +struct winioctl.h STORAGE_SPEC_VERSION +struct winioctl.h STORAGE_TEMPERATURE_DATA_DESCRIPTOR +struct winioctl.h STORAGE_TEMPERATURE_INFO +struct winioctl.h STORAGE_TEMPERATURE_THRESHOLD +struct winioctl.h STORAGE_WRITE_CACHE_PROPERTY +struct winioctl.h VERIFY_INFORMATION + +# Desktop App Technologies > Data Access and Storage > Local File Systems > File Management > File Management Reference > File Management Control Codes +control-code winioctl.h FSCTL_ALLOW_EXTENDED_DASD_IO +control-code winioctl.h FSCTL_CREATE_OR_GET_OBJECT_ID +control-code winioctl.h FSCTL_CSV_CONTROL +control-code winioctl.h FSCTL_DELETE_OBJECT_ID +control-code winioctl.h FSCTL_DUPLICATE_EXTENTS_TO_FILE +control-code winioctl.h FSCTL_FILE_LEVEL_TRIM +control-code winioctl.h FSCTL_FILESYSTEM_GET_STATISTICS +control-code winioctl.h FSCTL_FILESYSTEM_GET_STATISTICS_EX +control-code winioctl.h FSCTL_FIND_FILES_BY_SID +control-code winioctl.h FSCTL_GET_COMPRESSION +control-code winioctl.h FSCTL_GET_NTFS_FILE_RECORD +control-code winioctl.h FSCTL_GET_OBJECT_ID +control-code winioctl.h FSCTL_GET_REPAIR +control-code winioctl.h FSCTL_INITIATE_REPAIR +control-code winioctl.h FSCTL_MAKE_MEDIA_COMPATIBLE +control-code winioctl.h FSCTL_OPBATCH_ACK_CLOSE_PENDING +control-code winioctl.h FSCTL_OPLOCK_BREAK_ACK_NO_2 +control-code winioctl.h FSCTL_OPLOCK_BREAK_ACKNOWLEDGE +control-code winioctl.h FSCTL_OPLOCK_BREAK_NOTIFY +control-code winioctl.h FSCTL_QUERY_ALLOCATED_RANGES +control-code winioctl.h FSCTL_QUERY_ON_DISK_VOLUME_INFO +control-code winioctl.h FSCTL_QUERY_SPARING_INFO +control-code winioctl.h FSCTL_RECALL_FILE +control-code winioctl.h FSCTL_REQUEST_BATCH_OPLOCK +control-code winioctl.h FSCTL_REQUEST_FILTER_OPLOCK +control-code winioctl.h FSCTL_REQUEST_OPLOCK +control-code winioctl.h FSCTL_REQUEST_OPLOCK_LEVEL_1 +control-code winioctl.h FSCTL_REQUEST_OPLOCK_LEVEL_2 +control-code winioctl.h FSCTL_SET_COMPRESSION +control-code winioctl.h FSCTL_SET_DEFECT_MANAGEMENT +control-code winioctl.h FSCTL_SET_OBJECT_ID +control-code winioctl.h FSCTL_SET_OBJECT_ID_EXTENDED +control-code winioctl.h FSCTL_SET_REPAIR +control-code winioctl.h FSCTL_SET_SPARSE +control-code winioctl.h FSCTL_SET_ZERO_DATA +control-code winioctl.h FSCTL_SET_ZERO_ON_DEALLOCATION +control-code winioctl.h FSCTL_WAIT_FOR_REPAIR + +# Desktop App Technologies > Data Access and Storage > Local File Systems > File Management > File Management Reference > File Management Enumerations +enum fileapi.h STREAM_INFO_LEVELS +enum minwinbase.h FILE_INFO_BY_HANDLE_CLASS +enum minwinbase.h FINDEX_INFO_LEVELS +enum minwinbase.h FINDEX_SEARCH_OPS +enum minwinbase.h GET_FILEEX_INFO_LEVELS +enum winbase.h COPYFILE2_COPY_PHASE +enum winbase.h COPYFILE2_MESSAGE_ACTION +enum winbase.h COPYFILE2_MESSAGE_TYPE +enum winbase.h FILE_ID_TYPE +enum winbase.h PRIORITY_HINT +enum winioctl.h CSV_CONTROL_OP + +# Desktop App Technologies > Data Access and Storage > Local File Systems > File Management > File Management Reference > File Management Functions +callback minwinbase.h FileIOCompletionRoutine =lpoverlapped_completion_routine +callback winbase.h CopyProgressRoutine =lpprogress_routine +callback winbase.h ExportCallback =pfe_export_func +callback winbase.h ImportCallback =pfe_import_func +callback winbase.h PCOPYFILE2_PROGRESS_ROUTINE +callback wofapi.h WofEnumEntryProc +callback wofapi.h WofEnumFilesProc +function fileapi.h AreFileApisANSI +function fileapi.h CreateFile =createfilea +function fileapi.h CreateFile2 +function fileapi.h CreateFile3 +function fileapi.h DeleteFile =deletefilea +function fileapi.h DeleteFile2A +function fileapi.h DeleteFile2W +function fileapi.h FindClose +function fileapi.h FindFirstFile =findfirstfilea +function fileapi.h FindFirstFileEx =findfirstfileexa +function fileapi.h FindFirstFileNameW +function fileapi.h FindFirstStreamW +function fileapi.h FindNextFile =findnextfilea +function fileapi.h FindNextFileNameW +function fileapi.h FindNextStreamW +function fileapi.h FlushFileBuffers +function fileapi.h GetCompressedFileSize =getcompressedfilesizea +function fileapi.h GetFileAttributes =getfileattributesa +function fileapi.h GetFileAttributesEx =getfileattributesexa +function fileapi.h GetFileInformationByHandle +function fileapi.h GetFileSize +function fileapi.h GetFileSizeEx +function fileapi.h GetFileType +function fileapi.h GetFinalPathNameByHandle =getfinalpathnamebyhandlea +function fileapi.h GetFullPathName =getfullpathnamea +function fileapi.h GetLongPathName =getlongpathnamea +function fileapi.h GetShortPathName =getshortpathnamew +function fileapi.h GetTempFileName =gettempfilenamea +function fileapi.h GetTempPath =gettemppatha +function fileapi.h LockFile +function fileapi.h LockFileEx +function fileapi.h ReadFile +function fileapi.h ReadFileEx +function fileapi.h ReadFileScatter +function fileapi.h SetEndOfFile +function fileapi.h SetFileApisToANSI +function fileapi.h SetFileApisToOEM +function fileapi.h SetFileAttributes =setfileattributesa +function fileapi.h SetFileInformationByHandle +function fileapi.h SetFileIoOverlappedRange +function fileapi.h SetFilePointer +function fileapi.h SetFilePointerEx +function fileapi.h SetFileValidData +function fileapi.h UnlockFile +function fileapi.h UnlockFileEx +function fileapi.h WriteFile +function fileapi.h WriteFileEx +function fileapi.h WriteFileGather +function ioapiset.h GetQueuedCompletionStatus +function lzexpand.h GetExpandedName =getexpandednamea +function lzexpand.h LZClose +function lzexpand.h LZCopy +function lzexpand.h LZInit +function lzexpand.h LZOpenFile =lzopenfilea +function lzexpand.h LZRead +function lzexpand.h LZSeek +function processenv.h SearchPath =searchpathw +function winbase.h CheckNameLegalDOS8Dot3 =checknamelegaldos8dot3a +function winbase.h CloseEncryptedFileRaw +function winbase.h CopyFile +function winbase.h CopyFile2 +function winbase.h CopyFileEx =copyfileexa +function winbase.h CopyFileTransacted =copyfiletransacteda +function winbase.h CreateFileTransacted =createfiletransacteda +function winbase.h CreateHardLink =createhardlinka +function winbase.h CreateHardLinkTransacted =createhardlinktransacteda +function winbase.h CreateSymbolicLink =createsymboliclinka +function winbase.h CreateSymbolicLinkTransacted =createsymboliclinktransacteda +function winbase.h DecryptFile =decryptfilea +function winbase.h DeleteFileTransacted =deletefiletransacteda +function winbase.h EncryptFile =encryptfilea +function winbase.h FileEncryptionStatus =fileencryptionstatusa +function winbase.h FindFirstFileNameTransactedW +function winbase.h FindFirstFileTransacted =findfirstfiletransacteda +function winbase.h FindFirstStreamTransactedW +function winbase.h GetBinaryType =getbinarytypea +function winbase.h GetCompressedFileSizeTransacted =getcompressedfilesizetransacteda +function winbase.h GetFileAttributesTransacted =getfileattributestransacteda +function winbase.h GetFileBandwidthReservation +function winbase.h GetFileInformationByHandleEx +function winbase.h GetFullPathNameTransacted =getfullpathnametransacteda +function winbase.h GetLongPathNameTransacted =getlongpathnametransacteda +function winbase.h MoveFile +function winbase.h MoveFileEx =movefileexa +function winbase.h MoveFileTransacted =movefiletransacteda +function winbase.h MoveFileWithProgress =movefilewithprogressa +function winbase.h OpenEncryptedFileRaw =openencryptedfilerawa +function winbase.h OpenFile +function winbase.h OpenFileById +function winbase.h ReadEncryptedFileRaw +function winbase.h ReOpenFile +function winbase.h ReplaceFile =replacefilea +function winbase.h SetFileAttributesTransacted =setfileattributestransacteda +function winbase.h SetFileBandwidthReservation +function winbase.h SetFileCompletionNotificationModes +function winbase.h SetFileShortName =setfileshortnamea +function winbase.h SetSearchPathMode +function winbase.h WriteEncryptedFileRaw +function winefs.h AddUsersToEncryptedFile +function winefs.h DuplicateEncryptionInfoFile +function winefs.h EncryptionDisable +function winefs.h FreeEncryptionCertificateHashList +function winefs.h QueryRecoveryAgentsOnEncryptedFile +function winefs.h QueryUsersOnEncryptedFile +function winefs.h RemoveUsersFromEncryptedFile +function winefs.h SetUserFileEncryptionKey +function wofapi.h WofEnumEntries +function wofapi.h WofFileEnumFiles +function wofapi.h WofGetDriverVersion +function wofapi.h WofIsExternalFile +function wofapi.h WofSetFileDataLocation +function wofapi.h WofShouldCompressBinaries +function wofapi.h WofWimAddEntry +function wofapi.h WofWimEnumFiles +function wofapi.h WofWimRemoveEntry +function wofapi.h WofWimSuspendEntry +function wofapi.h WofWimUpdateEntry +function wow64apiset.h Wow64DisableWow64FsRedirection +function wow64apiset.h Wow64EnableWow64FsRedirection +function wow64apiset.h Wow64RevertWow64FsRedirection + +# Desktop App Technologies > Data Access and Storage > Local File Systems > File Management > File Management Reference > File Management Structures +struct fileapi.h BY_HANDLE_FILE_INFORMATION +struct fileapi.h CREATEFILE2_EXTENDED_PARAMETERS +struct fileapi.h LPCREATEFILE3_EXTENDED_PARAMETERS =createfile3_extended_parameters +struct fileapi.h WIN32_FILE_ATTRIBUTE_DATA +struct fileapi.h WIN32_FIND_STREAM_DATA +struct minwinbase.h OVERLAPPED_ENTRY +struct minwinbase.h WIN32_FIND_DATA =win32_find_dataa +struct winbase.h COPYFILE2_EXTENDED_PARAMETERS +struct winbase.h COPYFILE2_MESSAGE +struct winbase.h FILE_ALIGNMENT_INFO +struct winbase.h FILE_ALLOCATION_INFO +struct winbase.h FILE_ATTRIBUTE_TAG_INFO +struct winbase.h FILE_BASIC_INFO +struct winbase.h FILE_COMPRESSION_INFO +struct winbase.h FILE_DISPOSITION_INFO +struct winbase.h FILE_END_OF_FILE_INFO +struct winbase.h FILE_FULL_DIR_INFO +struct winbase.h FILE_ID_BOTH_DIR_INFO +struct winbase.h FILE_ID_DESCRIPTOR +struct winbase.h FILE_ID_EXTD_DIR_INFO +struct winbase.h FILE_ID_INFO +struct winbase.h FILE_IO_PRIORITY_HINT_INFO +struct winbase.h FILE_NAME_INFO +struct winbase.h FILE_REMOTE_PROTOCOL_INFO +struct winbase.h FILE_RENAME_INFO +struct winbase.h FILE_STANDARD_INFO +struct winbase.h FILE_STORAGE_INFO +struct winbase.h FILE_STREAM_INFO +struct winbase.h OFSTRUCT +struct winefs.h EFS_CERTIFICATE_BLOB +struct winefs.h EFS_HASH_BLOB +struct winefs.h ENCRYPTION_CERTIFICATE +struct winefs.h ENCRYPTION_CERTIFICATE_HASH +struct winefs.h ENCRYPTION_CERTIFICATE_HASH_LIST +struct winefs.h ENCRYPTION_CERTIFICATE_LIST +struct winioctl.h CSV_CONTROL_PARAM +struct winioctl.h CSV_QUERY_FILE_REVISION +struct winioctl.h CSV_QUERY_MDS_PATH +struct winioctl.h CSV_QUERY_REDIRECT_STATE +struct winioctl.h DUPLICATE_EXTENTS_DATA +struct winioctl.h EXFAT_STATISTICS +struct winioctl.h FAT_STATISTICS +struct winioctl.h FILE_ALLOCATED_RANGE_BUFFER +struct winioctl.h FILE_LEVEL_TRIM +struct winioctl.h FILE_LEVEL_TRIM_OUTPUT +struct winioctl.h FILE_LEVEL_TRIM_RANGE +struct winioctl.h FILE_MAKE_COMPATIBLE_BUFFER +struct winioctl.h FILE_OBJECTID_BUFFER +struct winioctl.h FILE_QUERY_ON_DISK_VOL_INFO_BUFFER +struct winioctl.h FILE_QUERY_SPARING_BUFFER +struct winioctl.h FILE_SET_DEFECT_MGMT_BUFFER +struct winioctl.h FILE_SET_SPARSE_BUFFER +struct winioctl.h FILE_ZERO_DATA_INFORMATION +struct winioctl.h FILESYSTEM_STATISTICS +struct winioctl.h FILESYSTEM_STATISTICS_EX +struct winioctl.h FIND_BY_SID_DATA +struct winioctl.h FIND_BY_SID_OUTPUT +struct winioctl.h NTFS_FILE_RECORD_INPUT_BUFFER +struct winioctl.h NTFS_FILE_RECORD_OUTPUT_BUFFER +struct winioctl.h NTFS_STATISTICS +struct winioctl.h NTFS_STATISTICS_EX +struct winioctl.h REQUEST_OPLOCK_INPUT_BUFFER +struct winioctl.h REQUEST_OPLOCK_OUTPUT_BUFFER +struct winnt.h FILE_ID_128 +struct wofapi.h WIM_ENTRY_INFO +struct wofapi.h WIM_EXTERNAL_FILE_INFO +struct wofapi.h WOF_FILE_COMPRESSION_INFO_V1 + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Transactional NTFS (TxF) > Transactional NTFS Reference > TxF Control Codes +control-code winioctl.h FSCTL_TXFS_CREATE_MINIVERSION +control-code winioctl.h FSCTL_TXFS_GET_METADATA_INFO +control-code winioctl.h FSCTL_TXFS_GET_TRANSACTED_VERSION +control-code winioctl.h FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES +control-code winioctl.h FSCTL_TXFS_LIST_TRANSACTIONS +control-code winioctl.h FSCTL_TXFS_MODIFY_RM +control-code winioctl.h FSCTL_TXFS_QUERY_RM_INFORMATION +control-code winioctl.h FSCTL_TXFS_READ_BACKUP_INFORMATION +control-code winioctl.h FSCTL_TXFS_SAVEPOINT_INFORMATION +control-code winioctl.h FSCTL_TXFS_TRANSACTION_ACTIVE +control-code winioctl.h FSCTL_TXFS_WRITE_BACKUP_INFORMATION + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Transactional NTFS (TxF) > Transactional NTFS Reference > TxF Functions +function txfw32.h TxfLogCreateFileReadContext +function txfw32.h TxfLogDestroyReadContext +function txfw32.h TxfLogReadRecords + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Transactional NTFS (TxF) > Transactional NTFS Reference > TxF Structures +struct txfw32.h TXF_ID +struct txfw32.h TXF_LOG_RECORD_AFFECTED_FILE +struct txfw32.h TXF_LOG_RECORD_BASE +struct txfw32.h TXF_LOG_RECORD_TRUNCATE +struct txfw32.h TXF_LOG_RECORD_WRITE +struct winioctl.h TXFS_CREATE_MINIVERSION_INFO +struct winioctl.h TXFS_GET_METADATA_INFO_OUT +struct winioctl.h TXFS_GET_TRANSACTED_VERSION +struct winioctl.h TXFS_LIST_TRANSACTION_LOCKED_FILES +struct winioctl.h TXFS_LIST_TRANSACTION_LOCKED_FILES_ENTRY +struct winioctl.h TXFS_LIST_TRANSACTIONS +struct winioctl.h TXFS_LIST_TRANSACTIONS_ENTRY +struct winioctl.h TXFS_MODIFY_RM +struct winioctl.h TXFS_QUERY_RM_INFORMATION +struct winioctl.h TXFS_READ_BACKUP_INFORMATION_OUT +struct winioctl.h TXFS_SAVEPOINT_INFORMATION +struct winioctl.h TXFS_TRANSACTION_ACTIVE_INFO +struct winioctl.h TXFS_WRITE_BACKUP_INFORMATION + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Volume Management > Volume Management Reference > Volume Management Control Codes +control-code winioctl.h FSCTL_CREATE_USN_JOURNAL +control-code winioctl.h FSCTL_CSV_QUERY_DOWN_LEVEL_FILE_SYSTEM_CHARACTERISTICS +control-code winioctl.h FSCTL_DELETE_USN_JOURNAL +control-code winioctl.h FSCTL_DISMOUNT_VOLUME +control-code winioctl.h FSCTL_ENUM_USN_DATA +control-code winioctl.h FSCTL_EXTEND_VOLUME +control-code winioctl.h FSCTL_GET_BOOT_AREA_INFO +control-code winioctl.h FSCTL_GET_INTEGRITY_INFORMATION +control-code winioctl.h FSCTL_GET_NTFS_VOLUME_DATA +control-code winioctl.h FSCTL_GET_RETRIEVAL_POINTER_BASE +control-code winioctl.h FSCTL_GET_RETRIEVAL_POINTERS +control-code winioctl.h FSCTL_GET_VOLUME_BITMAP +control-code winioctl.h FSCTL_IS_CSV_FILE +control-code winioctl.h FSCTL_IS_FILE_ON_CSV_VOLUME +control-code winioctl.h FSCTL_IS_VOLUME_MOUNTED +control-code winioctl.h FSCTL_IS_VOLUME_OWNED_BYCSVFS +control-code winioctl.h FSCTL_LOCK_VOLUME +control-code winioctl.h FSCTL_LOOKUP_STREAM_FROM_CLUSTER +control-code winioctl.h FSCTL_MARK_HANDLE +control-code winioctl.h FSCTL_MOVE_FILE +control-code winioctl.h FSCTL_QUERY_FILE_SYSTEM_RECOGNITION +control-code winioctl.h FSCTL_QUERY_REGION_INFO +control-code winioctl.h FSCTL_QUERY_STORAGE_CLASSES +control-code winioctl.h FSCTL_QUERY_USN_JOURNAL +control-code winioctl.h FSCTL_READ_FILE_USN_DATA +control-code winioctl.h FSCTL_READ_FROM_PLEX +control-code winioctl.h FSCTL_READ_USN_JOURNAL +control-code winioctl.h FSCTL_REPAIR_COPIES +control-code winioctl.h FSCTL_SET_INTEGRITY_INFORMATION +control-code winioctl.h FSCTL_SHRINK_VOLUME +control-code winioctl.h FSCTL_UNLOCK_VOLUME +control-code winioctl.h FSCTL_USN_TRACK_MODIFIED_RANGES +control-code winioctl.h FSCTL_WRITE_USN_CLOSE_RECORD +control-code winioctl.h IOCTL_VOLUME_GET_GPT_ATTRIBUTES +control-code winioctl.h IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS +control-code winioctl.h IOCTL_VOLUME_IS_CLUSTERED +control-code winioctl.h IOCTL_VOLUME_OFFLINE +control-code winioctl.h IOCTL_VOLUME_ONLINE + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Volume Management > Volume Management Reference > Volume Management Enumerations +enum winioctl.h FILE_STORAGE_TIER_MEDIA_TYPE + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Volume Management > Volume Management Reference > Volume Management Functions +function fileapi.h DefineDosDevice =definedosdevicew +function fileapi.h DeleteVolumeMountPoint =deletevolumemountpointw +function fileapi.h FindFirstVolume =findfirstvolumew +function fileapi.h FindNextVolume =findnextvolumew +function fileapi.h FindVolumeClose +function fileapi.h GetDriveType =getdrivetypea +function fileapi.h GetLogicalDrives +function fileapi.h GetLogicalDriveStrings =getlogicaldrivestringsw +function fileapi.h GetVolumeInformation =getvolumeinformationa +function fileapi.h GetVolumeInformationByHandleW +function fileapi.h GetVolumeNameForVolumeMountPoint =getvolumenameforvolumemountpointw +function fileapi.h GetVolumePathName =getvolumepathnamew +function fileapi.h GetVolumePathNamesForVolumeName =getvolumepathnamesforvolumenamew +function fileapi.h QueryDosDevice =querydosdevicew +function winbase.h FindFirstVolumeMountPoint =findfirstvolumemountpointa +function winbase.h FindNextVolumeMountPoint =findnextvolumemountpointa +function winbase.h FindVolumeMountPointClose +function winbase.h SetVolumeLabel =setvolumelabela +function winbase.h SetVolumeMountPoint =setvolumemountpointa + +# Desktop App Technologies > Data Access and Storage > Local File Systems > Volume Management > Volume Management Reference > Volume Management Structures +struct winioctl.h BOOT_AREA_INFO +struct winioctl.h CREATE_USN_JOURNAL_DATA +struct winioctl.h CSV_IS_OWNED_BY_CSVFS +struct winioctl.h CSV_NAMESPACE_INFO +struct winioctl.h CSV_QUERY_VETO_FILE_DIRECT_IO_OUTPUT +struct winioctl.h DELETE_USN_JOURNAL_DATA +struct winioctl.h FILE_STORAGE_TIER +struct winioctl.h FILE_STORAGE_TIER_REGION +struct winioctl.h FILE_SYSTEM_RECOGNITION_INFORMATION +struct winioctl.h FSCTL_GET_INTEGRITY_INFORMATION_BUFFER +struct winioctl.h FSCTL_QUERY_REGION_INFO_INPUT +struct winioctl.h FSCTL_QUERY_REGION_INFO_OUTPUT +struct winioctl.h FSCTL_QUERY_STORAGE_CLASSES_OUTPUT +struct winioctl.h FSCTL_SET_INTEGRITY_INFORMATION_BUFFER +struct winioctl.h LOOKUP_STREAM_FROM_CLUSTER_ENTRY +struct winioctl.h LOOKUP_STREAM_FROM_CLUSTER_INPUT +struct winioctl.h LOOKUP_STREAM_FROM_CLUSTER_OUTPUT +struct winioctl.h MARK_HANDLE_INFO +struct winioctl.h MFT_ENUM_DATA =mft_enum_data_v0 +struct winioctl.h MFT_ENUM_DATA_V1 +struct winioctl.h MOVE_FILE_DATA +struct winioctl.h NTFS_VOLUME_DATA_BUFFER =ntfs_extended_volume_data +struct winioctl.h PLEX_READ_DATA_REQUEST +struct winioctl.h READ_FILE_USN_DATA +struct winioctl.h READ_USN_JOURNAL_DATA_V0 +struct winioctl.h REPAIR_COPIES_INPUT +struct winioctl.h REPAIR_COPIES_OUTPUT +struct winioctl.h RETRIEVAL_POINTER_BASE +struct winioctl.h RETRIEVAL_POINTERS_BUFFER +struct winioctl.h SHRINK_VOLUME_INFORMATION +struct winioctl.h STARTING_LCN_INPUT_BUFFER +struct winioctl.h STARTING_VCN_INPUT_BUFFER +struct winioctl.h USN_JOURNAL_DATA_V0 +struct winioctl.h USN_JOURNAL_DATA_V2 +struct winioctl.h USN_RANGE_TRACK_OUTPUT +struct winioctl.h USN_RECORD_COMMON_HEADER +struct winioctl.h USN_RECORD_EXTENT +struct winioctl.h USN_RECORD_V2 +struct winioctl.h USN_RECORD_V3 +struct winioctl.h USN_RECORD_V4 +struct winioctl.h USN_TRACK_MODIFIED_RANGES +struct winioctl.h VOLUME_BITMAP_BUFFER +struct winioctl.h VOLUME_DISK_EXTENTS +struct winioctl.h VOLUME_GET_GPT_ATTRIBUTES_INFORMATION + +# Desktop App Technologies > Data Access and Storage > Windows Projected File System > Projected File System Reference > Projected File System callback functions +callback projectedfslib.h PRJ_CANCEL_COMMAND_CB +callback projectedfslib.h PRJ_END_DIRECTORY_ENUMERATION_CB +callback projectedfslib.h PRJ_GET_DIRECTORY_ENUMERATION_CB +callback projectedfslib.h PRJ_GET_FILE_DATA_CB +callback projectedfslib.h PRJ_GET_PLACEHOLDER_INFO_CB +callback projectedfslib.h PRJ_NOTIFICATION_CB +callback projectedfslib.h PRJ_QUERY_FILE_NAME_CB +callback projectedfslib.h PRJ_START_DIRECTORY_ENUMERATION_CB + +# Desktop App Technologies > Data Access and Storage > Windows Projected File System > Projected File System Reference > Projected File System functions +function projectedfslib.h PrjAllocateAlignedBuffer +function projectedfslib.h PrjClearNegativePathCache +function projectedfslib.h PrjCompleteCommand +function projectedfslib.h PrjDeleteFile +function projectedfslib.h PrjDoesNameContainWildCards +function projectedfslib.h PrjFileNameCompare +function projectedfslib.h PrjFileNameMatch +function projectedfslib.h PrjFillDirEntryBuffer +function projectedfslib.h PrjFillDirEntryBuffer2 +function projectedfslib.h PrjFreeAlignedBuffer +function projectedfslib.h PrjGetOnDiskFileState +function projectedfslib.h PrjGetVirtualizationInstanceInfo +function projectedfslib.h PrjMarkDirectoryAsPlaceholder +function projectedfslib.h PrjStartVirtualizing +function projectedfslib.h PrjStopVirtualizing +function projectedfslib.h PrjUpdateFileIfNeeded +function projectedfslib.h PrjWriteFileData +function projectedfslib.h PrjWritePlaceholderInfo +function projectedfslib.h PrjWritePlaceholderInfo2 + +# Desktop App Technologies > Data Access and Storage > Windows Projected File System > Projected File System Reference > Projected File System structures +struct projectedfslib.h PRJ_CALLBACK_DATA +struct projectedfslib.h PRJ_CALLBACKS +struct projectedfslib.h PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS +struct projectedfslib.h PRJ_FILE_BASIC_INFO +struct projectedfslib.h PRJ_NOTIFICATION_MAPPING +struct projectedfslib.h PRJ_NOTIFICATION_PARAMETERS +struct projectedfslib.h PRJ_PLACEHOLDER_INFO +struct projectedfslib.h PRJ_PLACEHOLDER_VERSION_INFO +struct projectedfslib.h PRJ_STARTVIRTUALIZING_OPTIONS +struct projectedfslib.h PRJ_VIRTUALIZATION_INSTANCE_INFO + +# Desktop App Technologies > Data Access and Storage > Windows Projected File System > Projected File System Reference > Projected File System enumerations +enum projectedfslib.h PRJ_CALLBACK_DATA_FLAGS +enum projectedfslib.h PRJ_COMPLETE_COMMAND_TYPE +enum projectedfslib.h PRJ_FILE_STATE +enum projectedfslib.h PRJ_NOTIFICATION +enum projectedfslib.h PRJ_NOTIFY_TYPES +enum projectedfslib.h PRJ_PLACEHOLDER_ID +enum projectedfslib.h PRJ_STARTVIRTUALIZING_FLAGS +enum projectedfslib.h PRJ_UPDATE_FAILURE_CAUSES +enum projectedfslib.h PRJ_UPDATE_TYPES + +# Desktop App Technologies > Data Access and Storage > Volume Shadow Copy Service > Volume Shadow Copy Reference > Volume Shadow Copy API Classes +class vswriter.h CVssWriter +class vswriter.h CVssWriterEx +class vswriter.h CVssWriterEx2 + +# Desktop App Technologies > Data Access and Storage > Volume Shadow Copy Service > Volume Shadow Copy Reference > Volume Shadow Copy API Enumerations +enum vsmgmt.h VSS_MGMT_OBJECT_TYPE +enum vsmgmt.h VSS_PROTECTION_FAULT +enum vsmgmt.h VSS_PROTECTION_LEVEL +enum vss.h _VSS_HARDWARE_OPTIONS +enum vss.h _VSS_PROVIDER_CAPABILITIES +enum vss.h _VSS_SNAPSHOT_CONTEXT +enum vss.h _VSS_VOLUME_SNAPSHOT_ATTRIBUTES +enum vss.h VSS_APPLICATION_LEVEL +enum vss.h VSS_BACKUP_SCHEMA +enum vss.h VSS_BACKUP_TYPE +enum vss.h VSS_FILE_SPEC_BACKUP_TYPE +enum vss.h VSS_OBJECT_TYPE +enum vss.h VSS_PROVIDER_TYPE +enum vss.h VSS_RECOVERY_OPTIONS +enum vss.h VSS_RESTORE_TYPE +enum vss.h VSS_ROLLFORWARD_TYPE +enum vss.h VSS_SNAPSHOT_COMPATIBILITY +enum vss.h VSS_SNAPSHOT_PROPERTY_ID +enum vss.h VSS_SNAPSHOT_STATE +enum vss.h VSS_WRITER_STATE +enum vswriter.h VSS_ALTERNATE_WRITER_STATE +enum vswriter.h VSS_COMPONENT_FLAGS +enum vswriter.h VSS_COMPONENT_TYPE +enum vswriter.h VSS_FILE_RESTORE_STATUS +enum vswriter.h VSS_RESTORE_TARGET +enum vswriter.h VSS_RESTOREMETHOD_ENUM +enum vswriter.h VSS_SOURCE_TYPE +enum vswriter.h VSS_SUBSCRIBE_MASK +enum vswriter.h VSS_USAGE_TYPE +enum vswriter.h VSS_WRITERRESTORE_ENUM + +# Desktop App Technologies > Data Access and Storage > Volume Shadow Copy Service > Volume Shadow Copy Reference > Volume Shadow Copy API Functions +function vsbackup.h CreateVssBackupComponents +function vsbackup.h CreateVssExamineWriterMetadata +function vsbackup.h IsVolumeSnapshotted +function vsbackup.h ShouldBlockRevert +function vsbackup.h VssFreeSnapshotProperties +function vswriter.h CreateVssExpressWriter +function vswriter.h CreateWriter +function vswriter.h CreateWriterEx + +# Desktop App Technologies > Data Access and Storage > Volume Shadow Copy Service > Volume Shadow Copy Reference > Volume Shadow Copy API Interfaces +class vsbackup.h IVssBackupComponents +class vsbackup.h IVssBackupComponentsEx +class vsbackup.h IVssBackupComponentsEx2 +class vsbackup.h IVssBackupComponentsEx3 +class vsbackup.h IVssBackupComponentsEx4 +class vsbackup.h IVssExamineWriterMetadata +class vsbackup.h IVssExamineWriterMetadataEx +class vsbackup.h IVssExamineWriterMetadataEx2 +class vsbackup.h IVssWMComponent +class vsbackup.h IVssWriterComponentsExt +class vswriter.h IVssComponent +class vswriter.h IVssComponentEx +class vswriter.h IVssComponentEx2 +class vswriter.h IVssCreateExpressWriterMetadata +class vswriter.h IVssCreateWriterMetadata +class vswriter.h IVssCreateWriterMetadataEx +class vswriter.h IVssExpressWriter +class vswriter.h IVssWMDependency +class vswriter.h IVssWMFiledesc +class vswriter.h IVssWriterComponents +interface vsadmin.h IVssAdmin +interface vsmgmt.h IVssDifferentialSoftwareSnapshotMgmt +interface vsmgmt.h IVssDifferentialSoftwareSnapshotMgmt2 +interface vsmgmt.h IVssDifferentialSoftwareSnapshotMgmt3 +interface vsmgmt.h IVssEnumMgmtObject +interface vsmgmt.h IVssSnapshotMgmt +interface vsmgmt.h IVssSnapshotMgmt2 +interface vsprov.h IVssFileShareSnapshotProvider +interface vsprov.h IVssHardwareSnapshotProvider +interface vsprov.h IVssHardwareSnapshotProviderEx +interface vsprov.h IVssProviderCreateSnapshotSet +interface vsprov.h IVssProviderNotifications +interface vsprov.h IVssSoftwareSnapshotProvider +interface vss.h IVssAsync +interface vss.h IVssEnumObject + +# Desktop App Technologies > Data Access and Storage > Volume Shadow Copy Service > Volume Shadow Copy Reference > Volume Shadow Copy API Structures +struct vsbackup.h VSS_COMPONENTINFO +struct vsmgmt.h VSS_DIFF_AREA_PROP +struct vsmgmt.h VSS_DIFF_VOLUME_PROP +struct vsmgmt.h VSS_MGMT_OBJECT_PROP +struct vsmgmt.h VSS_VOLUME_PROP +struct vsmgmt.h VSS_VOLUME_PROTECTION_INFO +struct vss.h VSS_OBJECT_PROP +struct vss.h VSS_PROVIDER_PROP +struct vss.h VSS_SNAPSHOT_PROP + +# Desktop App Technologies > Devices > Communications Resources > Communications Reference > Communications Functions +function winbase.h BuildCommDCB =buildcommdcba +function winbase.h BuildCommDCBAndTimeouts =buildcommdcbandtimeoutsa +function winbase.h ClearCommBreak +function winbase.h ClearCommError +function winbase.h CommConfigDialog =commconfigdialoga +function winbase.h EscapeCommFunction +function winbase.h GetCommConfig +function winbase.h GetCommMask +function winbase.h GetCommModemStatus +function winbase.h GetCommProperties +function winbase.h GetCommState +function winbase.h GetCommTimeouts +function winbase.h GetDefaultCommConfig =getdefaultcommconfiga +function winbase.h PurgeComm +function winbase.h SetCommBreak +function winbase.h SetCommConfig +function winbase.h SetCommMask +function winbase.h SetCommState +function winbase.h SetCommTimeouts +function winbase.h SetDefaultCommConfig =setdefaultcommconfiga +function winbase.h SetupComm +function winbase.h TransmitCommChar +function winbase.h WaitCommEvent + +# Desktop App Technologies > Devices > Communications Resources > Communications Reference > Communications Control Codes +control-code winioctl.h IOCTL_SERIAL_LSRMST_INSERT + +# Desktop App Technologies > Devices > Communications Resources > Communications Reference > Communications Structures +struct mcx.h MODEMDEVCAPS +struct mcx.h MODEMSETTINGS +struct winbase.h COMMCONFIG +struct winbase.h COMMPROP +struct winbase.h COMMTIMEOUTS +struct winbase.h COMSTAT +struct winbase.h DCB + +# Desktop App Technologies > Devices > Device Management > Device Management Reference > Device Management Functions +function ioapiset.h DeviceIoControl +function storprop.h CdromDisableDigitalPlayback +function storprop.h CdromEnableDigitalPlayback +function storprop.h CdromIsDigitalPlaybackEnabled +function storprop.h CdromKnownGoodDigitalPlayback +function winuser.h RegisterDeviceNotification =registerdevicenotificationa +function winuser.h UnregisterDeviceNotification + +# Desktop App Technologies > Devices > Device Management > Device Management Reference > Device Management Control Codes +control-code winioctl.h IOCTL_CHANGER_EXCHANGE_MEDIUM +control-code winioctl.h IOCTL_CHANGER_GET_ELEMENT_STATUS +control-code winioctl.h IOCTL_CHANGER_GET_PARAMETERS +control-code winioctl.h IOCTL_CHANGER_GET_PRODUCT_DATA +control-code winioctl.h IOCTL_CHANGER_GET_STATUS +control-code winioctl.h IOCTL_CHANGER_INITIALIZE_ELEMENT_STATUS +control-code winioctl.h IOCTL_CHANGER_MOVE_MEDIUM +control-code winioctl.h IOCTL_CHANGER_QUERY_VOLUME_TAGS +control-code winioctl.h IOCTL_CHANGER_REINITIALIZE_TRANSPORT +control-code winioctl.h IOCTL_CHANGER_SET_ACCESS +control-code winioctl.h IOCTL_CHANGER_SET_POSITION +control-code winioctl.h IOCTL_STORAGE_CHECK_VERIFY +control-code winioctl.h IOCTL_STORAGE_EJECT_MEDIA +control-code winioctl.h IOCTL_STORAGE_EJECTION_CONTROL +control-code winioctl.h IOCTL_STORAGE_GET_DEVICE_NUMBER +control-code winioctl.h IOCTL_STORAGE_GET_HOTPLUG_INFO +control-code winioctl.h IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER +control-code winioctl.h IOCTL_STORAGE_GET_MEDIA_TYPES +control-code winioctl.h IOCTL_STORAGE_GET_MEDIA_TYPES_EX +control-code winioctl.h IOCTL_STORAGE_LOAD_MEDIA +control-code winioctl.h IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES +control-code winioctl.h IOCTL_STORAGE_MCN_CONTROL +control-code winioctl.h IOCTL_STORAGE_MEDIA_REMOVAL +control-code winioctl.h IOCTL_STORAGE_READ_CAPACITY +control-code winioctl.h IOCTL_STORAGE_SET_HOTPLUG_INFO + +# Desktop App Technologies > Devices > Device Management > Device Management Reference > Device Management Structures +struct dbt.h _DEV_BROADCAST_USERDEFINED +struct dbt.h DEV_BROADCAST_DEVICEINTERFACE =dev_broadcast_deviceinterface_a +struct dbt.h DEV_BROADCAST_HANDLE +struct dbt.h DEV_BROADCAST_HDR +struct dbt.h DEV_BROADCAST_OEM +struct dbt.h DEV_BROADCAST_PORT =dev_broadcast_port_a +struct dbt.h DEV_BROADCAST_VOLUME +struct winioctl.h CHANGER_ELEMENT +struct winioctl.h CHANGER_ELEMENT_LIST +struct winioctl.h CHANGER_ELEMENT_STATUS +struct winioctl.h CHANGER_ELEMENT_STATUS_EX +struct winioctl.h CHANGER_EXCHANGE_MEDIUM +struct winioctl.h CHANGER_INITIALIZE_ELEMENT_STATUS +struct winioctl.h CHANGER_MOVE_MEDIUM +struct winioctl.h CHANGER_PRODUCT_DATA +struct winioctl.h CHANGER_READ_ELEMENT_STATUS +struct winioctl.h CHANGER_SEND_VOLUME_TAG_INFORMATION +struct winioctl.h CHANGER_SET_ACCESS +struct winioctl.h CHANGER_SET_POSITION +struct winioctl.h CLASS_MEDIA_CHANGE_CONTEXT +struct winioctl.h DEVICE_DATA_SET_LB_PROVISIONING_STATE +struct winioctl.h DEVICE_DATA_SET_RANGE +struct winioctl.h DEVICE_DATA_SET_REPAIR_PARAMETERS +struct winioctl.h DEVICE_DSM_NOTIFICATION_PARAMETERS +struct winioctl.h DEVICE_DSM_OFFLOAD_READ_PARAMETERS +struct winioctl.h DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS +struct winioctl.h DEVICE_MANAGE_DATA_SET_ATTRIBUTES +struct winioctl.h DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT +struct winioctl.h DEVICE_MEDIA_INFO +struct winioctl.h GET_CHANGER_PARAMETERS +struct winioctl.h GET_MEDIA_TYPES +struct winioctl.h PREVENT_MEDIA_REMOVAL +struct winioctl.h READ_ELEMENT_ADDRESS_INFO +struct winioctl.h STORAGE_DEVICE_NUMBER +struct winioctl.h STORAGE_HOTPLUG_INFO +struct winioctl.h STORAGE_OFFLOAD_READ_OUTPUT +struct winioctl.h STORAGE_OFFLOAD_TOKEN +struct winioctl.h STORAGE_OFFLOAD_WRITE_OUTPUT + +# Desktop App Technologies > Devices > Device Management > Device Management Reference > Device Management Enumeration Types +enum winioctl.h ELEMENT_TYPE +enum winioctl.h STORAGE_BUS_TYPE +enum winioctl.h STORAGE_MEDIA_TYPE + +# Desktop App Technologies > Devices > PWM API > PWM Control Codes +control-code pwm.h IOCTL_PWM_CONTROLLER_GET_ACTUAL_PERIOD +control-code pwm.h IOCTL_PWM_CONTROLLER_GET_INFO +control-code pwm.h IOCTL_PWM_CONTROLLER_SET_DESIRED_PERIOD +control-code pwm.h IOCTL_PWM_PIN_GET_ACTIVE_DUTY_CYCLE_PERCENTAGE +control-code pwm.h IOCTL_PWM_PIN_GET_POLARITY +control-code pwm.h IOCTL_PWM_PIN_IS_STARTED +control-code pwm.h IOCTL_PWM_PIN_SET_ACTIVE_DUTY_CYCLE_PERCENTAGE +control-code pwm.h IOCTL_PWM_PIN_SET_POLARITY +control-code pwm.h IOCTL_PWM_PIN_START +control-code pwm.h IOCTL_PWM_PIN_STOP + +# Desktop App Technologies > Devices > PWM API > PWM Structures +struct pwm.h PWM_CONTROLLER_INFO +struct pwm.h PWM_CONTROLLER_SET_DESIRED_PERIOD_INPUT +struct pwm.h PWM_CONTROLLER_SET_DESIRED_PERIOD_OUTPUT +struct pwm.h PWM_PIN_SET_POLARITY_INPUT + +# Desktop App Technologies > Devices > PWM API > PWM Enumeration Types +enum pwm.h PWM_POLARITY + +# Desktop App Technologies > Devices > Device Access API > Device Access API C++ Programming Reference > Functions +function deviceaccess.h CreateDeviceAccessInstance + +# Desktop App Technologies > Devices > Device Access API > Device Access API C++ Programming Reference > COM Interfaces +interface deviceaccess.h ICreateDeviceAccessAsync +interface deviceaccess.h IDeviceIoControl +interface deviceaccess.h IDeviceRequestCompletionCallback + +# Desktop App Technologies > Devices > Location API > Location API C++ Programming Reference > COM Interfaces +interface locationapi.h ICivicAddressReport +interface locationapi.h IDefaultLocation +interface locationapi.h ILatLongReport +interface locationapi.h ILocation +interface locationapi.h ILocationEvents +interface locationapi.h ILocationPower +interface locationapi.h ILocationReport + +# Desktop App Technologies > Devices > Location API > Location API C++ Programming Reference > Structures and Enumeration Types +enum locationapi.h LOCATION_REPORT_STATUS + +# Desktop App Technologies > Devices > Sensor API > Sensor API Programming Reference > COM Interfaces +interface sensorsapi.h ILocationPermissions +interface sensorsapi.h ISensor +interface sensorsapi.h ISensorCollection +interface sensorsapi.h ISensorDataReport +interface sensorsapi.h ISensorEvents +interface sensorsapi.h ISensorManager +interface sensorsapi.h ISensorManagerEvents + +# Desktop App Technologies > Devices > Sensor API > Sensor API Programming Reference > Enumeration Types +enum sensorsapi.h MagnetometerAccuracy +enum sensorsapi.h SensorConnectionType +enum sensorsapi.h SensorState + +# Desktop App Technologies > Devices > UPnP APIs > Control Point API > Control Point API Reference +interface upnp.h IUPnPAddressFamilyControl +interface upnp.h IUPnPAsyncResult +interface upnp.h IUPnPDescriptionDocument +interface upnp.h IUPnPDescriptionDocumentCallback +interface upnp.h IUPnPDevice +interface upnp.h IUPnPDeviceDocumentAccess +interface upnp.h IUPnPDeviceDocumentAccessEx +interface upnp.h IUPnPDeviceFinder +interface upnp.h IUPnPDeviceFinderAddCallbackWithInterface +interface upnp.h IUPnPDeviceFinderCallback +interface upnp.h IUPnPDevices +interface upnp.h IUPnPHttpHeaderControl +interface upnp.h IUPnPService +interface upnp.h IUPnPServiceAsync +interface upnp.h IUPnPServiceCallback +interface upnp.h IUPnPServiceDocumentAccess +interface upnp.h IUPnPServiceEnumProperty +interface upnp.h IUPnPServices + +# Desktop App Technologies > Devices > UPnP APIs > Device Host API > Device Host API Reference +interface upnphost.h IUPnPDeviceControl +interface upnphost.h IUPnPDeviceProvider +interface upnphost.h IUPnPEventSink +interface upnphost.h IUPnPEventSource +interface upnphost.h IUPnPRegistrar +interface upnphost.h IUPnPRemoteEndpointInfo +interface upnphost.h IUPnPReregistrar + +# Desktop App Technologies > Devices > Web Services on Devices > Web Services on Devices Reference > Web Services on Devices Enumerations +enum wsdbase.h WSD_CONFIG_PARAM_TYPE +enum wsdbase.h WSDUdpMessageType +enum wsdtypes.h WSD_PROTOCOL_TYPE +enum wsdtypes.h WSDEventType + +# Desktop App Technologies > Devices > Web Services on Devices > Web Services on Devices Reference > Web Services on Devices Functions +callback wsdtypes.h PWSD_SOAP_MESSAGE_HANDLER +callback wsdtypes.h WSD_STUB_FUNCTION +function wsdattachment.h WSDCreateOutboundAttachment +function wsdbase.h WSDCreateHttpAddress +function wsdbase.h WSDCreateHttpMessageParameters +function wsdbase.h WSDCreateUdpAddress +function wsdbase.h WSDCreateUdpMessageParameters +function wsdclient.h WSDCreateDeviceProxy +function wsdclient.h WSDCreateDeviceProxy2 +function wsdclient.h WSDCreateDeviceProxyAdvanced +function wsddisco.h WSDCreateDiscoveryProvider +function wsddisco.h WSDCreateDiscoveryProvider2 +function wsddisco.h WSDCreateDiscoveryPublisher +function wsddisco.h WSDCreateDiscoveryPublisher2 +function wsdhost.h WSDCreateDeviceHost +function wsdhost.h WSDCreateDeviceHost2 +function wsdhost.h WSDCreateDeviceHostAdvanced +function wsdutil.h WSDAllocateLinkedMemory +function wsdutil.h WSDAttachLinkedMemory +function wsdutil.h WSDDetachLinkedMemory +function wsdutil.h WSDFreeLinkedMemory +function wsdutil.h WSDGenerateFault +function wsdutil.h WSDGenerateFaultEx +function wsdutil.h WSDGetConfigurationOption +function wsdutil.h WSDSetConfigurationOption +function wsdutil.h WSDUriDecode +function wsdutil.h WSDUriEncode +function wsdutil.h WSDXMLAddChild +function wsdutil.h WSDXMLAddSibling +function wsdutil.h WSDXMLBuildAnyForSingleElement +function wsdutil.h WSDXMLCleanupElement +function wsdutil.h WSDXMLGetValueFromAny +function wsdxml.h WSDXMLCreateContext +function wsdxml.h WSDXMLGetNameFromBuiltinNamespace + +# Desktop App Technologies > Devices > Web Services on Devices > Web Services on Devices Reference > Web Services on Devices Interfaces +interface wsdattachment.h IWSDAttachment +interface wsdattachment.h IWSDInboundAttachment +interface wsdattachment.h IWSDOutboundAttachment +interface wsdbase.h IWSDAddress +interface wsdbase.h IWSDHttpAddress +interface wsdbase.h IWSDHttpAuthParameters +interface wsdbase.h IWSDHttpMessageParameters +interface wsdbase.h IWSDMessageParameters +interface wsdbase.h IWSDSignatureProperty +interface wsdbase.h IWSDSSLClientCertificate +interface wsdbase.h IWSDTransportAddress +interface wsdbase.h IWSDUdpAddress +interface wsdbase.h IWSDUdpMessageParameters +interface wsdclient.h IWSDAsyncCallback +interface wsdclient.h IWSDAsyncResult +interface wsdclient.h IWSDDeviceProxy +interface wsdclient.h IWSDEndpointProxy +interface wsdclient.h IWSDEventingStatus +interface wsdclient.h IWSDMetadataExchange +interface wsdclient.h IWSDServiceProxy +interface wsdclient.h IWSDServiceProxyEventing +interface wsddisco.h IWSDiscoveredService +interface wsddisco.h IWSDiscoveryProvider +interface wsddisco.h IWSDiscoveryProviderNotify +interface wsddisco.h IWSDiscoveryPublisher +interface wsddisco.h IWSDiscoveryPublisherNotify +interface wsddisco.h IWSDScopeMatchingRule +interface wsdhost.h IWSDDeviceHost +interface wsdhost.h IWSDDeviceHostNotify +interface wsdhost.h IWSDServiceMessaging +interface wsdxml.h IWSDXMLContext + +# Desktop App Technologies > Devices > Web Services on Devices > Web Services on Devices Reference > Web Services on Devices Structures +struct wsdbase.h WSD_CONFIG_ADDRESSES +struct wsdbase.h WSD_CONFIG_PARAM +struct wsdbase.h WSD_SECURITY_CERT_VALIDATION +struct wsdbase.h WSD_SECURITY_SIGNATURE_VALIDATION +struct wsdbase.h WSDUdpRetransmitParams +struct wsdtypes.h REQUESTBODY_GetStatus +struct wsdtypes.h REQUESTBODY_Renew +struct wsdtypes.h REQUESTBODY_Subscribe +struct wsdtypes.h REQUESTBODY_Unsubscribe +struct wsdtypes.h RESPONSEBODY_GetMetadata +struct wsdtypes.h RESPONSEBODY_GetStatus +struct wsdtypes.h RESPONSEBODY_Renew +struct wsdtypes.h RESPONSEBODY_Subscribe +struct wsdtypes.h RESPONSEBODY_SubscriptionEnd +struct wsdtypes.h WSD_APP_SEQUENCE +struct wsdtypes.h WSD_BYE +struct wsdtypes.h WSD_ENDPOINT_REFERENCE +struct wsdtypes.h WSD_ENDPOINT_REFERENCE_LIST +struct wsdtypes.h WSD_EVENT +struct wsdtypes.h WSD_EVENTING_DELIVERY_MODE +struct wsdtypes.h WSD_EVENTING_DELIVERY_MODE_PUSH +struct wsdtypes.h WSD_EVENTING_EXPIRES +struct wsdtypes.h WSD_EVENTING_FILTER +struct wsdtypes.h WSD_EVENTING_FILTER_ACTION +struct wsdtypes.h WSD_HANDLER_CONTEXT +struct wsdtypes.h WSD_HEADER_RELATESTO +struct wsdtypes.h WSD_HELLO +struct wsdtypes.h WSD_HOST_METADATA +struct wsdtypes.h WSD_LOCALIZED_STRING +struct wsdtypes.h WSD_LOCALIZED_STRING_LIST +struct wsdtypes.h WSD_METADATA_SECTION +struct wsdtypes.h WSD_METADATA_SECTION_LIST +struct wsdtypes.h WSD_NAME_LIST +struct wsdtypes.h WSD_OPERATION +struct wsdtypes.h WSD_PORT_TYPE +struct wsdtypes.h WSD_PROBE +struct wsdtypes.h WSD_PROBE_MATCH +struct wsdtypes.h WSD_PROBE_MATCH_LIST +struct wsdtypes.h WSD_PROBE_MATCHES +struct wsdtypes.h WSD_REFERENCE_PARAMETERS +struct wsdtypes.h WSD_REFERENCE_PROPERTIES +struct wsdtypes.h WSD_RELATIONSHIP_METADATA +struct wsdtypes.h WSD_RESOLVE +struct wsdtypes.h WSD_RESOLVE_MATCH +struct wsdtypes.h WSD_RESOLVE_MATCHES +struct wsdtypes.h WSD_SCOPES +struct wsdtypes.h WSD_SERVICE_METADATA +struct wsdtypes.h WSD_SERVICE_METADATA_LIST +struct wsdtypes.h WSD_SOAP_FAULT +struct wsdtypes.h WSD_SOAP_FAULT_CODE +struct wsdtypes.h WSD_SOAP_FAULT_REASON +struct wsdtypes.h WSD_SOAP_FAULT_SUBCODE +struct wsdtypes.h WSD_SOAP_HEADER +struct wsdtypes.h WSD_SOAP_MESSAGE +struct wsdtypes.h WSD_SYNCHRONOUS_RESPONSE_CONTEXT +struct wsdtypes.h WSD_THIS_DEVICE_METADATA +struct wsdtypes.h WSD_THIS_MODEL_METADATA +struct wsdtypes.h WSD_UNKNOWN_LOOKUP +struct wsdtypes.h WSD_URI_LIST +struct wsdxml.h WSD_DATETIME +struct wsdxml.h WSD_DURATION +struct wsdxmldom.h WSDXML_ATTRIBUTE +struct wsdxmldom.h WSDXML_ELEMENT +struct wsdxmldom.h WSDXML_ELEMENT_LIST +struct wsdxmldom.h WSDXML_NAME +struct wsdxmldom.h WSDXML_NAMESPACE +struct wsdxmldom.h WSDXML_NODE +struct wsdxmldom.h WSDXML_PREFIX_MAPPING +struct wsdxmldom.h WSDXML_TEXT +struct wsdxmldom.h WSDXML_TYPE + +# Desktop App Technologies > Windows Media Device Manager 11 SDK > Programming Reference > Interfaces for Applications +interface mswmdm.h IWMDeviceManager Interface =iwmdevicemanager +interface mswmdm.h IWMDeviceManager2 Interface =iwmdevicemanager2 +interface mswmdm.h IWMDeviceManager3 Interface =iwmdevicemanager3 +interface mswmdm.h IWMDMDevice Interface =iwmdmdevice +interface mswmdm.h IWMDMDevice2 Interface =iwmdmdevice2 +interface mswmdm.h IWMDMDevice3 Interface =iwmdmdevice3 +interface mswmdm.h IWMDMDeviceControl Interface =iwmdmdevicecontrol +interface mswmdm.h IWMDMDeviceSession Interface =iwmdmdevicesession +interface mswmdm.h IWMDMEnumDevice Interface =iwmdmenumdevice +interface mswmdm.h IWMDMEnumStorage Interface =iwmdmenumstorage +interface mswmdm.h IWMDMMetaData Interface =iwmdmmetadata +interface mswmdm.h IWMDMNotification Interface =iwmdmnotification +interface mswmdm.h IWMDMObjectInfo Interface =iwmdmobjectinfo +interface mswmdm.h IWMDMOperation Interface =iwmdmoperation +interface mswmdm.h IWMDMOperation2 Interface =iwmdmoperation2 +interface mswmdm.h IWMDMOperation3 Interface =iwmdmoperation3 +interface mswmdm.h IWMDMProgress Interface =iwmdmprogress +interface mswmdm.h IWMDMProgress2 Interface =iwmdmprogress2 +interface mswmdm.h IWMDMProgress3 Interface =iwmdmprogress3 +interface mswmdm.h IWMDMRevoked Interface =iwmdmrevoked +interface mswmdm.h IWMDMStorage Interface =iwmdmstorage +interface mswmdm.h IWMDMStorage2 Interface =iwmdmstorage2 +interface mswmdm.h IWMDMStorage3 Interface =iwmdmstorage3 +interface mswmdm.h IWMDMStorage4 Interface =iwmdmstorage4 +interface mswmdm.h IWMDMStorageControl Interface =iwmdmstoragecontrol +interface mswmdm.h IWMDMStorageControl2 Interface =iwmdmstoragecontrol2 +interface mswmdm.h IWMDMStorageControl3 Interface =iwmdmstoragecontrol3 +interface mswmdm.h IWMDMStorageGlobals Interface =iwmdmstorageglobals + +# Desktop App Technologies > Windows Media Device Manager 11 SDK > Programming Reference > Interfaces for Service Providers +interface mswmdm.h IMDServiceProvider Interface =imdserviceprovider +interface mswmdm.h IMDServiceProvider2 Interface =imdserviceprovider2 +interface mswmdm.h IMDServiceProvider3 Interface =imdserviceprovider3 +interface mswmdm.h IMDSPDevice Interface =imdspdevice +interface mswmdm.h IMDSPDevice2 Interface =imdspdevice2 +interface mswmdm.h IMDSPDevice3 Interface =imdspdevice3 +interface mswmdm.h IMDSPDeviceControl Interface =imdspdevicecontrol +interface mswmdm.h IMDSPDirectTransfer Interface =imdspdirecttransfer +interface mswmdm.h IMDSPEnumDevice Interface =imdspenumdevice +interface mswmdm.h IMDSPEnumStorage Interface =imdspenumstorage +interface mswmdm.h IMDSPObject Interface =imdspobject +interface mswmdm.h IMDSPObject2 Interface =imdspobject2 +interface mswmdm.h IMDSPObjectInfo Interface =imdspobjectinfo +interface mswmdm.h IMDSPRevoked Interface =imdsprevoked +interface mswmdm.h IMDSPStorage Interface =imdspstorage +interface mswmdm.h IMDSPStorage2 Interface =imdspstorage2 +interface mswmdm.h IMDSPStorage3 Interface =imdspstorage3 +interface mswmdm.h IMDSPStorage4 Interface =imdspstorage4 +interface mswmdm.h IMDSPStorageGlobals Interface =imdspstorageglobals + +# Desktop App Technologies > Windows Media Device Manager 11 SDK > Programming Reference > Interfaces for Service Providers and Applications +interface mswmdm.h IComponentAuthenticate Interface =icomponentauthenticate +interface wmdmlog.h IWMDMLogger Interface =iwmdmlogger + +# Desktop App Technologies > Windows Media Device Manager 11 SDK > Programming Reference > Interfaces for Secure Content Providers +interface mswmdm.h ISCPSecureAuthenticate Interface =iscpsecureauthenticate +interface mswmdm.h ISCPSecureAuthenticate2 Interface =iscpsecureauthenticate2 +interface mswmdm.h ISCPSecureExchange Interface =iscpsecureexchange +interface mswmdm.h ISCPSecureExchange2 Interface =iscpsecureexchange2 +interface mswmdm.h ISCPSecureExchange3 Interface =iscpsecureexchange3 +interface mswmdm.h ISCPSecureQuery Interface =iscpsecurequery +interface mswmdm.h ISCPSecureQuery2 Interface =iscpsecurequery2 +interface mswmdm.h ISCPSecureQuery3 Interface =iscpsecurequery3 +interface mswmdm.h ISCPSession Interface =iscpsession + +# Desktop App Technologies > Windows Media Device Manager 11 SDK > Programming Reference > Structures +struct mtpext.h MTP_COMMAND_DATA_IN +struct mtpext.h MTP_COMMAND_DATA_OUT + +# Desktop App Technologies > Windows Portable Devices > WPD Application Programming Interface > Programming Reference > Client Interfaces +interface portabledeviceapi.h IEnumPortableDeviceObjectIDs +interface portabledeviceapi.h IPortableDevice +interface portabledeviceapi.h IPortableDeviceCapabilities +interface portabledeviceapi.h IPortableDeviceContent +interface portabledeviceapi.h IPortableDeviceContent2 +interface portabledeviceapi.h IPortableDeviceDataStream +interface portabledeviceapi.h IPortableDeviceEventCallback +interface portabledeviceapi.h IPortableDeviceManager +interface portabledeviceapi.h IPortableDeviceProperties +interface portabledeviceapi.h IPortableDevicePropertiesBulk +interface portabledeviceapi.h IPortableDevicePropertiesBulkCallback +interface portabledeviceapi.h IPortableDeviceResources +interface portabledeviceapi.h IPortableDeviceService +interface portabledeviceapi.h IPortableDeviceServiceCapabilities +interface portabledeviceapi.h IPortableDeviceServiceManager +interface portabledeviceapi.h IPortableDeviceServiceMethodCallback +interface portabledeviceapi.h IPortableDeviceServiceMethods +interface portabledeviceapi.h IPortableDeviceUnitsStream + +# Desktop App Technologies > Windows Portable Devices > WPD Application Programming Interface > Programming Reference > Connection Interfaces +interface portabledeviceconnectapi.h IPortableDeviceConnector + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Application Recovery and Restart > Application Recovery and Restart Reference > Application Recovery and Restart Functions +function winbase.h ApplicationRecoveryFinished +function winbase.h ApplicationRecoveryInProgress +function winbase.h GetApplicationRecoveryCallback +function winbase.h GetApplicationRestartSettings +function winbase.h RegisterApplicationRecoveryCallback +function winbase.h RegisterApplicationRestart +function winbase.h UnregisterApplicationRecoveryCallback +function winbase.h UnregisterApplicationRestart + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Error Handling > Error Handling Reference > Error Handling Functions +function errhandlingapi.h GetErrorMode +function errhandlingapi.h GetLastError +function errhandlingapi.h GetThreadErrorMode +function errhandlingapi.h SetErrorMode +function errhandlingapi.h SetLastError +function errhandlingapi.h SetThreadErrorMode +function utilapiset.h Beep +function winbase.h FatalAppExit =fatalexit +function winbase.h FormatMessage +function winnt.h RtlLookupFunctionEntry +function winnt.h RtlPcToFileHeader +function winnt.h RtlUnwind +function winnt.h RtlUnwind2 +function winnt.h RtlUnwindEx +function winnt.h RtlVirtualUnwind +function winternl.h RtlNtStatusToDosError +function winuser.h FlashWindow +function winuser.h FlashWindowEx +function winuser.h MessageBeep +function winuser.h SetLastErrorEx + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Error Handling > Error Handling Reference > Error Handling Macros +function winnt.h C_ASSERT + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Error Handling > Error Handling Reference > Error Handling Structures +struct winuser.h FLASHWINFO + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Basic Debugging > Debugging Reference > Debugging Functions +function debugapi.h CheckRemoteDebuggerPresent +function debugapi.h ContinueDebugEvent +function debugapi.h DebugActiveProcess +function debugapi.h DebugActiveProcessStop +function debugapi.h DebugBreak +function debugapi.h IsDebuggerPresent +function debugapi.h OutputDebugString =outputdebugstringw +function debugapi.h WaitForDebugEvent +function debugapi.h WaitForDebugEventEx +function memoryapi.h ReadProcessMemory +function memoryapi.h WriteProcessMemory +function processthreadsapi.h FlushInstructionCache +function processthreadsapi.h GetThreadContext +function processthreadsapi.h SetThreadContext +function processthreadsapi.h Wow64GetThreadContext =getthreadcontext +function processthreadsapi.h Wow64SetThreadContext =setthreadcontext +function winbase.h DebugBreakProcess +function winbase.h DebugSetProcessKillOnExit +function winbase.h FatalExit +function winbase.h GetThreadSelectorEntry +function winbase.h Wow64GetThreadSelectorEntry + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Basic Debugging > Debugging Reference > Debugging Structures +struct minwinbase.h CREATE_PROCESS_DEBUG_INFO +struct minwinbase.h CREATE_THREAD_DEBUG_INFO +struct minwinbase.h DEBUG_EVENT +struct minwinbase.h EXCEPTION_DEBUG_INFO +struct minwinbase.h EXIT_PROCESS_DEBUG_INFO +struct minwinbase.h EXIT_THREAD_DEBUG_INFO +struct minwinbase.h LOAD_DLL_DEBUG_INFO +struct minwinbase.h OUTPUT_DEBUG_STRING_INFO +struct minwinbase.h RIP_INFO +struct minwinbase.h UNLOAD_DLL_DEBUG_INFO +struct winnt.h CONTEXT =arm64_nt_context +struct winnt.h LDT_ENTRY +struct winnt.h WOW64_CONTEXT +struct winnt.h WOW64_FLOATING_SAVE_AREA +struct winnt.h WOW64_LDT_ENTRY + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Debug Help Library > DbgHelp Reference > DbgHelp Enumerations +enum dbghelp.h IMAGEHLP_EXTENDED_OPTIONS +enum dbghelp.h IMAGEHLP_SYMBOL_TYPE_INFO +enum minidumpapiset.h MINIDUMP_CALLBACK_TYPE +enum minidumpapiset.h MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE +enum minidumpapiset.h MINIDUMP_SECONDARY_FLAGS +enum minidumpapiset.h MINIDUMP_STREAM_TYPE +enum minidumpapiset.h MINIDUMP_TYPE +enum minidumpapiset.h MODULE_WRITE_FLAGS +enum minidumpapiset.h THREAD_WRITE_FLAGS + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Debug Help Library > DbgHelp Reference > DbgHelp Functions +callback dbghelp.h EnumDirTreeProc =penumdirtree_callback +callback dbghelp.h EnumerateLoadedModulesProc64 =penumloaded_modules_callback +callback dbghelp.h FindDebugInfoFileProc =pfind_debug_file_callback +callback dbghelp.h FindExecutableImageProc =pfind_exe_file_callback +callback dbghelp.h FunctionTableAccessProc64 =pfunction_table_access_routine +callback dbghelp.h GetModuleBaseProc64 =pget_module_base_routine +callback dbghelp.h PENUMSOURCEFILETOKENSCALLBACK +callback dbghelp.h ReadProcessMemoryProc64 =pread_process_memory_routine +callback dbghelp.h SymEnumerateModulesProc64 =psym_enummodules_callback +callback dbghelp.h SymEnumerateSymbolsProc64 =psym_enumsymbols_callback +callback dbghelp.h SymEnumLinesProc =psym_enumlines_callback +callback dbghelp.h SymEnumProcessesProc =psym_enumprocesses_callback +callback dbghelp.h SymEnumSourceFilesProc =psym_enumsourcefiles_callback +callback dbghelp.h SymEnumSymbolsProc =psym_enumeratesymbols_callback +callback dbghelp.h SymFindFileInPathProc =pfindfileinpathcallback +callback dbghelp.h SymRegisterCallbackProc64 =psymbol_registered_callback +callback dbghelp.h SymRegisterFunctionEntryCallbackProc64 =psymbol_funcentry_callback +callback dbghelp.h TranslateAddressProc64 =ptranslate_address_routine +callback minidumpapiset.h MiniDumpCallback =minidump_callback_routine +function dbghelp.h EnumDirTree +function dbghelp.h EnumerateLoadedModules64 =enumerateloadedmodules +function dbghelp.h EnumerateLoadedModulesEx +function dbghelp.h FindDebugInfoFile +function dbghelp.h FindDebugInfoFileEx +function dbghelp.h FindExecutableImage +function dbghelp.h FindExecutableImageEx +function dbghelp.h GetSymLoadError +function dbghelp.h GetTimestampForLoadedLibrary +function dbghelp.h ImageDirectoryEntryToData +function dbghelp.h ImageDirectoryEntryToDataEx +function dbghelp.h ImagehlpApiVersion +function dbghelp.h ImagehlpApiVersionEx +function dbghelp.h ImageNtHeader +function dbghelp.h ImageRvaToSection +function dbghelp.h ImageRvaToVa +function dbghelp.h MakeSureDirectoryPathExists +function dbghelp.h MapDebugInformation +function dbghelp.h SearchTreeForFile +function dbghelp.h SetSymLoadError +function dbghelp.h StackWalk64 =stackwalk +function dbghelp.h StackWalkEx +function dbghelp.h SymAddrIncludeInlineTrace +function dbghelp.h SymAddSourceStream +function dbghelp.h SymAddSymbol +function dbghelp.h SymCleanup +function dbghelp.h SymCompareInlineTrace +function dbghelp.h SymDeleteSymbol +function dbghelp.h SymEnumerateModules64 =symenumeratemodules +function dbghelp.h SymEnumerateSymbols64 =symenumeratesymbols +function dbghelp.h SymEnumLines +function dbghelp.h SymEnumProcesses +function dbghelp.h SymEnumSourceFiles +function dbghelp.h SymEnumSourceFileTokens +function dbghelp.h SymEnumSourceLines +function dbghelp.h SymEnumSymbols +function dbghelp.h SymEnumSymbolsEx +function dbghelp.h SymEnumSymbolsForAddr +function dbghelp.h SymEnumTypes +function dbghelp.h SymEnumTypesByName +function dbghelp.h SymFindDebugInfoFile +function dbghelp.h SymFindExecutableImage +function dbghelp.h SymFindFileInPath +function dbghelp.h SymFromAddr +function dbghelp.h SymFromIndex +function dbghelp.h SymFromInlineContext +function dbghelp.h SymFromName +function dbghelp.h SymFromToken +function dbghelp.h SymFunctionTableAccess64 =symfunctiontableaccess +function dbghelp.h SymFunctionTableAccess64AccessRoutines +function dbghelp.h SymGetExtendedOption +function dbghelp.h SymGetFileLineOffsets64 +function dbghelp.h SymGetHomeDirectory +function dbghelp.h SymGetLineFromAddr64 =symgetlinefromaddr +function dbghelp.h SymGetLineFromInlineContext +function dbghelp.h SymGetLineFromName64 =symgetlinefromname +function dbghelp.h SymGetLineNext64 =symgetlinenext +function dbghelp.h SymGetLinePrev64 =symgetlineprev +function dbghelp.h SymGetModuleBase64 =symgetmodulebase +function dbghelp.h SymGetModuleInfo64 =symgetmoduleinfo +function dbghelp.h SymGetOmaps +function dbghelp.h SymGetOptions +function dbghelp.h SymGetScope +function dbghelp.h SymGetSearchPath +function dbghelp.h SymGetSourceFile +function dbghelp.h SymGetSourceFileChecksum +function dbghelp.h SymGetSourceFileFromToken +function dbghelp.h SymGetSourceFileToken +function dbghelp.h SymGetSourceVarFromToken +function dbghelp.h SymGetSymbolFile +function dbghelp.h SymGetSymFromAddr64 =symgetsymfromaddr +function dbghelp.h SymGetSymFromName64 =symgetsymfromname +function dbghelp.h SymGetSymNext64 =symgetsymnext +function dbghelp.h SymGetSymPrev64 =symgetsymprev +function dbghelp.h SymGetTypeFromName +function dbghelp.h SymGetTypeInfo +function dbghelp.h SymGetTypeInfoEx +function dbghelp.h SymInitialize +function dbghelp.h SymLoadModule64 =symloadmodule +function dbghelp.h SymLoadModuleEx +function dbghelp.h SymMatchFileName +function dbghelp.h SymMatchString +function dbghelp.h SymNext +function dbghelp.h SymPrev +function dbghelp.h SymQueryInlineTrace +function dbghelp.h SymRefreshModuleList +function dbghelp.h SymRegisterCallback64 =symregistercallback +function dbghelp.h SymRegisterFunctionEntryCallback64 =symregisterfunctionentrycallback +function dbghelp.h SymSearch +function dbghelp.h SymSetContext +function dbghelp.h SymSetExtendedOption +function dbghelp.h SymSetHomeDirectory +function dbghelp.h SymSetOptions +function dbghelp.h SymSetParentWindow +function dbghelp.h SymSetScopeFromAddr +function dbghelp.h SymSetScopeFromIndex +function dbghelp.h SymSetScopeFromInlineContext +function dbghelp.h SymSetSearchPath +function dbghelp.h SymSrvDeltaName +function dbghelp.h SymSrvGetFileIndexes +function dbghelp.h SymSrvGetFileIndexInfo +function dbghelp.h SymSrvGetFileIndexString +function dbghelp.h SymSrvGetSupplement +function dbghelp.h SymSrvIsStore +function dbghelp.h SymSrvStoreFile +function dbghelp.h SymSrvStoreSupplement +function dbghelp.h SymUnDName64 =symundname +function dbghelp.h SymUnloadModule64 =symunloadmodule +function dbghelp.h UnDecorateSymbolName +function dbghelp.h UnmapDebugInformation +function minidumpapiset.h MiniDumpReadDumpStream +function minidumpapiset.h MiniDumpWriteDump + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Debug Help Library > DbgHelp Reference > DbgHelp Structures +struct dbghelp.h ADDRESS64 =address +struct dbghelp.h API_VERSION +struct dbghelp.h IMAGE_DEBUG_INFORMATION +struct dbghelp.h IMAGEHLP_CBA_EVENT +struct dbghelp.h IMAGEHLP_CBA_READ_MEMORY +struct dbghelp.h IMAGEHLP_DEFERRED_SYMBOL_LOAD64 =imagehlp_deferred_symbol_load +struct dbghelp.h IMAGEHLP_DUPLICATE_SYMBOL64 =imagehlp_duplicate_symbol +struct dbghelp.h IMAGEHLP_GET_TYPE_INFO_PARAMS +struct dbghelp.h IMAGEHLP_LINE64 =imagehlp_line +struct dbghelp.h IMAGEHLP_MODULE64 =imagehlp_module +struct dbghelp.h IMAGEHLP_STACK_FRAME +struct dbghelp.h IMAGEHLP_SYMBOL64 =imagehlp_symbol +struct dbghelp.h KDHELP64 =kdhelp +struct dbghelp.h LOADED_IMAGE +struct dbghelp.h MODLOAD_CVMISC +struct dbghelp.h MODLOAD_DATA +struct dbghelp.h OMAP +struct dbghelp.h SOURCEFILE +struct dbghelp.h SRCCODEINFO +struct dbghelp.h STACKFRAME_EX +struct dbghelp.h STACKFRAME64 =stackframe +struct dbghelp.h SYMBOL_INFO +struct dbghelp.h SYMSRV_INDEX_INFO +struct dbghelp.h TI_FINDCHILDREN_PARAMS +struct minidumpapiset.h MINIDUMP_CALLBACK_INFORMATION +struct minidumpapiset.h MINIDUMP_CALLBACK_INPUT +struct minidumpapiset.h MINIDUMP_CALLBACK_OUTPUT +struct minidumpapiset.h MINIDUMP_DIRECTORY +struct minidumpapiset.h MINIDUMP_EXCEPTION +struct minidumpapiset.h MINIDUMP_EXCEPTION_INFORMATION +struct minidumpapiset.h MINIDUMP_EXCEPTION_STREAM +struct minidumpapiset.h MINIDUMP_FUNCTION_TABLE_DESCRIPTOR +struct minidumpapiset.h MINIDUMP_FUNCTION_TABLE_STREAM +struct minidumpapiset.h MINIDUMP_HANDLE_DATA_STREAM +struct minidumpapiset.h MINIDUMP_HANDLE_DESCRIPTOR +struct minidumpapiset.h MINIDUMP_HANDLE_DESCRIPTOR_2 +struct minidumpapiset.h MINIDUMP_HANDLE_OBJECT_INFORMATION +struct minidumpapiset.h MINIDUMP_HANDLE_OPERATION_LIST +struct minidumpapiset.h MINIDUMP_HEADER +struct minidumpapiset.h MINIDUMP_INCLUDE_MODULE_CALLBACK +struct minidumpapiset.h MINIDUMP_INCLUDE_THREAD_CALLBACK +struct minidumpapiset.h MINIDUMP_IO_CALLBACK +struct minidumpapiset.h MINIDUMP_LOCATION_DESCRIPTOR +struct minidumpapiset.h MINIDUMP_MEMORY_DESCRIPTOR +struct minidumpapiset.h MINIDUMP_MEMORY_INFO +struct minidumpapiset.h MINIDUMP_MEMORY_INFO_LIST +struct minidumpapiset.h MINIDUMP_MEMORY_LIST =minidump_memory64_list +struct minidumpapiset.h MINIDUMP_MISC_INFO +struct minidumpapiset.h MINIDUMP_MISC_INFO_2 +struct minidumpapiset.h MINIDUMP_MODULE +struct minidumpapiset.h MINIDUMP_MODULE_CALLBACK +struct minidumpapiset.h MINIDUMP_MODULE_LIST +struct minidumpapiset.h MINIDUMP_READ_MEMORY_FAILURE_CALLBACK +struct minidumpapiset.h MINIDUMP_STRING +struct minidumpapiset.h MINIDUMP_SYSTEM_INFO +struct minidumpapiset.h MINIDUMP_THREAD +struct minidumpapiset.h MINIDUMP_THREAD_CALLBACK +struct minidumpapiset.h MINIDUMP_THREAD_EX +struct minidumpapiset.h MINIDUMP_THREAD_EX_CALLBACK +struct minidumpapiset.h MINIDUMP_THREAD_EX_LIST +struct minidumpapiset.h MINIDUMP_THREAD_INFO +struct minidumpapiset.h MINIDUMP_THREAD_INFO_LIST +struct minidumpapiset.h MINIDUMP_THREAD_LIST +struct minidumpapiset.h MINIDUMP_UNLOADED_MODULE +struct minidumpapiset.h MINIDUMP_UNLOADED_MODULE_LIST +struct minidumpapiset.h MINIDUMP_USER_STREAM +struct minidumpapiset.h MINIDUMP_USER_STREAM_INFORMATION +struct winnt.h _IMAGE_RUNTIME_FUNCTION_ENTRY =runtime_function +struct winnt.h FPO_DATA + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Debug Help Library > DbgHelp Reference > Image Help Library > ImageHlp Reference > ImageHlp Functions +callback imagehlp.h DigestFunction +callback imagehlp.h StatusRoutine =pimagehlp_status_routine +function imagehlp.h BindImage +function imagehlp.h BindImageEx +function imagehlp.h CheckSumMappedFile +function imagehlp.h GetImageConfigInformation +function imagehlp.h GetImageUnusedHeaderBytes +function imagehlp.h ImageAddCertificate +function imagehlp.h ImageEnumerateCertificates +function imagehlp.h ImageGetCertificateData +function imagehlp.h ImageGetCertificateHeader +function imagehlp.h ImageGetDigestStream +function imagehlp.h ImageLoad +function imagehlp.h ImageRemoveCertificate +function imagehlp.h ImageUnload +function imagehlp.h MapAndLoad +function imagehlp.h MapFileAndCheckSum =mapfileandchecksuma +function imagehlp.h ReBaseImage +function imagehlp.h ReBaseImage64 +function imagehlp.h SetImageConfigInformation +function imagehlp.h SplitSymbols +function imagehlp.h TouchFileTimes +function imagehlp.h UnMapAndLoad +function imagehlp.h UpdateDebugInfoFile +function imagehlp.h UpdateDebugInfoFileEx + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Debug Help Library > DbgHelp Reference > Image Help Library > ImageHlp Reference > ImageHlp Structures +struct winnt.h IMAGE_COFF_SYMBOLS_HEADER +struct winnt.h IMAGE_DATA_DIRECTORY +struct winnt.h IMAGE_DEBUG_DIRECTORY +struct winnt.h IMAGE_FILE_HEADER +struct winnt.h IMAGE_FUNCTION_ENTRY +struct winnt.h IMAGE_LOAD_CONFIG_DIRECTORY64 =image_load_config_directory32 +struct winnt.h IMAGE_NT_HEADERS =image_nt_headers32 +struct winnt.h IMAGE_OPTIONAL_HEADER =image_optional_header32 +struct winnt.h IMAGE_SECTION_HEADER + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Structured Exception Handling > Structured Exception Handling Reference > Structured Exception Handling Functions +callback winnt.h VectoredHandler =pvectored_exception_handler +function errhandlingapi.h AddVectoredContinueHandler +function errhandlingapi.h AddVectoredExceptionHandler +function errhandlingapi.h RaiseException +function errhandlingapi.h RemoveVectoredContinueHandler +function errhandlingapi.h RemoveVectoredExceptionHandler +function errhandlingapi.h SetUnhandledExceptionFilter +function errhandlingapi.h UnhandledExceptionFilter +function winnt.h RtlAddFunctionTable +function winnt.h RtlAddGrowableFunctionTable +function winnt.h RtlCaptureContext +function winnt.h RtlDeleteFunctionTable +function winnt.h RtlDeleteGrowableFunctionTable +function winnt.h RtlGrowFunctionTable +function winnt.h RtlInstallFunctionTableCallback +function winnt.h RtlRestoreContext + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Structured Exception Handling > Structured Exception Handling Reference > Structured Exception Handling Structures +struct winnt.h EXCEPTION_POINTERS +struct winnt.h EXCEPTION_RECORD + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Wait Chain Traversal > WCT Reference +callback wct.h PWAITCHAINCALLBACK +function wct.h CloseThreadWaitChainSession +function wct.h GetThreadWaitChain +function wct.h OpenThreadWaitChainSession +function wct.h RegisterWaitChainCOMCallback +struct wct.h WAITCHAIN_NODE_INFO + +# Desktop App Technologies > Diagnostics > Debugging and Error Handling > Intel AVX > AVX Functions +function winbase.h CopyContext +function winbase.h GetEnabledXStateFeatures +function winbase.h GetXStateFeaturesMask +function winbase.h InitializeContext +function winbase.h LocateXStateFeature +function winbase.h SetXStateFeaturesMask + +# Desktop App Technologies > Diagnostics > Network Diagnostics Framework > NDF Reference > NDF Enumerated Types +enum ndattrib.h ATTRIBUTE_TYPE +enum ndattrib.h REPAIR_RISK +enum ndattrib.h REPAIR_SCOPE +enum ndattrib.h UI_INFO_TYPE +enum ndhelper.h DIAGNOSIS_STATUS +enum ndhelper.h PROBLEM_TYPE +enum ndhelper.h REPAIR_STATUS + +# Desktop App Technologies > Diagnostics > Network Diagnostics Framework > NDF Reference > NDF Functions +function ndfapi.h NdfCancelIncident +function ndfapi.h NdfCloseIncident +function ndfapi.h NdfCreateConnectivityIncident +function ndfapi.h NdfCreateDNSIncident +function ndfapi.h NdfCreateGroupingIncident +function ndfapi.h NdfCreateInboundIncident +function ndfapi.h NdfCreateIncident +function ndfapi.h NdfCreateNetConnectionIncident +function ndfapi.h NdfCreatePnrpIncident +function ndfapi.h NdfCreateSharingIncident +function ndfapi.h NdfCreateWebIncident +function ndfapi.h NdfCreateWebIncidentEx +function ndfapi.h NdfCreateWinSockIncident +function ndfapi.h NdfDiagnoseIncident +function ndfapi.h NdfExecuteDiagnosis +function ndfapi.h NdfGetTraceFile +function ndfapi.h NdfRepairIncident + +# Desktop App Technologies > Diagnostics > Network Diagnostics Framework > NDF Reference > NDF Interfaces +interface ndhelper.h INetDiagHelper +interface ndhelper.h INetDiagHelperEx +interface ndhelper.h INetDiagHelperInfo +interface ndhelper.h INetDiagHelperUtilFactory + +# Desktop App Technologies > Diagnostics > Network Diagnostics Framework > NDF Reference > NDF Structures +struct ndattrib.h DIAG_SOCKADDR +struct ndattrib.h HELPER_ATTRIBUTE +struct ndattrib.h LIFE_TIME +struct ndattrib.h OCTET_STRING +struct ndattrib.h RepairInfo +struct ndattrib.h RepairInfoEx +struct ndattrib.h RootCauseInfo +struct ndattrib.h ShellCommandInfo +struct ndattrib.h UiInfo +struct ndhelper.h DiagnosticsInfo +struct ndhelper.h HelperAttributeInfo +struct ndhelper.h HYPOTHESIS +struct ndhelper.h HypothesisResult + +# Desktop App Technologies > Diagnostics > Performance Counters > Performance Counters Reference > Performance Counter Enumerations +enum perflib.h PerfCounterDataType +enum perflib.h PerfRegInfoType + +# Desktop App Technologies > Diagnostics > Performance Counters > Performance Counters Reference > Performance Counters Structures +struct pdh.h PDH_BROWSE_DLG_CONFIG =pdh_browse_dlg_config_a +struct pdh.h PDH_BROWSE_DLG_CONFIG_H =pdh_browse_dlg_config_ha +struct pdh.h PDH_COUNTER_INFO =pdh_counter_info_a +struct pdh.h PDH_COUNTER_PATH_ELEMENTS =pdh_counter_path_elements_a +struct pdh.h PDH_DATA_ITEM_PATH_ELEMENTS =pdh_data_item_path_elements_a +struct pdh.h PDH_FMT_COUNTERVALUE +struct pdh.h PDH_FMT_COUNTERVALUE_ITEM =pdh_fmt_countervalue_item_a +struct pdh.h PDH_RAW_COUNTER +struct pdh.h PDH_RAW_COUNTER_ITEM =pdh_raw_counter_item_a +struct pdh.h PDH_RAW_LOG_RECORD +struct pdh.h PDH_STATISTICS +struct pdh.h PDH_TIME_INFO +struct perflib.h PERF_COUNTER_DATA +struct perflib.h PERF_COUNTER_HEADER +struct perflib.h PERF_COUNTER_IDENTIFIER +struct perflib.h PERF_COUNTER_IDENTITY +struct perflib.h PERF_COUNTER_INFO +struct perflib.h PERF_COUNTER_REG_INFO +struct perflib.h PERF_COUNTERSET_INFO +struct perflib.h PERF_COUNTERSET_INSTANCE +struct perflib.h PERF_COUNTERSET_REG_INFO +struct perflib.h PERF_DATA_HEADER +struct perflib.h PERF_INSTANCE_HEADER +struct perflib.h PERF_MULTI_COUNTERS +struct perflib.h PERF_MULTI_INSTANCES +struct perflib.h PERF_PROVIDER_CONTEXT +struct perflib.h PERF_STRING_BUFFER_HEADER +struct perflib.h PERF_STRING_COUNTER_HEADER +struct winperf.h PERF_COUNTER_BLOCK +struct winperf.h PERF_COUNTER_DEFINITION +struct winperf.h PERF_DATA_BLOCK +struct winperf.h PERF_INSTANCE_DEFINITION +struct winperf.h PERF_OBJECT_TYPE + +# Desktop App Technologies > Diagnostics > Performance Counters > Performance Counters Reference > Performance Counters Functions +callback pdh.h CounterPathCallBack +callback perflib.h PERF_MEM_ALLOC +callback perflib.h PERF_MEM_FREE +callback perflib.h PERFLIBREQUEST +callback winperf.h PM_CLOSE_PROC +callback winperf.h PM_COLLECT_PROC +function loadperf.h LoadPerfCounterTextStrings =loadperfcountertextstringsa +function loadperf.h UnloadPerfCounterTextStrings =unloadperfcountertextstringsa +function pdh.h PdhAddCounter =pdhaddcountera +function pdh.h PdhAddEnglishCounter =pdhaddenglishcountera +function pdh.h PdhBindInputDataSource =pdhbindinputdatasourcea +function pdh.h PdhBrowseCounters =pdhbrowsecountersa +function pdh.h PdhBrowseCountersH =pdhbrowsecountersha +function pdh.h PdhCalculateCounterFromRawValue +function pdh.h PdhCloseLog +function pdh.h PdhCloseQuery +function pdh.h PdhCollectQueryData +function pdh.h PdhCollectQueryDataEx +function pdh.h PdhCollectQueryDataWithTime +function pdh.h PdhComputeCounterStatistics +function pdh.h PdhConnectMachine =pdhconnectmachinea +function pdh.h PdhEnumLogSetNames =pdhenumlogsetnamesa +function pdh.h PdhEnumMachines =pdhenummachinesa +function pdh.h PdhEnumMachinesH =pdhenummachinesha +function pdh.h PdhEnumObjectItems =pdhenumobjectitemsa +function pdh.h PdhEnumObjectItemsH =pdhenumobjectitemsha +function pdh.h PdhEnumObjects =pdhenumobjectsa +function pdh.h PdhEnumObjectsH =pdhenumobjectsha +function pdh.h PdhExpandCounterPath =pdhexpandcounterpatha +function pdh.h PdhExpandWildCardPath =pdhexpandwildcardpatha +function pdh.h PdhExpandWildCardPathH =pdhexpandwildcardpathha +function pdh.h PdhFormatFromRawValue +function pdh.h PdhGetCounterInfo =pdhgetcounterinfoa +function pdh.h PdhGetCounterTimeBase +function pdh.h PdhGetDataSourceTimeRange =pdhgetdatasourcetimerangea +function pdh.h PdhGetDataSourceTimeRangeH +function pdh.h PdhGetDefaultPerfCounter =pdhgetdefaultperfcountera +function pdh.h PdhGetDefaultPerfCounterH =pdhgetdefaultperfcounterha +function pdh.h PdhGetDefaultPerfObject =pdhgetdefaultperfobjecta +function pdh.h PdhGetDefaultPerfObjectH =pdhgetdefaultperfobjectha +function pdh.h PdhGetDllVersion +function pdh.h PdhGetFormattedCounterArray =pdhgetformattedcounterarraya +function pdh.h PdhGetFormattedCounterValue +function pdh.h PdhGetLogFileSize +function pdh.h PdhGetRawCounterArray =pdhgetrawcounterarraya +function pdh.h PdhGetRawCounterValue +function pdh.h PdhIsRealTimeQuery +function pdh.h PdhLookupPerfIndexByName =pdhlookupperfindexbynamea +function pdh.h PdhLookupPerfNameByIndex =pdhlookupperfnamebyindexa +function pdh.h PdhMakeCounterPath =pdhmakecounterpatha +function pdh.h PdhOpenLog =pdhopenloga +function pdh.h PdhOpenQuery =pdhopenquerya +function pdh.h PdhOpenQueryH +function pdh.h PdhParseCounterPath =pdhparsecounterpatha +function pdh.h PdhParseInstanceName =pdhparseinstancenamea +function pdh.h PdhReadRawLogRecord +function pdh.h PdhRemoveCounter +function pdh.h PdhSelectDataSource =pdhselectdatasourcea +function pdh.h PdhSetCounterScaleFactor +function pdh.h PdhSetDefaultRealTimeDataSource +function pdh.h PdhSetQueryTimeRange +function pdh.h PdhUpdateLog =pdhupdateloga +function pdh.h PdhUpdateLogFileCatalog +function pdh.h PdhValidatePath =pdhvalidatepatha +function pdh.h PdhValidatePathEx =pdhvalidatepathexa +function perflib.h PerfAddCounters +function perflib.h PerfCloseQueryHandle +function perflib.h PerfCreateInstance +function perflib.h PerfDecrementULongCounterValue +function perflib.h PerfDecrementULongLongCounterValue +function perflib.h PerfDeleteCounters +function perflib.h PerfDeleteInstance +function perflib.h PerfEnumerateCounterSet +function perflib.h PerfEnumerateCounterSetInstances +function perflib.h PerfIncrementULongCounterValue +function perflib.h PerfIncrementULongLongCounterValue +function perflib.h PerfOpenQueryHandle +function perflib.h PerfQueryCounterData +function perflib.h PerfQueryCounterInfo +function perflib.h PerfQueryCounterSetRegistrationInfo +function perflib.h PerfQueryInstance +function perflib.h PerfSetCounterRefValue +function perflib.h PerfSetCounterSetInfo +function perflib.h PerfSetULongCounterValue +function perflib.h PerfSetULongLongCounterValue +function perflib.h PerfStartProvider +function perflib.h PerfStartProviderEx +function perflib.h PerfStopProvider + +# Desktop App Technologies > Diagnostics > Process Status API (PSAPI) > PSAPI Reference > PSAPI Functions +callback psapi.h EnumPageFilesProc =penum_page_file_callbacka +function psapi.h EmptyWorkingSet +function psapi.h EnumDeviceDrivers +function psapi.h EnumPageFiles =enumpagefilesa +function psapi.h EnumProcesses +function psapi.h EnumProcessModules +function psapi.h EnumProcessModulesEx +function psapi.h GetDeviceDriverBaseName =getdevicedriverbasenamea +function psapi.h GetDeviceDriverFileName =getdevicedriverfilenamea +function psapi.h GetMappedFileName =getmappedfilenamea +function psapi.h GetModuleBaseName =getmodulebasenamea +function psapi.h GetModuleFileNameEx =getmodulefilenameexa +function psapi.h GetModuleInformation +function psapi.h GetPerformanceInfo +function psapi.h GetProcessImageFileName =getprocessimagefilenamea +function psapi.h GetProcessMemoryInfo +function psapi.h GetWsChanges +function psapi.h GetWsChangesEx +function psapi.h InitializeProcessForWsWatch +function psapi.h QueryWorkingSet +function psapi.h QueryWorkingSetEx + +# Desktop App Technologies > Diagnostics > Process Status API (PSAPI) > PSAPI Reference > PSAPI Structures +struct psapi.h ENUM_PAGE_FILE_INFORMATION +struct psapi.h MODULEINFO +struct psapi.h PERFORMANCE_INFORMATION +struct psapi.h PROCESS_MEMORY_COUNTERS +struct psapi.h PROCESS_MEMORY_COUNTERS_EX +struct psapi.h PSAPI_WORKING_SET_BLOCK +struct psapi.h PSAPI_WORKING_SET_EX_BLOCK +struct psapi.h PSAPI_WORKING_SET_EX_INFORMATION +struct psapi.h PSAPI_WORKING_SET_INFORMATION +struct psapi.h PSAPI_WS_WATCH_INFORMATION +struct psapi.h PSAPI_WS_WATCH_INFORMATION_EX + +# Desktop App Technologies > Diagnostics > System Event Notification Service > SENS Reference > SENS Object +interface sensevts.h ISensLogon +interface sensevts.h ISensLogon2 +interface sensevts.h ISensNetwork +interface sensevts.h ISensOnNow + +# Desktop App Technologies > Diagnostics > System Event Notification Service > SENS Reference +function sensapi.h IsDestinationReachable =isdestinationreachablea +function sensapi.h IsNetworkAlive + +# Desktop App Technologies > Diagnostics > System Monitor > System Monitor Reference > SYSMON Enumerations +enum isysmon.h DataSourceTypeConstants +enum isysmon.h DisplayTypeConstants +enum isysmon.h ReportValueTypeConstants +enum isysmon.h SysmonBatchReason +enum isysmon.h SysmonDataType +enum isysmon.h SysmonFileType + +# Desktop App Technologies > Diagnostics > Tool Help Library > Tool Help Reference > Tool Help Functions +function tlhelp32.h CreateToolhelp32Snapshot +function tlhelp32.h Heap32First +function tlhelp32.h Heap32ListFirst +function tlhelp32.h Heap32ListNext +function tlhelp32.h Heap32Next +function tlhelp32.h Module32First +function tlhelp32.h Module32Next +function tlhelp32.h Process32First +function tlhelp32.h Process32Next +function tlhelp32.h Thread32First +function tlhelp32.h Thread32Next +function tlhelp32.h Toolhelp32ReadProcessMemory + +# Desktop App Technologies > Diagnostics > Tool Help Library > Tool Help Reference > Tool Help Structures +struct tlhelp32.h HEAPENTRY32 +struct tlhelp32.h HEAPLIST32 +struct tlhelp32.h MODULEENTRY32 +struct tlhelp32.h PROCESSENTRY32 +struct tlhelp32.h THREADENTRY32 + +# Desktop App Technologies > Diagnostics > Windows Error Reporting > WER Reference > WER Functions +callback werapi.h PFN_WER_RUNTIME_EXCEPTION_DEBUGGER_LAUNCH +callback werapi.h PFN_WER_RUNTIME_EXCEPTION_EVENT +callback werapi.h PFN_WER_RUNTIME_EXCEPTION_EVENT_SIGNATURE +function errorrep.h AddERExcludedApplication =adderexcludedapplicationa +function errorrep.h ReportFault +function errorrep.h WerReportHang +function werapi.h WerAddExcludedApplication +function werapi.h WerFreeString +function werapi.h WerGetFlags +function werapi.h WerRegisterAdditionalProcess +function werapi.h WerRegisterAppLocalDump +function werapi.h WerRegisterCustomMetadata +function werapi.h WerRegisterExcludedMemoryBlock +function werapi.h WerRegisterFile +function werapi.h WerRegisterMemoryBlock +function werapi.h WerRegisterRuntimeExceptionModule +function werapi.h WerRemoveExcludedApplication +function werapi.h WerReportAddDump +function werapi.h WerReportAddFile +function werapi.h WerReportCloseHandle +function werapi.h WerReportCreate +function werapi.h WerReportSetParameter +function werapi.h WerReportSetUIOption +function werapi.h WerReportSubmit +function werapi.h WerSetFlags +function werapi.h WerStoreClose +function werapi.h WerStoreGetFirstReportKey +function werapi.h WerStoreGetNextReportKey +function werapi.h WerStoreOpen +function werapi.h WerStoreQueryReportMetadataV2 +function werapi.h WerUnregisterAdditionalProcess +function werapi.h WerUnregisterAppLocalDump +function werapi.h WerUnregisterCustomMetadata +function werapi.h WerUnregisterExcludedMemoryBlock +function werapi.h WerUnregisterFile +function werapi.h WerUnregisterMemoryBlock +function werapi.h WerUnregisterRuntimeExceptionModule + +# Desktop App Technologies > Diagnostics > Windows Error Reporting > WER Reference > WER Structures +struct werapi.h WER_DUMP_CUSTOM_OPTIONS +struct werapi.h WER_EXCEPTION_INFORMATION +struct werapi.h WER_REPORT_INFORMATION +struct werapi.h WER_REPORT_METADATA_V2 +struct werapi.h WER_RUNTIME_EXCEPTION_INFORMATION + +# Desktop App Technologies > Diagnostics > Windows Error Reporting > WER Reference > WER Enumerations +enum werapi.h REPORT_STORE_TYPES +enum werapi.h WER_CONSENT +enum werapi.h WER_DUMP_TYPE +enum werapi.h WER_FILE_TYPE +enum werapi.h WER_REGISTER_FILE_TYPE +enum werapi.h WER_REPORT_TYPE +enum werapi.h WER_REPORT_UI +enum werapi.h WER_SUBMIT_RESULT + +# Desktop App Technologies > Diagnostics > Windows Events > TraceLogging > TraceLogging Reference > TraceLogging Classes +class traceloggingactivity.h TraceLoggingActivity +class traceloggingactivity.h TraceLoggingThreadActivity +class traceloggingactivity.h TraceLoggingThreadActivityIdSetter + +# Desktop App Technologies > Diagnostics > Windows Events > TraceLogging > TraceLogging Reference > TraceLogging Functions +function traceloggingprovider.h TraceLoggingProviderEnabled +function traceloggingprovider.h TraceLoggingProviderId +function traceloggingprovider.h TraceLoggingRegister +function traceloggingprovider.h TraceLoggingRegisterEx +function traceloggingprovider.h TraceLoggingSetInformation +function traceloggingprovider.h TraceLoggingUnregister + +# Desktop App Technologies > Diagnostics > Windows Events > TraceLogging > TraceLogging Reference > TraceLogging Macros > TraceLogging Wrapper Macros +function traceloggingprovider.h TraceLoggingBinary +function traceloggingprovider.h TraceLoggingChannel +function traceloggingprovider.h TraceLoggingCustomAttribute +function traceloggingprovider.h TraceLoggingDescription +function traceloggingprovider.h TraceLoggingEventTag +function traceloggingprovider.h TraceLoggingKeyword +function traceloggingprovider.h TraceLoggingLevel +function traceloggingprovider.h TraceLoggingOpcode +function traceloggingprovider.h TraceLoggingOptionGroup +function traceloggingprovider.h TraceLoggingSocketAddress +function traceloggingprovider.h TraceLoggingStruct +function traceloggingprovider.h TraceLoggingValue + +# Desktop App Technologies > Diagnostics > Windows Events > TraceLogging > TraceLogging Reference > TraceLogging Macros +function traceloggingactivity.h TraceLoggingFunction +function traceloggingactivity.h TraceLoggingWriteStart +function traceloggingactivity.h TraceLoggingWriteStop +function traceloggingactivity.h TraceLoggingWriteTagged +function traceloggingprovider.h TRACELOGGING_DECLARE_PROVIDER +function traceloggingprovider.h TRACELOGGING_DEFINE_PROVIDER +function traceloggingprovider.h TRACELOGGING_DEFINE_PROVIDER_STORAGE +function traceloggingprovider.h TraceLoggingCustom +function traceloggingprovider.h TraceLoggingWrite +function traceloggingprovider.h TraceLoggingWriteActivity + +# Desktop App Technologies > Diagnostics > Windows Events > Event Tracing > Event Tracing Reference > Event Tracing Macros +function evntprov.h EventDataDescCreate +function evntprov.h EventDescCreate +function evntprov.h EventDescGetChannel +function evntprov.h EventDescGetId +function evntprov.h EventDescGetKeyword +function evntprov.h EventDescGetLevel +function evntprov.h EventDescGetOpcode +function evntprov.h EventDescGetTask +function evntprov.h EventDescGetVersion +function evntprov.h EventDescOrKeyword +function evntprov.h EventDescSetChannel +function evntprov.h EventDescSetId +function evntprov.h EventDescSetKeyword +function evntprov.h EventDescSetLevel +function evntprov.h EventDescSetOpcode +function evntprov.h EventDescSetTask +function evntprov.h EventDescSetVersion +function evntprov.h EventDescZero + +# Desktop App Technologies > Diagnostics > Windows Events > Event Tracing > Event Tracing Reference > Event Tracing Functions +function securitybaseapi.h CveEventWrite (securitybaseapi.h) =cveeventwrite + +# Desktop App Technologies > Diagnostics > Windows Events > Event Logging > Event Logging Reference > Event Logging Functions +function winbase.h BackupEventLog =backupeventloga +function winbase.h ClearEventLog =cleareventloga +function winbase.h CloseEventLog +function winbase.h DeregisterEventSource +function winbase.h GetEventLogInformation +function winbase.h GetNumberOfEventLogRecords +function winbase.h GetOldestEventLogRecord +function winbase.h NotifyChangeEventLog +function winbase.h OpenBackupEventLog =openbackupeventloga +function winbase.h OpenEventLog =openeventloga +function winbase.h ReadEventLog =readeventloga +function winbase.h RegisterEventSource =registereventsourcea +function winbase.h ReportEvent =reporteventa + +# Desktop App Technologies > Diagnostics > Windows Events > Event Logging > Event Logging Reference > Event Logging Structures +struct winbase.h EVENTLOG_FULL_INFORMATION +struct winnt.h EVENTLOGRECORD + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Log > Windows Event Log Reference > Windows Event Log Enumerations +enum winevt.h EVT_CHANNEL_CLOCK_TYPE +enum winevt.h EVT_CHANNEL_CONFIG_PROPERTY_ID +enum winevt.h EVT_CHANNEL_ISOLATION_TYPE +enum winevt.h EVT_CHANNEL_REFERENCE_FLAGS +enum winevt.h EVT_CHANNEL_SID_TYPE +enum winevt.h EVT_CHANNEL_TYPE +enum winevt.h EVT_EVENT_METADATA_PROPERTY_ID +enum winevt.h EVT_EVENT_PROPERTY_ID +enum winevt.h EVT_EXPORTLOG_FLAGS +enum winevt.h EVT_FORMAT_MESSAGE_FLAGS +enum winevt.h EVT_LOG_PROPERTY_ID +enum winevt.h EVT_LOGIN_CLASS +enum winevt.h EVT_OPEN_LOG_FLAGS +enum winevt.h EVT_PUBLISHER_METADATA_PROPERTY_ID +enum winevt.h EVT_QUERY_FLAGS +enum winevt.h EVT_QUERY_PROPERTY_ID +enum winevt.h EVT_RENDER_CONTEXT_FLAGS +enum winevt.h EVT_RENDER_FLAGS +enum winevt.h EVT_RPC_LOGIN_FLAGS +enum winevt.h EVT_SEEK_FLAGS +enum winevt.h EVT_SUBSCRIBE_FLAGS +enum winevt.h EVT_SUBSCRIBE_NOTIFY_ACTION +enum winevt.h EVT_SYSTEM_PROPERTY_ID +enum winevt.h EVT_VARIANT_TYPE + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Log > Windows Event Log Reference > Windows Event Log Functions +callback winevt.h EVT_SUBSCRIBE_CALLBACK +function winevt.h EvtArchiveExportedLog +function winevt.h EvtCancel +function winevt.h EvtClearLog +function winevt.h EvtClose +function winevt.h EvtCreateBookmark +function winevt.h EvtCreateRenderContext +function winevt.h EvtExportLog +function winevt.h EvtFormatMessage +function winevt.h EvtGetChannelConfigProperty +function winevt.h EvtGetEventInfo +function winevt.h EvtGetEventMetadataProperty +function winevt.h EvtGetExtendedStatus +function winevt.h EvtGetLogInfo +function winevt.h EvtGetObjectArrayProperty +function winevt.h EvtGetObjectArraySize +function winevt.h EvtGetPublisherMetadataProperty +function winevt.h EvtGetQueryInfo +function winevt.h EvtNext +function winevt.h EvtNextChannelPath +function winevt.h EvtNextEventMetadata +function winevt.h EvtNextPublisherId +function winevt.h EvtOpenChannelConfig +function winevt.h EvtOpenChannelEnum +function winevt.h EvtOpenEventMetadataEnum +function winevt.h EvtOpenLog +function winevt.h EvtOpenPublisherEnum +function winevt.h EvtOpenPublisherMetadata +function winevt.h EvtOpenSession +function winevt.h EvtQuery +function winevt.h EvtRender +function winevt.h EvtSaveChannelConfig +function winevt.h EvtSeek +function winevt.h EvtSetChannelConfigProperty +function winevt.h EvtSubscribe +function winevt.h EvtUpdateBookmark + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Log > Windows Event Log Reference > Windows Event Log Structures +struct winevt.h EVT_RPC_LOGIN +struct winevt.h EVT_VARIANT + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Collector > Windows Event Collector Reference > Windows Event Collector Enumerations +enum evcoll.h EC_SUBSCRIPTION_CONFIGURATION_MODE +enum evcoll.h EC_SUBSCRIPTION_CONTENT_FORMAT +enum evcoll.h EC_SUBSCRIPTION_CREDENTIALS_TYPE +enum evcoll.h EC_SUBSCRIPTION_DELIVERY_MODE +enum evcoll.h EC_SUBSCRIPTION_PROPERTY_ID +enum evcoll.h EC_SUBSCRIPTION_RUNTIME_STATUS_ACTIVE_STATUS +enum evcoll.h EC_SUBSCRIPTION_RUNTIME_STATUS_INFO_ID +enum evcoll.h EC_SUBSCRIPTION_TYPE +enum evcoll.h EC_VARIANT_TYPE + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Collector > Windows Event Collector Reference > Windows Event Collector Functions +function evcoll.h EcClose +function evcoll.h EcDeleteSubscription +function evcoll.h EcEnumNextSubscription +function evcoll.h EcGetObjectArrayProperty +function evcoll.h EcGetObjectArraySize +function evcoll.h EcGetSubscriptionProperty +function evcoll.h EcGetSubscriptionRunTimeStatus +function evcoll.h EcInsertObjectArrayElement +function evcoll.h EcOpenSubscription +function evcoll.h EcOpenSubscriptionEnum +function evcoll.h EcRemoveObjectArrayElement +function evcoll.h EcRetrySubscription +function evcoll.h EcSaveSubscription +function evcoll.h EcSetObjectArrayProperty +function evcoll.h EcSetSubscriptionProperty + +# Desktop App Technologies > Diagnostics > Windows Events > Windows Event Collector > Windows Event Collector Reference > Windows Event Collector Structures +struct evcoll.h EC_VARIANT + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Document API > XPS Document API Reference > XPS Document Interfaces +interface xpsobjectmodel_1.h IXpsDocumentPackageTarget +interface xpsobjectmodel_1.h IXpsOMObjectFactory1 +interface xpsobjectmodel_1.h IXpsOMPackage1 +interface xpsobjectmodel_1.h IXpsOMPage1 +interface xpsobjectmodel_1.h IXpsOMRemoteDictionaryResource1 +interface xpsobjectmodel_2.h IXpsDocumentPackageTarget3D +interface xpsobjectmodel_2.h IXpsOMPackageWriter3D +interface xpsobjectmodel.h IXpsOMBrush +interface xpsobjectmodel.h IXpsOMCanvas +interface xpsobjectmodel.h IXpsOMColorProfileResource +interface xpsobjectmodel.h IXpsOMColorProfileResourceCollection +interface xpsobjectmodel.h IXpsOMCoreProperties +interface xpsobjectmodel.h IXpsOMDashCollection +interface xpsobjectmodel.h IXpsOMDictionary +interface xpsobjectmodel.h IXpsOMDocument +interface xpsobjectmodel.h IXpsOMDocumentCollection +interface xpsobjectmodel.h IXpsOMDocumentSequence +interface xpsobjectmodel.h IXpsOMDocumentStructureResource +interface xpsobjectmodel.h IXpsOMFontResource +interface xpsobjectmodel.h IXpsOMFontResourceCollection +interface xpsobjectmodel.h IXpsOMGeometry +interface xpsobjectmodel.h IXpsOMGeometryFigure +interface xpsobjectmodel.h IXpsOMGeometryFigureCollection +interface xpsobjectmodel.h IXpsOMGlyphs +interface xpsobjectmodel.h IXpsOMGlyphsEditor +interface xpsobjectmodel.h IXpsOMGradientBrush +interface xpsobjectmodel.h IXpsOMGradientStop +interface xpsobjectmodel.h IXpsOMGradientStopCollection +interface xpsobjectmodel.h IXpsOMImageBrush +interface xpsobjectmodel.h IXpsOMImageResource +interface xpsobjectmodel.h IXpsOMImageResourceCollection +interface xpsobjectmodel.h IXpsOMLinearGradientBrush +interface xpsobjectmodel.h IXpsOMMatrixTransform +interface xpsobjectmodel.h IXpsOMNameCollection +interface xpsobjectmodel.h IXpsOMObjectFactory +interface xpsobjectmodel.h IXpsOMPackage +interface xpsobjectmodel.h IXpsOMPackageWriter +interface xpsobjectmodel.h IXpsOMPage +interface xpsobjectmodel.h IXpsOMPageReference +interface xpsobjectmodel.h IXpsOMPageReferenceCollection +interface xpsobjectmodel.h IXpsOMPart +interface xpsobjectmodel.h IXpsOMPartResources +interface xpsobjectmodel.h IXpsOMPartUriCollection +interface xpsobjectmodel.h IXpsOMPath +interface xpsobjectmodel.h IXpsOMPrintTicketResource +interface xpsobjectmodel.h IXpsOMRadialGradientBrush +interface xpsobjectmodel.h IXpsOMRemoteDictionaryResource +interface xpsobjectmodel.h IXpsOMRemoteDictionaryResourceCollection +interface xpsobjectmodel.h IXpsOMShareable +interface xpsobjectmodel.h IXpsOMSignatureBlockResource +interface xpsobjectmodel.h IXpsOMSignatureBlockResourceCollection +interface xpsobjectmodel.h IXpsOMSolidColorBrush +interface xpsobjectmodel.h IXpsOMStoryFragmentsResource +interface xpsobjectmodel.h IXpsOMThumbnailGenerator +interface xpsobjectmodel.h IXpsOMTileBrush +interface xpsobjectmodel.h IXpsOMVisual +interface xpsobjectmodel.h IXpsOMVisualBrush +interface xpsobjectmodel.h IXpsOMVisualCollection + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Document API > XPS Document API Reference > XPS Document Structures > XPS_COLOR +struct xpsobjectmodel.h XPS_COLOR_TYPE_CONTEXT =xps_color + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Document API > XPS Document API Reference > XPS Document Structures +struct xpsobjectmodel.h XPS_DASH +struct xpsobjectmodel.h XPS_GLYPH_INDEX +struct xpsobjectmodel.h XPS_GLYPH_MAPPING +struct xpsobjectmodel.h XPS_MATRIX +struct xpsobjectmodel.h XPS_POINT +struct xpsobjectmodel.h XPS_RECT +struct xpsobjectmodel.h XPS_SIZE + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Document API > XPS Document API Reference > XPS Document Enumerations +enum xpsobjectmodel_1.h XPS_DOCUMENT_TYPE +enum xpsobjectmodel.h XPS_COLOR_INTERPOLATION +enum xpsobjectmodel.h XPS_COLOR_TYPE +enum xpsobjectmodel.h XPS_DASH_CAP +enum xpsobjectmodel.h XPS_FILL_RULE +enum xpsobjectmodel.h XPS_FONT_EMBEDDING +enum xpsobjectmodel.h XPS_IMAGE_TYPE +enum xpsobjectmodel.h XPS_INTERLEAVING +enum xpsobjectmodel.h XPS_LINE_CAP +enum xpsobjectmodel.h XPS_LINE_JOIN +enum xpsobjectmodel.h XPS_OBJECT_TYPE +enum xpsobjectmodel.h XPS_SEGMENT_STROKE_PATTERN +enum xpsobjectmodel.h XPS_SEGMENT_TYPE +enum xpsobjectmodel.h XPS_SPREAD_METHOD +enum xpsobjectmodel.h XPS_STYLE_SIMULATION +enum xpsobjectmodel.h XPS_THUMBNAIL_SIZE +enum xpsobjectmodel.h XPS_TILE_MODE + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Digital Signature API > XPS Digital Signature API Reference > XPS Digital Signature API Interfaces +interface xpsdigitalsignature.h IXpsSignature +interface xpsdigitalsignature.h IXpsSignatureBlock +interface xpsdigitalsignature.h IXpsSignatureBlockCollection +interface xpsdigitalsignature.h IXpsSignatureCollection +interface xpsdigitalsignature.h IXpsSignatureManager +interface xpsdigitalsignature.h IXpsSignatureRequest +interface xpsdigitalsignature.h IXpsSignatureRequestCollection +interface xpsdigitalsignature.h IXpsSigningOptions + +# Desktop App Technologies > Documents and Printing > XPS Documents > XPS Digital Signature API > XPS Digital Signature API Reference > XPS Digital Signature API Enumerations +enum xpsdigitalsignature.h XPS_SIGN_FLAGS +enum xpsdigitalsignature.h XPS_SIGN_POLICY +enum xpsdigitalsignature.h XPS_SIGNATURE_STATUS + +# Desktop App Technologies > Documents and Printing > Printing > Print Document Package API > Print Document Package API Interfaces +interface documenttarget.h IPrintDocumentPackageStatusEvent +interface documenttarget.h IPrintDocumentPackageTarget +interface documenttarget.h IPrintDocumentPackageTargetFactory + +# Desktop App Technologies > Documents and Printing > Printing > Print Document Package API > Print Document Package API Structures +struct documenttarget.h PRINTDOCUMENTPACKAGESTATUS + +# Desktop App Technologies > Documents and Printing > Printing > Print Document Package API > Print Document Package API Enumerations +enum documenttarget.h PRINTDOCUMENTPACKAGECOMPLETION + +# Desktop App Technologies > Documents and Printing > Printing > Print Spooler API > Asynchronous Printing Notification Reference > Asynchronous Printing Notification Functions +function prnasnot.h CreatePrintAsyncNotifyChannel +function prnasnot.h RegisterForPrintAsyncNotifications +function prnasnot.h UnRegisterForPrintAsyncNotifications + +# Desktop App Technologies > Documents and Printing > Printing > Print Spooler API > Asynchronous Printing Notification Reference > Asynchronous Printing Notification Interfaces +interface prnasnot.h IPrintAsyncNotifyCallback +interface prnasnot.h IPrintAsyncNotifyChannel +interface prnasnot.h IPrintAsyncNotifyDataObject + +# Desktop App Technologies > Documents and Printing > Printing > Print Spooler API > Asynchronous Printing Notification Reference > Asynchronous Printing Notification Enumerations +enum prnasnot.h PrintAsyncNotifyConversationStyle +enum prnasnot.h PrintAsyncNotifyError +enum prnasnot.h PrintAsyncNotifyUserFilter + +# Desktop App Technologies > Documents and Printing > Printing > Print Ticket API > Print Ticket API Functions +function prntvpt.h PTCloseProvider +function prntvpt.h PTConvertDevModeToPrintTicket +function prntvpt.h PTConvertPrintTicketToDevMode +function prntvpt.h PTGetPrintCapabilities +function prntvpt.h PTGetPrintDeviceCapabilities +function prntvpt.h PTGetPrintDeviceResources +function prntvpt.h PTMergeAndValidatePrintTicket +function prntvpt.h PTOpenProvider +function prntvpt.h PTOpenProviderEx +function prntvpt.h PTQuerySchemaVersionSupport +function prntvpt.h PTReleaseMemory + +# Desktop App Technologies > Documents and Printing > Printing > Print Ticket API > Print Ticket API Enumerations +enum prntvpt.h EDefaultDevmodeType +enum prntvpt.h EPrintTicketScope + +# Desktop App Technologies > Documents and Printing > Printing > GDI Print API > GDI Print API Reference > GDI Print API Functions +callback wingdi.h AbortProc +function wingdi.h AbortDoc +function wingdi.h DeviceCapabilities =devicecapabilitiesa +function wingdi.h EndDoc +function wingdi.h EndPage +function wingdi.h Escape +function wingdi.h ExtEscape +function wingdi.h SetAbortProc +function wingdi.h StartDoc =startdoca +function wingdi.h StartPage +function winuser.h PrintWindow + +# Desktop App Technologies > Documents and Printing > Printing > GDI Print API > GDI Print API Reference > GDI Print API Structures +struct wingdi.h DEVMODE =devmodea +struct wingdi.h DOCINFO =docinfoa +struct wingdi.h DRAWPATRECT +struct wingdi.h PSFEATURE_CUSTPAPER +struct wingdi.h PSFEATURE_OUTPUT +struct wingdi.h PSINJECTDATA + +# Desktop App Technologies > Documents and Printing > Printing > XPS Print API > XPS Print API Reference > XPS Print API Interfaces +interface xpsprint.h IXpsPrintJob +interface xpsprint.h IXpsPrintJobStream + +# Desktop App Technologies > Documents and Printing > Printing > XPS Print API > XPS Print API Reference > XPS Print API Functions +function xpsprint.h StartXpsPrintJob +function xpsprint.h StartXpsPrintJob1 + +# Desktop App Technologies > Documents and Printing > Printing > XPS Print API > XPS Print API Reference > XPS Print API Structures +struct xpsprint.h XPS_JOB_STATUS + +# Desktop App Technologies > Documents and Printing > Printing > XPS Print API > XPS Print API Reference > XPS Print API Enumerations +enum xpsprint.h XPS_JOB_COMPLETION + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > D2D1 Namespace > Classes +class d2d1_1helper.h Matrix4x4F +class d2d1helper.h ColorF +class d2d1helper.h Matrix3x2F + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > D2D1 Namespace > Functions +function d2d1_1helper.h BitmapBrushProperties1 +function d2d1_1helper.h BitmapProperties1 +function d2d1_1helper.h ConvertColorSpace +function d2d1_1helper.h CreationProperties +function d2d1_1helper.h DrawingStateDescription1 +function d2d1_1helper.h EffectInputDescription +function d2d1_1helper.h ImageBrushProperties +function d2d1_1helper.h LayerParameters1 +function d2d1_1helper.h Point2L +function d2d1_1helper.h PrintControlProperties +function d2d1_1helper.h RectL +function d2d1_1helper.h RenderingControls +function d2d1_1helper.h SetDpiCompensatedEffectInput +function d2d1_1helper.h StrokeStyleProperties1 +function d2d1_1helper.h Vector2F +function d2d1_1helper.h Vector3F +function d2d1_1helper.h Vector4F +function d2d1_3helper.h GradientMeshPatch +function d2d1_3helper.h GradientMeshPatchFromCoonsPatch +function d2d1_3helper.h InkBezierSegment +function d2d1_3helper.h InkPoint +function d2d1_3helper.h InkStyleProperties +function d2d1helper.h ArcSegment +function d2d1helper.h BezierSegment +function d2d1helper.h BitmapBrushProperties +function d2d1helper.h BitmapProperties +function d2d1helper.h BrushProperties +function d2d1helper.h DrawingStateDescription +function d2d1helper.h Ellipse +function d2d1helper.h FloatMax +function d2d1helper.h GradientStop +function d2d1helper.h HwndRenderTargetProperties +function d2d1helper.h IdentityMatrix +function d2d1helper.h InfiniteRect +function d2d1helper.h LayerParameters +function d2d1helper.h LinearGradientBrushProperties +function d2d1helper.h operator* (D2D1_POINT_2F&,D2D1_MATRIX_F&) =matrix3x2f-operator-mult +function d2d1helper.h PixelFormat +function d2d1helper.h Point2F +function d2d1helper.h Point2U +function d2d1helper.h QuadraticBezierSegment +function d2d1helper.h RadialGradientBrushProperties +function d2d1helper.h RectF +function d2d1helper.h RectU +function d2d1helper.h RenderTargetProperties +function d2d1helper.h RoundedRect +function d2d1helper.h SizeF +function d2d1helper.h SizeU +function d2d1helper.h StrokeStyleProperties + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > D2D1 Namespace > Structures +struct d2d1helper.h TypeTraits(Type) =typetraits + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > Direct2D enumerations +enum d2d1_1.h D2D1_BITMAP_OPTIONS +enum d2d1_1.h D2D1_BUFFER_PRECISION +enum d2d1_1.h D2D1_COLOR_INTERPOLATION_MODE +enum d2d1_1.h D2D1_COLOR_SPACE +enum d2d1_1.h D2D1_COMPOSITE_MODE +enum d2d1_1.h D2D1_DEVICE_CONTEXT_OPTIONS +enum d2d1_1.h D2D1_INTERPOLATION_MODE +enum d2d1_1.h D2D1_LAYER_OPTIONS1 +enum d2d1_1.h D2D1_MAP_OPTIONS +enum d2d1_1.h D2D1_PRIMITIVE_BLEND +enum d2d1_1.h D2D1_PRINT_FONT_SUBSET_MODE +enum d2d1_1.h D2D1_PROPERTY +enum d2d1_1.h D2D1_PROPERTY_TYPE +enum d2d1_1.h D2D1_STROKE_TRANSFORM_TYPE +enum d2d1_1.h D2D1_SUBPROPERTY +enum d2d1_1.h D2D1_THREADING_MODE +enum d2d1_1.h D2D1_UNIT_MODE +enum d2d1_2.h D2D1_RENDERING_PRIORITY +enum d2d1_3.h D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION +enum d2d1_3.h D2D1_COLOR_CONTEXT_TYPE +enum d2d1_3.h D2D1_GAMMA1 +enum d2d1_3.h D2D1_IMAGE_SOURCE_FROM_DXGI_OPTIONS +enum d2d1_3.h D2D1_IMAGE_SOURCE_LOADING_OPTIONS +enum d2d1_3.h D2D1_INK_NIB_SHAPE +enum d2d1_3.h D2D1_ORIENTATION +enum d2d1_3.h D2D1_PATCH_EDGE_MODE +enum d2d1_3.h D2D1_SPRITE_OPTIONS +enum d2d1_3.h D2D1_TRANSFORMED_IMAGE_SOURCE_OPTIONS +enum d2d1.h D2D1_ANTIALIAS_MODE +enum d2d1.h D2D1_ARC_SIZE +enum d2d1.h D2D1_BITMAP_INTERPOLATION_MODE +enum d2d1.h D2D1_CAP_STYLE +enum d2d1.h D2D1_COMBINE_MODE +enum d2d1.h D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS +enum d2d1.h D2D1_DASH_STYLE +enum d2d1.h D2D1_DC_INITIALIZE_MODE +enum d2d1.h D2D1_DEBUG_LEVEL +enum d2d1.h D2D1_DRAW_TEXT_OPTIONS +enum d2d1.h D2D1_EXTEND_MODE +enum d2d1.h D2D1_FACTORY_TYPE +enum d2d1.h D2D1_FEATURE_LEVEL +enum d2d1.h D2D1_FIGURE_BEGIN +enum d2d1.h D2D1_FIGURE_END +enum d2d1.h D2D1_FILL_MODE +enum d2d1.h D2D1_GAMMA +enum d2d1.h D2D1_GEOMETRY_RELATION +enum d2d1.h D2D1_GEOMETRY_SIMPLIFICATION_OPTION +enum d2d1.h D2D1_LAYER_OPTIONS +enum d2d1.h D2D1_LINE_JOIN +enum d2d1.h D2D1_OPACITY_MASK_CONTENT +enum d2d1.h D2D1_PATH_SEGMENT +enum d2d1.h D2D1_PRESENT_OPTIONS +enum d2d1.h D2D1_RENDER_TARGET_TYPE +enum d2d1.h D2D1_RENDER_TARGET_USAGE +enum d2d1.h D2D1_SWEEP_DIRECTION +enum d2d1.h D2D1_TEXT_ANTIALIAS_MODE +enum d2d1.h D2D1_WINDOW_STATE +enum d2d1effectauthor.h D2D1_BLEND +enum d2d1effectauthor.h D2D1_BLEND_OPERATION +enum d2d1effectauthor.h D2D1_CHANGE_TYPE +enum d2d1effectauthor.h D2D1_CHANNEL_DEPTH +enum d2d1effectauthor.h D2D1_FEATURE +enum d2d1effectauthor.h D2D1_FILTER +enum d2d1effectauthor.h D2D1_PIXEL_OPTIONS +enum d2d1effectauthor.h D2D1_VERTEX_OPTIONS +enum d2d1effectauthor.h D2D1_VERTEX_USAGE +enum d2d1effects_1.h D2D1_YCBCR_CHROMA_SUBSAMPLING +enum d2d1effects_1.h D2D1_YCBCR_INTERPOLATION_MODE +enum d2d1effects_1.h D2D1_YCBCR_PROP +enum d2d1effects_2.h D2D1_CHROMAKEY_PROP +enum d2d1effects_2.h D2D1_CONTRAST_PROP +enum d2d1effects_2.h D2D1_EDGEDETECTION_MODE +enum d2d1effects_2.h D2D1_EDGEDETECTION_PROP +enum d2d1effects_2.h D2D1_EMBOSS_PROP +enum d2d1effects_2.h D2D1_EXPOSURE_PROP +enum d2d1effects_2.h D2D1_HDRTONEMAP_DISPLAY_MODE +enum d2d1effects_2.h D2D1_HDRTONEMAP_PROP +enum d2d1effects_2.h D2D1_HIGHLIGHTSANDSHADOWS_INPUT_GAMMA +enum d2d1effects_2.h D2D1_HIGHLIGHTSANDSHADOWS_PROP +enum d2d1effects_2.h D2D1_HUETORGB_INPUT_COLOR_SPACE +enum d2d1effects_2.h D2D1_HUETORGB_PROP +enum d2d1effects_2.h D2D1_LOOKUPTABLE3D_PROP +enum d2d1effects_2.h D2D1_POSTERIZE_PROP +enum d2d1effects_2.h D2D1_RGBTOHUE_OUTPUT_COLOR_SPACE +enum d2d1effects_2.h D2D1_RGBTOHUE_PROP +enum d2d1effects_2.h D2D1_SEPIA_PROP +enum d2d1effects_2.h D2D1_SHARPEN_PROP +enum d2d1effects_2.h D2D1_STRAIGHTEN_PROP +enum d2d1effects_2.h D2D1_STRAIGHTEN_SCALE_MODE +enum d2d1effects_2.h D2D1_TEMPERATUREANDTINT_PROP +enum d2d1effects_2.h D2D1_VIGNETTE_PROP +enum d2d1effects_2.h D2D1_WHITELEVELADJUSTMENT_PROP +enum d2d1effects.h D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE +enum d2d1effects.h D2D1_2DAFFINETRANSFORM_PROP +enum d2d1effects.h D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE +enum d2d1effects.h D2D1_3DPERSPECTIVETRANSFORM_PROP +enum d2d1effects.h D2D1_3DTRANSFORM_INTERPOLATION_MODE +enum d2d1effects.h D2D1_3DTRANSFORM_PROP +enum d2d1effects.h D2D1_ARITHMETICCOMPOSITE_PROP +enum d2d1effects.h D2D1_ATLAS_PROP +enum d2d1effects.h D2D1_BITMAPSOURCE_ALPHA_MODE +enum d2d1effects.h D2D1_BITMAPSOURCE_INTERPOLATION_MODE +enum d2d1effects.h D2D1_BITMAPSOURCE_ORIENTATION +enum d2d1effects.h D2D1_BITMAPSOURCE_PROP +enum d2d1effects.h D2D1_BLEND_MODE +enum d2d1effects.h D2D1_BLEND_PROP +enum d2d1effects.h D2D1_BORDER_EDGE_MODE +enum d2d1effects.h D2D1_BORDER_MODE +enum d2d1effects.h D2D1_BORDER_PROP +enum d2d1effects.h D2D1_BRIGHTNESS_PROP +enum d2d1effects.h D2D1_CHANNEL_SELECTOR +enum d2d1effects.h D2D1_COLORMANAGEMENT_ALPHA_MODE +enum d2d1effects.h D2D1_COLORMANAGEMENT_PROP +enum d2d1effects.h D2D1_COLORMANAGEMENT_QUALITY +enum d2d1effects.h D2D1_COLORMANAGEMENT_RENDERING_INTENT +enum d2d1effects.h D2D1_COLORMATRIX_ALPHA_MODE +enum d2d1effects.h D2D1_COLORMATRIX_PROP +enum d2d1effects.h D2D1_COMPOSITE_PROP +enum d2d1effects.h D2D1_CONVOLVEMATRIX_PROP +enum d2d1effects.h D2D1_CONVOLVEMATRIX_SCALE_MODE +enum d2d1effects.h D2D1_CROP_PROP +enum d2d1effects.h D2D1_DIRECTIONALBLUR_OPTIMIZATION +enum d2d1effects.h D2D1_DIRECTIONALBLUR_PROP +enum d2d1effects.h D2D1_DISCRETETRANSFER_PROP +enum d2d1effects.h D2D1_DISPLACEMENTMAP_PROP +enum d2d1effects.h D2D1_DISTANTDIFFUSE_PROP +enum d2d1effects.h D2D1_DISTANTDIFFUSE_SCALE_MODE +enum d2d1effects.h D2D1_DISTANTSPECULAR_PROP +enum d2d1effects.h D2D1_DISTANTSPECULAR_SCALE_MODE +enum d2d1effects.h D2D1_DPICOMPENSATION_INTERPOLATION_MODE +enum d2d1effects.h D2D1_DPICOMPENSATION_PROP +enum d2d1effects.h D2D1_FLOOD_PROP +enum d2d1effects.h D2D1_GAMMATRANSFER_PROP +enum d2d1effects.h D2D1_GAUSSIANBLUR_OPTIMIZATION +enum d2d1effects.h D2D1_GAUSSIANBLUR_PROP +enum d2d1effects.h D2D1_HISTOGRAM_PROP +enum d2d1effects.h D2D1_HUEROTATION_PROP +enum d2d1effects.h D2D1_LINEARTRANSFER_PROP +enum d2d1effects.h D2D1_MORPHOLOGY_MODE +enum d2d1effects.h D2D1_MORPHOLOGY_PROP +enum d2d1effects.h D2D1_OPACITYMETADATA_PROP +enum d2d1effects.h D2D1_POINTDIFFUSE_PROP +enum d2d1effects.h D2D1_POINTDIFFUSE_SCALE_MODE +enum d2d1effects.h D2D1_POINTSPECULAR_PROP +enum d2d1effects.h D2D1_POINTSPECULAR_SCALE_MODE +enum d2d1effects.h D2D1_SATURATION_PROP +enum d2d1effects.h D2D1_SCALE_INTERPOLATION_MODE +enum d2d1effects.h D2D1_SCALE_PROP +enum d2d1effects.h D2D1_SHADOW_OPTIMIZATION +enum d2d1effects.h D2D1_SHADOW_PROP +enum d2d1effects.h D2D1_SPOTDIFFUSE_PROP +enum d2d1effects.h D2D1_SPOTDIFFUSE_SCALE_MODE +enum d2d1effects.h D2D1_SPOTSPECULAR_PROP +enum d2d1effects.h D2D1_SPOTSPECULAR_SCALE_MODE +enum d2d1effects.h D2D1_TABLETRANSFER_PROP +enum d2d1effects.h D2D1_TILE_PROP +enum d2d1effects.h D2D1_TURBULENCE_NOISE +enum d2d1effects.h D2D1_TURBULENCE_PROP +enum d2d1svg.h D2D1_SVG_ASPECT_ALIGN +enum d2d1svg.h D2D1_SVG_ASPECT_SCALING +enum d2d1svg.h D2D1_SVG_ATTRIBUTE_POD_TYPE +enum d2d1svg.h D2D1_SVG_ATTRIBUTE_STRING_TYPE +enum d2d1svg.h D2D1_SVG_DISPLAY +enum d2d1svg.h D2D1_SVG_LENGTH_UNITS +enum d2d1svg.h D2D1_SVG_LINE_CAP +enum d2d1svg.h D2D1_SVG_LINE_JOIN +enum d2d1svg.h D2D1_SVG_OVERFLOW +enum d2d1svg.h D2D1_SVG_PAINT_TYPE +enum d2d1svg.h D2D1_SVG_PATH_COMMAND +enum d2d1svg.h D2D1_SVG_UNIT_TYPE +enum d2d1svg.h D2D1_SVG_VISIBILITY +enum dcommon.h D2D1_ALPHA_MODE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > Direct2D functions +callback d2d1effectauthor.h PD2D1_PROPERTY_GET_FUNCTION +callback d2d1effectauthor.h PD2D1_PROPERTY_SET_FUNCTION +function d2d1_1.h D2D1ConvertColorSpace +function d2d1_1.h D2D1CreateDevice +function d2d1_1.h D2D1CreateDeviceContext +function d2d1_1.h D2D1SinCos +function d2d1_1.h D2D1Tan +function d2d1_1.h D2D1Vec3Length +function d2d1_2.h D2D1ComputeMaximumScaleFactor +function d2d1_3.h D2D1GetGradientMeshInteriorPointsFromCoonsPatch +function d2d1.h D2D1CreateFactory(D2D1_FACTORY_TYPE,REFIID,D2D1_FACTORY_OPTIONS*,void**) =d2d1createfactory +function d2d1.h D2D1CreateFactory(D2D1_FACTORY_TYPE,REFIID,void**) =d2d1createfactory-r1 +function d2d1.h D2D1InvertMatrix +function d2d1.h D2D1IsMatrixInvertible +function d2d1.h D2D1MakeRotateMatrix +function d2d1.h D2D1MakeSkewMatrix +function d2d1effecthelpers.h StringGetter +function d2d1effecthelpers.h StringSetter +function d2d1effecthelpers.h ValueGetter +function d2d1effecthelpers.h ValueSetter +function d2d1helper.h operator* (const D2D1_MATRIX_3X2_F&,const D2D1_MATRIX_3X2_F&) =matrix3x2f-setproduct + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > Direct2D interfaces +interface d2d1_1.h ID2D1Bitmap1 +interface d2d1_1.h ID2D1BitmapBrush1 +interface d2d1_1.h ID2D1ColorContext +interface d2d1_1.h ID2D1CommandList +interface d2d1_1.h ID2D1CommandSink +interface d2d1_1.h ID2D1Device +interface d2d1_1.h ID2D1DeviceContext +interface d2d1_1.h ID2D1DrawingStateBlock1 +interface d2d1_1.h ID2D1Effect +interface d2d1_1.h ID2D1Factory1 +interface d2d1_1.h ID2D1GdiMetafile +interface d2d1_1.h ID2D1GdiMetafileSink +interface d2d1_1.h ID2D1GradientStopCollection1 +interface d2d1_1.h ID2D1ImageBrush +interface d2d1_1.h ID2D1Multithread +interface d2d1_1.h ID2D1PathGeometry1 +interface d2d1_1.h ID2D1PrintControl +interface d2d1_1.h ID2D1Properties +interface d2d1_1.h ID2D1StrokeStyle1 +interface d2d1_2.h ID2D1CommandSink1 +interface d2d1_2.h ID2D1Device1 +interface d2d1_2.h ID2D1DeviceContext1 +interface d2d1_2.h ID2D1Factory2 +interface d2d1_2.h ID2D1GeometryRealization +interface d2d1_3.h ID2D1ColorContext1 +interface d2d1_3.h ID2D1CommandSink2 +interface d2d1_3.h ID2D1CommandSink3 +interface d2d1_3.h ID2D1CommandSink4 +interface d2d1_3.h ID2D1CommandSink5 +interface d2d1_3.h ID2D1Device2 +interface d2d1_3.h ID2D1Device3 +interface d2d1_3.h ID2D1Device4 +interface d2d1_3.h ID2D1Device5 +interface d2d1_3.h ID2D1Device6 +interface d2d1_3.h ID2D1DeviceContext2 +interface d2d1_3.h ID2D1DeviceContext3 +interface d2d1_3.h ID2D1DeviceContext4 +interface d2d1_3.h ID2D1DeviceContext5 +interface d2d1_3.h ID2D1DeviceContext6 +interface d2d1_3.h ID2D1Factory3 +interface d2d1_3.h ID2D1Factory4 +interface d2d1_3.h ID2D1Factory5 +interface d2d1_3.h ID2D1Factory6 +interface d2d1_3.h ID2D1Factory7 +interface d2d1_3.h ID2D1GdiMetafile1 +interface d2d1_3.h ID2D1GdiMetafileSink1 +interface d2d1_3.h ID2D1GradientMesh +interface d2d1_3.h ID2D1ImageSource +interface d2d1_3.h ID2D1ImageSourceFromWic +interface d2d1_3.h ID2D1Ink +interface d2d1_3.h ID2D1InkStyle +interface d2d1_3.h ID2D1LookupTable3D +interface d2d1_3.h ID2D1SpriteBatch +interface d2d1_3.h ID2D1SvgGlyphStyle +interface d2d1_3.h ID2D1TransformedImageSource +interface d2d1.h ID2D1Bitmap +interface d2d1.h ID2D1BitmapBrush +interface d2d1.h ID2D1BitmapRenderTarget +interface d2d1.h ID2D1Brush +interface d2d1.h ID2D1DCRenderTarget +interface d2d1.h ID2D1DrawingStateBlock +interface d2d1.h ID2D1EllipseGeometry +interface d2d1.h ID2D1Factory +interface d2d1.h ID2D1GdiInteropRenderTarget +interface d2d1.h ID2D1Geometry +interface d2d1.h ID2D1GeometryGroup +interface d2d1.h ID2D1GeometrySink +interface d2d1.h ID2D1GradientStopCollection +interface d2d1.h ID2D1HwndRenderTarget +interface d2d1.h ID2D1Image +interface d2d1.h ID2D1Layer +interface d2d1.h ID2D1LinearGradientBrush +interface d2d1.h ID2D1Mesh +interface d2d1.h ID2D1PathGeometry +interface d2d1.h ID2D1RadialGradientBrush +interface d2d1.h ID2D1RectangleGeometry +interface d2d1.h ID2D1RenderTarget +interface d2d1.h ID2D1Resource +interface d2d1.h ID2D1RoundedRectangleGeometry +interface d2d1.h ID2D1SimplifiedGeometrySink +interface d2d1.h ID2D1SolidColorBrush +interface d2d1.h ID2D1StrokeStyle +interface d2d1.h ID2D1TessellationSink +interface d2d1.h ID2D1TransformedGeometry +interface d2d1effectauthor_1.h ID2D1EffectContext1 +interface d2d1effectauthor.h ID2D1AnalysisTransform +interface d2d1effectauthor.h ID2D1BlendTransform +interface d2d1effectauthor.h ID2D1BorderTransform +interface d2d1effectauthor.h ID2D1BoundsAdjustmentTransform +interface d2d1effectauthor.h ID2D1ComputeInfo +interface d2d1effectauthor.h ID2D1ComputeTransform +interface d2d1effectauthor.h ID2D1ConcreteTransform +interface d2d1effectauthor.h ID2D1DrawInfo +interface d2d1effectauthor.h ID2D1DrawTransform +interface d2d1effectauthor.h ID2D1EffectContext +interface d2d1effectauthor.h ID2D1EffectImpl +interface d2d1effectauthor.h ID2D1OffsetTransform +interface d2d1effectauthor.h ID2D1RenderInfo +interface d2d1effectauthor.h ID2D1ResourceTexture +interface d2d1effectauthor.h ID2D1SourceTransform +interface d2d1effectauthor.h ID2D1Transform +interface d2d1effectauthor.h ID2D1TransformGraph +interface d2d1effectauthor.h ID2D1TransformNode +interface d2d1effectauthor.h ID2D1VertexBuffer +interface d2d1svg.h ID2D1SvgAttribute +interface d2d1svg.h ID2D1SvgDocument +interface d2d1svg.h ID2D1SvgElement +interface d2d1svg.h ID2D1SvgPaint +interface d2d1svg.h ID2D1SvgPathData +interface d2d1svg.h ID2D1SvgPointCollection +interface d2d1svg.h ID2D1SvgStrokeDashArray + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct2D > Direct2D reference > Direct2D structures +callback d2d1_1.h PD2D1_EFFECT_FACTORY +struct d2d1_1.h D2D1_BITMAP_BRUSH_PROPERTIES1 +struct d2d1_1.h D2D1_BITMAP_PROPERTIES1 +struct d2d1_1.h D2D1_CREATION_PROPERTIES +struct d2d1_1.h D2D1_DRAWING_STATE_DESCRIPTION1 +struct d2d1_1.h D2D1_EFFECT_INPUT_DESCRIPTION +struct d2d1_1.h D2D1_IMAGE_BRUSH_PROPERTIES +struct d2d1_1.h D2D1_LAYER_PARAMETERS1 +struct d2d1_1.h D2D1_MAPPED_RECT +struct d2d1_1.h D2D1_POINT_DESCRIPTION +struct d2d1_1.h D2D1_PRINT_CONTROL_PROPERTIES +struct d2d1_1.h D2D1_RENDERING_CONTROLS +struct d2d1_1.h D2D1_STROKE_STYLE_PROPERTIES1 +struct d2d1_3.h D2D1_GRADIENT_MESH_PATCH +struct d2d1_3.h D2D1_INK_BEZIER_SEGMENT +struct d2d1_3.h D2D1_INK_POINT +struct d2d1_3.h D2D1_INK_STYLE_PROPERTIES +struct d2d1_3.h D2D1_SIMPLE_COLOR_PROFILE +struct d2d1_3.h D2D1_TRANSFORMED_IMAGE_SOURCE_PROPERTIES +struct d2d1.h D2D1_ARC_SEGMENT +struct d2d1.h D2D1_BEZIER_SEGMENT +struct d2d1.h D2D1_BITMAP_BRUSH_PROPERTIES +struct d2d1.h D2D1_BITMAP_PROPERTIES +struct d2d1.h D2D1_BRUSH_PROPERTIES +struct d2d1.h D2D1_DRAWING_STATE_DESCRIPTION +struct d2d1.h D2D1_ELLIPSE +struct d2d1.h D2D1_FACTORY_OPTIONS +struct d2d1.h D2D1_GRADIENT_STOP +struct d2d1.h D2D1_HWND_RENDER_TARGET_PROPERTIES +struct d2d1.h D2D1_LAYER_PARAMETERS +struct d2d1.h D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES +struct d2d1.h D2D1_QUADRATIC_BEZIER_SEGMENT +struct d2d1.h D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES +struct d2d1.h D2D1_RENDER_TARGET_PROPERTIES +struct d2d1.h D2D1_ROUNDED_RECT +struct d2d1.h D2D1_STROKE_STYLE_PROPERTIES +struct d2d1.h D2D1_TRIANGLE +struct d2d1effectauthor.h D2D1_BLEND_DESCRIPTION +struct d2d1effectauthor.h D2D1_CUSTOM_VERTEX_BUFFER_PROPERTIES +struct d2d1effectauthor.h D2D1_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS +struct d2d1effectauthor.h D2D1_FEATURE_DATA_DOUBLES +struct d2d1effectauthor.h D2D1_INPUT_DESCRIPTION +struct d2d1effectauthor.h D2D1_INPUT_ELEMENT_DESC +struct d2d1effectauthor.h D2D1_PROPERTY_BINDING +struct d2d1effectauthor.h D2D1_RESOURCE_TEXTURE_PROPERTIES +struct d2d1effectauthor.h D2D1_VERTEX_BUFFER_PROPERTIES +struct d2d1effectauthor.h D2D1_VERTEX_RANGE +struct d2d1svg.h D2D1_SVG_LENGTH +struct d2d1svg.h D2D1_SVG_PRESERVE_ASPECT_RATIO +struct d2d1svg.h D2D1_SVG_VIEWBOX +struct dcommon.h D2D_MATRIX_3X2_F +struct dcommon.h D2D_MATRIX_4X3_F +struct dcommon.h D2D_MATRIX_4X4_F +struct dcommon.h D2D_MATRIX_5X4_F +struct dcommon.h D2D_POINT_2F +struct dcommon.h D2D_POINT_2U +struct dcommon.h D2D_RECT_F +struct dcommon.h D2D_RECT_U +struct dcommon.h D2D_SIZE_F +struct dcommon.h D2D_SIZE_U +struct dcommon.h D2D_VECTOR_2F +struct dcommon.h D2D_VECTOR_3F +struct dcommon.h D2D_VECTOR_4F +struct dcommon.h D2D1_PIXEL_FORMAT +struct dcommon.h D2D1_VECTOR_2F =d2d_vector_2f +struct dcommon.h D2D1_VECTOR_3F =d2d_vector_3f +struct dcommon.h D2D1_VECTOR_4F =d2d_vector_4f + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Core reference > Core interfaces +interface d3d12.h ID3D12CommandAllocator +interface d3d12.h ID3D12CommandList +interface d3d12.h ID3D12CommandQueue +interface d3d12.h ID3D12CommandSignature +interface d3d12.h ID3D12DescriptorHeap +interface d3d12.h ID3D12Device +interface d3d12.h ID3D12Device1 +interface d3d12.h ID3D12Device10 +interface d3d12.h ID3D12Device2 +interface d3d12.h ID3D12Device3 +interface d3d12.h ID3D12Device4 +interface d3d12.h ID3D12Device5 +interface d3d12.h ID3D12Device6 +interface d3d12.h ID3D12Device7 +interface d3d12.h ID3D12Device8 +interface d3d12.h ID3D12Device9 +interface d3d12.h ID3D12DeviceChild +interface d3d12.h ID3D12DeviceRemovedExtendedData +interface d3d12.h ID3D12DeviceRemovedExtendedDataSettings +interface d3d12.h ID3D12Fence +interface d3d12.h ID3D12Fence1 +interface d3d12.h ID3D12GraphicsCommandList +interface d3d12.h ID3D12GraphicsCommandList1 +interface d3d12.h ID3D12GraphicsCommandList2 +interface d3d12.h ID3D12GraphicsCommandList3 +interface d3d12.h ID3D12GraphicsCommandList4 +interface d3d12.h ID3D12GraphicsCommandList5 +interface d3d12.h ID3D12GraphicsCommandList7 +interface d3d12.h ID3D12Heap +interface d3d12.h ID3D12LifetimeOwner +interface d3d12.h ID3D12LifetimeTracker +interface d3d12.h ID3D12MetaCommand +interface d3d12.h ID3D12Object +interface d3d12.h ID3D12Pageable +interface d3d12.h ID3D12PipelineLibrary +interface d3d12.h ID3D12PipelineLibrary1 +interface d3d12.h ID3D12PipelineState +interface d3d12.h ID3D12ProtectedResourceSession +interface d3d12.h ID3D12ProtectedSession +interface d3d12.h ID3D12QueryHeap +interface d3d12.h ID3D12Resource +interface d3d12.h ID3D12RootSignature +interface d3d12.h ID3D12RootSignatureDeserializer +interface d3d12.h ID3D12SDKConfiguration +interface d3d12.h ID3D12ShaderCacheSession +interface d3d12.h ID3D12StateObject +interface d3d12.h ID3D12StateObjectProperties +interface d3d12.h ID3D12Tools +interface d3d12.h ID3D12VersionedRootSignatureDeserializer +interface d3d12.h ID3D12VirtualizationGuestDevice + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Core reference > Core functions +function d3d12.h D3D12CreateDevice +function d3d12.h D3D12CreateRootSignatureDeserializer +function d3d12.h D3D12CreateVersionedRootSignatureDeserializer +function d3d12.h D3D12EnableExperimentalFeatures +function d3d12.h D3D12GetDebugInterface +function d3d12.h D3D12GetInterface +function d3d12.h D3D12SerializeRootSignature +function d3d12.h D3D12SerializeVersionedRootSignature + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Core reference > Core structures +struct d3d12.h D3D12_AUTO_BREADCRUMB_NODE +struct d3d12.h D3D12_BARRIER_GROUP +struct d3d12.h D3D12_BARRIER_SUBRESOURCE_RANGE +struct d3d12.h D3D12_BLEND_DESC +struct d3d12.h D3D12_BOX +struct d3d12.h D3D12_BUFFER_BARRIER +struct d3d12.h D3D12_BUFFER_RTV +struct d3d12.h D3D12_BUFFER_SRV +struct d3d12.h D3D12_BUFFER_UAV +struct d3d12.h D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC +struct d3d12.h D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS +struct d3d12.h D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_TOOLS_VISUALIZATION_HEADER +struct d3d12.h D3D12_CACHED_PIPELINE_STATE +struct d3d12.h D3D12_CLEAR_VALUE +struct d3d12.h D3D12_COMMAND_QUEUE_DESC +struct d3d12.h D3D12_COMMAND_SIGNATURE_DESC +struct d3d12.h D3D12_COMPUTE_PIPELINE_STATE_DESC +struct d3d12.h D3D12_CONSTANT_BUFFER_VIEW_DESC +struct d3d12.h D3D12_CPU_DESCRIPTOR_HANDLE +struct d3d12.h D3D12_DEPTH_STENCIL_DESC +struct d3d12.h D3D12_DEPTH_STENCIL_DESC1 +struct d3d12.h D3D12_DEPTH_STENCIL_VALUE +struct d3d12.h D3D12_DEPTH_STENCIL_VIEW_DESC +struct d3d12.h D3D12_DEPTH_STENCILOP_DESC +struct d3d12.h D3D12_DESCRIPTOR_HEAP_DESC +struct d3d12.h D3D12_DESCRIPTOR_RANGE +struct d3d12.h D3D12_DESCRIPTOR_RANGE1 +struct d3d12.h D3D12_DEVICE_REMOVED_EXTENDED_DATA +struct d3d12.h D3D12_DEVICE_REMOVED_EXTENDED_DATA1 +struct d3d12.h D3D12_DISCARD_REGION +struct d3d12.h D3D12_DISPATCH_ARGUMENTS +struct d3d12.h D3D12_DISPATCH_RAYS_DESC +struct d3d12.h D3D12_DRAW_ARGUMENTS +struct d3d12.h D3D12_DRAW_INDEXED_ARGUMENTS +struct d3d12.h D3D12_DRED_ALLOCATION_NODE +struct d3d12.h D3D12_DRED_AUTO_BREADCRUMBS_OUTPUT +struct d3d12.h D3D12_DRED_PAGE_FAULT_OUTPUT +struct d3d12.h D3D12_DXIL_LIBRARY_DESC +struct d3d12.h D3D12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION +struct d3d12.h D3D12_EXISTING_COLLECTION_DESC +struct d3d12.h D3D12_EXPORT_DESC =d3d12_existing_collection_desc +struct d3d12.h D3D12_FEATURE_DATA_ARCHITECTURE +struct d3d12.h D3D12_FEATURE_DATA_ARCHITECTURE1 +struct d3d12.h D3D12_FEATURE_DATA_COMMAND_QUEUE_PRIORITY +struct d3d12.h D3D12_FEATURE_DATA_CROSS_NODE +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS1 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS10 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS11 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS12 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS13 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS2 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS3 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS4 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS5 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS6 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS7 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS8 +struct d3d12.h D3D12_FEATURE_DATA_D3D12_OPTIONS9 +struct d3d12.h D3D12_FEATURE_DATA_EXISTING_HEAPS +struct d3d12.h D3D12_FEATURE_DATA_FEATURE_LEVELS +struct d3d12.h D3D12_FEATURE_DATA_FORMAT_INFO +struct d3d12.h D3D12_FEATURE_DATA_FORMAT_SUPPORT +struct d3d12.h D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORT +struct d3d12.h D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS +struct d3d12.h D3D12_FEATURE_DATA_PROTECTED_RESOURCE_SESSION_SUPPORT +struct d3d12.h D3D12_FEATURE_DATA_PROTECTED_RESOURCE_SESSION_TYPE_COUNT +struct d3d12.h D3D12_FEATURE_DATA_PROTECTED_RESOURCE_SESSION_TYPES +struct d3d12.h D3D12_FEATURE_DATA_QUERY_META_COMMAND +struct d3d12.h D3D12_FEATURE_DATA_ROOT_SIGNATURE +struct d3d12.h D3D12_FEATURE_DATA_SERIALIZATION +struct d3d12.h D3D12_FEATURE_DATA_SHADER_CACHE +struct d3d12.h D3D12_FEATURE_DATA_SHADER_MODEL +struct d3d12.h D3D12_GLOBAL_BARRIER +struct d3d12.h D3D12_GLOBAL_ROOT_SIGNATURE +struct d3d12.h D3D12_GPU_DESCRIPTOR_HANDLE +struct d3d12.h D3D12_GPU_VIRTUAL_ADDRESS_AND_STRIDE +struct d3d12.h D3D12_GPU_VIRTUAL_ADDRESS_RANGE +struct d3d12.h D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE +struct d3d12.h D3D12_GRAPHICS_PIPELINE_STATE_DESC +struct d3d12.h D3D12_HEAP_DESC +struct d3d12.h D3D12_HEAP_PROPERTIES +struct d3d12.h D3D12_HIT_GROUP_DESC +struct d3d12.h D3D12_INDEX_BUFFER_VIEW +struct d3d12.h D3D12_INDIRECT_ARGUMENT_DESC +struct d3d12.h D3D12_INPUT_ELEMENT_DESC +struct d3d12.h D3D12_INPUT_LAYOUT_DESC +struct d3d12.h D3D12_LOCAL_ROOT_SIGNATURE +struct d3d12.h D3D12_MEMCPY_DEST +struct d3d12.h D3D12_META_COMMAND_DESC +struct d3d12.h D3D12_META_COMMAND_PARAMETER_DESC +struct d3d12.h D3D12_NODE_MASK +struct d3d12.h D3D12_PACKED_MIP_INFO +struct d3d12.h D3D12_PIPELINE_STATE_STREAM_DESC +struct d3d12.h D3D12_PLACED_SUBRESOURCE_FOOTPRINT +struct d3d12.h D3D12_PROTECTED_RESOURCE_SESSION_DESC +struct d3d12.h D3D12_QUERY_DATA_PIPELINE_STATISTICS +struct d3d12.h D3D12_QUERY_DATA_SO_STATISTICS +struct d3d12.h D3D12_QUERY_HEAP_DESC +struct d3d12.h D3D12_RANGE +struct d3d12.h D3D12_RANGE_UINT64 +struct d3d12.h D3D12_RASTERIZER_DESC +struct d3d12.h D3D12_RAYTRACING_AABB +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_COMPACTED_SIZE_DESC +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_CURRENT_SIZE_DESC +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_SERIALIZATION_DESC +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_TOOLS_VISUALIZATION_DESC +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO +struct d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_SRV +struct d3d12.h D3D12_RAYTRACING_GEOMETRY_AABBS_DESC +struct d3d12.h D3D12_RAYTRACING_GEOMETRY_DESC +struct d3d12.h D3D12_RAYTRACING_GEOMETRY_TRIANGLES_DESC +struct d3d12.h D3D12_RAYTRACING_INSTANCE_DESC +struct d3d12.h D3D12_RAYTRACING_PIPELINE_CONFIG +struct d3d12.h D3D12_RAYTRACING_PIPELINE_CONFIG1 +struct d3d12.h D3D12_RAYTRACING_SHADER_CONFIG +struct d3d12.h D3D12_RENDER_PASS_BEGINNING_ACCESS +struct d3d12.h D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS +struct d3d12.h D3D12_RENDER_PASS_DEPTH_STENCIL_DESC +struct d3d12.h D3D12_RENDER_PASS_ENDING_ACCESS +struct d3d12.h D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS +struct d3d12.h D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS +struct d3d12.h D3D12_RENDER_PASS_RENDER_TARGET_DESC +struct d3d12.h D3D12_RENDER_TARGET_BLEND_DESC +struct d3d12.h D3D12_RENDER_TARGET_VIEW_DESC +struct d3d12.h D3D12_RESOURCE_ALIASING_BARRIER +struct d3d12.h D3D12_RESOURCE_ALLOCATION_INFO +struct d3d12.h D3D12_RESOURCE_ALLOCATION_INFO1 +struct d3d12.h D3D12_RESOURCE_BARRIER +struct d3d12.h D3D12_RESOURCE_DESC +struct d3d12.h D3D12_RESOURCE_TRANSITION_BARRIER +struct d3d12.h D3D12_RESOURCE_UAV_BARRIER +struct d3d12.h D3D12_ROOT_CONSTANTS +struct d3d12.h D3D12_ROOT_DESCRIPTOR +struct d3d12.h D3D12_ROOT_DESCRIPTOR_TABLE +struct d3d12.h D3D12_ROOT_DESCRIPTOR_TABLE1 +struct d3d12.h D3D12_ROOT_DESCRIPTOR1 +struct d3d12.h D3D12_ROOT_PARAMETER +struct d3d12.h D3D12_ROOT_PARAMETER1 +struct d3d12.h D3D12_ROOT_SIGNATURE_DESC +struct d3d12.h D3D12_ROOT_SIGNATURE_DESC1 +struct d3d12.h D3D12_RT_FORMAT_ARRAY +struct d3d12.h D3D12_SAMPLE_POSITION +struct d3d12.h D3D12_SAMPLER_DESC +struct d3d12.h D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER +struct d3d12.h D3D12_SERIALIZED_RAYTRACING_ACCELERATION_STRUCTURE_HEADER +struct d3d12.h D3D12_SHADER_BYTECODE +struct d3d12.h D3D12_SHADER_CACHE_SESSION_DESC +struct d3d12.h D3D12_SHADER_RESOURCE_VIEW_DESC +struct d3d12.h D3D12_SO_DECLARATION_ENTRY +struct d3d12.h D3D12_STATE_OBJECT_CONFIG +struct d3d12.h D3D12_STATE_OBJECT_DESC +struct d3d12.h D3D12_STATE_SUBOBJECT +struct d3d12.h D3D12_STATIC_SAMPLER_DESC +struct d3d12.h D3D12_STREAM_OUTPUT_BUFFER_VIEW +struct d3d12.h D3D12_STREAM_OUTPUT_DESC +struct d3d12.h D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION +struct d3d12.h D3D12_SUBRESOURCE_DATA +struct d3d12.h D3D12_SUBRESOURCE_FOOTPRINT +struct d3d12.h D3D12_SUBRESOURCE_INFO +struct d3d12.h D3D12_SUBRESOURCE_RANGE_UINT64 +struct d3d12.h D3D12_SUBRESOURCE_TILING +struct d3d12.h D3D12_TEX1D_ARRAY_DSV +struct d3d12.h D3D12_TEX1D_ARRAY_RTV +struct d3d12.h D3D12_TEX1D_ARRAY_SRV +struct d3d12.h D3D12_TEX1D_ARRAY_UAV +struct d3d12.h D3D12_TEX1D_DSV +struct d3d12.h D3D12_TEX1D_RTV +struct d3d12.h D3D12_TEX1D_SRV +struct d3d12.h D3D12_TEX1D_UAV +struct d3d12.h D3D12_TEX2D_ARRAY_DSV +struct d3d12.h D3D12_TEX2D_ARRAY_RTV +struct d3d12.h D3D12_TEX2D_ARRAY_SRV +struct d3d12.h D3D12_TEX2D_ARRAY_UAV +struct d3d12.h D3D12_TEX2D_DSV +struct d3d12.h D3D12_TEX2D_RTV +struct d3d12.h D3D12_TEX2D_SRV +struct d3d12.h D3D12_TEX2D_UAV +struct d3d12.h D3D12_TEX2DMS_ARRAY_DSV +struct d3d12.h D3D12_TEX2DMS_ARRAY_RTV +struct d3d12.h D3D12_TEX2DMS_ARRAY_SRV +struct d3d12.h D3D12_TEX2DMS_DSV +struct d3d12.h D3D12_TEX2DMS_RTV +struct d3d12.h D3D12_TEX2DMS_SRV +struct d3d12.h D3D12_TEX3D_RTV +struct d3d12.h D3D12_TEX3D_SRV +struct d3d12.h D3D12_TEX3D_UAV +struct d3d12.h D3D12_TEXCUBE_ARRAY_SRV +struct d3d12.h D3D12_TEXCUBE_SRV +struct d3d12.h D3D12_TEXTURE_BARRIER +struct d3d12.h D3D12_TEXTURE_COPY_LOCATION +struct d3d12.h D3D12_TILE_REGION_SIZE +struct d3d12.h D3D12_TILE_SHAPE +struct d3d12.h D3D12_TILED_RESOURCE_COORDINATE +struct d3d12.h D3D12_UNORDERED_ACCESS_VIEW_DESC +struct d3d12.h D3D12_VERSIONED_DEVICE_REMOVED_EXTENDED_DATA +struct d3d12.h D3D12_VERSIONED_ROOT_SIGNATURE_DESC +struct d3d12.h D3D12_VERTEX_BUFFER_VIEW +struct d3d12.h D3D12_VIEW_INSTANCE_LOCATION +struct d3d12.h D3D12_VIEW_INSTANCING_DESC +struct d3d12.h D3D12_VIEWPORT +struct d3d12.h D3D12_WRITEBUFFERIMMEDIATE_PARAMETER + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Core reference > Core enumerations +enum d3d12.h D3D_ROOT_SIGNATURE_VERSION +enum d3d12.h D3D_SHADER_MODEL +enum d3d12.h D3D12_AUTO_BREADCRUMB_OP +enum d3d12.h D3D12_AXIS_SHADING_RATE +enum d3d12.h D3D12_BACKGROUND_PROCESSING_MODE +enum d3d12.h D3D12_BLEND +enum d3d12.h D3D12_BLEND_OP +enum d3d12.h D3D12_BUFFER_SRV_FLAGS +enum d3d12.h D3D12_BUFFER_UAV_FLAGS +enum d3d12.h D3D12_CLEAR_FLAGS +enum d3d12.h D3D12_COLOR_WRITE_ENABLE +enum d3d12.h D3D12_COMMAND_LIST_SUPPORT_FLAGS +enum d3d12.h D3D12_COMMAND_LIST_TYPE +enum d3d12.h D3D12_COMMAND_QUEUE_FLAGS +enum d3d12.h D3D12_COMMAND_QUEUE_PRIORITY +enum d3d12.h D3D12_COMPARISON_FUNC +enum d3d12.h D3D12_CONSERVATIVE_RASTERIZATION_MODE +enum d3d12.h D3D12_CONSERVATIVE_RASTERIZATION_TIER +enum d3d12.h D3D12_CPU_PAGE_PROPERTY +enum d3d12.h D3D12_CROSS_NODE_SHARING_TIER +enum d3d12.h D3D12_CULL_MODE +enum d3d12.h D3D12_DEPTH_WRITE_MASK +enum d3d12.h D3D12_DESCRIPTOR_HEAP_FLAGS +enum d3d12.h D3D12_DESCRIPTOR_HEAP_TYPE +enum d3d12.h D3D12_DESCRIPTOR_RANGE_FLAGS +enum d3d12.h D3D12_DESCRIPTOR_RANGE_TYPE +enum d3d12.h D3D12_DRED_ALLOCATION_TYPE +enum d3d12.h D3D12_DRED_ENABLEMENT +enum d3d12.h D3D12_DRIVER_MATCHING_IDENTIFIER_STATUS +enum d3d12.h D3D12_DSV_DIMENSION +enum d3d12.h D3D12_DSV_FLAGS +enum d3d12.h D3D12_ELEMENTS_LAYOUT +enum d3d12.h D3D12_EXPORT_FLAGS +enum d3d12.h D3D12_FEATURE +enum d3d12.h D3D12_FENCE_FLAGS +enum d3d12.h D3D12_FILL_MODE +enum d3d12.h D3D12_FILTER +enum d3d12.h D3D12_FILTER_REDUCTION_TYPE +enum d3d12.h D3D12_FILTER_TYPE +enum d3d12.h D3D12_FORMAT_SUPPORT1 +enum d3d12.h D3D12_FORMAT_SUPPORT2 +enum d3d12.h D3D12_GRAPHICS_STATES +enum d3d12.h D3D12_HEAP_FLAGS +enum d3d12.h D3D12_HEAP_SERIALIZATION_TIER +enum d3d12.h D3D12_HEAP_TYPE +enum d3d12.h D3D12_HIT_GROUP_TYPE +enum d3d12.h D3D12_INDEX_BUFFER_STRIP_CUT_VALUE +enum d3d12.h D3D12_INDIRECT_ARGUMENT_TYPE +enum d3d12.h D3D12_INPUT_CLASSIFICATION +enum d3d12.h D3D12_LIFETIME_STATE +enum d3d12.h D3D12_LOGIC_OP +enum d3d12.h D3D12_MEASUREMENTS_ACTION +enum d3d12.h D3D12_MEMORY_POOL +enum d3d12.h D3D12_MESH_SHADER_TIER +enum d3d12.h D3D12_META_COMMAND_PARAMETER_FLAGS +enum d3d12.h D3D12_META_COMMAND_PARAMETER_STAGE +enum d3d12.h D3D12_META_COMMAND_PARAMETER_TYPE +enum d3d12.h D3D12_MULTIPLE_FENCE_WAIT_FLAGS +enum d3d12.h D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG =d3d12_multisample_quality_level_flags +enum d3d12.h D3D12_PIPELINE_STATE_FLAGS +enum d3d12.h D3D12_PIPELINE_STATE_SUBOBJECT_TYPE +enum d3d12.h D3D12_PREDICATION_OP +enum d3d12.h D3D12_PRIMITIVE_TOPOLOGY_TYPE +enum d3d12.h D3D12_PROGRAMMABLE_SAMPLE_POSITIONS_TIER +enum d3d12.h D3D12_PROTECTED_RESOURCE_SESSION_FLAGS +enum d3d12.h D3D12_PROTECTED_RESOURCE_SESSION_SUPPORT_FLAGS +enum d3d12.h D3D12_PROTECTED_SESSION_STATUS +enum d3d12.h D3D12_QUERY_HEAP_TYPE +enum d3d12.h D3D12_QUERY_TYPE +enum d3d12.h D3D12_RAY_FLAGS +enum d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS +enum d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_COPY_MODE +enum d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_TYPE +enum d3d12.h D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE +enum d3d12.h D3D12_RAYTRACING_GEOMETRY_FLAGS +enum d3d12.h D3D12_RAYTRACING_GEOMETRY_TYPE +enum d3d12.h D3D12_RAYTRACING_INSTANCE_FLAGS +enum d3d12.h D3D12_RAYTRACING_PIPELINE_FLAGS +enum d3d12.h D3D12_RAYTRACING_TIER +enum d3d12.h D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE +enum d3d12.h D3D12_RENDER_PASS_ENDING_ACCESS_TYPE +enum d3d12.h D3D12_RENDER_PASS_FLAGS +enum d3d12.h D3D12_RESIDENCY_FLAGS +enum d3d12.h D3D12_RESIDENCY_PRIORITY +enum d3d12.h D3D12_RESOLVE_MODE +enum d3d12.h D3D12_RESOURCE_BARRIER_FLAGS +enum d3d12.h D3D12_RESOURCE_BARRIER_TYPE +enum d3d12.h D3D12_RESOURCE_BINDING_TIER +enum d3d12.h D3D12_RESOURCE_DIMENSION +enum d3d12.h D3D12_RESOURCE_FLAGS +enum d3d12.h D3D12_RESOURCE_HEAP_TIER +enum d3d12.h D3D12_RESOURCE_STATES +enum d3d12.h D3D12_ROOT_DESCRIPTOR_FLAGS +enum d3d12.h D3D12_ROOT_PARAMETER_TYPE +enum d3d12.h D3D12_ROOT_SIGNATURE_FLAGS +enum d3d12.h D3D12_RTV_DIMENSION +enum d3d12.h D3D12_SAMPLER_FEEDBACK_TIER +enum d3d12.h D3D12_SERIALIZED_DATA_TYPE +enum d3d12.h D3D12_SHADER_CACHE_CONTROL_FLAGS +enum d3d12.h D3D12_SHADER_CACHE_FLAGS +enum d3d12.h D3D12_SHADER_CACHE_KIND_FLAGS +enum d3d12.h D3D12_SHADER_CACHE_MODE +enum d3d12.h D3D12_SHADER_CACHE_SUPPORT_FLAGS +enum d3d12.h D3D12_SHADER_COMPONENT_MAPPING +enum d3d12.h D3D12_SHADER_MIN_PRECISION_SUPPORT +enum d3d12.h D3D12_SHADER_VISIBILITY +enum d3d12.h D3D12_SHADING_RATE +enum d3d12.h D3D12_SHADING_RATE_COMBINER +enum d3d12.h D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER +enum d3d12.h D3D12_SRV_DIMENSION +enum d3d12.h D3D12_STATE_OBJECT_FLAGS +enum d3d12.h D3D12_STATE_OBJECT_TYPE +enum d3d12.h D3D12_STATE_SUBOBJECT_TYPE +enum d3d12.h D3D12_STATIC_BORDER_COLOR +enum d3d12.h D3D12_STENCIL_OP +enum d3d12.h D3D12_TEXTURE_ADDRESS_MODE +enum d3d12.h D3D12_TEXTURE_COPY_TYPE +enum d3d12.h D3D12_TEXTURE_LAYOUT +enum d3d12.h D3D12_TILE_COPY_FLAGS +enum d3d12.h D3D12_TILE_MAPPING_FLAGS +enum d3d12.h D3D12_TILE_RANGE_FLAGS +enum d3d12.h D3D12_TILED_RESOURCES_TIER +enum d3d12.h D3D12_TRI_STATE +enum d3d12.h D3D12_UAV_DIMENSION +enum d3d12.h D3D12_VARIABLE_SHADING_RATE_TIER +enum d3d12.h D3D12_VIEW_INSTANCE_FLAGS =d3d12_view_instancing_flags +enum d3d12.h D3D12_VIEW_INSTANCING_TIER +enum d3d12.h D3D12_WAVE_MMA_TIER +enum d3d12.h D3D12_WRITEBUFFERIMMEDIATE_MODE +enum d3d12sdklayers.h D3D12_DEBUG_DEVICE_PARAMETER_TYPE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Debug Layer Reference > Debug Layer Interfaces +interface d3d12sdklayers.h ID3D12Debug +interface d3d12sdklayers.h ID3D12Debug1 +interface d3d12sdklayers.h ID3D12Debug2 +interface d3d12sdklayers.h ID3D12Debug3 +interface d3d12sdklayers.h ID3D12Debug4 +interface d3d12sdklayers.h ID3D12Debug5 +interface d3d12sdklayers.h ID3D12Debug6 +interface d3d12sdklayers.h ID3D12DebugCommandList +interface d3d12sdklayers.h ID3D12DebugCommandList1 +interface d3d12sdklayers.h ID3D12DebugCommandQueue +interface d3d12sdklayers.h ID3D12DebugDevice +interface d3d12sdklayers.h ID3D12DebugDevice1 +interface d3d12sdklayers.h ID3D12InfoQueue +interface d3d12sdklayers.h ID3D12SharingContract + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Debug Layer Reference > Debug Layer Structures +struct d3d12sdklayers.h D3D12_DEBUG_COMMAND_LIST_GPU_BASED_VALIDATION_SETTINGS +struct d3d12sdklayers.h D3D12_DEBUG_DEVICE_GPU_BASED_VALIDATION_SETTINGS +struct d3d12sdklayers.h D3D12_DEBUG_DEVICE_GPU_SLOWDOWN_PERFORMANCE_FACTOR +struct d3d12sdklayers.h D3D12_INFO_QUEUE_FILTER +struct d3d12sdklayers.h D3D12_INFO_QUEUE_FILTER_DESC +struct d3d12sdklayers.h D3D12_MESSAGE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Debug Layer Reference > Debug Layer Enumerations +enum d3d12sdklayers.h D3D12_DEBUG_COMMAND_LIST_PARAMETER_TYPE +enum d3d12sdklayers.h D3D12_DEBUG_DEVICE_PARAMETER_TYPE +enum d3d12sdklayers.h D3D12_DEBUG_FEATURE +enum d3d12sdklayers.h D3D12_GPU_BASED_VALIDATION_FLAGS +enum d3d12sdklayers.h D3D12_GPU_BASED_VALIDATION_PIPELINE_STATE_CREATE_FLAGS +enum d3d12sdklayers.h D3D12_GPU_BASED_VALIDATION_SHADER_PATCH_MODE +enum d3d12sdklayers.h D3D12_MESSAGE_CATEGORY +enum d3d12sdklayers.h D3D12_MESSAGE_ID +enum d3d12sdklayers.h D3D12_MESSAGE_SEVERITY +enum d3d12sdklayers.h D3D12_RLDO_FLAGS + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Shader Reference > Shader Interfaces +interface d3d12shader.h ID3D12FunctionParameterReflection +interface d3d12shader.h ID3D12FunctionReflection +interface d3d12shader.h ID3D12LibraryReflection +interface d3d12shader.h ID3D12ShaderReflection +interface d3d12shader.h ID3D12ShaderReflectionConstantBuffer +interface d3d12shader.h ID3D12ShaderReflectionType +interface d3d12shader.h ID3D12ShaderReflectionVariable + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Shader Reference > Shader Structures +struct d3d12shader.h D3D12_FUNCTION_DESC +struct d3d12shader.h D3D12_LIBRARY_DESC +struct d3d12shader.h D3D12_PARAMETER_DESC +struct d3d12shader.h D3D12_SHADER_BUFFER_DESC +struct d3d12shader.h D3D12_SHADER_DESC +struct d3d12shader.h D3D12_SHADER_INPUT_BIND_DESC +struct d3d12shader.h D3D12_SHADER_TYPE_DESC +struct d3d12shader.h D3D12_SHADER_VARIABLE_DESC +struct d3d12shader.h D3D12_SIGNATURE_PARAMETER_DESC + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Shader Reference > Shader Enumerations +enum d3d12shader.h D3D12_SHADER_VERSION_TYPE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > 11on12 Reference > 11on12 Interfaces +interface d3d11on12.h ID3D11On12Device +interface d3d11on12.h ID3D11On12Device1 +interface d3d11on12.h ID3D11On12Device2 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > 11on12 Reference > 11on12 Functions +function d3d11on12.h D3D11On12CreateDevice + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > 11on12 Reference > 11on12 Structures +struct d3d11on12.h D3D11_RESOURCE_FLAGS + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 12 Graphics > Direct3D 12 Reference > Direct3D 12 on Windows Mixed Reality +interface windows.graphics.holographic.interop.h IHologIHolographicQuadLayerUpdateParametersInteropraphicQuadLayerInterop =windows-graphics-holographic-interop-iholographicquadlayerupdateparametersinterop +interface windows.graphics.holographic.interop.h IHolographicCameraInterop =windows-graphics-holographic-interop-iholographiccamerainterop +interface windows.graphics.holographic.interop.h IHolographicCameraRenderingParametersInterop =windows-graphics-holographic-interop-iholographiccamerarenderingparametersinterop +interface windows.graphics.holographic.interop.h IHolographicQuadLayerInterop =windows-graphics-holographic-interop-iholographicquadlayerinterop + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Core reference > Core interfaces +interface d3d11_1.h ID3D11BlendState1 +interface d3d11_1.h ID3D11Device1 +interface d3d11_1.h ID3D11DeviceContext1 +interface d3d11_1.h ID3D11RasterizerState1 +interface d3d11_1.h ID3DDeviceContextState +interface d3d11_2.h ID3D11Device2 +interface d3d11_2.h ID3D11DeviceContext2 +interface d3d11_3.h ID3D11Device3 +interface d3d11_3.h ID3D11DeviceContext3 +interface d3d11_3.h ID3D11DeviceContext4 +interface d3d11_3.h ID3D11Fence +interface d3d11_3.h ID3D11Query1 +interface d3d11_3.h ID3D11RasterizerState2 +interface d3d11_4.h ID3D11Device4 +interface d3d11_4.h ID3D11Device5 +interface d3d11_4.h ID3D11Multithread +interface d3d11.h ID3D11Asynchronous +interface d3d11.h ID3D11BlendState +interface d3d11.h ID3D11CommandList +interface d3d11.h ID3D11Counter +interface d3d11.h ID3D11DepthStencilState +interface d3d11.h ID3D11Device +interface d3d11.h ID3D11DeviceChild +interface d3d11.h ID3D11DeviceContext +interface d3d11.h ID3D11InputLayout +interface d3d11.h ID3D11Predicate +interface d3d11.h ID3D11Query +interface d3d11.h ID3D11RasterizerState +interface d3d11.h ID3D11SamplerState + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Core reference > Core functions +function d3d11.h D3D11CreateDevice +function d3d11.h D3D11CreateDeviceAndSwapChain + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Core reference > Core structures +struct d3d11_1.h D3D11_BLEND_DESC1 =cd3d11_blend_desc1 +struct d3d11_1.h D3D11_RASTERIZER_DESC1 =cd3d11_rasterizer_desc1 +struct d3d11_1.h D3D11_RENDER_TARGET_BLEND_DESC1 +struct d3d11_3.h D3D11_QUERY_DESC1 =cd3d11_query_desc1 +struct d3d11_3.h D3D11_RASTERIZER_DESC2 =cd3d11_rasterizer_desc2 +struct d3d11_4.h D3D11_FEATURE_DATA_D3D11_OPTIONS4 +struct d3d11.h D3D11_BLEND_DESC +struct d3d11.h D3D11_BOX +struct d3d11.h D3D11_COUNTER_DESC +struct d3d11.h D3D11_COUNTER_INFO +struct d3d11.h D3D11_DEPTH_STENCIL_DESC +struct d3d11.h D3D11_DEPTH_STENCILOP_DESC +struct d3d11.h D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS +struct d3d11.h D3D11_DRAW_INSTANCED_INDIRECT_ARGS +struct d3d11.h D3D11_FEATURE_DATA_ARCHITECTURE_INFO +struct d3d11.h D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS +struct d3d11.h D3D11_FEATURE_DATA_D3D11_OPTIONS +struct d3d11.h D3D11_FEATURE_DATA_D3D11_OPTIONS1 +struct d3d11.h D3D11_FEATURE_DATA_D3D11_OPTIONS2 +struct d3d11.h D3D11_FEATURE_DATA_D3D11_OPTIONS3 +struct d3d11.h D3D11_FEATURE_DATA_D3D11_OPTIONS5 +struct d3d11.h D3D11_FEATURE_DATA_D3D9_OPTIONS +struct d3d11.h D3D11_FEATURE_DATA_D3D9_OPTIONS1 +struct d3d11.h D3D11_FEATURE_DATA_D3D9_SHADOW_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_D3D9_SIMPLE_INSTANCING_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_DISPLAYABLE +struct d3d11.h D3D11_FEATURE_DATA_DOUBLES +struct d3d11.h D3D11_FEATURE_DATA_FORMAT_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_FORMAT_SUPPORT2 +struct d3d11.h D3D11_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_MARKER_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_SHADER_CACHE +struct d3d11.h D3D11_FEATURE_DATA_SHADER_MIN_PRECISION_SUPPORT +struct d3d11.h D3D11_FEATURE_DATA_THREADING +struct d3d11.h D3D11_INPUT_ELEMENT_DESC +struct d3d11.h D3D11_QUERY_DATA_PIPELINE_STATISTICS +struct d3d11.h D3D11_QUERY_DATA_SO_STATISTICS +struct d3d11.h D3D11_QUERY_DATA_TIMESTAMP_DISJOINT +struct d3d11.h D3D11_QUERY_DESC +struct d3d11.h D3D11_RASTERIZER_DESC +struct d3d11.h D3D11_RENDER_TARGET_BLEND_DESC +struct d3d11.h D3D11_SAMPLER_DESC +struct d3d11.h D3D11_SO_DECLARATION_ENTRY +struct d3d11.h D3D11_VIEWPORT + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Core reference > Core enumerations +enum d3d11_1.h D3D11_1_CREATE_DEVICE_CONTEXT_STATE_FLAG +enum d3d11_1.h D3D11_COPY_FLAGS +enum d3d11_1.h D3D11_LOGIC_OP +enum d3d11_3.h D3D11_CONSERVATIVE_RASTERIZATION_MODE +enum d3d11_3.h D3D11_CONTEXT_TYPE +enum d3d11_3.h D3D11_FENCE_FLAG +enum d3d11.h D3D11_ASYNC_GETDATA_FLAG +enum d3d11.h D3D11_BLEND +enum d3d11.h D3D11_BLEND_OP +enum d3d11.h D3D11_CLEAR_FLAG +enum d3d11.h D3D11_COLOR_WRITE_ENABLE +enum d3d11.h D3D11_COMPARISON_FUNC +enum d3d11.h D3D11_CONSERVATIVE_RASTERIZATION_TIER +enum d3d11.h D3D11_COUNTER +enum d3d11.h D3D11_COUNTER_TYPE +enum d3d11.h D3D11_CREATE_DEVICE_FLAG +enum d3d11.h D3D11_CULL_MODE +enum d3d11.h D3D11_DEPTH_WRITE_MASK +enum d3d11.h D3D11_DEVICE_CONTEXT_TYPE +enum d3d11.h D3D11_FEATURE +enum d3d11.h D3D11_FILL_MODE +enum d3d11.h D3D11_FILTER +enum d3d11.h D3D11_FILTER_REDUCTION_TYPE +enum d3d11.h D3D11_FILTER_TYPE +enum d3d11.h D3D11_FORMAT_SUPPORT +enum d3d11.h D3D11_FORMAT_SUPPORT2 +enum d3d11.h D3D11_INPUT_CLASSIFICATION +enum d3d11.h D3D11_QUERY +enum d3d11.h D3D11_QUERY_MISC_FLAG +enum d3d11.h D3D11_RAISE_FLAG +enum d3d11.h D3D11_SHADER_CACHE_SUPPORT_FLAGS +enum d3d11.h D3D11_SHADER_MIN_PRECISION_SUPPORT +enum d3d11.h D3D11_SHARED_RESOURCE_TIER +enum d3d11.h D3D11_STENCIL_OP +enum d3d11.h D3D11_TEXTURE_ADDRESS_MODE +enum d3d11.h D3D11_TEXTURECUBE_FACE +enum d3d11.h D3D11_TILED_RESOURCES_TIER +enum d3dcommon.h D3D11_PRIMITIVE =d3d_primitive + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Layer Reference > Layer Interfaces +interface d3d11sdklayers.h ID3D11Debug +interface d3d11sdklayers.h ID3D11InfoQueue +interface d3d11sdklayers.h ID3D11RefDefaultTrackingOptions +interface d3d11sdklayers.h ID3D11RefTrackingOptions +interface d3d11sdklayers.h ID3D11SwitchToRef +interface d3d11sdklayers.h ID3D11TracingDevice + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Layer Reference > Layer Structures +struct d3d11sdklayers.h D3D11_INFO_QUEUE_FILTER +struct d3d11sdklayers.h D3D11_INFO_QUEUE_FILTER_DESC +struct d3d11sdklayers.h D3D11_MESSAGE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Layer Reference > Layer Enumerations +enum d3d11sdklayers.h D3D11_MESSAGE_CATEGORY +enum d3d11sdklayers.h D3D11_MESSAGE_ID +enum d3d11sdklayers.h D3D11_MESSAGE_SEVERITY +enum d3d11sdklayers.h D3D11_RLDO_FLAGS +enum d3d11sdklayers.h D3D11_SHADER_TRACKING_OPTIONS +enum d3d11sdklayers.h D3D11_SHADER_TRACKING_RESOURCE_TYPE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Resource Reference > Resource Interfaces +interface d3d11_3.h ID3D11RenderTargetView1 +interface d3d11_3.h ID3D11ShaderResourceView1 +interface d3d11_3.h ID3D11Texture2D1 +interface d3d11_3.h ID3D11Texture3D1 +interface d3d11_3.h ID3D11UnorderedAccessView1 +interface d3d11.h ID3D11Buffer +interface d3d11.h ID3D11DepthStencilView +interface d3d11.h ID3D11RenderTargetView +interface d3d11.h ID3D11Resource +interface d3d11.h ID3D11ShaderResourceView +interface d3d11.h ID3D11Texture1D +interface d3d11.h ID3D11Texture2D +interface d3d11.h ID3D11Texture3D +interface d3d11.h ID3D11UnorderedAccessView +interface d3d11.h ID3D11View + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Resource Reference > Resource Functions +function d3d11.h D3D11CalcSubresource + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Resource Reference > Resource Structures +struct d3d11_2.h D3D11_PACKED_MIP_DESC +struct d3d11_2.h D3D11_SUBRESOURCE_TILING +struct d3d11_2.h D3D11_TILE_REGION_SIZE +struct d3d11_2.h D3D11_TILE_SHAPE +struct d3d11_2.h D3D11_TILED_RESOURCE_COORDINATE +struct d3d11_3.h D3D11_RENDER_TARGET_VIEW_DESC1 =cd3d11_render_target_view_desc1 +struct d3d11_3.h D3D11_SHADER_RESOURCE_VIEW_DESC1 =cd3d11_shader_resource_view_desc1 +struct d3d11_3.h D3D11_TEX2D_ARRAY_RTV1 +struct d3d11_3.h D3D11_TEX2D_ARRAY_SRV1 +struct d3d11_3.h D3D11_TEX2D_ARRAY_UAV1 +struct d3d11_3.h D3D11_TEX2D_RTV1 +struct d3d11_3.h D3D11_TEX2D_SRV1 +struct d3d11_3.h D3D11_TEX2D_UAV1 +struct d3d11_3.h D3D11_TEXTURE2D_DESC1 =cd3d11_texture2d_desc1 +struct d3d11_3.h D3D11_TEXTURE3D_DESC1 =cd3d11_texture3d_desc1 +struct d3d11_3.h D3D11_UNORDERED_ACCESS_VIEW_DESC1 =cd3d11_unordered_access_view_desc1 +struct d3d11.h D3D11_BUFFER_DESC +struct d3d11.h D3D11_BUFFER_RTV +struct d3d11.h D3D11_BUFFER_SRV +struct d3d11.h D3D11_BUFFER_UAV +struct d3d11.h D3D11_BUFFEREX_SRV +struct d3d11.h D3D11_DEPTH_STENCIL_VIEW_DESC +struct d3d11.h D3D11_MAPPED_SUBRESOURCE +struct d3d11.h D3D11_RENDER_TARGET_VIEW_DESC +struct d3d11.h D3D11_SHADER_RESOURCE_VIEW_DESC +struct d3d11.h D3D11_SUBRESOURCE_DATA +struct d3d11.h D3D11_TEX1D_ARRAY_DSV +struct d3d11.h D3D11_TEX1D_ARRAY_RTV +struct d3d11.h D3D11_TEX1D_ARRAY_SRV +struct d3d11.h D3D11_TEX1D_ARRAY_UAV +struct d3d11.h D3D11_TEX1D_DSV +struct d3d11.h D3D11_TEX1D_RTV +struct d3d11.h D3D11_TEX1D_SRV +struct d3d11.h D3D11_TEX1D_UAV +struct d3d11.h D3D11_TEX2D_ARRAY_DSV +struct d3d11.h D3D11_TEX2D_ARRAY_RTV +struct d3d11.h D3D11_TEX2D_ARRAY_SRV +struct d3d11.h D3D11_TEX2D_ARRAY_UAV +struct d3d11.h D3D11_TEX2D_DSV +struct d3d11.h D3D11_TEX2D_RTV +struct d3d11.h D3D11_TEX2D_SRV +struct d3d11.h D3D11_TEX2D_UAV +struct d3d11.h D3D11_TEX2DMS_ARRAY_DSV +struct d3d11.h D3D11_TEX2DMS_ARRAY_RTV +struct d3d11.h D3D11_TEX2DMS_ARRAY_SRV +struct d3d11.h D3D11_TEX2DMS_DSV +struct d3d11.h D3D11_TEX2DMS_RTV +struct d3d11.h D3D11_TEX2DMS_SRV +struct d3d11.h D3D11_TEX3D_RTV +struct d3d11.h D3D11_TEX3D_SRV +struct d3d11.h D3D11_TEX3D_UAV +struct d3d11.h D3D11_TEXCUBE_ARRAY_SRV +struct d3d11.h D3D11_TEXCUBE_SRV +struct d3d11.h D3D11_TEXTURE1D_DESC +struct d3d11.h D3D11_TEXTURE2D_DESC +struct d3d11.h D3D11_TEXTURE3D_DESC +struct d3d11.h D3D11_UNORDERED_ACCESS_VIEW_DESC + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Resource Reference > Resource Enumerations +enum d3d11_2.h D3D11_CHECK_MULTISAMPLE_QUALITY_LEVELS_FLAG +enum d3d11_2.h D3D11_TILE_COPY_FLAG +enum d3d11_2.h D3D11_TILE_MAPPING_FLAG +enum d3d11_2.h D3D11_TILE_RANGE_FLAG +enum d3d11_3.h D3D11_TEXTURE_LAYOUT +enum d3d11.h D3D11_BIND_FLAG +enum d3d11.h D3D11_BUFFER_UAV_FLAG +enum d3d11.h D3D11_BUFFEREX_SRV_FLAG +enum d3d11.h D3D11_CPU_ACCESS_FLAG +enum d3d11.h D3D11_DSV_DIMENSION +enum d3d11.h D3D11_DSV_FLAG +enum d3d11.h D3D11_MAP +enum d3d11.h D3D11_MAP_FLAG +enum d3d11.h D3D11_RESOURCE_DIMENSION +enum d3d11.h D3D11_RESOURCE_MISC_FLAG +enum d3d11.h D3D11_RTV_DIMENSION +enum d3d11.h D3D11_STANDARD_MULTISAMPLE_QUALITY_LEVELS +enum d3d11.h D3D11_UAV_DIMENSION +enum d3d11.h D3D11_USAGE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Shader Reference > Shader Interfaces +interface d3d11.h ID3D11ClassInstance +interface d3d11.h ID3D11ClassLinkage +interface d3d11.h ID3D11ComputeShader +interface d3d11.h ID3D11DomainShader +interface d3d11.h ID3D11GeometryShader +interface d3d11.h ID3D11HullShader +interface d3d11.h ID3D11PixelShader +interface d3d11.h ID3D11VertexShader +interface d3d11shader.h ID3D11FunctionLinkingGraph +interface d3d11shader.h ID3D11FunctionParameterReflection +interface d3d11shader.h ID3D11FunctionReflection +interface d3d11shader.h ID3D11LibraryReflection +interface d3d11shader.h ID3D11Linker +interface d3d11shader.h ID3D11LinkingNode +interface d3d11shader.h ID3D11Module +interface d3d11shader.h ID3D11ModuleInstance +interface d3d11shader.h ID3D11ShaderReflection +interface d3d11shader.h ID3D11ShaderReflectionConstantBuffer +interface d3d11shader.h ID3D11ShaderReflectionType +interface d3d11shader.h ID3D11ShaderReflectionVariable +interface d3d11shadertracing.h ID3D11ShaderTrace +interface d3d11shadertracing.h ID3D11ShaderTraceFactory + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Shader Reference > Shader Functions +function d3d11shadertracing.h D3DDisassemble11Trace + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Shader Reference > Shader Structures +struct d3d11.h D3D11_CLASS_INSTANCE_DESC +struct d3d11shader.h D3D11_FUNCTION_DESC +struct d3d11shader.h D3D11_LIBRARY_DESC +struct d3d11shader.h D3D11_PARAMETER_DESC +struct d3d11shader.h D3D11_SHADER_BUFFER_DESC +struct d3d11shader.h D3D11_SHADER_DESC +struct d3d11shader.h D3D11_SHADER_INPUT_BIND_DESC +struct d3d11shader.h D3D11_SHADER_TYPE_DESC +struct d3d11shader.h D3D11_SHADER_VARIABLE_DESC +struct d3d11shader.h D3D11_SIGNATURE_PARAMETER_DESC +struct d3d11shadertracing.h D3D11_COMPUTE_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_DOMAIN_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_GEOMETRY_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_HULL_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_PIXEL_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_SHADER_TRACE_DESC +struct d3d11shadertracing.h D3D11_TRACE_REGISTER +struct d3d11shadertracing.h D3D11_TRACE_STATS +struct d3d11shadertracing.h D3D11_TRACE_STEP +struct d3d11shadertracing.h D3D11_TRACE_VALUE +struct d3d11shadertracing.h D3D11_VERTEX_SHADER_TRACE_DESC + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Shader Reference > Shader Enumerations +enum d3d11shader.h D3D11_SHADER_VERSION_TYPE +enum d3d11shadertracing.h D3D11_SHADER_TYPE +enum d3d11shadertracing.h D3D11_TRACE_GS_INPUT_PRIMITIVE +enum d3d11shadertracing.h D3D11_TRACE_REGISTER_TYPE +enum d3dcommon.h D3D_PARAMETER_FLAGS +enum d3dcommon.h D3D11_RESOURCE_RETURN_TYPE =d3d_resource_return_type +enum d3dcommon.h D3D11_TESSELLATOR_DOMAIN =d3d_tessellator_domain +enum d3dcommon.h D3D11_TESSELLATOR_OUTPUT_PRIMITIVE =d3d_tessellator_output_primitive +enum d3dcommon.h D3D11_TESSELLATOR_PARTITIONING =d3d_tessellator_partitioning + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Common Version Reference > Common Version Interfaces +interface d3d11_1.h ID3DUserDefinedAnnotation +interface d3dcommon.h ID3D10Blob +interface d3dcommon.h ID3DDestructionNotifier =id3ddestructionotifier +interface d3dcommon.h ID3DInclude + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Common Version Reference > Common Version Structures +struct d3dcommon.h D3D_SHADER_MACRO + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > Common Version Reference > Common Version Enumerations +enum d3dcommon.h D3D_CBUFFER_TYPE +enum d3dcommon.h D3D_DRIVER_TYPE +enum d3dcommon.h D3D_FEATURE_LEVEL +enum d3dcommon.h D3D_INCLUDE_TYPE +enum d3dcommon.h D3D_INTERPOLATION_MODE +enum d3dcommon.h D3D_MIN_PRECISION +enum d3dcommon.h D3D_NAME +enum d3dcommon.h D3D_PRIMITIVE +enum d3dcommon.h D3D_PRIMITIVE_TOPOLOGY +enum d3dcommon.h D3D_REGISTER_COMPONENT_TYPE +enum d3dcommon.h D3D_RESOURCE_RETURN_TYPE +enum d3dcommon.h D3D_SHADER_CBUFFER_FLAGS +enum d3dcommon.h D3D_SHADER_INPUT_FLAGS +enum d3dcommon.h D3D_SHADER_INPUT_TYPE +enum d3dcommon.h D3D_SHADER_VARIABLE_CLASS +enum d3dcommon.h D3D_SHADER_VARIABLE_FLAGS +enum d3dcommon.h D3D_SHADER_VARIABLE_TYPE +enum d3dcommon.h D3D_SRV_DIMENSION +enum d3dcommon.h D3D_TESSELLATOR_DOMAIN +enum d3dcommon.h D3D_TESSELLATOR_OUTPUT_PRIMITIVE +enum d3dcommon.h D3D_TESSELLATOR_PARTITIONING + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_BOX +function d3d11.h ~CD3D11_BOX() destructor =cd3d11_box-~cd3d11_box +function d3d11.h CD3D11_BOX() constructor =cd3d11_box-cd3d11_box +function d3d11.h CD3D11_BOX(constD3D11_BOX) constructor =cd3d11_box-cd3d11_box(constd3d11_box_) +function d3d11.h CD3D11_BOX(LONG, LONG, LONG, LONG, LONG, LONG) constructor =cd3d11_box-cd3d11_box(long_long_long_long_long_long) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_DEPTH_STENCIL_DESC +function d3d11.h ~CD3D11_DEPTH_STENCIL_DESC() destructor =cd3d11_depth_stencil_desc-~cd3d11_depth_stencil_desc +function d3d11.h CD3D11_DEPTH_STENCIL_DESC() constructor =cd3d11_depth_stencil_desc-cd3d11_depth_stencil_desc +function d3d11.h CD3D11_DEPTH_STENCIL_DESC(CD3D11_DEFAULT) constructor =cd3d11_depth_stencil_desc-cd3d11_depth_stencil_desc(cd3d11_default) +function d3d11.h CD3D11_DEPTH_STENCIL_DESC(constD3D11_DEPTH_STENCIL_DESC) constructor =cd3d11_depth_stencil_desc-cd3d11_depth_stencil_desc(constd3d11_depth_stencil_desc_) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_RASTERIZER_DESC +function d3d11.h ~CD3D11_RASTERIZER_DESC() destructor =cd3d11_rasterizer_desc-~cd3d11_rasterizer_desc +function d3d11.h CD3D11_RASTERIZER_DESC() constructor =cd3d11_rasterizer_desc-cd3d11_rasterizer_desc +function d3d11.h CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT) constructor =cd3d11_rasterizer_desc-cd3d11_rasterizer_desc(cd3d11_default) +function d3d11.h CD3D11_RASTERIZER_DESC(constD3D11_RASTERIZER_DESC) constructor =cd3d11_rasterizer_desc-cd3d11_rasterizer_desc(constd3d11_rasterizer_desc_) +function d3d11.h CD3D11_RASTERIZER_DESC(D3D11_FILL_MODE, D3D11_CULL_MODE, BOOL, INT, FLOAT, FLOAT, BOOL, BOOL, BOOL, BOOL) constructor =cd3d11_rasterizer_desc-cd3d11_rasterizer_desc(d3d11_fill_mode_d3d11_cull_mode_bool_int_float_float_bool_bool_bool_bool) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_TEXTURE1D_DESC +function d3d11.h ~CD3D11_TEXTURE1D_DESC() destructor =cd3d11_texture1d_desc-~cd3d11_texture1d_desc +function d3d11.h CD3D11_TEXTURE1D_DESC() constructor =cd3d11_texture1d_desc-cd3d11_texture1d_desc +function d3d11.h CD3D11_TEXTURE1D_DESC(constD3D11_TEXTURE1D_DESC&) constructor =cd3d11_texture1d_desc-cd3d11_texture1d_desc(constd3d11_texture1d_desc_) +function d3d11.h CD3D11_TEXTURE1D_DESC(DXGI_FORMAT, UINT, UINT, UINT, UINT, D3D11_USAGE, UINT, UINT) constructor =cd3d11_texture1d_desc-cd3d11_texture1d_desc(dxgi_format_uint_uint_uint_uint_d3d11_usage_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_TEXTURE2D_DESC +function d3d11.h ~CD3D11_TEXTURE2D_DESC() destructor =cd3d11_texture2d_desc-~cd3d11_texture2d_desc +function d3d11.h CD3D11_TEXTURE2D_DESC() constructor =cd3d11_texture2d_desc-cd3d11_texture2d_desc +function d3d11.h CD3D11_TEXTURE2D_DESC(constD3D11_TEXTURE2D_DESC&) constructor =cd3d11_texture2d_desc-cd3d11_texture2d_desc(constd3d11_texture2d_desc_) +function d3d11.h CD3D11_TEXTURE2D_DESC(DXGI_FORMAT, UINT, UINT, UINT, UINT, UINT, D3D11_USAGE, UINT, UINT, UINT, UINT) constructor =cd3d11_texture2d_desc-cd3d11_texture2d_desc(dxgi_format_uint_uint_uint_uint_uint_d3d11_usage_uint_uint_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_TEXTURE3D_DESC +function d3d11.h ~CD3D11_TEXTURE3D_DESC() destructor =cd3d11_texture3d_desc-~cd3d11_texture3d_desc +function d3d11.h CD3D11_TEXTURE3D_DESC() constructor =cd3d11_texture3d_desc-cd3d11_texture3d_desc +function d3d11.h CD3D11_TEXTURE3D_DESC(constD3D11_TEXTURE3D_DESC&) constructor =cd3d11_texture3d_desc-cd3d11_texture3d_desc(constd3d11_texture3d_desc_) +function d3d11.h CD3D11_TEXTURE3D_DESC(DXGI_FORMAT, UINT, UINT, UINT, UINT, UINT, D3D11_USAGE, UINT, UINT) constructor =cd3d11_texture3d_desc-cd3d11_texture3d_desc(dxgi_format_uint_uint_uint_uint_uint_d3d11_usage_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_SHADER_RESOURCE_VIEW_DESC +function d3d11.h ~CD3D11_SHADER_RESOURCE_VIEW_DESC() destructor =cd3d11_shader_resource_view_desc-~cd3d11_shader_resource_view_desc +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC() constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(constD3D11_SHADER_RESOURCE_VIEW_DESC&) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(constd3d11_shader_resource_view_desc_) +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(D3D11_SRV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT, UINT) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(d3d11_srv_dimension_dxgi_format_uint_uint_uint_uint_uint) +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(ID3D11Buffer*, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(id3d11buffer_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(ID3D11Texture1D*, D3D11_SRV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(id3d11texture1d_d3d11_srv_dimension_dxgi_format_uint_uint_uint_uint) +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(ID3D11Texture2D*, D3D11_SRV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(id3d11texture1d_d3d11_srv_dimension_dxgi_format_uint_uint_uint_uint) +function d3d11.h CD3D11_SHADER_RESOURCE_VIEW_DESC(ID3D11Texture3D*, DXGI_FORMAT, UINT, UINT) constructor =cd3d11_shader_resource_view_desc-cd3d11_shader_resource_view_desc(id3d11texture3d_dxgi_format_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_RENDER_TARGET_VIEW_DESC +function d3d11.h ~CD3D11_RENDER_TARGET_VIEW_DESC() destructor =cd3d11_render_target_view_desc-~cd3d11_render_target_view_desc +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC() constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(constD3D11_RENDER_TARGET_VIEW_DESC&) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(constd3d11_render_target_view_desc_) +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(D3D11_RTV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(d3d11_rtv_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(ID3D11Buffer*, DXGI_FORMAT, UINT, UINT) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(id3d11texture1d_d3d11_rtv_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(ID3D11Texture1D*, D3D11_RTV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(id3d11texture1d_d3d11_rtv_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(ID3D11Texture2D*, D3D11_RTV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(id3d11texture2d_d3d11_rtv_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_RENDER_TARGET_VIEW_DESC(ID3D11Texture3D*, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_render_target_view_desc-cd3d11_render_target_view_desc(id3d11texture3d_dxgi_format_uint_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_VIEWPORT +function d3d11.h ~CD3D11_VIEWPORT() destructor =cd3d11_viewport-~cd3d11_viewport +function d3d11.h CD3D11_VIEWPORT() constructor =cd3d11_viewport-cd3d11_viewport +function d3d11.h CD3D11_VIEWPORT(constD3D11_VIEWPORT&) constructor =cd3d11_viewport-cd3d11_viewport(constd3d11_viewport_) +function d3d11.h CD3D11_VIEWPORT(FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT) constructor =cd3d11_viewport-cd3d11_viewport(float_float_float_float_float_float) +function d3d11.h CD3D11_VIEWPORT(ID3D11Buffer*, ID3D11RenderTargetView*, FLOAT, FLOAT, FLOAT) constructor =cd3d11_viewport-cd3d11_viewport(id3d11texture2d_id3d11rendertargetview_float_float_float_float) +function d3d11.h CD3D11_VIEWPORT(ID3D11Texture1D*, ID3D11RenderTargetView*, FLOAT, FLOAT, FLOAT) constructor =cd3d11_viewport-cd3d11_viewport(id3d11texture1d_id3d11rendertargetview_float_float_float) +function d3d11.h CD3D11_VIEWPORT(ID3D11Texture2D*, ID3D11RenderTargetView*, FLOAT, FLOAT, FLOAT, FLOAT) constructor =cd3d11_viewport-cd3d11_viewport(float_float_float_float_float_float) +function d3d11.h CD3D11_VIEWPORT(ID3D11Texture3D*, ID3D11RenderTargetView*, FLOAT, FLOAT, FLOAT, FLOAT) constructor =cd3d11_viewport-cd3d11_viewport(float_float_float_float_float_float) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_DEPTH_STENCIL_VIEW_DESC +function d3d11.h ~CD3D11_DEPTH_STENCIL_VIEW_DESC() destructor =cd3d11_depth_stencil_view_desc-~cd3d11_depth_stencil_view_desc +function d3d11.h CD3D11_DEPTH_STENCIL_VIEW_DESC() constructor =cd3d11_depth_stencil_view_desc-cd3d11_depth_stencil_view_desc +function d3d11.h CD3D11_DEPTH_STENCIL_VIEW_DESC(constD3D11_DEPTH_STENCIL_VIEW_DESC&) constructor =cd3d11_depth_stencil_view_desc-cd3d11_depth_stencil_view_desc(constd3d11_depth_stencil_view_desc_) +function d3d11.h CD3D11_DEPTH_STENCIL_VIEW_DESC(D3D11_DSV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_depth_stencil_view_desc-cd3d11_depth_stencil_view_desc(d3d11_dsv_dimension_dxgi_format_uint_uint_uint_uint) +function d3d11.h CD3D11_DEPTH_STENCIL_VIEW_DESC(ID3D11Texture1D*, D3D11_DSV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_depth_stencil_view_desc-cd3d11_depth_stencil_view_desc(id3d11texture2d_d3d11_dsv_dimension_dxgi_format_uint_uint_uint_uint) +function d3d11.h CD3D11_DEPTH_STENCIL_VIEW_DESC(ID3D11Texture2D*, D3D11_DSV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_depth_stencil_view_desc-cd3d11_depth_stencil_view_desc(d3d11_dsv_dimension_dxgi_format_uint_uint_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_UNORDERED_ACCESS_VIEW_DESC +function d3d11.h ~CD3D11_UNORDERED_ACCESS_VIEW_DESC() destructor =cd3d11_unordered_access_view_desc-~cd3d11_unordered_access_view_desc +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC() constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(constD3D11_UNORDERED_ACCESS_VIEW_DESC&) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(constd3d11_unordered_access_view_desc_) +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(D3D11_UAV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT, UINT) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(d3d11_uav_dimension_dxgi_format_uint_uint_uint_uint) +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(ID3D11Buffer*, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(id3d11buffer_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(ID3D11Texture1D*, D3D11_UAV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(id3d11texture1d_d3d11_uav_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(ID3D11Texture2D*, D3D11_UAV_DIMENSION, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(id3d11texture2d_d3d11_uav_dimension_dxgi_format_uint_uint_uint) +function d3d11.h CD3D11_UNORDERED_ACCESS_VIEW_DESC(ID3D11Texture3D*, DXGI_FORMAT, UINT, UINT, UINT) constructor =cd3d11_unordered_access_view_desc-cd3d11_unordered_access_view_desc(id3d11texture3d_dxgi_format_uint_uint_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_SAMPLER_DESC +function d3d11.h ~CD3D11_SAMPLER_DESC() destructor =cd3d11_sampler_desc-~cd3d11_sampler_desc +function d3d11.h CD3D11_SAMPLER_DESC() constructor =cd3d11_sampler_desc-cd3d11_sampler_desc +function d3d11.h CD3D11_SAMPLER_DESC(constD3D11_SAMPLER_DESC&) constructor =cd3d11_sampler_desc-cd3d11_sampler_desc(constd3d11_sampler_desc_) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_QUERY_DESC +function d3d11.h ~CD3D11_QUERY_DESC() destructor =cd3d11_query_desc-~cd3d11_query_desc +function d3d11.h CD3D11_QUERY_DESC() constructor =cd3d11_query_desc-cd3d11_query_desc +function d3d11.h CD3D11_QUERY_DESC(constD3D11_QUERY_DESC&) constructor =cd3d11_query_desc-cd3d11_query_desc(constd3d11_query_desc_) +function d3d11.h CD3D11_QUERY_DESC(D3D11_QUERY, UINT) constructor =cd3d11_query_desc-cd3d11_query_desc(d3d11_query_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures > CD3D11_COUNTER_DESC +function d3d11.h ~CD3D11_COUNTER_DESC() method =cd3d11_counter_desc-~cd3d11_counter_desc +function d3d11.h CD3D11_COUNTER_DESC() constructor =cd3d11_counter_desc-cd3d11_counter_desc +function d3d11.h CD3D11_COUNTER_DESC(constD3D11_COUNTER_DESC&) constructor =cd3d11_counter_desc-cd3d11_counter_desc(constd3d11_counter_desc_) +function d3d11.h CD3D11_COUNTER_DESC(D3D11_COUNTER, UINT) constructor =cd3d11_counter_desc-cd3d11_counter_desc(d3d11_counter_uint) + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > Direct3D 11 Reference > CD3D11 Helper Structures +struct d3d11.h CD3D11_BLEND_DESC +struct d3d11.h CD3D11_BUFFER_DESC +struct d3d11.h CD3D11_RECT + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > D3DCSX 11 Reference > D3DCSX 11 Interfaces +interface d3dcsx.h ID3DX11FFT +interface d3dcsx.h ID3DX11Scan +interface d3dcsx.h ID3DX11SegmentedScan + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > D3DCSX 11 Reference > D3DCSX 11 Functions +function d3dcsx.h D3DX11CreateFFT +function d3dcsx.h D3DX11CreateFFT1DComplex +function d3dcsx.h D3DX11CreateFFT1DReal +function d3dcsx.h D3DX11CreateFFT2DComplex +function d3dcsx.h D3DX11CreateFFT2DReal +function d3dcsx.h D3DX11CreateFFT3DComplex +function d3dcsx.h D3DX11CreateFFT3DReal +function d3dcsx.h D3DX11CreateScan +function d3dcsx.h D3DX11CreateSegmentedScan + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > D3DCSX 11 Reference > D3DCSX 11 Structures +struct d3dcsx.h D3DX11_FFT_BUFFER_INFO +struct d3dcsx.h D3DX11_FFT_DESC + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > Direct3D 11 Graphics > Direct3D 11, utilities, and effects reference > D3DCSX 11 Reference > D3DCSX 11 Enumerations +enum d3dcsx.h D3DX11_FFT_CREATE_FLAG +enum d3dcsx.h D3DX11_FFT_DATA_TYPE +enum d3dcsx.h D3DX11_FFT_DIM_MASK +enum d3dcsx.h D3DX11_SCAN_DATA_TYPE +enum d3dcsx.h D3DX11_SCAN_DIRECTION +enum d3dcsx.h D3DX11_SCAN_OPCODE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > DXGI > DXGI reference > DXGI interfaces +interface dxgi.h IDXGIAdapter +interface dxgi.h IDXGIAdapter1 +interface dxgi.h IDXGIDevice +interface dxgi.h IDXGIDevice1 +interface dxgi.h IDXGIDeviceSubObject +interface dxgi.h IDXGIFactory +interface dxgi.h IDXGIFactory1 +interface dxgi.h IDXGIKeyedMutex +interface dxgi.h IDXGIObject +interface dxgi.h IDXGIOutput +interface dxgi.h IDXGIResource +interface dxgi.h IDXGISurface +interface dxgi.h IDXGISurface1 +interface dxgi.h IDXGISwapChain +interface dxgi1_2.h IDXGIAdapter2 +interface dxgi1_2.h IDXGIDevice2 +interface dxgi1_2.h IDXGIDisplayControl +interface dxgi1_2.h IDXGIFactory2 +interface dxgi1_2.h IDXGIOutput1 +interface dxgi1_2.h IDXGIOutputDuplication +interface dxgi1_2.h IDXGIResource1 +interface dxgi1_2.h IDXGISurface2 +interface dxgi1_2.h IDXGISwapChain1 +interface dxgi1_3.h IDXGIDecodeSwapChain +interface dxgi1_3.h IDXGIDevice3 +interface dxgi1_3.h IDXGIFactory3 +interface dxgi1_3.h IDXGIFactoryMedia +interface dxgi1_3.h IDXGIOutput2 +interface dxgi1_3.h IDXGIOutput3 +interface dxgi1_3.h IDXGISwapChain2 +interface dxgi1_3.h IDXGISwapChainMedia +interface dxgi1_4.h IDXGIAdapter3 +interface dxgi1_4.h IDXGIFactory4 +interface dxgi1_4.h IDXGIOutput4 +interface dxgi1_4.h IDXGISwapChain3 +interface dxgi1_5.h IDXGIDevice4 +interface dxgi1_5.h IDXGIFactory5 +interface dxgi1_5.h IDXGIOutput5 +interface dxgi1_5.h IDXGISwapChain4 +interface dxgi1_6.h IDXGIAdapter4 +interface dxgi1_6.h IDXGIFactory6 +interface dxgi1_6.h IDXGIOutput6 +interface dxgidebug.h IDXGIDebug +interface dxgidebug.h IDXGIDebug1 +interface dxgidebug.h IDXGIInfoQueue + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > DXGI > DXGI reference > DXGI functions +function dxgi.h CreateDXGIFactory +function dxgi.h CreateDXGIFactory1 +function dxgi1_3.h CreateDXGIFactory2 +function dxgi1_3.h DXGIGetDebugInterface1 +function dxgi1_6.h DXGIDeclareAdapterRemovalSupport +function dxgi1_6.h DXGIDisableVBlankVirtualization +function dxgidebug.h DXGIGetDebugInterface + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > DXGI > DXGI reference > DXGI structures +struct dxgi.h DXGI_ADAPTER_DESC +struct dxgi.h DXGI_ADAPTER_DESC1 +struct dxgi.h DXGI_DISPLAY_COLOR_SPACE +struct dxgi.h DXGI_FRAME_STATISTICS +struct dxgi.h DXGI_MAPPED_RECT +struct dxgi.h DXGI_OUTPUT_DESC +struct dxgi.h DXGI_SHARED_RESOURCE +struct dxgi.h DXGI_SURFACE_DESC +struct dxgi.h DXGI_SWAP_CHAIN_DESC +struct dxgi1_2.h DXGI_ADAPTER_DESC2 +struct dxgi1_2.h DXGI_MODE_DESC1 +struct dxgi1_2.h DXGI_OUTDUPL_DESC +struct dxgi1_2.h DXGI_OUTDUPL_FRAME_INFO +struct dxgi1_2.h DXGI_OUTDUPL_MOVE_RECT +struct dxgi1_2.h DXGI_OUTDUPL_POINTER_POSITION +struct dxgi1_2.h DXGI_OUTDUPL_POINTER_SHAPE_INFO +struct dxgi1_2.h DXGI_PRESENT_PARAMETERS +struct dxgi1_2.h DXGI_SWAP_CHAIN_DESC1 +struct dxgi1_2.h DXGI_SWAP_CHAIN_FULLSCREEN_DESC +struct dxgi1_3.h DXGI_DECODE_SWAP_CHAIN_DESC +struct dxgi1_3.h DXGI_FRAME_STATISTICS_MEDIA +struct dxgi1_3.h DXGI_MATRIX_3X2_F +struct dxgi1_4.h DXGI_QUERY_VIDEO_MEMORY_INFO +struct dxgi1_5.h DXGI_HDR_METADATA_HDR10 +struct dxgi1_6.h DXGI_ADAPTER_DESC3 +struct dxgi1_6.h DXGI_OUTPUT_DESC1 +struct dxgicommon.h DXGI_RATIONAL +struct dxgicommon.h DXGI_SAMPLE_DESC +struct dxgidebug.h DXGI_INFO_QUEUE_FILTER +struct dxgidebug.h DXGI_INFO_QUEUE_FILTER_DESC +struct dxgidebug.h DXGI_INFO_QUEUE_MESSAGE +struct winnt.h _LUID + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > DXGI > DXGI reference > DXGI enumerations +enum dxgi.h DXGI_ADAPTER_FLAG +enum dxgi.h DXGI_RESIDENCY +enum dxgi.h DXGI_SWAP_CHAIN_FLAG +enum dxgi.h DXGI_SWAP_EFFECT +enum dxgi1_2.h DXGI_ALPHA_MODE +enum dxgi1_2.h DXGI_COMPUTE_PREEMPTION_GRANULARITY +enum dxgi1_2.h DXGI_GRAPHICS_PREEMPTION_GRANULARITY +enum dxgi1_2.h DXGI_OFFER_RESOURCE_PRIORITY +enum dxgi1_2.h DXGI_OUTDUPL_POINTER_SHAPE_TYPE +enum dxgi1_2.h DXGI_SCALING +enum dxgi1_3.h DXGI_FRAME_PRESENTATION_MODE +enum dxgi1_3.h DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS +enum dxgi1_3.h DXGI_OVERLAY_SUPPORT_FLAG +enum dxgi1_4.h DXGI_MEMORY_SEGMENT_GROUP +enum dxgi1_4.h DXGI_OVERLAY_COLOR_SPACE_SUPPORT_FLAG +enum dxgi1_4.h DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG +enum dxgi1_5.h DXGI_FEATURE +enum dxgi1_5.h DXGI_HDR_METADATA_TYPE +enum dxgi1_5.h DXGI_OFFER_RESOURCE_FLAGS +enum dxgi1_5.h DXGI_RECLAIM_RESOURCE_RESULTS +enum dxgi1_6.h DXGI_ADAPTER_FLAG3 +enum dxgi1_6.h DXGI_GPU_PREFERENCE +enum dxgi1_6.h DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAGS +enum dxgicommon.h DXGI_COLOR_SPACE_TYPE +enum dxgidebug.h DXGI_DEBUG_RLO_FLAGS +enum dxgidebug.h DXGI_INFO_QUEUE_MESSAGE_CATEGORY +enum dxgidebug.h DXGI_INFO_QUEUE_MESSAGE_SEVERITY +enum dxgiformat.h DXGI_FORMAT + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > High-level shader language (HLSL) > Reference for HLSL > D3DCompiler Reference > Functions +function d3d11shadertracing.h D3DDisassemble11Trace +function d3dcompiler.h D3DCompile +function d3dcompiler.h D3DCompile2 +function d3dcompiler.h D3DCompileFromFile +function d3dcompiler.h D3DCompressShaders +function d3dcompiler.h D3DCreateBlob +function d3dcompiler.h D3DCreateFunctionLinkingGraph +function d3dcompiler.h D3DCreateLinker +function d3dcompiler.h D3DDecompressShaders +function d3dcompiler.h D3DDisassemble +function d3dcompiler.h D3DDisassemble10Effect +function d3dcompiler.h D3DDisassembleRegion +function d3dcompiler.h D3DGetBlobPart +function d3dcompiler.h D3DGetDebugInfo +function d3dcompiler.h D3DGetInputAndOutputSignatureBlob +function d3dcompiler.h D3DGetInputSignatureBlob +function d3dcompiler.h D3DGetOutputSignatureBlob +function d3dcompiler.h D3DGetTraceInstructionOffsets +function d3dcompiler.h D3DLoadModule +function d3dcompiler.h D3DPreprocess +function d3dcompiler.h D3DReadFileToBlob +function d3dcompiler.h D3DReflect +function d3dcompiler.h D3DReflectLibrary +function d3dcompiler.h D3DSetBlobPart +function d3dcompiler.h D3DStripShader +function d3dcompiler.h D3DWriteBlobToFile + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > High-level shader language (HLSL) > Reference for HLSL > D3DCompiler Reference > Structures +struct d3dcompiler.h D3D_SHADER_DATA + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Direct3D > High-level shader language (HLSL) > Reference for HLSL > D3DCompiler Reference > Enumerations +enum d3dcompiler.h D3D_BLOB_PART +enum d3dcompiler.h D3DCOMPILER_STRIP_FLAGS + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DXCore > DXCore reference > DXCore callbacks +callback dxcore_interface.h PFN_DXCORE_NOTIFICATION_CALLBACK + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DXCore > DXCore reference > DXCore enumerations +enum dxcore_interface.h DXCoreAdapterPreference +enum dxcore_interface.h DXCoreAdapterProperty +enum dxcore_interface.h DXCoreAdapterState +enum dxcore_interface.h DXCoreNotificationType +enum dxcore_interface.h DXCoreSegmentGroup + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DXCore > DXCore reference > DXCore functions +function dxcore.h DXCoreCreateAdapterFactory + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DXCore > DXCore reference > DXCore structures +struct dxcore_interface.h DXCoreAdapterMemoryBudget +struct dxcore_interface.h DXCoreAdapterMemoryBudgetNodeSegmentGroup +struct dxcore_interface.h DXCoreHardwareID + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > Functions +function dwrite.h DWriteCreateFactory + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > DirectWrite enumerations > DWRITE_RENDERING_MODE enumerations +enum dwrite.h DWRITE_RENDERING_MODE (Windows 8 and later) =dwrite_rendering_mode + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > DirectWrite enumerations +enum dcommon.h DWRITE_GLYPH_IMAGE_FORMATS +enum dcommon.h DWRITE_MEASURING_MODE +enum dwrite_1.h DWRITE_BASELINE +enum dwrite_1.h DWRITE_GLYPH_ORIENTATION_ANGLE +enum dwrite_1.h DWRITE_OUTLINE_THRESHOLD +enum dwrite_1.h DWRITE_PANOSE_ARM_STYLE +enum dwrite_1.h DWRITE_PANOSE_ASPECT +enum dwrite_1.h DWRITE_PANOSE_ASPECT_RATIO +enum dwrite_1.h DWRITE_PANOSE_CHARACTER_RANGES +enum dwrite_1.h DWRITE_PANOSE_CONTRAST +enum dwrite_1.h DWRITE_PANOSE_DECORATIVE_CLASS +enum dwrite_1.h DWRITE_PANOSE_DECORATIVE_TOPOLOGY +enum dwrite_1.h DWRITE_PANOSE_FAMILY +enum dwrite_1.h DWRITE_PANOSE_FILL +enum dwrite_1.h DWRITE_PANOSE_FINIALS +enum dwrite_1.h DWRITE_PANOSE_LETTERFORM +enum dwrite_1.h DWRITE_PANOSE_LINING +enum dwrite_1.h DWRITE_PANOSE_MIDLINE +enum dwrite_1.h DWRITE_PANOSE_PROPORTION +enum dwrite_1.h DWRITE_PANOSE_SCRIPT_FORM +enum dwrite_1.h DWRITE_PANOSE_SCRIPT_TOPOLOGY +enum dwrite_1.h DWRITE_PANOSE_SERIF_STYLE +enum dwrite_1.h DWRITE_PANOSE_SPACING +enum dwrite_1.h DWRITE_PANOSE_STROKE_VARIATION +enum dwrite_1.h DWRITE_PANOSE_SYMBOL_ASPECT_RATIO +enum dwrite_1.h DWRITE_PANOSE_SYMBOL_KIND +enum dwrite_1.h DWRITE_PANOSE_TOOL_KIND +enum dwrite_1.h DWRITE_PANOSE_WEIGHT +enum dwrite_1.h DWRITE_PANOSE_XASCENT +enum dwrite_1.h DWRITE_PANOSE_XHEIGHT +enum dwrite_1.h DWRITE_TEXT_ANTIALIAS_MODE +enum dwrite_1.h DWRITE_VERTICAL_GLYPH_ORIENTATION +enum dwrite_2.h DWRITE_GRID_FIT_MODE +enum dwrite_2.h DWRITE_OPTICAL_ALIGNMENT +enum dwrite_3.h DWRITE_AUTOMATIC_FONT_AXES +enum dwrite_3.h DWRITE_CONTAINER_TYPE +enum dwrite_3.h DWRITE_FONT_AXIS_ATTRIBUTES +enum dwrite_3.h DWRITE_FONT_AXIS_TAG +enum dwrite_3.h DWRITE_FONT_FAMILY_MODEL +enum dwrite_3.h DWRITE_FONT_LINE_GAP_USAGE +enum dwrite_3.h DWRITE_FONT_PROPERTY_ID +enum dwrite_3.h DWRITE_FONT_SOURCE_TYPE +enum dwrite_3.h DWRITE_LOCALITY +enum dwrite_3.h DWRITE_RENDERING_MODE1 +enum dwrite.h DWRITE_BREAK_CONDITION +enum dwrite.h DWRITE_FACTORY_TYPE +enum dwrite.h DWRITE_FLOW_DIRECTION +enum dwrite.h DWRITE_FONT_FACE_TYPE +enum dwrite.h DWRITE_FONT_FEATURE_TAG +enum dwrite.h DWRITE_FONT_FILE_TYPE +enum dwrite.h DWRITE_FONT_SIMULATIONS +enum dwrite.h DWRITE_FONT_STRETCH +enum dwrite.h DWRITE_FONT_STYLE +enum dwrite.h DWRITE_FONT_WEIGHT +enum dwrite.h DWRITE_INFORMATIONAL_STRING_ID +enum dwrite.h DWRITE_LINE_SPACING_METHOD +enum dwrite.h DWRITE_NUMBER_SUBSTITUTION_METHOD +enum dwrite.h DWRITE_PARAGRAPH_ALIGNMENT +enum dwrite.h DWRITE_PIXEL_GEOMETRY +enum dwrite.h DWRITE_READING_DIRECTION +enum dwrite.h DWRITE_SCRIPT_SHAPES +enum dwrite.h DWRITE_TEXT_ALIGNMENT +enum dwrite.h DWRITE_TEXTURE_TYPE +enum dwrite.h DWRITE_TRIMMING_GRANULARITY +enum dwrite.h DWRITE_WORD_WRAPPING + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > DirectWrite interfaces +interface dwrite_1.h IDWriteBitmapRenderTarget1 +interface dwrite_1.h IDWriteFactory1 +interface dwrite_1.h IDWriteFont1 +interface dwrite_1.h IDWriteFontFace1 +interface dwrite_1.h IDWriteRenderingParams1 +interface dwrite_1.h IDWriteTextAnalysisSink1 +interface dwrite_1.h IDWriteTextAnalysisSource1 +interface dwrite_1.h IDWriteTextAnalyzer1 +interface dwrite_1.h IDWriteTextLayout1 +interface dwrite_2.h IDWriteColorGlyphRunEnumerator +interface dwrite_2.h IDWriteFactory2 +interface dwrite_2.h IDWriteFont2 +interface dwrite_2.h IDWriteFontFace2 +interface dwrite_2.h IDWriteFontFallback +interface dwrite_2.h IDWriteFontFallbackBuilder +interface dwrite_2.h IDWriteRenderingParams2 +interface dwrite_2.h IDWriteTextAnalyzer2 +interface dwrite_2.h IDWriteTextFormat1 +interface dwrite_2.h IDWriteTextLayout2 +interface dwrite_2.h IDWriteTextRenderer1 +interface dwrite_3.h IDWriteAsyncResult +interface dwrite_3.h IDWriteColorGlyphRunEnumerator1 +interface dwrite_3.h IDWriteFactory3 +interface dwrite_3.h IDWriteFactory4 +interface dwrite_3.h IDWriteFactory5 +interface dwrite_3.h IDWriteFactory6 +interface dwrite_3.h IDWriteFactory7 +interface dwrite_3.h IDWriteFont3 +interface dwrite_3.h IDWriteFontCollection1 +interface dwrite_3.h IDWriteFontCollection2 +interface dwrite_3.h IDWriteFontCollection3 +interface dwrite_3.h IDWriteFontDownloadListener +interface dwrite_3.h IDWriteFontDownloadQueue +interface dwrite_3.h IDWriteFontFace3 +interface dwrite_3.h IDWriteFontFace4 +interface dwrite_3.h IDWriteFontFace5 +interface dwrite_3.h IDWriteFontFaceReference +interface dwrite_3.h IDWriteFontFaceReference1 +interface dwrite_3.h IDWriteFontFamily1 +interface dwrite_3.h IDWriteFontFamily2 +interface dwrite_3.h IDWriteFontList1 +interface dwrite_3.h IDWriteFontList2 +interface dwrite_3.h IDWriteFontResource +interface dwrite_3.h IDWriteFontSet +interface dwrite_3.h IDWriteFontSet1 +interface dwrite_3.h IDWriteFontSet2 +interface dwrite_3.h IDWriteFontSet3 +interface dwrite_3.h IDWriteFontSet4 +interface dwrite_3.h IDWriteFontSetBuilder +interface dwrite_3.h IDWriteFontSetBuilder1 +interface dwrite_3.h IDWriteFontSetBuilder2 +interface dwrite_3.h IDWriteGdiInterop1 +interface dwrite_3.h IDWriteInMemoryFontFileLoader +interface dwrite_3.h IDWriteRemoteFontFileLoader +interface dwrite_3.h IDWriteRemoteFontFileStream +interface dwrite_3.h IDWriteRenderingParams3 +interface dwrite_3.h IDWriteStringList +interface dwrite_3.h IDWriteTextFormat2 +interface dwrite_3.h IDWriteTextFormat3 +interface dwrite_3.h IDWriteTextLayout3 +interface dwrite.h IDWriteBitmapRenderTarget +interface dwrite.h IDWriteFactory +interface dwrite.h IDWriteFont +interface dwrite.h IDWriteFontCollection +interface dwrite.h IDWriteFontCollectionLoader +interface dwrite.h IDWriteFontFace +interface dwrite.h IDWriteFontFamily +interface dwrite.h IDWriteFontFile +interface dwrite.h IDWriteFontFileEnumerator +interface dwrite.h IDWriteFontFileLoader +interface dwrite.h IDWriteFontFileStream +interface dwrite.h IDWriteFontList +interface dwrite.h IDWriteGdiInterop +interface dwrite.h IDWriteGlyphRunAnalysis +interface dwrite.h IDWriteInlineObject +interface dwrite.h IDWriteLocalFontFileLoader +interface dwrite.h IDWriteLocalizedStrings +interface dwrite.h IDWriteNumberSubstitution +interface dwrite.h IDWritePixelSnapping +interface dwrite.h IDWriteRenderingParams +interface dwrite.h IDWriteTextAnalysisSink +interface dwrite.h IDWriteTextAnalysisSource +interface dwrite.h IDWriteTextAnalyzer +interface dwrite.h IDWriteTextFormat +interface dwrite.h IDWriteTextLayout +interface dwrite.h IDWriteTextRenderer +interface dwrite.h IDWriteTypography + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > DirectWrite macros +function dwrite_3.h DWRITE_MAKE_FONT_AXIS_TAG +function dwrite.h DWRITE_MAKE_OPENTYPE_TAG + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectWrite > API Reference > DirectWrite structures +struct dwrite_1.h DWRITE_CARET_METRICS +struct dwrite_1.h DWRITE_FONT_METRICS1 +struct dwrite_1.h DWRITE_JUSTIFICATION_OPPORTUNITY +struct dwrite_1.h DWRITE_PANOSE +struct dwrite_1.h DWRITE_SCRIPT_PROPERTIES +struct dwrite_1.h DWRITE_UNICODE_RANGE +struct dwrite_2.h DWRITE_COLOR_GLYPH_RUN +struct dwrite_2.h DWRITE_TEXT_METRICS1 +struct dwrite_3.h DWRITE_COLOR_GLYPH_RUN1 +struct dwrite_3.h DWRITE_FILE_FRAGMENT +struct dwrite_3.h DWRITE_FONT_AXIS_RANGE +struct dwrite_3.h DWRITE_FONT_AXIS_VALUE +struct dwrite_3.h DWRITE_FONT_PROPERTY +struct dwrite_3.h DWRITE_GLYPH_IMAGE_DATA +struct dwrite_3.h DWRITE_LINE_METRICS1 +struct dwrite_3.h DWRITE_LINE_SPACING +struct dwrite.h DWRITE_CLUSTER_METRICS +struct dwrite.h DWRITE_FONT_FEATURE +struct dwrite.h DWRITE_FONT_METRICS +struct dwrite.h DWRITE_GLYPH_METRICS +struct dwrite.h DWRITE_GLYPH_OFFSET +struct dwrite.h DWRITE_GLYPH_RUN +struct dwrite.h DWRITE_GLYPH_RUN_DESCRIPTION +struct dwrite.h DWRITE_HIT_TEST_METRICS +struct dwrite.h DWRITE_INLINE_OBJECT_METRICS +struct dwrite.h DWRITE_LINE_BREAKPOINT +struct dwrite.h DWRITE_LINE_METRICS +struct dwrite.h DWRITE_MATRIX +struct dwrite.h DWRITE_OVERHANG_METRICS +struct dwrite.h DWRITE_SCRIPT_ANALYSIS +struct dwrite.h DWRITE_SHAPING_GLYPH_PROPERTIES +struct dwrite.h DWRITE_SHAPING_TEXT_PROPERTIES +struct dwrite.h DWRITE_STRIKETHROUGH +struct dwrite.h DWRITE_TEXT_METRICS +struct dwrite.h DWRITE_TEXT_RANGE +struct dwrite.h DWRITE_TRIMMING +struct dwrite.h DWRITE_TYPOGRAPHIC_FEATURES +struct dwrite.h DWRITE_UNDERLINE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library color functions +function directxmath.h XMColorAdjustContrast +function directxmath.h XMColorAdjustSaturation +function directxmath.h XMColorEqual +function directxmath.h XMColorGreater +function directxmath.h XMColorGreaterOrEqual +function directxmath.h XMColorHSLToRGB +function directxmath.h XMColorHSVToRGB +function directxmath.h XMColorIsInfinite +function directxmath.h XMColorIsNaN +function directxmath.h XMColorLess +function directxmath.h XMColorLessOrEqual +function directxmath.h XMColorModulate +function directxmath.h XMColorNegative +function directxmath.h XMColorNotEqual +function directxmath.h XMColorRGBToHSL +function directxmath.h XMColorRGBToHSV +function directxmath.h XMColorRGBToSRGB +function directxmath.h XMColorRGBToXYZ +function directxmath.h XMColorRGBToYUV +function directxmath.h XMColorRGBToYUV_HD +function directxmath.h XMColorRGBToYUV_UHD +function directxmath.h XMColorSRGBToRGB +function directxmath.h XMColorSRGBToXYZ +function directxmath.h XMColorXYZToRGB +function directxmath.h XMColorXYZToSRGB +function directxmath.h XMColorYUVToRGB +function directxmath.h XMColorYUVToRGB_HD +function directxmath.h XMColorYUVToRGB_UHD + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library conversion functions +function directxmath.h XMConvertToDegrees +function directxmath.h XMConvertToRadians +function directxmath.h XMConvertVectorFloatToInt +function directxmath.h XMConvertVectorFloatToUInt +function directxmath.h XMConvertVectorIntToFloat +function directxmath.h XMConvertVectorUIntToFloat +function directxpackedvector.h XMConvertFloatToHalf +function directxpackedvector.h XMConvertFloatToHalfStream +function directxpackedvector.h XMConvertHalfToFloat +function directxpackedvector.h XMConvertHalfToFloatStream + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library matrix functions +function directxmath.h XMMatrixAffineTransformation +function directxmath.h XMMatrixAffineTransformation2D +function directxmath.h XMMatrixDecompose +function directxmath.h XMMatrixDeterminant +function directxmath.h XMMatrixIdentity +function directxmath.h XMMatrixInverse +function directxmath.h XMMatrixIsIdentity +function directxmath.h XMMatrixIsInfinite +function directxmath.h XMMatrixIsNaN +function directxmath.h XMMatrixLookAtLH +function directxmath.h XMMatrixLookAtRH +function directxmath.h XMMatrixLookToLH +function directxmath.h XMMatrixLookToRH +function directxmath.h XMMatrixMultiply +function directxmath.h XMMatrixMultiplyTranspose +function directxmath.h XMMatrixOrthographicLH +function directxmath.h XMMatrixOrthographicOffCenterLH +function directxmath.h XMMatrixOrthographicOffCenterRH +function directxmath.h XMMatrixOrthographicRH +function directxmath.h XMMatrixPerspectiveFovLH +function directxmath.h XMMatrixPerspectiveFovRH +function directxmath.h XMMatrixPerspectiveLH +function directxmath.h XMMatrixPerspectiveOffCenterLH +function directxmath.h XMMatrixPerspectiveOffCenterRH +function directxmath.h XMMatrixPerspectiveRH +function directxmath.h XMMatrixReflect +function directxmath.h XMMatrixRotationAxis +function directxmath.h XMMatrixRotationNormal +function directxmath.h XMMatrixRotationQuaternion +function directxmath.h XMMatrixRotationRollPitchYaw +function directxmath.h XMMatrixRotationRollPitchYawFromVector +function directxmath.h XMMatrixRotationX +function directxmath.h XMMatrixRotationY +function directxmath.h XMMatrixRotationZ +function directxmath.h XMMatrixScaling +function directxmath.h XMMatrixScalingFromVector +function directxmath.h XMMatrixSet +function directxmath.h XMMatrixShadow +function directxmath.h XMMatrixTransformation +function directxmath.h XMMatrixTransformation2D +function directxmath.h XMMatrixTranslation +function directxmath.h XMMatrixTranslationFromVector +function directxmath.h XMMatrixTranspose +function directxmath.h XMMatrixVectorTensorProduct + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library plane functions +function directxmath.h XMPlaneDot +function directxmath.h XMPlaneDotCoord +function directxmath.h XMPlaneDotNormal +function directxmath.h XMPlaneEqual +function directxmath.h XMPlaneFromPointNormal +function directxmath.h XMPlaneFromPoints +function directxmath.h XMPlaneIntersectLine +function directxmath.h XMPlaneIntersectPlane +function directxmath.h XMPlaneIsInfinite +function directxmath.h XMPlaneIsNaN +function directxmath.h XMPlaneNearEqual +function directxmath.h XMPlaneNormalize +function directxmath.h XMPlaneNormalizeEst +function directxmath.h XMPlaneNotEqual +function directxmath.h XMPlaneTransform +function directxmath.h XMPlaneTransformStream + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library quaternion functions +function directxmath.h XMQuaternionBaryCentric +function directxmath.h XMQuaternionBaryCentricV +function directxmath.h XMQuaternionConjugate +function directxmath.h XMQuaternionDot +function directxmath.h XMQuaternionEqual +function directxmath.h XMQuaternionExp +function directxmath.h XMQuaternionIdentity +function directxmath.h XMQuaternionInverse +function directxmath.h XMQuaternionIsIdentity +function directxmath.h XMQuaternionIsInfinite +function directxmath.h XMQuaternionIsNaN +function directxmath.h XMQuaternionLength +function directxmath.h XMQuaternionLengthSq +function directxmath.h XMQuaternionLn +function directxmath.h XMQuaternionMultiply +function directxmath.h XMQuaternionNormalize +function directxmath.h XMQuaternionNormalizeEst +function directxmath.h XMQuaternionNotEqual +function directxmath.h XMQuaternionReciprocalLength +function directxmath.h XMQuaternionRotationAxis +function directxmath.h XMQuaternionRotationMatrix +function directxmath.h XMQuaternionRotationNormal +function directxmath.h XMQuaternionRotationRollPitchYaw +function directxmath.h XMQuaternionRotationRollPitchYawFromVector +function directxmath.h XMQuaternionSlerp +function directxmath.h XMQuaternionSlerpV +function directxmath.h XMQuaternionSquad +function directxmath.h XMQuaternionSquadSetup +function directxmath.h XMQuaternionSquadV +function directxmath.h XMQuaternionToAxisAngle + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library scalar functions +function directxmath.h XMScalarACos +function directxmath.h XMScalarACosEst +function directxmath.h XMScalarASin +function directxmath.h XMScalarASinEst +function directxmath.h XMScalarCos +function directxmath.h XMScalarCosEst +function directxmath.h XMScalarModAngle +function directxmath.h XMScalarNearEqual +function directxmath.h XMScalarSin +function directxmath.h XMScalarSinCos +function directxmath.h XMScalarSinCosEst +function directxmath.h XMScalarSinEst + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Vector arithmetic functions +function directxmath.h XMVectorAbs +function directxmath.h XMVectorAdd +function directxmath.h XMVectorAddAngles +function directxmath.h XMVectorCeiling +function directxmath.h XMVectorClamp +function directxmath.h XMVectorDivide +function directxmath.h XMVectorFloor +function directxmath.h XMVectorIsInfinite +function directxmath.h XMVectorIsNaN +function directxmath.h XMVectorMax +function directxmath.h XMVectorMin +function directxmath.h XMVectorMod +function directxmath.h XMVectorModAngles +function directxmath.h XMVectorMultiply +function directxmath.h XMVectorMultiplyAdd +function directxmath.h XMVectorNegate +function directxmath.h XMVectorNegativeMultiplySubtract +function directxmath.h XMVectorPow +function directxmath.h XMVectorReciprocal +function directxmath.h XMVectorReciprocalEst +function directxmath.h XMVectorReciprocalSqrt +function directxmath.h XMVectorReciprocalSqrtEst +function directxmath.h XMVectorRound +function directxmath.h XMVectorSaturate +function directxmath.h XMVectorScale +function directxmath.h XMVectorSqrt +function directxmath.h XMVectorSqrtEst +function directxmath.h XMVectorSubtract +function directxmath.h XMVectorSubtractAngles +function directxmath.h XMVectorSum +function directxmath.h XMVectorTruncate + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Bit-Wise vector functions +function directxmath.h XMVectorAndCInt +function directxmath.h XMVectorAndInt +function directxmath.h XMVectorNorInt +function directxmath.h XMVectorNotEqual +function directxmath.h XMVectorNotEqualInt +function directxmath.h XMVectorOrInt +function directxmath.h XMVectorXorInt + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Vector comparison functions +function directxmath.h XMVectorEqual +function directxmath.h XMVectorEqualInt +function directxmath.h XMVectorEqualIntR +function directxmath.h XMVectorEqualR +function directxmath.h XMVectorGreater +function directxmath.h XMVectorGreaterOrEqual +function directxmath.h XMVectorGreaterOrEqualR +function directxmath.h XMVectorGreaterR +function directxmath.h XMVectorLess +function directxmath.h XMVectorLessOrEqual +function directxmath.h XMVectorNearEqual + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Component-wise vector functions +function directxmath.h XMVectorInsert +function directxmath.h XMVectorMergeXY +function directxmath.h XMVectorMergeZW +function directxmath.h XMVectorPermute +function directxmath.h XMVectorRotateLeft +function directxmath.h XMVectorRotateRight +function directxmath.h XMVectorSelect +function directxmath.h XMVectorSelectControl +function directxmath.h XMVectorShiftLeft +function directxmath.h XMVectorSplatW +function directxmath.h XMVectorSplatX +function directxmath.h XMVectorSplatY +function directxmath.h XMVectorSplatZ +function directxmath.h XMVectorSwizzle + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Geometric vector functions +function directxmath.h XMVectorBaryCentric +function directxmath.h XMVectorBaryCentricV +function directxmath.h XMVectorCatmullRom +function directxmath.h XMVectorCatmullRomV +function directxmath.h XMVectorHermite +function directxmath.h XMVectorHermiteV +function directxmath.h XMVectorInBounds +function directxmath.h XMVectorInBoundsR +function directxmath.h XMVectorLerp +function directxmath.h XMVectorLerpV + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Vector initialization functions +function directxmath.h XMVectorFalseInt +function directxmath.h XMVectorReplicate +function directxmath.h XMVectorReplicateInt +function directxmath.h XMVectorReplicateIntPtr +function directxmath.h XMVectorReplicatePtr +function directxmath.h XMVectorSet +function directxmath.h XMVectorSetBinaryConstant +function directxmath.h XMVectorSetInt +function directxmath.h XMVectorSplatConstant +function directxmath.h XMVectorSplatConstantInt +function directxmath.h XMVectorSplatEpsilon +function directxmath.h XMVectorSplatInfinity +function directxmath.h XMVectorSplatOne +function directxmath.h XMVectorSplatQNaN +function directxmath.h XMVectorSplatSignMask +function directxmath.h XMVectorTrueInt +function directxmath.h XMVectorZero + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector functions > Transcendental vector functions +function directxmath.h XMVectorACos +function directxmath.h XMVectorACosEst +function directxmath.h XMVectorASin +function directxmath.h XMVectorASinEst +function directxmath.h XMVectorATan +function directxmath.h XMVectorATan2 +function directxmath.h XMVectorATan2Est +function directxmath.h XMVectorATanEst +function directxmath.h XMVectorCos +function directxmath.h XMVectorCosEst +function directxmath.h XMVectorCosH +function directxmath.h XMVectorExp +function directxmath.h XMVectorExp10 +function directxmath.h XMVectorExp2 +function directxmath.h XMVectorExpE +function directxmath.h XMVectorLog +function directxmath.h XMVectorLog10 +function directxmath.h XMVectorLog2 +function directxmath.h XMVectorLogE +function directxmath.h XMVectorSin +function directxmath.h XMVectorSinCos +function directxmath.h XMVectorSinCosEst +function directxmath.h XMVectorSinEst +function directxmath.h XMVectorSinH +function directxmath.h XMVectorTan +function directxmath.h XMVectorTanEst +function directxmath.h XMVectorTanH + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 2D vector functions > DirectXMath Library 2D vector comparison functions +function directxmath.h XMVector2Equal +function directxmath.h XMVector2EqualInt +function directxmath.h XMVector2EqualIntR +function directxmath.h XMVector2EqualR +function directxmath.h XMVector2Greater +function directxmath.h XMVector2GreaterOrEqual +function directxmath.h XMVector2GreaterOrEqualR +function directxmath.h XMVector2GreaterR +function directxmath.h XMVector2IsInfinite +function directxmath.h XMVector2IsNaN +function directxmath.h XMVector2Less +function directxmath.h XMVector2LessOrEqual +function directxmath.h XMVector2NearEqual +function directxmath.h XMVector2NotEqual +function directxmath.h XMVector2NotEqualInt + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 2D vector functions > DirectXMath Library 2D vector geometric functions +function directxmath.h XMVector2AngleBetweenNormals +function directxmath.h XMVector2AngleBetweenNormalsEst +function directxmath.h XMVector2AngleBetweenVectors +function directxmath.h XMVector2ClampLength +function directxmath.h XMVector2ClampLengthV +function directxmath.h XMVector2Cross +function directxmath.h XMVector2Dot +function directxmath.h XMVector2InBounds +function directxmath.h XMVector2IntersectLine +function directxmath.h XMVector2Length +function directxmath.h XMVector2LengthEst +function directxmath.h XMVector2LengthSq +function directxmath.h XMVector2LinePointDistance +function directxmath.h XMVector2Normalize +function directxmath.h XMVector2NormalizeEst +function directxmath.h XMVector2Orthogonal +function directxmath.h XMVector2ReciprocalLength +function directxmath.h XMVector2ReciprocalLengthEst +function directxmath.h XMVector2Reflect +function directxmath.h XMVector2Refract +function directxmath.h XMVector2RefractV + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 2D vector functions > DirectXMath 3D vector transformation functions +function directxmath.h XMVector2Transform +function directxmath.h XMVector2TransformCoord +function directxmath.h XMVector2TransformCoordStream +function directxmath.h XMVector2TransformNormal +function directxmath.h XMVector2TransformNormalStream +function directxmath.h XMVector2TransformStream + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 3D vector functions > DirectXMath Library 3D vector comparison functions +function directxmath.h XMVector3Equal +function directxmath.h XMVector3EqualInt +function directxmath.h XMVector3EqualIntR +function directxmath.h XMVector3EqualR +function directxmath.h XMVector3Greater +function directxmath.h XMVector3GreaterOrEqual +function directxmath.h XMVector3GreaterOrEqualR +function directxmath.h XMVector3GreaterR +function directxmath.h XMVector3IsInfinite +function directxmath.h XMVector3IsNaN +function directxmath.h XMVector3Less +function directxmath.h XMVector3LessOrEqual +function directxmath.h XMVector3NearEqual +function directxmath.h XMVector3NotEqual +function directxmath.h XMVector3NotEqualInt + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 3D vector functions > DirectXMath Library 3D vector geometric functions +function directxmath.h XMVector3AngleBetweenNormals +function directxmath.h XMVector3AngleBetweenNormalsEst +function directxmath.h XMVector3AngleBetweenVectors +function directxmath.h XMVector3ClampLength +function directxmath.h XMVector3ClampLengthV +function directxmath.h XMVector3ComponentsFromNormal +function directxmath.h XMVector3Cross +function directxmath.h XMVector3Dot +function directxmath.h XMVector3InBounds +function directxmath.h XMVector3Length +function directxmath.h XMVector3LengthEst +function directxmath.h XMVector3LengthSq +function directxmath.h XMVector3LinePointDistance +function directxmath.h XMVector3Normalize +function directxmath.h XMVector3NormalizeEst +function directxmath.h XMVector3Orthogonal +function directxmath.h XMVector3ReciprocalLength +function directxmath.h XMVector3ReciprocalLengthEst +function directxmath.h XMVector3Reflect +function directxmath.h XMVector3Refract +function directxmath.h XMVector3RefractV + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 3D vector functions > DirectXMath Library 3D vector transformation functions +function directxmath.h XMVector3InverseRotate +function directxmath.h XMVector3Project +function directxmath.h XMVector3ProjectStream +function directxmath.h XMVector3Rotate +function directxmath.h XMVector3Transform +function directxmath.h XMVector3TransformCoord +function directxmath.h XMVector3TransformCoordStream +function directxmath.h XMVector3TransformNormal +function directxmath.h XMVector3TransformNormalStream +function directxmath.h XMVector3TransformStream +function directxmath.h XMVector3Unproject +function directxmath.h XMVector3UnprojectStream + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 4D vector functions > DirectXMath Library 4D vector comparison functions +function directxmath.h XMVector4Equal +function directxmath.h XMVector4EqualInt +function directxmath.h XMVector4EqualIntR +function directxmath.h XMVector4EqualR +function directxmath.h XMVector4Greater +function directxmath.h XMVector4GreaterOrEqual +function directxmath.h XMVector4GreaterOrEqualR +function directxmath.h XMVector4GreaterR +function directxmath.h XMVector4IsInfinite +function directxmath.h XMVector4IsNaN +function directxmath.h XMVector4Less +function directxmath.h XMVector4LessOrEqual +function directxmath.h XMVector4NearEqual +function directxmath.h XMVector4NotEqual +function directxmath.h XMVector4NotEqualInt + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 4D vector functions > DirectXMath Library 4D vector geometric functions +function directxmath.h XMVector4AngleBetweenNormals +function directxmath.h XMVector4AngleBetweenNormalsEst +function directxmath.h XMVector4AngleBetweenVectors +function directxmath.h XMVector4ClampLength +function directxmath.h XMVector4ClampLengthV +function directxmath.h XMVector4Cross +function directxmath.h XMVector4Dot +function directxmath.h XMVector4InBounds +function directxmath.h XMVector4Length +function directxmath.h XMVector4LengthEst +function directxmath.h XMVector4LengthSq +function directxmath.h XMVector4Normalize +function directxmath.h XMVector4NormalizeEst +function directxmath.h XMVector4Orthogonal +function directxmath.h XMVector4ReciprocalLength +function directxmath.h XMVector4ReciprocalLengthEst +function directxmath.h XMVector4Reflect +function directxmath.h XMVector4Refract +function directxmath.h XMVector4RefractV + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library 4D vector functions > DirectXMath Library 4D vector transformation functions +function directxmath.h XMVector4Transform +function directxmath.h XMVector4TransformStream + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath triangle test functions +function directxcollision.h ContainedBy =boundingbox-containedby + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library utility functions +function directxmath.h XMComparisonAllFalse +function directxmath.h XMComparisonAllInBounds +function directxmath.h XMComparisonAllTrue +function directxmath.h XMComparisonAnyFalse +function directxmath.h XMComparisonAnyOutOfBounds +function directxmath.h XMComparisonAnyTrue +function directxmath.h XMComparisonMixed +function directxmath.h XMFresnelTerm +function directxmath.h XMVerifyCPUSupport + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector accessor functions +function directxmath.h XMVectorGetByIndex +function directxmath.h XMVectorGetByIndexPtr +function directxmath.h XMVectorGetIntByIndex +function directxmath.h XMVectorGetIntByIndexPtr +function directxmath.h XMVectorGetIntW +function directxmath.h XMVectorGetIntWPtr +function directxmath.h XMVectorGetIntX +function directxmath.h XMVectorGetIntXPtr +function directxmath.h XMVectorGetIntY +function directxmath.h XMVectorGetIntYPtr +function directxmath.h XMVectorGetIntZ +function directxmath.h XMVectorGetIntZPtr +function directxmath.h XMVectorGetW +function directxmath.h XMVectorGetWPtr +function directxmath.h XMVectorGetX +function directxmath.h XMVectorGetXPtr +function directxmath.h XMVectorGetY +function directxmath.h XMVectorGetYPtr +function directxmath.h XMVectorGetZ +function directxmath.h XMVectorGetZPtr +function directxmath.h XMVectorSetByIndexPtr +function directxmath.h XMVectorSetIntByIndexPtr +function directxmath.h XMVectorSetIntW +function directxmath.h XMVectorSetIntWPtr +function directxmath.h XMVectorSetIntX +function directxmath.h XMVectorSetIntXPtr +function directxmath.h XMVectorSetIntY +function directxmath.h XMVectorSetIntYPtr +function directxmath.h XMVectorSetIntZ +function directxmath.h XMVectorSetIntZPtr +function directxmath.h XMVectorSetW +function directxmath.h XMVectorSetWPtr +function directxmath.h XMVectorSetX +function directxmath.h XMVectorSetXPtr +function directxmath.h XMVectorSetY +function directxmath.h XMVectorSetYPtr +function directxmath.h XMVectorSetZ +function directxmath.h XMVectorSetZPtr + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector load functions +function directxmath.h XMLoadFloat +function directxmath.h XMLoadFloat2 +function directxmath.h XMLoadFloat2A +function directxmath.h XMLoadFloat3 +function directxmath.h XMLoadFloat3A +function directxmath.h XMLoadFloat3x3 +function directxmath.h XMLoadFloat3x4 +function directxmath.h XMLoadFloat3x4A +function directxmath.h XMLoadFloat4 +function directxmath.h XMLoadFloat4A +function directxmath.h XMLoadFloat4x3 +function directxmath.h XMLoadFloat4x3A +function directxmath.h XMLoadFloat4x4 +function directxmath.h XMLoadFloat4x4A +function directxmath.h XMLoadInt +function directxmath.h XMLoadInt2 +function directxmath.h XMLoadInt2A +function directxmath.h XMLoadInt3 +function directxmath.h XMLoadInt3A +function directxmath.h XMLoadInt4 +function directxmath.h XMLoadInt4A +function directxmath.h XMLoadSInt2 +function directxmath.h XMLoadSInt3 +function directxmath.h XMLoadSInt4 +function directxmath.h XMLoadUInt2 +function directxmath.h XMLoadUInt3 +function directxmath.h XMLoadUInt4 +function directxpackedvector.h XMLoadByte2 +function directxpackedvector.h XMLoadByte4 +function directxpackedvector.h XMLoadByteN2 +function directxpackedvector.h XMLoadByteN4 +function directxpackedvector.h XMLoadColor +function directxpackedvector.h XMLoadDec4 +function directxpackedvector.h XMLoadDecN4 +function directxpackedvector.h XMLoadFloat3PK +function directxpackedvector.h XMLoadFloat3SE +function directxpackedvector.h XMLoadHalf2 +function directxpackedvector.h XMLoadHalf4 +function directxpackedvector.h XMLoadShort2 +function directxpackedvector.h XMLoadShort4 +function directxpackedvector.h XMLoadShortN2 +function directxpackedvector.h XMLoadShortN4 +function directxpackedvector.h XMLoadU555 +function directxpackedvector.h XMLoadU565 +function directxpackedvector.h XMLoadUByte2 +function directxpackedvector.h XMLoadUByte4 +function directxpackedvector.h XMLoadUByteN2 +function directxpackedvector.h XMLoadUByteN4 +function directxpackedvector.h XMLoadUDec4 +function directxpackedvector.h XMLoadUDecN4 +function directxpackedvector.h XMLoadUDecN4_XR +function directxpackedvector.h XMLoadUNibble4 +function directxpackedvector.h XMLoadUShort2 +function directxpackedvector.h XMLoadUShort4 +function directxpackedvector.h XMLoadUShortN2 +function directxpackedvector.h XMLoadUShortN4 +function directxpackedvector.h XMLoadXDec4 +function directxpackedvector.h XMLoadXDecN4 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library functions > DirectXMath Library vector store functions +function directxmath.h XMStoreFloat +function directxmath.h XMStoreFloat2 +function directxmath.h XMStoreFloat2A +function directxmath.h XMStoreFloat3 +function directxmath.h XMStoreFloat3A +function directxmath.h XMStoreFloat3x3 +function directxmath.h XMStoreFloat3x4 +function directxmath.h XMStoreFloat3x4A +function directxmath.h XMStoreFloat4 +function directxmath.h XMStoreFloat4A +function directxmath.h XMStoreFloat4x3 +function directxmath.h XMStoreFloat4x3A +function directxmath.h XMStoreFloat4x4 +function directxmath.h XMStoreFloat4x4A +function directxmath.h XMStoreInt +function directxmath.h XMStoreInt2 +function directxmath.h XMStoreInt2A +function directxmath.h XMStoreInt3 +function directxmath.h XMStoreInt3A +function directxmath.h XMStoreInt4 +function directxmath.h XMStoreInt4A +function directxmath.h XMStoreSInt2 +function directxmath.h XMStoreSInt3 +function directxmath.h XMStoreSInt4 +function directxmath.h XMStoreUInt2 +function directxmath.h XMStoreUInt3 +function directxmath.h XMStoreUInt4 +function directxpackedvector.h XMStoreByte2 +function directxpackedvector.h XMStoreByte4 +function directxpackedvector.h XMStoreByteN2 +function directxpackedvector.h XMStoreByteN4 +function directxpackedvector.h XMStoreColor +function directxpackedvector.h XMStoreDec4 +function directxpackedvector.h XMStoreDecN4 +function directxpackedvector.h XMStoreFloat3PK +function directxpackedvector.h XMStoreFloat3SE +function directxpackedvector.h XMStoreHalf2 +function directxpackedvector.h XMStoreHalf4 +function directxpackedvector.h XMStoreShort2 +function directxpackedvector.h XMStoreShort4 +function directxpackedvector.h XMStoreShortN2 +function directxpackedvector.h XMStoreShortN4 +function directxpackedvector.h XMStoreU555 +function directxpackedvector.h XMStoreU565 +function directxpackedvector.h XMStoreUByte2 +function directxpackedvector.h XMStoreUByte4 +function directxpackedvector.h XMStoreUByteN2 +function directxpackedvector.h XMStoreUByteN4 +function directxpackedvector.h XMStoreUDec4 +function directxpackedvector.h XMStoreUDecN4 +function directxpackedvector.h XMStoreUDecN4_XR +function directxpackedvector.h XMStoreUNibble4 +function directxpackedvector.h XMStoreUShort2 +function directxpackedvector.h XMStoreUShort4 +function directxpackedvector.h XMStoreUShortN2 +function directxpackedvector.h XMStoreUShortN4 +function directxpackedvector.h XMStoreXDec4 +function directxpackedvector.h XMStoreXDecN4 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library structures +struct directxmath.h XMFLOAT2 +struct directxmath.h XMFLOAT2A +struct directxmath.h XMFLOAT3 +struct directxmath.h XMFLOAT3A +struct directxmath.h XMFLOAT3X3 +struct directxmath.h XMFLOAT3X4 +struct directxmath.h XMFLOAT3X4A +struct directxmath.h XMFLOAT4 +struct directxmath.h XMFLOAT4A +struct directxmath.h XMFLOAT4X3 +struct directxmath.h XMFLOAT4X3A +struct directxmath.h XMFLOAT4X4 +struct directxmath.h XMFLOAT4X4A +struct directxmath.h XMINT2 +struct directxmath.h XMINT3 +struct directxmath.h XMINT4 +struct directxmath.h XMMATRIX +struct directxmath.h XMUINT2 +struct directxmath.h XMUINT3 +struct directxmath.h XMUINT4 +struct directxpackedvector.h XMBYTE2 +struct directxpackedvector.h XMBYTE4 +struct directxpackedvector.h XMBYTEN2 +struct directxpackedvector.h XMBYTEN4 +struct directxpackedvector.h XMCOLOR +struct directxpackedvector.h XMDEC4 +struct directxpackedvector.h XMDECN4 +struct directxpackedvector.h XMFLOAT3PK +struct directxpackedvector.h XMFLOAT3SE +struct directxpackedvector.h XMHALF2 +struct directxpackedvector.h XMHALF4 +struct directxpackedvector.h XMSHORT2 +struct directxpackedvector.h XMSHORT4 +struct directxpackedvector.h XMSHORTN2 +struct directxpackedvector.h XMSHORTN4 +struct directxpackedvector.h XMU555 +struct directxpackedvector.h XMU565 +struct directxpackedvector.h XMUBYTE2 +struct directxpackedvector.h XMUBYTE4 +struct directxpackedvector.h XMUBYTEN2 +struct directxpackedvector.h XMUBYTEN4 +struct directxpackedvector.h XMUDEC4 +struct directxpackedvector.h XMUDECN4 +struct directxpackedvector.h XMUNIBBLE4 +struct directxpackedvector.h XMUSHORT2 +struct directxpackedvector.h XMUSHORT4 +struct directxpackedvector.h XMUSHORTN2 +struct directxpackedvector.h XMUSHORTN4 +struct directxpackedvector.h XMXDEC4 +struct directxpackedvector.h XMXDECN4 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath Library classes +struct directxcollision.h BoundingBox +struct directxcollision.h BoundingFrustum +struct directxcollision.h BoundingOrientedBox +struct directxcollision.h BoundingSphere + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > DirectXMath > DirectXMath programming reference > DirectXMath enumerations +enum directxcollision.h ContainmentType +enum directxcollision.h PlaneIntersectionType + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Core Reference > Core Interfaces +interface d3d10_1.h ID3D10BlendState1 +interface d3d10_1.h ID3D10Device1 +interface d3d10.h ID3D10Asynchronous +interface d3d10.h ID3D10BlendState +interface d3d10.h ID3D10Counter +interface d3d10.h ID3D10DepthStencilState +interface d3d10.h ID3D10Device +interface d3d10.h ID3D10DeviceChild +interface d3d10.h ID3D10InputLayout +interface d3d10.h ID3D10Multithread +interface d3d10.h ID3D10Predicate +interface d3d10.h ID3D10Query +interface d3d10.h ID3D10RasterizerState +interface d3d10.h ID3D10SamplerState +interface d3d10effect.h ID3D10StateBlock +interface d3d10sdklayers.h ID3D10Debug +interface d3d10sdklayers.h ID3D10InfoQueue +interface d3d10sdklayers.h ID3D10SwitchToRef +interface d3dcommon.h ID3D10Blob + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Core Reference > Core Functions +function d3d10_1.h D3D10CreateDevice1 +function d3d10_1.h D3D10CreateDeviceAndSwapChain1 +function d3d10misc.h D3D10CreateBlob +function d3d10misc.h D3D10CreateDevice +function d3d10misc.h D3D10CreateDeviceAndSwapChain + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Core Reference > Core Structures +struct d3d10_1.h D3D10_BLEND_DESC1 +struct d3d10_1.h D3D10_RENDER_TARGET_BLEND_DESC1 +struct d3d10_1.h D3D10_SHADER_RESOURCE_VIEW_DESC1 +struct d3d10.h D3D10_BLEND_DESC +struct d3d10.h D3D10_BOX +struct d3d10.h D3D10_COUNTER_DESC +struct d3d10.h D3D10_COUNTER_INFO +struct d3d10.h D3D10_DEPTH_STENCIL_DESC +struct d3d10.h D3D10_DEPTH_STENCILOP_DESC +struct d3d10.h D3D10_INPUT_ELEMENT_DESC +struct d3d10.h D3D10_QUERY_DATA_PIPELINE_STATISTICS +struct d3d10.h D3D10_QUERY_DATA_SO_STATISTICS +struct d3d10.h D3D10_QUERY_DATA_TIMESTAMP_DISJOINT +struct d3d10.h D3D10_QUERY_DESC +struct d3d10.h D3D10_RASTERIZER_DESC +struct d3d10.h D3D10_SAMPLER_DESC +struct d3d10.h D3D10_SHADER_RESOURCE_VIEW_DESC +struct d3d10.h D3D10_SO_DECLARATION_ENTRY +struct d3d10.h D3D10_VIEWPORT +struct d3d10effect.h D3D10_STATE_BLOCK_MASK +struct d3d10sdklayers.h D3D10_INFO_QUEUE_FILTER +struct d3d10sdklayers.h D3D10_INFO_QUEUE_FILTER_DESC +struct d3d10sdklayers.h D3D10_MESSAGE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Core Reference > Core Enumerations +enum d3d10_1.h D3D10_FEATURE_LEVEL1 +enum d3d10.h D3D10_ASYNC_GETDATA_FLAG +enum d3d10.h D3D10_BLEND +enum d3d10.h D3D10_BLEND_OP +enum d3d10.h D3D10_CLEAR_FLAG +enum d3d10.h D3D10_COLOR_WRITE_ENABLE +enum d3d10.h D3D10_COMPARISON_FUNC +enum d3d10.h D3D10_COUNTER +enum d3d10.h D3D10_COUNTER_TYPE +enum d3d10.h D3D10_CREATE_DEVICE_FLAG +enum d3d10.h D3D10_CULL_MODE +enum d3d10.h D3D10_DEPTH_WRITE_MASK +enum d3d10.h D3D10_FILL_MODE +enum d3d10.h D3D10_FILTER +enum d3d10.h D3D10_FILTER_TYPE +enum d3d10.h D3D10_FORMAT_SUPPORT +enum d3d10.h D3D10_INPUT_CLASSIFICATION +enum d3d10.h D3D10_QUERY +enum d3d10.h D3D10_QUERY_MISC_FLAG +enum d3d10.h D3D10_RAISE_FLAG +enum d3d10.h D3D10_STENCIL_OP +enum d3d10.h D3D10_TEXTURE_ADDRESS_MODE +enum d3d10.h D3D10_TEXTURECUBE_FACE +enum d3d10effect.h D3D10_DEVICE_STATE_TYPES +enum d3d10misc.h D3D10_DRIVER_TYPE +enum d3d10sdklayers.h D3D10_MESSAGE_CATEGORY +enum d3d10sdklayers.h D3D10_MESSAGE_ID +enum d3d10sdklayers.h D3D10_MESSAGE_SEVERITY +enum d3dcommon.h D3D10_REGISTER_COMPONENT_TYPE =d3d_register_component_type +enum d3dcommon.h D3D10_RESOURCE_RETURN_TYPE =d3d_resource_return_type + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Resource Reference > Resource Interfaces +interface d3d10_1.h ID3D10ShaderResourceView1 +interface d3d10.h ID3D10Buffer +interface d3d10.h ID3D10DepthStencilView +interface d3d10.h ID3D10RenderTargetView +interface d3d10.h ID3D10Resource +interface d3d10.h ID3D10ShaderResourceView +interface d3d10.h ID3D10Texture1D +interface d3d10.h ID3D10Texture2D +interface d3d10.h ID3D10Texture3D +interface d3d10.h ID3D10View + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Resource Reference > Resource Functions +function d3d10.h D3D10CalcSubresource + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Resource Reference > Resource Structures +struct d3d10_1.h D3D10_TEXCUBE_ARRAY_SRV1 +struct d3d10.h D3D10_BUFFER_DESC =cd3d10_buffer_desc +struct d3d10.h D3D10_BUFFER_RTV +struct d3d10.h D3D10_BUFFER_SRV +struct d3d10.h D3D10_DEPTH_STENCIL_VIEW_DESC +struct d3d10.h D3D10_MAPPED_TEXTURE2D +struct d3d10.h D3D10_MAPPED_TEXTURE3D +struct d3d10.h D3D10_RENDER_TARGET_VIEW_DESC +struct d3d10.h D3D10_SUBRESOURCE_DATA +struct d3d10.h D3D10_TEX1D_ARRAY_DSV +struct d3d10.h D3D10_TEX1D_ARRAY_RTV +struct d3d10.h D3D10_TEX1D_ARRAY_SRV +struct d3d10.h D3D10_TEX1D_DSV +struct d3d10.h D3D10_TEX1D_RTV +struct d3d10.h D3D10_TEX1D_SRV +struct d3d10.h D3D10_TEX2D_ARRAY_DSV +struct d3d10.h D3D10_TEX2D_ARRAY_RTV +struct d3d10.h D3D10_TEX2D_ARRAY_SRV +struct d3d10.h D3D10_TEX2D_DSV +struct d3d10.h D3D10_TEX2D_RTV +struct d3d10.h D3D10_TEX2D_SRV +struct d3d10.h D3D10_TEX2DMS_ARRAY_DSV +struct d3d10.h D3D10_TEX2DMS_ARRAY_RTV +struct d3d10.h D3D10_TEX2DMS_ARRAY_SRV +struct d3d10.h D3D10_TEX2DMS_DSV +struct d3d10.h D3D10_TEX2DMS_RTV +struct d3d10.h D3D10_TEX2DMS_SRV +struct d3d10.h D3D10_TEX3D_RTV +struct d3d10.h D3D10_TEX3D_SRV +struct d3d10.h D3D10_TEXCUBE_SRV +struct d3d10.h D3D10_TEXTURE1D_DESC =cd3d10_texture1d_desc +struct d3d10.h D3D10_TEXTURE2D_DESC =cd3d10_texture2d_desc +struct d3d10.h D3D10_TEXTURE3D_DESC =cd3d10_texture3d_desc + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Resource Reference > Resource Enumerations +enum d3d10.h D3D10_BIND_FLAG +enum d3d10.h D3D10_CPU_ACCESS_FLAG +enum d3d10.h D3D10_DSV_DIMENSION +enum d3d10.h D3D10_MAP +enum d3d10.h D3D10_MAP_FLAG +enum d3d10.h D3D10_RESOURCE_DIMENSION +enum d3d10.h D3D10_RESOURCE_MISC_FLAG +enum d3d10.h D3D10_RTV_DIMENSION +enum d3d10.h D3D10_USAGE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Shader Reference > Shader Interfaces +interface d3d10_1shader.h ID3D10ShaderReflection1 +interface d3d10.h ID3D10GeometryShader +interface d3d10.h ID3D10PixelShader +interface d3d10.h ID3D10VertexShader +interface d3d10shader.h ID3D10ShaderReflection +interface d3d10shader.h ID3D10ShaderReflectionConstantBuffer +interface d3d10shader.h ID3D10ShaderReflectionType +interface d3d10shader.h ID3D10ShaderReflectionVariable + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Shader Reference > Shader Functions +function d3d10shader.h D3D10CompileShader +function d3d10shader.h D3D10DisassembleShader +function d3d10shader.h D3D10GetGeometryShaderProfile +function d3d10shader.h D3D10GetInputAndOutputSignatureBlob +function d3d10shader.h D3D10GetInputSignatureBlob +function d3d10shader.h D3D10GetOutputSignatureBlob +function d3d10shader.h D3D10GetPixelShaderProfile +function d3d10shader.h D3D10GetShaderDebugInfo +function d3d10shader.h D3D10GetVertexShaderProfile +function d3d10shader.h D3D10PreprocessShader +function d3d10shader.h D3D10ReflectShader + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Shader Reference > Shader Structures +enum d3d10_1shader.h D3D10_SHADER_DEBUG_VARTYPE +struct d3d10_1shader.h D3D10_SHADER_DEBUG_FILE_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_INPUT_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_INST_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_OUTPUTREG_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_OUTPUTVAR +struct d3d10_1shader.h D3D10_SHADER_DEBUG_SCOPE_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_SCOPEVAR_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_TOKEN_INFO +struct d3d10_1shader.h D3D10_SHADER_DEBUG_VAR_INFO +struct d3d10shader.h D3D10_SHADER_BUFFER_DESC +struct d3d10shader.h D3D10_SHADER_DESC +struct d3d10shader.h D3D10_SHADER_INPUT_BIND_DESC +struct d3d10shader.h D3D10_SHADER_TYPE_DESC +struct d3d10shader.h D3D10_SHADER_VARIABLE_DESC +struct d3d10shader.h D3D10_SIGNATURE_PARAMETER_DESC +struct d3dcommon.h D3D10_SHADER_MACRO =d3d_shader_macro + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Shader Reference > Shader Enumerations +enum d3d10_1shader.h D3D10_SHADER_DEBUG_REGTYPE +enum d3d10_1shader.h D3D10_SHADER_DEBUG_SCOPETYPE +enum d3dcommon.h D3D10_INCLUDE_TYPE =d3d_include_type +enum d3dcommon.h D3D10_NAME =d3d_name +enum d3dcommon.h D3D10_PRIMITIVE =d3d_primitive +enum d3dcommon.h D3D10_SHADER_CBUFFER_FLAGS =d3d_shader_cbuffer_flags +enum d3dcommon.h D3D10_SHADER_INPUT_FLAGS =d3d_shader_input_flags +enum d3dcommon.h D3D10_SHADER_INPUT_TYPE =d3d_shader_input_type +enum d3dcommon.h D3D10_SHADER_VARIABLE_CLASS =d3d_shader_variable_class +enum d3dcommon.h D3D10_SHADER_VARIABLE_FLAGS =d3d_shader_variable_flags +enum d3dcommon.h D3D10_SHADER_VARIABLE_TYPE =d3d_shader_variable_type + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Effect Reference > Effect Interfaces +interface d3d10effect.h ID3D10Effect +interface d3d10effect.h ID3D10EffectBlendVariable +interface d3d10effect.h ID3D10EffectConstantBuffer +interface d3d10effect.h ID3D10EffectDepthStencilVariable +interface d3d10effect.h ID3D10EffectDepthStencilViewVariable +interface d3d10effect.h ID3D10EffectMatrixVariable +interface d3d10effect.h ID3D10EffectPass +interface d3d10effect.h ID3D10EffectPool +interface d3d10effect.h ID3D10EffectRasterizerVariable +interface d3d10effect.h ID3D10EffectRenderTargetViewVariable +interface d3d10effect.h ID3D10EffectSamplerVariable +interface d3d10effect.h ID3D10EffectScalarVariable +interface d3d10effect.h ID3D10EffectShaderResourceVariable +interface d3d10effect.h ID3D10EffectShaderVariable +interface d3d10effect.h ID3D10EffectStringVariable +interface d3d10effect.h ID3D10EffectTechnique +interface d3d10effect.h ID3D10EffectType +interface d3d10effect.h ID3D10EffectVariable +interface d3d10effect.h ID3D10EffectVectorVariable + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Effect Reference > Effect Functions +function d3d10effect.h D3D10CompileEffectFromMemory +function d3d10effect.h D3D10CreateEffectFromMemory +function d3d10effect.h D3D10CreateEffectPoolFromMemory +function d3d10effect.h D3D10CreateStateBlock +function d3d10effect.h D3D10DisassembleEffect +function d3d10effect.h D3D10StateBlockMaskDifference +function d3d10effect.h D3D10StateBlockMaskDisableAll +function d3d10effect.h D3D10StateBlockMaskDisableCapture +function d3d10effect.h D3D10StateBlockMaskEnableAll +function d3d10effect.h D3D10StateBlockMaskEnableCapture +function d3d10effect.h D3D10StateBlockMaskGetSetting +function d3d10effect.h D3D10StateBlockMaskIntersect +function d3d10effect.h D3D10StateBlockMaskUnion + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 10 Graphics > Reference for Direct3D 10 > Direct3D Reference > Effect Reference > Effect Structures +struct d3d10effect.h D3D10_EFFECT_DESC +struct d3d10effect.h D3D10_EFFECT_SHADER_DESC +struct d3d10effect.h D3D10_EFFECT_TYPE_DESC +struct d3d10effect.h D3D10_EFFECT_VARIABLE_DESC +struct d3d10effect.h D3D10_PASS_DESC +struct d3d10effect.h D3D10_PASS_SHADER_DESC +struct d3d10effect.h D3D10_TECHNIQUE_DESC + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 9 Graphics > Reference for Direct3D 9 > Direct3D Reference > Direct3D Interfaces +interface d3d9.h IDirect3D9Ex +interface d3d9.h IDirect3DDevice9Ex +interface d3d9.h IDirect3DSwapChain9Ex +interface d3d9helper.h IDirect3D9 +interface d3d9helper.h IDirect3DBaseTexture9 +interface d3d9helper.h IDirect3DCubeTexture9 +interface d3d9helper.h IDirect3DDevice9 +interface d3d9helper.h IDirect3DIndexBuffer9 +interface d3d9helper.h IDirect3DPixelShader9 +interface d3d9helper.h IDirect3DQuery9 +interface d3d9helper.h IDirect3DResource9 +interface d3d9helper.h IDirect3DStateBlock9 +interface d3d9helper.h IDirect3DSurface9 +interface d3d9helper.h IDirect3DSwapChain9 +interface d3d9helper.h IDirect3DTexture9 +interface d3d9helper.h IDirect3DVertexBuffer9 +interface d3d9helper.h IDirect3DVertexDeclaration9 +interface d3d9helper.h IDirect3DVertexShader9 +interface d3d9helper.h IDirect3DVolume9 +interface d3d9helper.h IDirect3DVolumeTexture9 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 9 Graphics > Reference for Direct3D 9 > Direct3D Reference > Direct3D Functions +function d3d9.h Direct3DCreate9 +function d3d9.h Direct3DCreate9Ex + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > Direct3D 9 Graphics > Reference for Direct3D 9 > Direct3D Reference > Direct3D Structures +struct d3d9caps.h D3DCAPS9 +struct d3d9caps.h D3DPSHADERCAPS2_0 +struct d3d9caps.h D3DVSHADERCAPS2_0 +struct wingdi.h PALETTEENTRY + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > DirectDraw > DirectDraw Reference > DirectDraw Interfaces +interface ddraw.h IDirectDraw7 +interface ddraw.h IDirectDrawClipper +interface ddraw.h IDirectDrawColorControl +interface ddraw.h IDirectDrawGammaControl +interface ddraw.h IDirectDrawPalette +interface ddraw.h IDirectDrawSurface7 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > DirectDraw > DirectDraw Reference > DirectDraw Functions +function ddraw.h DirectDrawCreate +function ddraw.h DirectDrawCreateClipper +function ddraw.h DirectDrawCreateEx +function ddraw.h DirectDrawEnumerate =directdrawenumeratew +function ddraw.h DirectDrawEnumerateEx =directdrawenumerateexa + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > DirectDraw > DirectDraw Reference > DirectDraw Callback Functions +callback ddraw.h LPDDENUMCALLBACK =lpddenumcallbacka +callback ddraw.h LPDDENUMCALLBACKEX =lpddenumcallbackexa +callback ddraw.h LPDDENUMMODESCALLBACK +callback ddraw.h LPDDENUMMODESCALLBACK2 +callback ddraw.h LPDDENUMSURFACESCALLBACK +callback ddraw.h LPDDENUMSURFACESCALLBACK2 +callback ddraw.h LPDDENUMSURFACESCALLBACK7 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > Classic DirectX Graphics > DirectDraw > DirectDraw Reference > DirectDraw structures +struct ddraw.h DDBLTBATCH +struct ddraw.h DDBLTFX +struct ddraw.h DDCAPS =ddcaps_dx3 +struct ddraw.h DDCOLORCONTROL +struct ddraw.h DDCOLORKEY +struct ddraw.h DDDEVICEIDENTIFIER2 +struct ddraw.h DDGAMMARAMP +struct ddraw.h DDOVERLAYFX +struct ddraw.h DDPIXELFORMAT +struct ddraw.h DDSCAPS +struct ddraw.h DDSCAPS2 +struct ddraw.h DDSURFACEDESC +struct ddraw.h DDSURFACEDESC2 + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Classes +class xapobase.h CXAPOBase +class xapobase.h CXAPOParametersBase + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Enumerations +enum hrtfapoapi.h HrtfDirectivityType +enum hrtfapoapi.h HrtfDistanceDecayType +enum hrtfapoapi.h HrtfEnvironment +enum xapo.h XAPO_BUFFER_FLAGS +enum xaudio2.h XAUDIO2_FILTER_TYPE + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Functions +function hrtfapoapi.h CreateHrtfApo +function x3daudio.h X3DAudioCalculate +function x3daudio.h X3DAudioInitialize +function xapofx.h CreateFX +function xaudio2.h XAudio2AmplitudeRatioToDecibels +function xaudio2.h XAudio2Create +function xaudio2.h XAudio2CutoffFrequencyToOnePoleCoefficient +function xaudio2.h XAudio2CutoffFrequencyToRadians +function xaudio2.h XAudio2DecibelsToAmplitudeRatio +function xaudio2.h XAudio2FrequencyRatioToSemitones +function xaudio2.h XAudio2RadiansToCutoffFrequency +function xaudio2.h XAudio2SemitonesToFrequencyRatio +function xaudio2fx.h ReverbConvertI3DL2ToNative +function xaudio2fx.h XAudio2CreateReverb +function xaudio2fx.h XAudio2CreateVolumeMeter + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Interfaces +interface hrtfapoapi.h IXAPOHrtfParameters +interface xapo.h IXAPO +interface xapo.h IXAPOParameters +interface xaudio2.h IXAudio2 +interface xaudio2.h IXAudio2EngineCallback +interface xaudio2.h IXAudio2MasteringVoice +interface xaudio2.h IXAudio2SourceVoice +interface xaudio2.h IXAudio2SubmixVoice +interface xaudio2.h IXAudio2Voice +interface xaudio2.h IXAudio2VoiceCallback + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Macros +function xapo.h XAPOAlloc +function xapo.h XAPOFree + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XAudio2 APIs > Programming Reference > Structures +struct hrtfapoapi.h HrtfApoInit +struct hrtfapoapi.h HrtfDirectivity +struct hrtfapoapi.h HrtfDirectivityCardioid +struct hrtfapoapi.h HrtfDirectivityCone +struct hrtfapoapi.h HrtfDistanceDecay +struct hrtfapoapi.h HrtfOrientation +struct hrtfapoapi.h HrtfPosition +struct x3daudio.h X3DAUDIO_CONE +struct x3daudio.h X3DAUDIO_DISTANCE_CURVE +struct x3daudio.h X3DAUDIO_DISTANCE_CURVE_POINT +struct x3daudio.h X3DAUDIO_DSP_SETTINGS +struct x3daudio.h X3DAUDIO_EMITTER +struct x3daudio.h X3DAUDIO_LISTENER +struct xapo.h XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS =xapo_lockforprocess_parameters +struct xapo.h XAPO_PROCESS_BUFFER_PARAMETERS +struct xapo.h XAPO_REGISTRATION_PROPERTIES +struct xapofx.h FXECHO_INITDATA +struct xapofx.h FXECHO_PARAMETERS +struct xapofx.h FXEQ_PARAMETERS +struct xapofx.h FXMASTERINGLIMITER_PARAMETERS +struct xapofx.h FXREVERB_PARAMETERS +struct xaudio2.h XAUDIO2_BUFFER +struct xaudio2.h XAUDIO2_BUFFER_WMA +struct xaudio2.h XAUDIO2_DEBUG_CONFIGURATION +struct xaudio2.h XAUDIO2_EFFECT_CHAIN +struct xaudio2.h XAUDIO2_EFFECT_DESCRIPTOR +struct xaudio2.h XAUDIO2_FILTER_PARAMETERS +struct xaudio2.h XAUDIO2_PERFORMANCE_DATA +struct xaudio2.h XAUDIO2_SEND_DESCRIPTOR +struct xaudio2.h XAUDIO2_VOICE_DETAILS +struct xaudio2.h XAUDIO2_VOICE_SENDS +struct xaudio2.h XAUDIO2_VOICE_STATE +struct xaudio2fx.h XAUDIO2FX_REVERB_I3DL2_PARAMETERS +struct xaudio2fx.h XAUDIO2FX_REVERB_PARAMETERS +struct xaudio2fx.h XAUDIO2FX_VOLUMEMETER_LEVELS + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XInput Game Controller APIs > Programming Reference > Functions +function xinput.h XInputEnable +function xinput.h XInputGetAudioDeviceIds +function xinput.h XInputGetBatteryInformation +function xinput.h XInputGetCapabilities +function xinput.h XInputGetDSoundAudioDeviceGuids +function xinput.h XInputGetKeystroke +function xinput.h XInputGetState +function xinput.h XInputSetState + +# Desktop App Technologies > Graphics and Gaming > DirectX graphics and gaming > XInput Game Controller APIs > Programming Reference > Structures +struct xinput.h XINPUT_BATTERY_INFORMATION +struct xinput.h XINPUT_CAPABILITIES +struct xinput.h XINPUT_GAMEPAD +struct xinput.h XINPUT_KEYSTROKE +struct xinput.h XINPUT_STATE +struct xinput.h XINPUT_VIBRATION + +# Desktop App Technologies > Graphics and Gaming > DirectComposition > DirectComposition reference > Functions +function dcomp.h DCompositionAttachMouseDragToHwnd +function dcomp.h DCompositionAttachMouseWheelToHwnd +function dcomp.h DCompositionCreateDevice +function dcomp.h DCompositionCreateDevice2 +function dcomp.h DCompositionCreateDevice3 +function dcomp.h DCompositionCreateSurfaceHandle + +# Desktop App Technologies > Graphics and Gaming > DirectComposition > DirectComposition reference > Interfaces +interface dcomp.h IDCompositionAffineTransform2DEffect +interface dcomp.h IDCompositionArithmeticCompositeEffect +interface dcomp.h IDCompositionBlendEffect +interface dcomp.h IDCompositionBrightnessEffect +interface dcomp.h IDCompositionClip +interface dcomp.h IDCompositionColorMatrixEffect +interface dcomp.h IDCompositionCompositeEffect +interface dcomp.h IDCompositionDesktopDevice +interface dcomp.h IDCompositionDevice +interface dcomp.h IDCompositionDevice2 +interface dcomp.h IDCompositionDevice3 +interface dcomp.h IDCompositionDevice4 +interface dcomp.h IDCompositionDeviceDebug +interface dcomp.h IDCompositionEffect +interface dcomp.h IDCompositionEffectGroup +interface dcomp.h IDCompositionFilterEffect +interface dcomp.h IDCompositionGaussianBlurEffect +interface dcomp.h IDCompositionHueRotationEffect +interface dcomp.h IDCompositionLinearTransferEffect +interface dcomp.h IDCompositionMatrixTransform +interface dcomp.h IDCompositionMatrixTransform3D +interface dcomp.h IDCompositionRectangleClip +interface dcomp.h IDCompositionRotateTransform +interface dcomp.h IDCompositionRotateTransform3D +interface dcomp.h IDCompositionSaturationEffect +interface dcomp.h IDCompositionScaleTransform +interface dcomp.h IDCompositionScaleTransform3D +interface dcomp.h IDCompositionShadowEffect +interface dcomp.h IDCompositionSkewTransform +interface dcomp.h IDCompositionSurface +interface dcomp.h IDCompositionSurfaceFactory +interface dcomp.h IDCompositionTableTransferEffect +interface dcomp.h IDCompositionTarget +interface dcomp.h IDCompositionTexture +interface dcomp.h IDCompositionTransform +interface dcomp.h IDCompositionTransform3D +interface dcomp.h IDCompositionTranslateTransform +interface dcomp.h IDCompositionTranslateTransform3D +interface dcomp.h IDCompositionTurbulenceEffect +interface dcomp.h IDCompositionVirtualSurface +interface dcomp.h IDCompositionVisual +interface dcomp.h IDCompositionVisual2 +interface dcomp.h IDCompositionVisual3 +interface dcomp.h IDCompositionVisualDebug +interface dcompanimation.h IDCompositionAnimation + +# Desktop App Technologies > Graphics and Gaming > DirectComposition > DirectComposition reference > Enumerations +enum dcomptypes.h DCOMPOSITION_BACKFACE_VISIBILITY +enum dcomptypes.h DCOMPOSITION_BITMAP_INTERPOLATION_MODE +enum dcomptypes.h DCOMPOSITION_BORDER_MODE +enum dcomptypes.h DCOMPOSITION_COMPOSITE_MODE +enum dcomptypes.h DCOMPOSITION_OPACITY_MODE + +# Desktop App Technologies > Graphics and Gaming > DirectComposition > DirectComposition reference > Structures +struct dcomptypes.h DCOMPOSITION_FRAME_STATISTICS + +# Desktop App Technologies > Graphics and Gaming > GDI+ > GDI+ Reference > Classes +class gdiplusbase.h GdiplusBase +class gdiplusbrush.h Brush +class gdiplusbrush.h HatchBrush +class gdiplusbrush.h LinearGradientBrush +class gdiplusbrush.h SolidBrush +class gdiplusbrush.h TextureBrush +class gdipluscolor.h Color +class gdipluseffects.h Blur +class gdipluseffects.h BrightnessContrast +class gdipluseffects.h ColorBalance +class gdipluseffects.h ColorCurve +class gdipluseffects.h ColorLUT +class gdipluseffects.h ColorMatrixEffect +class gdipluseffects.h Effect +class gdipluseffects.h HueSaturationLightness +class gdipluseffects.h Levels +class gdipluseffects.h RedEyeCorrection +class gdipluseffects.h Sharpen +class gdipluseffects.h Tint +class gdiplusgraphics.h Graphics +class gdiplusheaders.h Bitmap +class gdiplusheaders.h CachedBitmap +class gdiplusheaders.h CustomLineCap +class gdiplusheaders.h Font +class gdiplusheaders.h FontCollection +class gdiplusheaders.h FontFamily +class gdiplusheaders.h Image +class gdiplusheaders.h InstalledFontCollection +class gdiplusheaders.h Metafile +class gdiplusheaders.h PrivateFontCollection +class gdiplusheaders.h Region +class gdiplusimageattributes.h ImageAttributes +class gdipluslinecaps.h AdjustableArrowCap +class gdiplusmatrix.h Matrix +class gdiplusmetaheader.h MetafileHeader +class gdipluspath.h GraphicsPath +class gdipluspath.h GraphicsPathIterator +class gdipluspath.h PathGradientBrush +class gdipluspen.h Pen +class gdiplusstringformat.h StringFormat +class gdiplustypes.h CharacterRange +class gdiplustypes.h PathData +class gdiplustypes.h Point +class gdiplustypes.h PointF +class gdiplustypes.h Rect +class gdiplustypes.h RectF +class gdiplustypes.h Size +class gdiplustypes.h SizeF + +# Desktop App Technologies > Graphics and Gaming > GDI+ > GDI+ Reference > Functions +function gdiplusenums.h ObjectTypeIsValid +function gdiplusimagecodec.h GetImageDecoders +function gdiplusimagecodec.h GetImageDecodersSize +function gdiplusimagecodec.h GetImageEncoders +function gdiplusimagecodec.h GetImageEncodersSize +function gdiplusinit.h GdiplusShutdown +function gdiplusinit.h GdiplusStartup +function gdipluspixelformats.h GetPixelFormatSize +function gdipluspixelformats.h IsAlphaPixelFormat +function gdipluspixelformats.h IsCanonicalPixelFormat +function gdipluspixelformats.h IsExtendedPixelFormat +function gdipluspixelformats.h IsIndexedPixelFormat + +# Desktop App Technologies > Graphics and Gaming > GDI+ > GDI+ Reference > Enumerations +enum gdipluscolor.h ColorChannelFlags +enum gdipluscolormatrix.h ColorAdjustType +enum gdipluscolormatrix.h ColorMatrixFlags +enum gdipluscolormatrix.h HistogramFormat +enum gdipluseffects.h CurveAdjustments +enum gdipluseffects.h CurveChannel +enum gdiplusenums.h BrushType +enum gdiplusenums.h CombineMode +enum gdiplusenums.h CompositingMode +enum gdiplusenums.h CompositingQuality +enum gdiplusenums.h CoordinateSpace +enum gdiplusenums.h DashCap +enum gdiplusenums.h DashStyle +enum gdiplusenums.h DriverStringOptions +enum gdiplusenums.h EmfPlusRecordType +enum gdiplusenums.h EmfToWmfBitsFlags +enum gdiplusenums.h EmfType +enum gdiplusenums.h EncoderParameterValueType +enum gdiplusenums.h EncoderValue +enum gdiplusenums.h FillMode +enum gdiplusenums.h FlushIntention +enum gdiplusenums.h FontStyle +enum gdiplusenums.h HatchStyle +enum gdiplusenums.h HotkeyPrefix +enum gdiplusenums.h ImageType +enum gdiplusenums.h InterpolationMode +enum gdiplusenums.h LinearGradientMode +enum gdiplusenums.h LineCap +enum gdiplusenums.h LineJoin +enum gdiplusenums.h MatrixOrder +enum gdiplusenums.h MetafileFrameUnit +enum gdiplusenums.h MetafileType +enum gdiplusenums.h ObjectType +enum gdiplusenums.h PathPointType +enum gdiplusenums.h PenAlignment +enum gdiplusenums.h PenType +enum gdiplusenums.h PixelOffsetMode +enum gdiplusenums.h SmoothingMode +enum gdiplusenums.h StringAlignment +enum gdiplusenums.h StringDigitSubstitute +enum gdiplusenums.h StringFormatFlags +enum gdiplusenums.h StringTrimming +enum gdiplusenums.h TextRenderingHint +enum gdiplusenums.h Unit +enum gdiplusenums.h WarpMode +enum gdiplusenums.h WrapMode +enum gdiplusimaging.h ImageCodecFlags +enum gdiplusimaging.h ImageFlags +enum gdiplusimaging.h ImageLockMode +enum gdiplusimaging.h ItemDataPosition +enum gdiplusimaging.h RotateFlipType +enum gdiplusinit.h DebugEventLevel +enum gdiplusinit.h GdiplusStartupParams +enum gdipluspixelformats.h DitherType +enum gdipluspixelformats.h PaletteFlags +enum gdipluspixelformats.h PaletteType +enum gdiplustypes.h Status + +# Desktop App Technologies > Graphics and Gaming > GDI+ > GDI+ Reference > Structures +struct gdipluscolormatrix.h ColorMap +struct gdipluscolormatrix.h ColorMatrix +struct gdipluseffects.h BlurParams +struct gdipluseffects.h BrightnessContrastParams +struct gdipluseffects.h ColorBalanceParams +struct gdipluseffects.h ColorCurveParams +struct gdipluseffects.h ColorLUTParams +struct gdipluseffects.h HueSaturationLightnessParams +struct gdipluseffects.h LevelsParams +struct gdipluseffects.h RedEyeCorrectionParams +struct gdipluseffects.h SharpenParams +struct gdipluseffects.h TintParams +struct gdiplusinit.h GdiplusStartupInput +struct gdiplusinit.h GdiplusStartupInputEx +struct gdiplusinit.h GdiplusStartupOutput +struct gdiplusmetaheader.h ENHMETAHEADER3 +struct gdiplusmetaheader.h PWMFRect16 +struct gdiplusmetaheader.h WmfPlaceableFileHeader +struct gdipluspixelformats.h ColorPalette +struct gdiplustypes.h GdiplusAbort + +# Desktop App Technologies > Graphics and Gaming > GDI+ > GDI+ Reference +callback gdiplusinit.h DebugEventProc callback =debugeventproc +callback gdiplusinit.h NotificationHookProc callback =notificationhookproc +callback gdiplusinit.h NotificationUnhookProc callback =notificationunhookproc +callback gdiplustypes.h EnumerateMetafileProc callback =enumeratemetafileproc +callback gdiplustypes.h ImageAbort callback =imageabort + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Bitmaps > Bitmap Reference > Bitmap Functions +function wingdi.h AlphaBlend +function wingdi.h BitBlt +function wingdi.h CreateBitmap +function wingdi.h CreateBitmapIndirect +function wingdi.h CreateCompatibleBitmap +function wingdi.h CreateDIBitmap +function wingdi.h CreateDIBSection +function wingdi.h CreateDiscardableBitmap +function wingdi.h ExtFloodFill +function wingdi.h FloodFill +function wingdi.h GdiAlphaBlend +function wingdi.h GdiGradientFill +function wingdi.h GdiTransparentBlt +function wingdi.h GetBitmapBits +function wingdi.h GetBitmapDimensionEx +function wingdi.h GetDIBColorTable +function wingdi.h GetDIBits +function wingdi.h GetPixel +function wingdi.h GetStretchBltMode +function wingdi.h GradientFill +function wingdi.h MaskBlt +function wingdi.h PlgBlt +function wingdi.h SetBitmapBits +function wingdi.h SetBitmapDimensionEx +function wingdi.h SetDIBColorTable +function wingdi.h SetDIBits +function wingdi.h SetDIBitsToDevice +function wingdi.h SetPixel +function wingdi.h SetPixelV +function wingdi.h SetStretchBltMode +function wingdi.h StretchBlt +function wingdi.h StretchDIBits +function wingdi.h TransparentBlt +function winuser.h LoadBitmap =loadbitmapa + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Bitmaps > Bitmap Reference > Bitmap Structures +struct windef.h SIZE +struct wingdi.h BITMAP +struct wingdi.h BITMAPCOREHEADER +struct wingdi.h BITMAPCOREINFO +struct wingdi.h BITMAPFILEHEADER +struct wingdi.h BITMAPINFO +struct wingdi.h BITMAPINFOHEADER +struct wingdi.h BITMAPV4HEADER +struct wingdi.h BITMAPV5HEADER +struct wingdi.h BLENDFUNCTION +struct wingdi.h COLORADJUSTMENT +struct wingdi.h DIBSECTION +struct wingdi.h GRADIENT_RECT +struct wingdi.h GRADIENT_TRIANGLE +struct wingdi.h RGBQUAD +struct wingdi.h RGBTRIPLE +struct wingdi.h TRIVERTEX + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Bitmaps > Bitmap Reference > Bitmap Macros +function wingdi.h MAKEROP4 + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Brushes > Brush Reference > Brush Functions +function wingdi.h CreateBrushIndirect +function wingdi.h CreateDIBPatternBrush +function wingdi.h CreateDIBPatternBrushPt +function wingdi.h CreateHatchBrush +function wingdi.h CreatePatternBrush +function wingdi.h CreateSolidBrush +function wingdi.h GetBrushOrgEx +function wingdi.h PatBlt +function wingdi.h SetBrushOrgEx +function winuser.h GetSysColorBrush + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Brushes > Brush Reference > Brush Structures +struct wingdi.h LOGBRUSH +struct wingdi.h LOGBRUSH32 + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Clipping > Clipping Reference > Clipping Functions +function wingdi.h ExcludeClipRect +function wingdi.h ExtSelectClipRgn +function wingdi.h GetClipBox +function wingdi.h GetClipRgn +function wingdi.h GetMetaRgn +function wingdi.h GetRandomRgn +function wingdi.h IntersectClipRect +function wingdi.h OffsetClipRgn +function wingdi.h PtVisible +function wingdi.h RectVisible +function wingdi.h SelectClipPath +function wingdi.h SelectClipRgn +function wingdi.h SetMetaRgn + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Colors > Color Reference > Color Functions +function wingdi.h AnimatePalette +function wingdi.h CreateHalftonePalette +function wingdi.h CreatePalette +function wingdi.h GetColorAdjustment +function wingdi.h GetNearestColor +function wingdi.h GetNearestPaletteIndex +function wingdi.h GetPaletteEntries +function wingdi.h GetSystemPaletteEntries +function wingdi.h GetSystemPaletteUse +function wingdi.h RealizePalette +function wingdi.h ResizePalette +function wingdi.h SelectPalette +function wingdi.h SetColorAdjustment +function wingdi.h SetPaletteEntries +function wingdi.h SetSystemPaletteUse +function wingdi.h UnrealizeObject +function wingdi.h UpdateColors + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Colors > Color Reference > Color Structures +struct wingdi.h LOGPALETTE + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Colors > Color Reference > Color Macros +function mmsystem.h DIBINDEX +function wingdi.h GetBValue +function wingdi.h GetGValue +function wingdi.h GetRValue +function wingdi.h PALETTEINDEX +function wingdi.h PALETTERGB +function wingdi.h RGB + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Coordinate Spaces and Transformations > Coordinate Space and Transformation Reference > Coordinate Space and Transformation Functions +function wingdi.h CombineTransform +function wingdi.h DPtoLP +function wingdi.h GetCurrentPositionEx +function wingdi.h GetGraphicsMode +function wingdi.h GetMapMode +function wingdi.h GetViewportExtEx +function wingdi.h GetViewportOrgEx +function wingdi.h GetWindowExtEx +function wingdi.h GetWindowOrgEx +function wingdi.h GetWorldTransform +function wingdi.h LPtoDP +function wingdi.h ModifyWorldTransform +function wingdi.h OffsetViewportOrgEx +function wingdi.h OffsetWindowOrgEx +function wingdi.h ScaleViewportExtEx +function wingdi.h ScaleWindowExtEx +function wingdi.h SetGraphicsMode +function wingdi.h SetMapMode +function wingdi.h SetViewportExtEx +function wingdi.h SetViewportOrgEx +function wingdi.h SetWindowExtEx +function wingdi.h SetWindowOrgEx +function wingdi.h SetWorldTransform +function winuser.h ClientToScreen +function winuser.h MapWindowPoints +function winuser.h ScreenToClient + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Coordinate Spaces and Transformations > Coordinate Space and Transformation Reference > Coordinate Space and Transformation Structures +struct wingdi.h XFORM + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Device Contexts > Device Context Reference > Device Context Functions +function wingdi.h CancelDC +function wingdi.h CreateCompatibleDC +function wingdi.h CreateDC =createdca +function wingdi.h CreateIC =createica +function wingdi.h DeleteDC +function wingdi.h DeleteObject +function wingdi.h DrawEscape +function wingdi.h EnumObjects +function wingdi.h GetCurrentObject +function wingdi.h GetDCBrushColor +function wingdi.h GetDCOrgEx +function wingdi.h GetDCPenColor +function wingdi.h GetDeviceCaps +function wingdi.h GetLayout +function wingdi.h GetObject +function wingdi.h GetObjectType +function wingdi.h GetStockObject +function wingdi.h ResetDC =resetdca +function wingdi.h RestoreDC +function wingdi.h SaveDC +function wingdi.h SelectObject +function wingdi.h SetDCBrushColor +function wingdi.h SetDCPenColor +function wingdi.h SetLayout +function winuser.h ChangeDisplaySettings =changedisplaysettingsa +function winuser.h ChangeDisplaySettingsEx =changedisplaysettingsexa +function winuser.h EnumDisplayDevices =enumdisplaydevicesa +function winuser.h EnumDisplaySettings =enumdisplaysettingsa +function winuser.h EnumDisplaySettingsEx =enumdisplaysettingsexa +function winuser.h GetDC +function winuser.h GetDCEx +function winuser.h ReleaseDC +function winuser.h WindowFromDC + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Device Contexts > Device Context Reference > Device Context Structures +struct wingdi.h DISPLAY_DEVICE =display_devicea + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Filled Shapes > Filled Shape Reference > Filled Shape Functions +function wingdi.h Chord +function wingdi.h Ellipse +function wingdi.h Pie +function wingdi.h Polygon +function wingdi.h PolyPolygon +function wingdi.h Rectangle +function wingdi.h RoundRect +function winuser.h FillRect +function winuser.h FrameRect +function winuser.h InvertRect + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Fonts and Text > Font and Text Reference > Font and Text Functions +function wingdi.h AddFontMemResourceEx +function wingdi.h AddFontResource =addfontresourcea +function wingdi.h AddFontResourceEx =addfontresourceexa +function wingdi.h CreateFont =createfonta +function wingdi.h CreateFontIndirect =createfontindirecta +function wingdi.h CreateFontIndirectEx =createfontindirectexa +function wingdi.h CreateScalableFontResource =createscalablefontresourcea +function wingdi.h EnumFontFamilies =enumfontfamiliesa +function wingdi.h EnumFontFamiliesEx =enumfontfamiliesexa +function wingdi.h EnumFonts =enumfontsa +function wingdi.h ExtTextOut =exttextouta +function wingdi.h GetAspectRatioFilterEx +function wingdi.h GetCharABCWidths =getcharabcwidthsa +function wingdi.h GetCharABCWidthsFloat =getcharabcwidthsfloata +function wingdi.h GetCharABCWidthsI +function wingdi.h GetCharacterPlacement =getcharacterplacementa +function wingdi.h GetCharWidth =getcharwidtha +function wingdi.h GetCharWidth32 =getcharwidth32a +function wingdi.h GetCharWidthFloat =getcharwidthfloata +function wingdi.h GetCharWidthI +function wingdi.h GetFontData +function wingdi.h GetFontLanguageInfo +function wingdi.h GetFontUnicodeRanges +function wingdi.h GetGlyphIndices =getglyphindicesa +function wingdi.h GetGlyphOutline =getglyphoutlinea +function wingdi.h GetKerningPairs =getkerningpairsa +function wingdi.h GetOutlineTextMetrics =getoutlinetextmetricsa +function wingdi.h GetRasterizerCaps +function wingdi.h GetTextAlign +function wingdi.h GetTextCharacterExtra +function wingdi.h GetTextColor +function wingdi.h GetTextExtentExPoint =gettextextentexpointa +function wingdi.h GetTextExtentExPointI +function wingdi.h GetTextExtentPoint =gettextextentpointa +function wingdi.h GetTextExtentPoint32 =gettextextentpoint32a +function wingdi.h GetTextExtentPointI +function wingdi.h GetTextFace =gettextfacea +function wingdi.h GetTextMetrics +function wingdi.h PolyTextOut =polytextouta +function wingdi.h RemoveFontMemResourceEx +function wingdi.h RemoveFontResource =removefontresourcea +function wingdi.h RemoveFontResourceEx =removefontresourceexa +function wingdi.h SetMapperFlags +function wingdi.h SetTextAlign +function wingdi.h SetTextCharacterExtra +function wingdi.h SetTextColor +function wingdi.h SetTextJustification +function wingdi.h TextOut =textouta +function winuser.h DrawText +function winuser.h DrawTextEx =drawtextexa +function winuser.h GetTabbedTextExtent =gettabbedtextextenta +function winuser.h TabbedTextOut =tabbedtextouta + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Fonts and Text > Font and Text Reference > Font and Text Structures +struct wingdi.h ABC +struct wingdi.h ABCFLOAT +struct wingdi.h AXESLIST =axeslista +struct wingdi.h AXISINFO =axisinfoa +struct wingdi.h DESIGNVECTOR +struct wingdi.h ENUMLOGFONT =enumlogfonta +struct wingdi.h ENUMLOGFONTEX =enumlogfontexa +struct wingdi.h ENUMLOGFONTEXDV =enumlogfontexdva +struct wingdi.h ENUMTEXTMETRIC =enumtextmetrica +struct wingdi.h EXTLOGFONT =extlogfonta +struct wingdi.h FIXED +struct wingdi.h GCP_RESULTS =gcp_resultsa +struct wingdi.h GLYPHMETRICS +struct wingdi.h GLYPHSET +struct wingdi.h KERNINGPAIR +struct wingdi.h LOGFONT =logfonta +struct wingdi.h MAT2 +struct wingdi.h NEWTEXTMETRIC =newtextmetrica +struct wingdi.h NEWTEXTMETRICEX =newtextmetricexa +struct wingdi.h OUTLINETEXTMETRIC =outlinetextmetrica +struct wingdi.h PANOSE +struct wingdi.h POINTFX +struct wingdi.h POLYTEXT =polytexta +struct wingdi.h RASTERIZER_STATUS +struct wingdi.h TEXTMETRIC =textmetrica +struct wingdi.h TTPOLYCURVE +struct wingdi.h TTPOLYGONHEADER +struct wingdi.h WCRANGE +struct winuser.h DRAWTEXTPARAMS + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Fonts and Text > Font and Text Reference > Font and Text Macros +function windowsx.h DeleteFont +function windowsx.h SelectFont + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Fonts and Text > Font Embedding Reference > Font Embedding Functions +callback fontsub.h CFP_ALLOCPROC +callback fontsub.h CFP_FREEPROC +callback fontsub.h CFP_REALLOCPROC +function fontsub.h CreateFontPackage +function fontsub.h MergeFontPackage +function t2embapi.h TTCharToUnicode +function t2embapi.h TTDeleteEmbeddedFont +function t2embapi.h TTEmbedFont +function t2embapi.h TTEmbedFontEx +function t2embapi.h TTEmbedFontFromFileA +function t2embapi.h TTEnableEmbeddingForFacename +function t2embapi.h TTGetEmbeddedFontInfo +function t2embapi.h TTGetEmbeddingType +function t2embapi.h TTGetNewFontName +function t2embapi.h TTIsEmbeddingEnabled +function t2embapi.h TTIsEmbeddingEnabledForFacename +function t2embapi.h TTLoadEmbeddedFont +function t2embapi.h TTRunValidationTests +function t2embapi.h TTRunValidationTestsEx + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Fonts and Text > Font Embedding Reference > Font Embedding Structures +struct t2embapi.h TTEMBEDINFO +struct t2embapi.h TTLOADINFO +struct t2embapi.h TTVALIDATIONTESTSPARAMS +struct t2embapi.h TTVALIDATIONTESTSPARAMSEX + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Lines and Curves > Line and Curve Reference > Line and Curve Functions +callback wingdi.h LineDDAProc +function wingdi.h AngleArc +function wingdi.h Arc +function wingdi.h ArcTo +function wingdi.h GetArcDirection +function wingdi.h LineDDA +function wingdi.h LineTo +function wingdi.h MoveToEx +function wingdi.h PolyBezier +function wingdi.h PolyBezierTo +function wingdi.h PolyDraw +function wingdi.h Polyline +function wingdi.h PolylineTo +function wingdi.h PolyPolyline +function wingdi.h SetArcDirection + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Metafiles > Metafile Reference > Metafile Functions +function wingdi.h CloseEnhMetaFile +function wingdi.h CloseMetaFile +function wingdi.h CopyEnhMetaFile =copyenhmetafilea +function wingdi.h CopyMetaFile =copymetafilea +function wingdi.h CreateEnhMetaFile =createenhmetafilea +function wingdi.h CreateMetaFile =createmetafilea +function wingdi.h DeleteEnhMetaFile +function wingdi.h DeleteMetaFile +function wingdi.h EnumEnhMetaFile +function wingdi.h EnumMetaFile +function wingdi.h GdiComment +function wingdi.h GetEnhMetaFile =getenhmetafilea +function wingdi.h GetEnhMetaFileBits +function wingdi.h GetEnhMetaFileDescription =getenhmetafiledescriptiona +function wingdi.h GetEnhMetaFileHeader +function wingdi.h GetEnhMetaFilePaletteEntries +function wingdi.h GetMetaFile =getmetafilea +function wingdi.h GetMetaFileBitsEx +function wingdi.h GetWinMetaFileBits +function wingdi.h PlayEnhMetaFile +function wingdi.h PlayEnhMetaFileRecord +function wingdi.h PlayMetaFile +function wingdi.h PlayMetaFileRecord +function wingdi.h SetEnhMetaFileBits +function wingdi.h SetMetaFileBitsEx +function wingdi.h SetWinMetaFileBits + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Metafiles > Metafile Reference > Metafile Structures +struct windef.h POINTL +struct windef.h RECTL +struct wingdi.h EMR +struct wingdi.h EMRALPHABLEND +struct wingdi.h EMRANGLEARC +struct wingdi.h EMRARC, EMRARCTO, EMRCHORD, EMRPIE =emrarc +struct wingdi.h EMRBITBLT +struct wingdi.h EMRCOLORCORRECTPALETTE +struct wingdi.h EMRCOLORMATCHTOTARGET +struct wingdi.h EMRCREATEBRUSHINDIRECT +struct wingdi.h EMRCREATECOLORSPACE +struct wingdi.h EMRCREATECOLORSPACEW +struct wingdi.h EMRCREATEDIBPATTERNBRUSHPT +struct wingdi.h EMRCREATEMONOBRUSH +struct wingdi.h EMRCREATEPALETTE +struct wingdi.h EMRCREATEPEN +struct wingdi.h EMRELLIPSE, EMRRECTANGLE =emrellipse +struct wingdi.h EMREOF +struct wingdi.h EMREXCLUDECLIPRECT, EMRINTERSECTCLIPRECT =emrexcludecliprect +struct wingdi.h EMREXTCREATEFONTINDIRECTW +struct wingdi.h EMREXTCREATEPEN +struct wingdi.h EMREXTFLOODFILL +struct wingdi.h EMREXTSELECTCLIPRGN +struct wingdi.h EMREXTTEXTOUTA, EMREXTTEXTOUTW =emrexttextouta +struct wingdi.h EMRFILLPATH, EMRSTROKEANDFILLPATH, EMRSTROKEPATH =emrfillpath +struct wingdi.h EMRFILLRGN +struct wingdi.h EMRFORMAT +struct wingdi.h EMRFRAMERGN +struct wingdi.h EMRGDICOMMENT +struct wingdi.h EMRGLSBOUNDEDRECORD +struct wingdi.h EMRGLSRECORD +struct wingdi.h EMRGRADIENTFILL +struct wingdi.h EMRINVERTRGN, EMRPAINTRGN =emrinvertrgn +struct wingdi.h EMRLINETO, EMRMOVETOEX =emrlineto +struct wingdi.h EMRMASKBLT +struct wingdi.h EMRMODIFYWORLDTRANSFORM +struct wingdi.h EMROFFSETCLIPRGN +struct wingdi.h EMRPIXELFORMAT +struct wingdi.h EMRPLGBLT +struct wingdi.h EMRPOLYDRAW +struct wingdi.h EMRPOLYDRAW16 +struct wingdi.h EMRPOLYLINE, EMRPOLYBEZIER, EMRPOLYGON, EMRPOLYBEZIERTO, EMRPOLYLINETO =emrpolyline +struct wingdi.h EMRPOLYLINE16, EMRPOLYBEZIER16, EMRPOLYGON16, EMRPOLYBEZIERTO16, EMRPOLYLINETO16 =emrpolyline16 +struct wingdi.h EMRPOLYPOLYLINE, EMRPOLYPOLYGON =emrpolypolyline +struct wingdi.h EMRPOLYPOLYLINE16, EMRPOLYPOLYGON16 =emrpolypolyline16 +struct wingdi.h EMRPOLYTEXTOUTA, EMRPOLYTEXTOUTW =emrpolytextouta +struct wingdi.h EMRRESIZEPALETTE +struct wingdi.h EMRRESTOREDC +struct wingdi.h EMRROUNDRECT +struct wingdi.h EMRSCALEVIEWPORTEXTEX, EMRSCALEWINDOWEXTEX =emrscaleviewportextex +struct wingdi.h EMRSELECTOBJECT, EMRDELETEOBJECT =emrselectobject +struct wingdi.h EMRSELECTPALETTE +struct wingdi.h EMRSETARCDIRECTION +struct wingdi.h EMRSETBKCOLOR, EMRSETTEXTCOLOR =emrsetbkcolor +struct wingdi.h EMRSETCOLORADJUSTMENT +struct wingdi.h EMRSETCOLORSPACE, EMRSELECTCOLORSPACE, EMRDELETECOLORSPACE =emrsetcolorspace +struct wingdi.h EMRSETDIBITSTODEVICE +struct wingdi.h EMRSETICMPROFILE +struct wingdi.h EMRSETMAPPERFLAGS +struct wingdi.h EMRSETMITERLIMIT +struct wingdi.h EMRSETPALETTEENTRIES +struct wingdi.h EMRSETPIXELV +struct wingdi.h EMRSETVIEWPORTEXTEX, EMRSETWINDOWEXTEX =emrsetviewportextex +struct wingdi.h EMRSETVIEWPORTORGEX, EMRSETWINDOWORGEX, EMRSETBRUSHORGEX =emrsetviewportorgex +struct wingdi.h EMRSETWORLDTRANSFORM +struct wingdi.h EMRSTRETCHBLT +struct wingdi.h EMRSTRETCHDIBITS +struct wingdi.h EMRTEXT +struct wingdi.h EMRTRANSPARENTBLT +struct wingdi.h Enhanced Metafile Records with No Parameters =emrabortpath +struct wingdi.h Enhanced Metafile Records with One Parameter =emrselectclippath +struct wingdi.h ENHMETAHEADER +struct wingdi.h ENHMETARECORD +struct wingdi.h HANDLETABLE +struct wingdi.h METAHEADER +struct wingdi.h METARECORD + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Multiple Display Monitors > Multiple Display Monitors Reference > Multiple Display Monitors Functions +callback winuser.h MonitorEnumProc +function winuser.h EnumDisplayMonitors +function winuser.h GetMonitorInfo =getmonitorinfoa +function winuser.h MonitorFromPoint +function winuser.h MonitorFromRect +function winuser.h MonitorFromWindow + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Multiple Display Monitors > Multiple Display Monitors Reference > Multiple Display Monitors Structures +struct winuser.h MONITORINFO +struct winuser.h MONITORINFOEX =monitorinfoexa + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Painting and Drawing > Painting and Drawing Reference > Painting and Drawing Functions +callback winuser.h DrawStateProc +callback winuser.h GRAYSTRINGPROC +function wingdi.h GdiFlush +function wingdi.h GdiGetBatchLimit +function wingdi.h GdiSetBatchLimit +function wingdi.h GetBkColor +function wingdi.h GetBkMode +function wingdi.h GetBoundsRect +function wingdi.h GetROP2 +function wingdi.h SetBkColor +function wingdi.h SetBkMode +function wingdi.h SetBoundsRect +function wingdi.h SetROP2 +function winuser.h BeginPaint +function winuser.h DrawAnimatedRects +function winuser.h DrawCaption +function winuser.h DrawEdge +function winuser.h DrawFocusRect +function winuser.h DrawFrameControl +function winuser.h DrawState =drawstatea +function winuser.h EndPaint +function winuser.h ExcludeUpdateRgn +function winuser.h GetUpdateRect +function winuser.h GetUpdateRgn +function winuser.h GetWindowDC +function winuser.h GetWindowRgn +function winuser.h GetWindowRgnBox +function winuser.h GrayString =graystringa +function winuser.h InvalidateRect +function winuser.h InvalidateRgn +function winuser.h LockWindowUpdate +function winuser.h PaintDesktop +function winuser.h RedrawWindow +function winuser.h SetWindowRgn +function winuser.h UpdateWindow +function winuser.h ValidateRect +function winuser.h ValidateRgn + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Painting and Drawing > Painting and Drawing Reference > Painting and Drawing Structures +struct winuser.h PAINTSTRUCT + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Paths > Path Reference > Path Functions +function wingdi.h AbortPath +function wingdi.h BeginPath +function wingdi.h CloseFigure +function wingdi.h EndPath +function wingdi.h FillPath +function wingdi.h FlattenPath +function wingdi.h GetMiterLimit +function wingdi.h GetPath +function wingdi.h PathToRegion +function wingdi.h SetMiterLimit +function wingdi.h StrokeAndFillPath +function wingdi.h StrokePath +function wingdi.h WidenPath + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Pens > Pen Reference > Pen Functions +function wingdi.h CreatePen +function wingdi.h CreatePenIndirect +function wingdi.h ExtCreatePen + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Pens > Pen Reference > Pen Structures +struct wingdi.h EXTLOGPEN +struct wingdi.h LOGPEN + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Rectangles > Rectangle Reference > Rectangle Functions +function winuser.h CopyRect +function winuser.h EqualRect +function winuser.h InflateRect +function winuser.h IntersectRect +function winuser.h IsRectEmpty +function winuser.h OffsetRect +function winuser.h PtInRect +function winuser.h SetRect +function winuser.h SetRectEmpty +function winuser.h SubtractRect +function winuser.h UnionRect + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Rectangles > Rectangle Reference > Rectangle Structures +struct windef.h POINT +struct windef.h POINTS +struct windef.h RECT + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Rectangles > Rectangle Reference > Rectangle Macros +function wingdi.h MAKEPOINTS +function winuser.h POINTSTOPOINT +function winuser.h POINTTOPOINTS + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Regions > Region Reference > Region Functions +function wingdi.h CombineRgn +function wingdi.h CreateEllipticRgn +function wingdi.h CreateEllipticRgnIndirect +function wingdi.h CreatePolygonRgn +function wingdi.h CreatePolyPolygonRgn +function wingdi.h CreateRectRgn +function wingdi.h CreateRectRgnIndirect +function wingdi.h CreateRoundRectRgn +function wingdi.h EqualRgn +function wingdi.h ExtCreateRegion +function wingdi.h FillRgn +function wingdi.h FrameRgn +function wingdi.h GetPolyFillMode +function wingdi.h GetRegionData +function wingdi.h GetRgnBox +function wingdi.h InvertRgn +function wingdi.h OffsetRgn +function wingdi.h PaintRgn +function wingdi.h PtInRegion +function wingdi.h RectInRegion +function wingdi.h SetPolyFillMode +function wingdi.h SetRectRgn + +# Desktop App Technologies > Graphics and Gaming > Windows GDI > Regions > Region Reference > Region Structures +struct wingdi.h RGNDATA +struct wingdi.h RGNDATAHEADER + +# Desktop App Technologies > Graphics and Gaming > Monitor Configuration > Monitor Configuration Reference > Monitor Configuration Functions +function highlevelmonitorconfigurationapi.h DegaussMonitor Function =degaussmonitor +function highlevelmonitorconfigurationapi.h GetMonitorBrightness Function =getmonitorbrightness +function highlevelmonitorconfigurationapi.h GetMonitorCapabilities Function =getmonitorcapabilities +function highlevelmonitorconfigurationapi.h GetMonitorColorTemperature Function =getmonitorcolortemperature +function highlevelmonitorconfigurationapi.h GetMonitorContrast Function =getmonitorcontrast +function highlevelmonitorconfigurationapi.h GetMonitorDisplayAreaPosition Function =getmonitordisplayareaposition +function highlevelmonitorconfigurationapi.h GetMonitorDisplayAreaSize Function =getmonitordisplayareasize +function highlevelmonitorconfigurationapi.h GetMonitorRedGreenOrBlueDrive Function =getmonitorredgreenorbluedrive +function highlevelmonitorconfigurationapi.h GetMonitorRedGreenOrBlueGain Function =getmonitorredgreenorbluegain +function highlevelmonitorconfigurationapi.h GetMonitorTechnologyType Function =getmonitortechnologytype +function highlevelmonitorconfigurationapi.h RestoreMonitorFactoryColorDefaults Function =restoremonitorfactorycolordefaults +function highlevelmonitorconfigurationapi.h RestoreMonitorFactoryDefaults Function =restoremonitorfactorydefaults +function highlevelmonitorconfigurationapi.h SaveCurrentMonitorSettings Function =savecurrentmonitorsettings +function highlevelmonitorconfigurationapi.h SetMonitorBrightness Function =setmonitorbrightness +function highlevelmonitorconfigurationapi.h SetMonitorColorTemperature Function =setmonitorcolortemperature +function highlevelmonitorconfigurationapi.h SetMonitorContrast Function =setmonitorcontrast +function highlevelmonitorconfigurationapi.h SetMonitorDisplayAreaPosition Function =setmonitordisplayareaposition +function highlevelmonitorconfigurationapi.h SetMonitorDisplayAreaSize Function =setmonitordisplayareasize +function highlevelmonitorconfigurationapi.h SetMonitorRedGreenOrBlueDrive Function =setmonitorredgreenorbluedrive +function highlevelmonitorconfigurationapi.h SetMonitorRedGreenOrBlueGain Function =setmonitorredgreenorbluegain +function lowlevelmonitorconfigurationapi.h CapabilitiesRequestAndCapabilitiesReply Function =capabilitiesrequestandcapabilitiesreply +function lowlevelmonitorconfigurationapi.h GetCapabilitiesStringLength Function =getcapabilitiesstringlength +function lowlevelmonitorconfigurationapi.h GetTimingReport Function =gettimingreport +function lowlevelmonitorconfigurationapi.h GetVCPFeatureAndVCPFeatureReply Function =getvcpfeatureandvcpfeaturereply +function lowlevelmonitorconfigurationapi.h SaveCurrentSettings Function =savecurrentsettings +function lowlevelmonitorconfigurationapi.h SetVCPFeature Function =setvcpfeature +function physicalmonitorenumerationapi.h DestroyPhysicalMonitor Function =destroyphysicalmonitor +function physicalmonitorenumerationapi.h DestroyPhysicalMonitors Function =destroyphysicalmonitors +function physicalmonitorenumerationapi.h GetNumberOfPhysicalMonitorsFromHMONITOR Function =getnumberofphysicalmonitorsfromhmonitor +function physicalmonitorenumerationapi.h GetNumberOfPhysicalMonitorsFromIDirect3DDevice9 Function =getnumberofphysicalmonitorsfromidirect3ddevice9 +function physicalmonitorenumerationapi.h GetPhysicalMonitorsFromHMONITOR Function =getphysicalmonitorsfromhmonitor +function physicalmonitorenumerationapi.h GetPhysicalMonitorsFromIDirect3DDevice9 Function =getphysicalmonitorsfromidirect3ddevice9 + +# Desktop App Technologies > Graphics and Gaming > Monitor Configuration > Monitor Configuration Reference > Monitor Configuration Structures +struct lowlevelmonitorconfigurationapi.h MC_TIMING_REPORT Structure =mc_timing_report +struct physicalmonitorenumerationapi.h PHYSICAL_MONITOR Structure =physical_monitor + +# Desktop App Technologies > Graphics and Gaming > Monitor Configuration > Monitor Configuration Reference > Monitor Configuration Enumeration Types +enum highlevelmonitorconfigurationapi.h MC_COLOR_TEMPERATURE Enumeration =mc_color_temperature +enum highlevelmonitorconfigurationapi.h MC_DISPLAY_TECHNOLOGY_TYPE Enumeration =mc_display_technology_type +enum highlevelmonitorconfigurationapi.h MC_DRIVE_TYPE Enumeration =mc_drive_type +enum highlevelmonitorconfigurationapi.h MC_GAIN_TYPE Enumeration =mc_gain_type +enum highlevelmonitorconfigurationapi.h MC_POSITION_TYPE Enumeration =mc_position_type +enum highlevelmonitorconfigurationapi.h MC_SIZE_TYPE Enumeration =mc_size_type +enum lowlevelmonitorconfigurationapi.h MC_VCP_CODE_TYPE Enumeration =mc_vcp_code_type + +# Desktop App Technologies > Graphics and Gaming > OpenGL > Windows Extensions to OpenGL > WGL and Windows Reference +function wingdi.h ChoosePixelFormat +function wingdi.h DescribePixelFormat +function wingdi.h GetEnhMetaFilePixelFormat +function wingdi.h GetPixelFormat +function wingdi.h SetPixelFormat +function wingdi.h SwapBuffers +function wingdi.h wglCopyContext +function wingdi.h wglCreateContext +function wingdi.h wglCreateLayerContext +function wingdi.h wglDeleteContext +function wingdi.h wglDescribeLayerPlane +function wingdi.h wglGetCurrentContext +function wingdi.h wglGetCurrentDC +function wingdi.h wglGetLayerPaletteEntries +function wingdi.h wglGetProcAddress +function wingdi.h wglMakeCurrent +function wingdi.h wglRealizeLayerPalette +function wingdi.h wglSetLayerPaletteEntries +function wingdi.h wglShareLists +function wingdi.h wglSwapLayerBuffers +function wingdi.h wglUseFontBitmaps =wglusefontbitmapsa +function wingdi.h wglUseFontOutlines =wglusefontoutlinesa +struct wingdi.h GLYPHMETRICSFLOAT +struct wingdi.h LAYERPLANEDESCRIPTOR +struct wingdi.h PIXELFORMATDESCRIPTOR +struct wingdi.h POINTFLOAT + +# Desktop App Technologies > Graphics and Gaming > Windows Imaging Component > Reference > Interfaces +interface wincodec.h IWICBitmap +interface wincodec.h IWICBitmapClipper +interface wincodec.h IWICBitmapCodecInfo +interface wincodec.h IWICBitmapCodecProgressNotification +interface wincodec.h IWICBitmapDecoder +interface wincodec.h IWICBitmapDecoderInfo +interface wincodec.h IWICBitmapEncoder +interface wincodec.h IWICBitmapEncoderInfo +interface wincodec.h IWICBitmapFlipRotator +interface wincodec.h IWICBitmapFrameDecode +interface wincodec.h IWICBitmapFrameEncode +interface wincodec.h IWICBitmapLock +interface wincodec.h IWICBitmapScaler +interface wincodec.h IWICBitmapSource +interface wincodec.h IWICBitmapSourceTransform +interface wincodec.h IWICColorContext +interface wincodec.h IWICColorTransform +interface wincodec.h IWICComponentInfo +interface wincodec.h IWICDdsDecoder +interface wincodec.h IWICDdsEncoder +interface wincodec.h IWICDdsFrameDecode +interface wincodec.h IWICDevelopRaw +interface wincodec.h IWICDevelopRawNotificationCallback +interface wincodec.h IWICEnumMetadataItem +interface wincodec.h IWICFastMetadataEncoder +interface wincodec.h IWICFormatConverter +interface wincodec.h IWICFormatConverterInfo +interface wincodec.h IWICImageEncoder +interface wincodec.h IWICImagingFactory +interface wincodec.h IWICImagingFactory2 +interface wincodec.h IWICJpegFrameDecode +interface wincodec.h IWICJpegFrameEncode +interface wincodec.h IWICMetadataQueryReader +interface wincodec.h IWICMetadataQueryWriter +interface wincodec.h IWICPalette +interface wincodec.h IWICPixelFormatInfo +interface wincodec.h IWICPixelFormatInfo2 +interface wincodec.h IWICPlanarBitmapFrameEncode +interface wincodec.h IWICPlanarBitmapSourceTransform +interface wincodec.h IWICPlanarFormatConverter +interface wincodec.h IWICProgressCallback +interface wincodec.h IWICProgressiveLevelControl +interface wincodec.h IWICStream +interface wincodecsdk.h IWICComponentFactory +interface wincodecsdk.h IWICMetadataBlockReader +interface wincodecsdk.h IWICMetadataBlockWriter +interface wincodecsdk.h IWICMetadataHandlerInfo +interface wincodecsdk.h IWICMetadataReader +interface wincodecsdk.h IWICMetadataReaderInfo +interface wincodecsdk.h IWICMetadataWriter +interface wincodecsdk.h IWICMetadataWriterInfo +interface wincodecsdk.h IWICPersistStream +interface wincodecsdk.h IWICStreamProvider + +# Desktop App Technologies > Graphics and Gaming > Windows Imaging Component > Reference > Constants, Enumerations, and Flags +enum wincodec.h WIC8BIMIptcDigestProperties +enum wincodec.h WIC8BIMIptcProperties +enum wincodec.h WIC8BIMResolutionInfoProperties +enum wincodec.h WICBitmapAlphaChannelOption +enum wincodec.h WICBitmapCreateCacheOption +enum wincodec.h WICBitmapDecoderCapabilities +enum wincodec.h WICBitmapDitherType +enum wincodec.h WICBitmapEncoderCacheOption +enum wincodec.h WICBitmapInterpolationMode +enum wincodec.h WICBitmapLockFlags +enum wincodec.h WICBitmapPaletteType +enum wincodec.h WICBitmapTransformOptions +enum wincodec.h WICColorContextType +enum wincodec.h WICComponentEnumerateOptions +enum wincodec.h WICComponentSigning +enum wincodec.h WICComponentType +enum wincodec.h WICDdsAlphaMode +enum wincodec.h WICDdsDimension +enum wincodec.h WICDecodeOptions +enum wincodec.h WICGifApplicationExtensionProperties +enum wincodec.h WICGifCommentExtensionProperties +enum wincodec.h WICGifGraphicControlExtensionProperties +enum wincodec.h WICGifImageDescriptorProperties +enum wincodec.h WICGifLogicalScreenDescriptorProperties +enum wincodec.h WICHeifCompressionOption +enum wincodec.h WICJpegChrominanceProperties +enum wincodec.h WICJpegCommentProperties +enum wincodec.h WICJpegIndexingOptions +enum wincodec.h WICJpegLuminanceProperties +enum wincodec.h WICJpegScanType +enum wincodec.h WICJpegTransferMatrix +enum wincodec.h WICJpegYCrCbSubsamplingOption +enum wincodec.h WICNamedWhitePoint +enum wincodec.h WICPixelFormatNumericRepresentation +enum wincodec.h WICPlanarOptions +enum wincodec.h WICPngBkgdProperties +enum wincodec.h WICPngChrmProperties +enum wincodec.h WICPngFilterOption +enum wincodec.h WICPngGamaProperties +enum wincodec.h WICPngHistProperties +enum wincodec.h WICPngIccpProperties +enum wincodec.h WICPngItxtProperties +enum wincodec.h WICPngSrgbProperties +enum wincodec.h WICPngTimeProperties +enum wincodec.h WICProgressNotification +enum wincodec.h WICProgressOperation +enum wincodec.h WICRawCapabilities +enum wincodec.h WICRawParameterSet +enum wincodec.h WICRawRenderMode +enum wincodec.h WICRawRotationCapabilities +enum wincodec.h WICSectionAccessLevel +enum wincodec.h WICTiffCompressionOption +enum wincodecsdk.h WICMetadataCreationOptions +enum wincodecsdk.h WICPersistOptions + +# Desktop App Technologies > Graphics and Gaming > Windows Imaging Component > Reference > Structures +struct wincodec.h WICBitmapPattern +struct wincodec.h WICBitmapPlane +struct wincodec.h WICBitmapPlaneDescription +struct wincodec.h WICDdsFormatInfo +struct wincodec.h WICDdsParameters +struct wincodec.h WICImageParameters +struct wincodec.h WICJpegFrameHeader +struct wincodec.h WICJpegScanHeader +struct wincodec.h WICRawCapabilitiesInfo +struct wincodec.h WICRawToneCurve +struct wincodec.h WICRawToneCurvePoint +struct wincodec.h WICRect +struct wincodecsdk.h WICMetadataHeader +struct wincodecsdk.h WICMetadataPattern + +# Desktop App Technologies > Graphics and Gaming > Windows Imaging Component > Reference > Functions +callback wincodec.h ProgressNotificationCallback =pfnprogressnotification +function wincodec.h WICConvertBitmapSource +function wincodec.h WICCreateBitmapFromSection +function wincodec.h WICCreateBitmapFromSectionEx +function wincodec.h WICMapGuidToShortName +function wincodec.h WICMapSchemaToName +function wincodec.h WICMapShortNameToGuid +function wincodecsdk.h WICGetMetadataContentSize +function wincodecsdk.h WICMatchMetadataContent +function wincodecsdk.h WICSerializeMetadataContent + +# Desktop App Technologies > Graphics and Gaming > Windows Mixed Reality > Windows Mixed Reality COM Interfaces +interface holographicspaceinterop.h IHolographicSpaceInterop +interface spatialinteractionmanagerinterop.h ISpatialInteractionManagerInterop + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Functions > Alphabetical List of All WCS Functions +callback icm.h PBMCALLBACKFN +callback icm.h PCMSCALLBACKA +callback icm.h PCMSCALLBACKW +function icm.h AssociateColorProfileWithDeviceA +function icm.h AssociateColorProfileWithDeviceW +function icm.h CheckBitmapBits +function icm.h CheckColors +function icm.h CloseColorProfile +function icm.h CMCheckColors +function icm.h CMCheckColorsInGamut +function icm.h CMCheckRGBs +function icm.h CMConvertColorNameToIndex +function icm.h CMConvertIndexToColorName +function icm.h CMCreateDeviceLinkProfile +function icm.h CMCreateMultiProfileTransform +function icm.h CMCreateProfile +function icm.h CMCreateProfileW +function icm.h CMCreateTransform +function icm.h CMCreateTransformExt +function icm.h CMCreateTransformExtW +function icm.h CMCreateTransformW +function icm.h CMDeleteTransform +function icm.h CMGetInfo +function icm.h CMGetNamedProfileInfo +function icm.h CMGetPS2ColorRenderingDictionary +function icm.h CMGetPS2ColorRenderingIntent +function icm.h CMGetPS2ColorSpaceArray +function icm.h CMIsProfileValid +function icm.h CMTranslateColors +function icm.h CMTranslateRGB +function icm.h CMTranslateRGBs +function icm.h CMTranslateRGBsExt +function icm.h ColorProfileAddDisplayAssociation +function icm.h ColorProfileGetDisplayDefault +function icm.h ColorProfileGetDisplayList +function icm.h ColorProfileGetDisplayUserScope +function icm.h ColorProfileRemoveDisplayAssociation +function icm.h ColorProfileSetDisplayDefaultAssociation +function icm.h ConvertColorNameToIndex +function icm.h ConvertIndexToColorName +function icm.h CreateColorTransformA +function icm.h CreateColorTransformW +function icm.h CreateDeviceLinkProfile +function icm.h CreateMultiProfileTransform +function icm.h CreateProfileFromLogColorSpaceA +function icm.h CreateProfileFromLogColorSpaceW +function icm.h DeleteColorTransform +function icm.h DisassociateColorProfileFromDeviceA +function icm.h DisassociateColorProfileFromDeviceW +function icm.h EnumColorProfilesA +function icm.h EnumColorProfilesW +function icm.h GetCMMInfo +function icm.h GetColorDirectoryA +function icm.h GetColorDirectoryW +function icm.h GetColorProfileElement +function icm.h GetColorProfileElementTag +function icm.h GetColorProfileFromHandle +function icm.h GetColorProfileHeader +function icm.h GetCountColorProfileElements +function icm.h GetNamedProfileInfo +function icm.h GetPS2ColorRenderingDictionary +function icm.h GetPS2ColorRenderingIntent +function icm.h GetPS2ColorSpaceArray +function icm.h GetStandardColorSpaceProfileA +function icm.h GetStandardColorSpaceProfileW +function icm.h InstallColorProfileA +function icm.h InstallColorProfileW +function icm.h IsColorProfileTagPresent +function icm.h IsColorProfileValid +function icm.h OpenColorProfileA +function icm.h OpenColorProfileW +function icm.h RegisterCMMA +function icm.h RegisterCMMW +function icm.h SelectCMM +function icm.h SetColorProfileElement +function icm.h SetColorProfileElementReference +function icm.h SetColorProfileElementSize +function icm.h SetColorProfileHeader +function icm.h SetStandardColorSpaceProfileA +function icm.h SetStandardColorSpaceProfileW +function icm.h SetupColorMatchingA +function icm.h SetupColorMatchingW +function icm.h TranslateBitmapBits +function icm.h TranslateColors +function icm.h UninstallColorProfileA +function icm.h UninstallColorProfileW +function icm.h UnregisterCMMA +function icm.h UnregisterCMMW +function icm.h WcsAssociateColorProfileWithDevice +function icm.h WcsCheckColors +function icm.h WcsCreateIccProfile +function icm.h WcsDisassociateColorProfileFromDevice +function icm.h WcsEnumColorProfiles +function icm.h WcsEnumColorProfilesSize +function icm.h WcsGetCalibrationManagementState +function icm.h WcsGetDefaultColorProfile +function icm.h WcsGetDefaultColorProfileSize +function icm.h WcsGetDefaultRenderingIntent +function icm.h WcsGetUsePerUserProfiles =wcsgetdefaultrenderingintent +function icm.h WcsOpenColorProfileA +function icm.h WcsOpenColorProfileW +function icm.h WcsSetCalibrationManagementState +function icm.h WcsSetDefaultColorProfile +function icm.h WcsSetDefaultRenderingIntent +function icm.h WcsSetUsePerUserProfiles +function icm.h WcsTranslateColors +function wingdi.h CheckColorsInGamut +function wingdi.h ColorCorrectPalette +function wingdi.h ColorMatchToTarget +function wingdi.h CreateColorSpace =createcolorspacea +function wingdi.h DeleteColorSpace +function wingdi.h EnumICMProfiles =enumicmprofilesa +function wingdi.h GetColorSpace +function wingdi.h GetDeviceGammaRamp +function wingdi.h GetICMProfile =geticmprofilea +function wingdi.h GetLogColorSpace =getlogcolorspacea +function wingdi.h SetColorSpace +function wingdi.h SetDeviceGammaRamp +function wingdi.h SetICMMode +function wingdi.h SetICMProfile =seticmprofilea + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Obsolete WCS Functions +function wingdi.h UpdateICMRegKey =updateicmregkeya + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Enumerations +enum icm.h BMFORMAT +enum icm.h COLORDATATYPE +enum icm.h COLORPROFILESUBTYPE +enum icm.h COLORPROFILETYPE +enum icm.h COLORTYPE +enum icm.h WCS_PROFILE_MANAGEMENT_SCOPE + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Interfaces +interface wcsplugin.h IDeviceModelPlugIn +interface wcsplugin.h IGamutMapModelPlugIn + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Structures +struct icm.h CMYKCOLOR +struct icm.h COLOR +struct icm.h COLORMATCHSETUPA =colormatchsetupw +struct icm.h COLORMATCHSETUPW +struct icm.h ENUMTYPEA +struct icm.h ENUMTYPEW +struct icm.h GENERIC3CHANNEL +struct icm.h GRAYCOLOR +struct icm.h HiFiCOLOR +struct icm.h LabCOLOR +struct icm.h NAMED_PROFILE_INFO +struct icm.h NAMEDCOLOR +struct icm.h PROFILE +struct icm.h PROFILEHEADER +struct icm.h RGBCOLOR +struct icm.h XYZCOLOR +struct icm.h YxyCOLOR +struct wcsplugin.h BlackInformation +struct wcsplugin.h GamutBoundaryDescription +struct wcsplugin.h GamutShell +struct wcsplugin.h GamutShellTriangle +struct wcsplugin.h JabColorF +struct wcsplugin.h JChColorF +struct wcsplugin.h PrimaryJabColors +struct wcsplugin.h PrimaryXYZColors +struct wcsplugin.h XYZColorF +struct wingdi.h CIEXYZ +struct wingdi.h CIEXYZTRIPLE +struct wingdi.h LOGCOLORSPACE =logcolorspacea + +# Desktop App Technologies > Graphics and Gaming > Windows Color System > Reference > Macros for CMYK Values and Colors +function wingdi.h CMYK +function wingdi.h GetCValue +function wingdi.h GetKValue +function wingdi.h GetMValue +function wingdi.h GetYValue + +# Desktop App Technologies > Networking and Internet > Delivery Optimization > Delivery Optimization reference > Delivery Optimization enumerations +enum deliveryoptimization.h DODownloadCostPolicy +enum deliveryoptimization.h DODownloadProperty +enum deliveryoptimization.h DODownloadState + +# Desktop App Technologies > Networking and Internet > Delivery Optimization > Delivery Optimization reference > Delivery Optimization interfaces +interface deliveryoptimization.h IDODownload +interface deliveryoptimization.h IDODownloadStatusCallback +interface deliveryoptimization.h IDOManager + +# Desktop App Technologies > Networking and Internet > Delivery Optimization > Delivery Optimization reference > Delivery Optimization structures and unions +struct deliveryoptimization.h DO_DOWNLOAD_ENUM_CATEGORY +struct deliveryoptimization.h DO_DOWNLOAD_RANGE +struct deliveryoptimization.h DO_DOWNLOAD_RANGES_INFO +struct deliveryoptimization.h DO_DOWNLOAD_STATUS + +# Desktop App Technologies > Networking and Internet > Domain Name System (DNS) > DNS reference > DNS callbacks +callback windns.h DNS_QUERY_COMPLETION_ROUTINE +callback windns.h DNS_QUERY_RAW_COMPLETION_ROUTINE +callback windns.h DNS_SERVICE_BROWSE_CALLBACK +callback windns.h DNS_SERVICE_REGISTER_COMPLETE +callback windns.h DNS_SERVICE_RESOLVE_COMPLETE +callback windns.h MDNS_QUERY_CALLBACK + +# Desktop App Technologies > Networking and Internet > Domain Name System (DNS) > DNS reference > DNS enumerations +enum windns.h DNS_CONFIG_TYPE +enum windns.h DNS_FREE_TYPE +enum windns.h DNS_NAME_FORMAT +enum windns.h DNS_PROXY_INFORMATION_TYPE +enum windnsdef.h DNS_CHARSET +enum windnsdef.h DNS_SECTION + +# Desktop App Technologies > Networking and Internet > Domain Name System (DNS) > DNS reference > DNS functions +function windns.h DnsAcquireContextHandle =dnsacquirecontexthandle_a +function windns.h DnsCancelQuery +function windns.h DnsCancelQueryRaw +function windns.h DnsExtractRecordsFromMessage =dnsextractrecordsfrommessage_utf8 +function windns.h DnsFree +function windns.h DnsFreeProxyName +function windns.h DnsGetProxyInformation +function windns.h DnsIsFlatRecord +function windns.h DnsModifyRecordsInSet =dnsmodifyrecordsinset_a +function windns.h DnsNameCompare +function windns.h DnsQuery =dnsquery_a +function windns.h DnsQueryConfig +function windns.h DnsQueryEx +function windns.h DnsQueryRaw +function windns.h DnsQueryRawResultFree +function windns.h DnsRecordCompare +function windns.h DnsRecordCopyEx +function windns.h DnsRecordListFree +function windns.h DnsRecordSetCompare +function windns.h DnsRecordSetCopyEx +function windns.h DnsRecordSetDetach +function windns.h DnsReleaseContextHandle +function windns.h DnsReplaceRecordSet =dnsreplacerecordseta +function windns.h DnsServiceBrowse +function windns.h DnsServiceBrowseCancel +function windns.h DnsServiceConstructInstance +function windns.h DnsServiceCopyInstance +function windns.h DnsServiceDeRegister +function windns.h DnsServiceFreeInstance +function windns.h DnsServiceRegister +function windns.h DnsServiceRegisterCancel +function windns.h DnsServiceResolve +function windns.h DnsServiceResolveCancel +function windns.h DnsStartMulticastQuery +function windns.h DnsStopMulticastQuery +function windns.h DnsValidateName +function windns.h DnsValidateServerStatus +function windns.h DnsWriteQuestionToBuffer =dnswritequestiontobuffer_utf8 + +# Desktop App Technologies > Networking and Internet > Domain Name System (DNS) > DNS reference > DNS structures +struct windns.h DNS_DHCID_DATA +struct windns.h DNS_PROXY_INFORMATION +struct windns.h DNS_QUERY_CANCEL +struct windns.h DNS_QUERY_RAW_CANCEL +struct windns.h DNS_QUERY_RAW_REQUEST +struct windns.h DNS_QUERY_RAW_RESULT +struct windns.h DNS_QUERY_REQUEST +struct windns.h DNS_QUERY_REQUEST3 +struct windns.h DNS_QUERY_RESULT +struct windns.h DNS_RRSET +struct windns.h DNS_SERVICE_BROWSE_REQUEST +struct windns.h DNS_SERVICE_CANCEL +struct windns.h DNS_SERVICE_INSTANCE +struct windns.h DNS_SERVICE_REGISTER_REQUEST +struct windns.h DNS_SERVICE_RESOLVE_REQUEST +struct windns.h DNS_WIRE_QUESTION +struct windns.h DNS_WIRE_RECORD +struct windns.h IP4_ARRAY +struct windns.h MDNS_QUERY_HANDLE +struct windns.h MDNS_QUERY_REQUEST +struct windnsdef.h DNS_A_DATA +struct windnsdef.h DNS_AAAA_DATA +struct windnsdef.h DNS_ADDR +struct windnsdef.h DNS_ADDR_ARRAY +struct windnsdef.h DNS_ATMA_DATA +struct windnsdef.h DNS_CUSTOM_SERVER +struct windnsdef.h DNS_DS_DATA +struct windnsdef.h DNS_HEADER +struct windnsdef.h DNS_KEY_DATA +struct windnsdef.h DNS_LOC_DATA +struct windnsdef.h DNS_MESSAGE_BUFFER +struct windnsdef.h DNS_MINFO_DATA =dns_minfo_dataa +struct windnsdef.h DNS_MX_DATA =dns_mx_dataa +struct windnsdef.h DNS_NAPTR_DATA =dns_naptr_dataa +struct windnsdef.h DNS_NSEC_DATA =dns_nsec_dataa +struct windnsdef.h DNS_NULL_DATA +struct windnsdef.h DNS_NXT_DATA =dns_nxt_dataa +struct windnsdef.h DNS_OPT_DATA +struct windnsdef.h DNS_PTR_DATA =dns_ptr_dataa +struct windnsdef.h DNS_RECORD =dns_recorda +struct windnsdef.h DNS_RECORD_FLAGS +struct windnsdef.h DNS_SIG_DATA =dns_sig_dataa +struct windnsdef.h DNS_SOA_DATA =dns_soa_dataa +struct windnsdef.h DNS_SRV_DATA =dns_srv_dataa +struct windnsdef.h DNS_SVCB_DATA +struct windnsdef.h DNS_TKEY_DATA =dns_tkey_dataa +struct windnsdef.h DNS_TSIG_DATA =dns_tsig_dataa +struct windnsdef.h DNS_TXT_DATA =dns_txt_dataa +struct windnsdef.h DNS_WINS_DATA +struct windnsdef.h DNS_WINSR_DATA =dns_winsr_dataa +struct windnsdef.h DNS_WKS_DATA +struct windnsdef.h IP6_ADDRESS + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 1.0 Reference > HTTP Server API Version 1.0 Functions +function http.h HttpAddFragmentToCache +function http.h HttpAddUrl +function http.h HttpCreateHttpHandle +function http.h HttpDeleteServiceConfiguration +function http.h HttpFlushResponseCache +function http.h HttpInitialize +function http.h HttpPrepareUrl +function http.h HttpQueryServiceConfiguration +function http.h HttpReadFragmentFromCache +function http.h HttpReceiveClientCertificate +function http.h HttpReceiveHttpRequest +function http.h HttpReceiveRequestEntityBody +function http.h HttpRemoveUrl +function http.h HttpSendHttpResponse +function http.h HttpSendResponseEntityBody +function http.h HttpSetServiceConfiguration +function http.h HttpTerminate +function http.h HttpUpdateServiceConfiguration +function http.h HttpWaitForDisconnect + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 1.0 Reference > HTTP Server API Version 1.0 Structures +struct http.h HTTP_BYTE_RANGE +struct http.h HTTP_CACHE_POLICY +struct http.h HTTP_COOKED_URL +struct http.h HTTP_DATA_CHUNK +struct http.h HTTP_KNOWN_HEADER +struct http.h HTTP_REQUEST_HEADERS +struct http.h HTTP_RESPONSE_HEADERS +struct http.h HTTP_SERVICE_CONFIG_IP_LISTEN_PARAM +struct http.h HTTP_SERVICE_CONFIG_IP_LISTEN_QUERY +struct http.h HTTP_SERVICE_CONFIG_SSL_CCS_KEY +struct http.h HTTP_SERVICE_CONFIG_SSL_CCS_QUERY +struct http.h HTTP_SERVICE_CONFIG_SSL_CCS_SET +struct http.h HTTP_SERVICE_CONFIG_SSL_KEY +struct http.h HTTP_SERVICE_CONFIG_SSL_PARAM +struct http.h HTTP_SERVICE_CONFIG_SSL_QUERY +struct http.h HTTP_SERVICE_CONFIG_SSL_SET +struct http.h HTTP_SERVICE_CONFIG_SSL_SNI_KEY +struct http.h HTTP_SERVICE_CONFIG_SSL_SNI_QUERY +struct http.h HTTP_SERVICE_CONFIG_SSL_SNI_SET +struct http.h HTTP_SERVICE_CONFIG_URLACL_KEY +struct http.h HTTP_SERVICE_CONFIG_URLACL_PARAM +struct http.h HTTP_SERVICE_CONFIG_URLACL_QUERY +struct http.h HTTP_SERVICE_CONFIG_URLACL_SET +struct http.h HTTP_SSL_CLIENT_CERT_INFO +struct http.h HTTP_SSL_INFO +struct http.h HTTP_TRANSPORT_ADDRESS +struct http.h HTTP_UNKNOWN_HEADER +struct http.h HTTP_VERSION +struct http.h HTTPAPI_VERSION + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 1.0 Reference > HTTP Server API Version 1.0 Macros +function http.h HTTP_EQUAL_VERSION +function http.h HTTP_GREATER_EQUAL_VERSION +function http.h HTTP_GREATER_VERSION +function http.h HTTP_LESS_EQUAL_VERSION +function http.h HTTP_LESS_VERSION +function http.h HTTP_NOT_EQUAL_VERSION +function http.h HTTP_SET_VERSION +function http.h HTTPAPI_EQUAL_VERSION +function http.h HTTPAPI_GREATER_VERSION +function http.h HTTPAPI_LESS_VERSION +function http.h HTTPAPI_VERSION_GREATER_OR_EQUAL + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 1.0 Reference > HTTP Server API Version 1.0 Enumeration Types +enum http.h HTTP_CACHE_POLICY_TYPE +enum http.h HTTP_DATA_CHUNK_TYPE +enum http.h HTTP_HEADER_ID +enum http.h HTTP_SERVICE_CONFIG_ID +enum http.h HTTP_SERVICE_CONFIG_QUERY_TYPE +enum http.h HTTP_VERB + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 2.0 Reference > HTTP Server API Version 2.0 Functions +function http.h HttpAddUrlToUrlGroup +function http.h HttpCancelHttpRequest +function http.h HttpCloseRequestQueue +function http.h HttpCloseServerSession +function http.h HttpCloseUrlGroup +function http.h HttpCreateRequestQueue +function http.h HttpCreateServerSession +function http.h HttpCreateUrlGroup +function http.h HttpDeclarePush +function http.h HttpDelegateRequestEx +function http.h HttpFindUrlGroupId +function http.h HttpIsFeatureSupported +function http.h HttpQueryRequestQueueProperty +function http.h HttpQueryServerSessionProperty +function http.h HttpQueryUrlGroupProperty +function http.h HttpRemoveUrlFromUrlGroup +function http.h HttpSetRequestQueueProperty +function http.h HttpSetServerSessionProperty +function http.h HttpSetUrlGroupProperty +function http.h HttpShutdownRequestQueue +function http.h HttpWaitForDemandStart +function http.h HttpWaitForDisconnectEx + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 2.0 Reference > HTTP Server API Version 2.0 Structures +enum http.h HTTP_SERVICE_BINDING_TYPE +struct http.h HTTP_BANDWIDTH_LIMIT_INFO +struct http.h HTTP_BINDING_INFO +struct http.h HTTP_CHANNEL_BIND_INFO +struct http.h HTTP_CONNECTION_LIMIT_INFO +struct http.h HTTP_FLOWRATE_INFO +struct http.h HTTP_LISTEN_ENDPOINT_INFO +struct http.h HTTP_LOG_DATA +struct http.h HTTP_LOG_FIELDS_DATA +struct http.h HTTP_LOGGING_INFO +struct http.h HTTP_MULTIPLE_KNOWN_HEADERS +struct http.h HTTP_PROPERTY_FLAGS +struct http.h HTTP_QOS_SETTING_INFO +struct http.h HTTP_REQUEST_AUTH_INFO +struct http.h HTTP_REQUEST_CHANNEL_BIND_STATUS +struct http.h HTTP_REQUEST_INFO +struct http.h HTTP_REQUEST_V1 +struct http.h HTTP_REQUEST_V2 +struct http.h HTTP_RESPONSE_INFO +struct http.h HTTP_RESPONSE_V1 +struct http.h HTTP_RESPONSE_V2 +struct http.h HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS +struct http.h HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS +struct http.h HTTP_SERVER_AUTHENTICATION_INFO +struct http.h HTTP_SERVICE_BINDING_A +struct http.h HTTP_SERVICE_BINDING_BASE +struct http.h HTTP_SERVICE_BINDING_W +struct http.h HTTP_SERVICE_CONFIG_CACHE_SET +struct http.h HTTP_SERVICE_CONFIG_TIMEOUT_SET +struct http.h HTTP_STATE_INFO +struct http.h HTTP_TIMEOUT_LIMIT_INFO + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 2.0 Reference > HTTP Server API Version 2.0 Macros +function http.h HTTP_IS_NULL_ID +function http.h HTTP_SET_NULL_ID + +# Desktop App Technologies > Networking and Internet > HTTP Server API > HTTP Server API Reference > HTTP Server API Version 2.0 Reference > HTTP Server API Version 2.0 Enumeration Types +enum http.h HTTP_503_RESPONSE_VERBOSITY +enum http.h HTTP_AUTH_STATUS +enum http.h HTTP_AUTHENTICATION_HARDENING_LEVELS +enum http.h HTTP_DELEGATE_REQUEST_PROPERTY_ID +enum http.h HTTP_ENABLED_STATE +enum http.h HTTP_FEATURE_ID +enum http.h HTTP_LOG_DATA_TYPE +enum http.h HTTP_LOGGING_ROLLOVER_TYPE +enum http.h HTTP_LOGGING_TYPE +enum http.h HTTP_QOS_SETTING_TYPE +enum http.h HTTP_REQUEST_AUTH_TYPE +enum http.h HTTP_REQUEST_INFO_TYPE +enum http.h HTTP_RESPONSE_INFO_TYPE +enum http.h HTTP_SERVER_PROPERTY +enum http.h HTTP_SERVICE_CONFIG_CACHE_KEY +enum http.h HTTP_SERVICE_CONFIG_TIMEOUT_KEY + +# Desktop App Technologies > Networking and Internet > IP Helper > IP Helper reference > IP Helper callbacks +callback netioapi.h PNETWORK_CONNECTIVITY_HINT_CHANGE_CALLBACK +function iphlpapi.h INTERFACE_TIMESTAMP_CONFIG_CHANGE_CALLBACK =captureinterfacehardwarecrosstimestamp + +# Desktop App Technologies > Networking and Internet > IP Helper > IP Helper reference > IP Helper enumerations +enum ifdef.h IF_OPER_STATUS +enum iphlpapi.h NET_ADDRESS_FORMAT +enum iprtrmib.h TCP_TABLE_CLASS +enum iprtrmib.h TCPIP_OWNER_MODULE_INFO_CLASS +enum iprtrmib.h UDP_TABLE_CLASS +enum netioapi.h DNS_SERVER_PROPERTY_TYPE +enum nldef.h IP_DAD_STATE =nl_dad_state +enum nldef.h IP_PREFIX_ORIGIN =nl_prefix_origin +enum nldef.h IP_SUFFIX_ORIGIN =nl_suffix_origin +enum nldef.h NL_NETWORK_CONNECTIVITY_COST_HINT +enum nldef.h NL_NETWORK_CONNECTIVITY_LEVEL_HINT +enum tcpestats.h TCP_BOOLEAN_OPTIONAL +enum tcpestats.h TCP_ESTATS_TYPE +enum tcpestats.h TCP_SOFT_ERROR +enum ws2def.h SCOPE_LEVEL + +# Desktop App Technologies > Networking and Internet > IP Helper > IP Helper reference > IP Helper functions +function icmpapi.h Icmp6CreateFile +function icmpapi.h Icmp6ParseReplies +function icmpapi.h Icmp6SendEcho2 +function icmpapi.h IcmpCloseHandle +function icmpapi.h IcmpCreateFile +function icmpapi.h IcmpParseReplies +function icmpapi.h IcmpSendEcho +function icmpapi.h IcmpSendEcho2 +function icmpapi.h IcmpSendEcho2Ex +function ip2string.h RtlEthernetAddressToString =rtlethernetaddresstostringa +function ip2string.h RtlEthernetStringToAddress =rtlethernetstringtoaddressa +function ip2string.h RtlIpv4AddressToString =rtlipv4addresstostringa +function ip2string.h RtlIpv4AddressToStringEx =rtlipv4addresstostringexw +function ip2string.h RtlIpv4StringToAddress =rtlipv4stringtoaddressa +function ip2string.h RtlIpv4StringToAddressEx =rtlipv4stringtoaddressexw +function ip2string.h RtlIpv6AddressToString =rtlipv6addresstostringa +function ip2string.h RtlIpv6AddressToStringEx =rtlipv6addresstostringexw +function ip2string.h RtlIpv6StringToAddress =rtlipv6stringtoaddressa +function ip2string.h RtlIpv6StringToAddressEx =rtlipv6stringtoaddressexw +function iphlpapi.h AddIPAddress +function iphlpapi.h AllocateAndGetTcpExTableFromStack +function iphlpapi.h AllocateAndGetUdpExTableFromStack +function iphlpapi.h CancelIPChangeNotify +function iphlpapi.h CaptureInterfaceHardwareCrossTimestamp +function iphlpapi.h CreateIpForwardEntry +function iphlpapi.h CreateIpNetEntry +function iphlpapi.h CreatePersistentTcpPortReservation +function iphlpapi.h CreatePersistentUdpPortReservation +function iphlpapi.h CreateProxyArpEntry +function iphlpapi.h DeleteIPAddress +function iphlpapi.h DeleteIpForwardEntry +function iphlpapi.h DeleteIpNetEntry +function iphlpapi.h DeletePersistentTcpPortReservation +function iphlpapi.h DeletePersistentUdpPortReservation +function iphlpapi.h DeleteProxyArpEntry +function iphlpapi.h DisableMediaSense +function iphlpapi.h EnableRouter +function iphlpapi.h FlushIpNetTable +function iphlpapi.h GetAdapterIndex +function iphlpapi.h GetAdapterOrderMap +function iphlpapi.h GetAdaptersAddresses +function iphlpapi.h GetAdaptersInfo +function iphlpapi.h GetBestInterface +function iphlpapi.h GetBestInterfaceEx +function iphlpapi.h GetBestRoute +function iphlpapi.h GetExtendedTcpTable +function iphlpapi.h GetExtendedUdpTable +function iphlpapi.h GetFriendlyIfIndex +function iphlpapi.h GetIcmpStatistics +function iphlpapi.h GetIcmpStatisticsEx +function iphlpapi.h GetIfEntry +function iphlpapi.h GetIfTable +function iphlpapi.h GetInterfaceActiveTimestampCapabilities +function iphlpapi.h GetInterfaceInfo +function iphlpapi.h GetInterfaceSupportedTimestampCapabilities +function iphlpapi.h GetIpAddrTable +function iphlpapi.h GetIpErrorString +function iphlpapi.h GetIpForwardTable +function iphlpapi.h GetIpNetTable +function iphlpapi.h GetIpStatistics +function iphlpapi.h GetIpStatisticsEx +function iphlpapi.h GetNetworkParams +function iphlpapi.h GetNumberOfInterfaces +function iphlpapi.h GetOwnerModuleFromTcp6Entry +function iphlpapi.h GetOwnerModuleFromTcpEntry +function iphlpapi.h GetOwnerModuleFromUdp6Entry +function iphlpapi.h GetOwnerModuleFromUdpEntry +function iphlpapi.h GetPerAdapterInfo +function iphlpapi.h GetPerTcp6ConnectionEStats +function iphlpapi.h GetPerTcpConnectionEStats +function iphlpapi.h GetRTTAndHopCount +function iphlpapi.h GetTcp6Table +function iphlpapi.h GetTcp6Table2 +function iphlpapi.h GetTcpStatistics +function iphlpapi.h GetTcpStatisticsEx +function iphlpapi.h GetTcpStatisticsEx2 +function iphlpapi.h GetTcpTable +function iphlpapi.h GetTcpTable2 +function iphlpapi.h GetUdp6Table +function iphlpapi.h GetUdpStatistics +function iphlpapi.h GetUdpStatisticsEx +function iphlpapi.h GetUdpStatisticsEx2 +function iphlpapi.h GetUdpTable +function iphlpapi.h GetUniDirectionalAdapterInfo +function iphlpapi.h IpReleaseAddress +function iphlpapi.h IpRenewAddress +function iphlpapi.h LookupPersistentTcpPortReservation +function iphlpapi.h LookupPersistentUdpPortReservation +function iphlpapi.h NhpAllocateAndGetInterfaceInfoFromStack +function iphlpapi.h NotifyAddrChange +function iphlpapi.h NotifyRouteChange +function iphlpapi.h ParseNetworkString +function iphlpapi.h RegisterInterfaceTimestampConfigChange +function iphlpapi.h ResolveNeighbor +function iphlpapi.h RestoreMediaSense +function iphlpapi.h SendARP +function iphlpapi.h SetIfEntry +function iphlpapi.h SetIpForwardEntry +function iphlpapi.h SetIpNetEntry +function iphlpapi.h SetIpStatistics +function iphlpapi.h SetIpStatisticsEx +function iphlpapi.h SetIpTTL +function iphlpapi.h SetPerTcp6ConnectionEStats +function iphlpapi.h SetPerTcpConnectionEStats +function iphlpapi.h SetTcpEntry +function iphlpapi.h UnenableRouter +function iphlpapi.h UnregisterInterfaceTimestampConfigChange +function netioapi.h CancelMibChangeNotify2 +function netioapi.h ConvertInterfaceAliasToLuid +function netioapi.h ConvertInterfaceGuidToLuid +function netioapi.h ConvertInterfaceIndexToLuid +function netioapi.h ConvertInterfaceLuidToAlias +function netioapi.h ConvertInterfaceLuidToGuid +function netioapi.h ConvertInterfaceLuidToIndex +function netioapi.h ConvertInterfaceLuidToNameA +function netioapi.h ConvertInterfaceLuidToNameW +function netioapi.h ConvertInterfaceNameToLuidA +function netioapi.h ConvertInterfaceNameToLuidW +function netioapi.h ConvertIpv4MaskToLength +function netioapi.h ConvertLengthToIpv4Mask +function netioapi.h CreateAnycastIpAddressEntry +function netioapi.h CreateIpForwardEntry2 +function netioapi.h CreateIpNetEntry2 +function netioapi.h CreateSortedAddressPairs +function netioapi.h CreateUnicastIpAddressEntry +function netioapi.h DeleteAnycastIpAddressEntry +function netioapi.h DeleteIpForwardEntry2 +function netioapi.h DeleteIpNetEntry2 +function netioapi.h DeleteUnicastIpAddressEntry +function netioapi.h FlushIpNetTable2 +function netioapi.h FlushIpPathTable +function netioapi.h FreeInterfaceDnsSettings +function netioapi.h FreeMibTable +function netioapi.h GetAnycastIpAddressEntry +function netioapi.h GetAnycastIpAddressTable +function netioapi.h GetBestRoute2 +function netioapi.h GetDefaultCompartmentId +function netioapi.h GetIfEntry2 +function netioapi.h GetIfEntry2Ex +function netioapi.h GetIfStackTable +function netioapi.h GetIfTable2 +function netioapi.h GetIfTable2Ex +function netioapi.h GetInterfaceDnsSettings +function netioapi.h GetInvertedIfStackTable +function netioapi.h GetIpForwardEntry2 +function netioapi.h GetIpForwardTable2 +function netioapi.h GetIpInterfaceEntry +function netioapi.h GetIpInterfaceTable +function netioapi.h GetIpNetEntry2 +function netioapi.h GetIpNetTable2 +function netioapi.h GetIpNetworkConnectionBandwidthEstimates +function netioapi.h GetIpPathEntry +function netioapi.h GetIpPathTable +function netioapi.h GetMulticastIpAddressEntry +function netioapi.h GetMulticastIpAddressTable +function netioapi.h GetNetworkConnectivityHint +function netioapi.h GetNetworkConnectivityHintForInterface +function netioapi.h GetTeredoPort +function netioapi.h GetUnicastIpAddressEntry +function netioapi.h GetUnicastIpAddressTable +function netioapi.h if_indextoname +function netioapi.h if_nametoindex +function netioapi.h InitializeIpForwardEntry +function netioapi.h InitializeIpInterfaceEntry +function netioapi.h InitializeUnicastIpAddressEntry +function netioapi.h NotifyIpInterfaceChange +function netioapi.h NotifyNetworkConnectivityHintChange +function netioapi.h NotifyRouteChange2 +function netioapi.h NotifyStableUnicastIpAddressTable +function netioapi.h NotifyTeredoPortChange +function netioapi.h NotifyUnicastIpAddressChange +function netioapi.h ResolveIpNetEntry2 +function netioapi.h SetInterfaceDnsSettings +function netioapi.h SetIpForwardEntry2 +function netioapi.h SetIpInterfaceEntry +function netioapi.h SetIpNetEntry2 +function netioapi.h SetUnicastIpAddressEntry + +# Desktop App Technologies > Networking and Internet > IP Helper > IP Helper reference > IP Helper structures +struct ifdef.h NET_LUID =net_luid_lh +struct inaddr.h in_addr +struct ipexport.h ARP_SEND_REPLY +struct ipexport.h ICMP_ECHO_REPLY +struct ipexport.h ICMP_ECHO_REPLY32 +struct ipexport.h ICMPV6_ECHO_REPLY =icmpv6_echo_reply_lh +struct ipexport.h IP_ADAPTER_INDEX_MAP +struct ipexport.h IP_ADAPTER_ORDER_MAP +struct ipexport.h IP_INTERFACE_INFO +struct ipexport.h IP_MCAST_COUNTER_INFO +struct ipexport.h IP_OPTION_INFORMATION +struct ipexport.h IP_OPTION_INFORMATION32 +struct ipexport.h IP_UNIDIRECTIONAL_ADAPTER_ADDRESS +struct ipexport.h IPV6_ADDRESS_EX +struct ipexport.h TCP_RESERVE_PORT_RANGE +struct iphlpapi.h INTERFACE_HARDWARE_CROSSTIMESTAMP +struct iphlpapi.h INTERFACE_HARDWARE_TIMESTAMP_CAPABILITIES +struct iphlpapi.h INTERFACE_SOFTWARE_TIMESTAMP_CAPABILITIES +struct iphlpapi.h INTERFACE_TIMESTAMP_CAPABILITIES +struct iphlpapi.h NET_ADDRESS_INFO +struct iprtrmib.h TCPIP_OWNER_MODULE_BASIC_INFO +struct iptypes.h FIXED_INFO =fixed_info_w2ksp1 +struct iptypes.h IP_ADAPTER_ADDRESSES =ip_adapter_addresses_lh +struct iptypes.h IP_ADAPTER_ANYCAST_ADDRESS =ip_adapter_anycast_address_xp +struct iptypes.h IP_ADAPTER_DNS_SERVER_ADDRESS =ip_adapter_dns_server_address_xp +struct iptypes.h IP_ADAPTER_DNS_SUFFIX +struct iptypes.h IP_ADAPTER_GATEWAY_ADDRESS =ip_adapter_gateway_address_lh +struct iptypes.h IP_ADAPTER_INFO +struct iptypes.h IP_ADAPTER_MULTICAST_ADDRESS =ip_adapter_multicast_address_xp +struct iptypes.h IP_ADAPTER_PREFIX =ip_adapter_prefix_xp +struct iptypes.h IP_ADAPTER_UNICAST_ADDRESS =ip_adapter_unicast_address_lh +struct iptypes.h IP_ADAPTER_WINS_SERVER_ADDRESS =ip_adapter_wins_server_address_lh +struct iptypes.h IP_ADDR_STRING +struct iptypes.h IP_ADDRESS_STRING +struct iptypes.h IP_INTERFACE_NAME_INFO =ip_interface_name_info_w2ksp1 +struct iptypes.h IP_PER_ADAPTER_INFO =ip_per_adapter_info_w2ksp1 +struct netioapi.h DNS_DOH_SERVER_SETTINGS +struct netioapi.h DNS_INTERFACE_SETTINGS +struct netioapi.h DNS_INTERFACE_SETTINGS3 +struct netioapi.h DNS_SERVER_PROPERTY +struct netioapi.h DNS_SERVER_PROPERTY_TYPES +struct netioapi.h IP_ADDRESS_PREFIX +struct nldef.h NL_BANDWIDTH_INFORMATION +struct tcpestats.h TCP_ESTATS_BANDWIDTH_ROD_v0 +struct tcpestats.h TCP_ESTATS_BANDWIDTH_RW_v0 +struct tcpestats.h TCP_ESTATS_DATA_ROD_v0 +struct tcpestats.h TCP_ESTATS_DATA_RW_v0 +struct tcpestats.h TCP_ESTATS_FINE_RTT_ROD_v0 +struct tcpestats.h TCP_ESTATS_FINE_RTT_RW_v0 +struct tcpestats.h TCP_ESTATS_OBS_REC_ROD_v0 +struct tcpestats.h TCP_ESTATS_OBS_REC_RW_v0 +struct tcpestats.h TCP_ESTATS_PATH_ROD_v0 +struct tcpestats.h TCP_ESTATS_PATH_RW_v0 +struct tcpestats.h TCP_ESTATS_REC_ROD_v0 +struct tcpestats.h TCP_ESTATS_REC_RW_v0 +struct tcpestats.h TCP_ESTATS_SEND_BUFF_ROD_v0 +struct tcpestats.h TCP_ESTATS_SEND_BUFF_RW_v0 +struct tcpestats.h TCP_ESTATS_SND_CONG_ROD_v0 +struct tcpestats.h TCP_ESTATS_SND_CONG_ROS_v0 +struct tcpestats.h TCP_ESTATS_SND_CONG_RW_v0 +struct tcpestats.h TCP_ESTATS_SYN_OPTS_ROS_v0 +struct ws2ipdef.h ICMP_ERROR_INFO +struct ws2ipdef.h SOCKADDR_IN6_PAIR +struct ws2ipdef.h SOCKADDR_INET + +# Desktop App Technologies > Networking and Internet > IP Helper > IP Helper reference > IP Helper Enumerations +enum ifdef.h IF_OPER_STATUS +enum iphlpapi.h NET_ADDRESS_FORMAT +enum iprtrmib.h TCP_TABLE_CLASS +enum iprtrmib.h TCPIP_OWNER_MODULE_INFO_CLASS +enum iprtrmib.h UDP_TABLE_CLASS +enum nldef.h IP_DAD_STATE =nl_dad_state +enum nldef.h IP_PREFIX_ORIGIN =nl_prefix_origin +enum nldef.h IP_SUFFIX_ORIGIN =nl_suffix_origin +enum tcpestats.h TCP_BOOLEAN_OPTIONAL +enum tcpestats.h TCP_ESTATS_TYPE +enum tcpestats.h TCP_SOFT_ERROR +enum ws2def.h SCOPE_LEVEL + +# Desktop App Technologies > Networking and Internet > Network List Manager > Network List Manager API Reference > NLM interfaces +interface netlistmgr.h IEnumNetworkConnections +interface netlistmgr.h IEnumNetworks +interface netlistmgr.h INetwork +interface netlistmgr.h INetwork2 +interface netlistmgr.h INetworkConnection +interface netlistmgr.h INetworkConnection2 +interface netlistmgr.h INetworkConnectionCost +interface netlistmgr.h INetworkConnectionCostEvents +interface netlistmgr.h INetworkConnectionEvents +interface netlistmgr.h INetworkCostManager +interface netlistmgr.h INetworkCostManagerEvents +interface netlistmgr.h INetworkEvents +interface netlistmgr.h INetworkListManager +interface netlistmgr.h INetworkListManagerEvents + +# Desktop App Technologies > Networking and Internet > Network List Manager > Network List Manager API Reference > NLM structures +struct netlistmgr.h NLM_DATAPLAN_STATUS +struct netlistmgr.h NLM_SIMULATED_PROFILE_INFO +struct netlistmgr.h NLM_SOCKADDR +struct netlistmgr.h NLM_USAGE_DATA + +# Desktop App Technologies > Networking and Internet > Network List Manager > Network List Manager API Reference > NLM enumerations +enum netlistmgr.h NLM_CONNECTION_COST +enum netlistmgr.h NLM_CONNECTION_PROPERTY_CHANGE +enum netlistmgr.h NLM_CONNECTIVITY +enum netlistmgr.h NLM_DOMAIN_AUTHENTICATION_KIND +enum netlistmgr.h NLM_DOMAIN_TYPE +enum netlistmgr.h NLM_ENUM_NETWORK +enum netlistmgr.h NLM_INTERNET_CONNECTIVITY +enum netlistmgr.h NLM_NETWORK_CATEGORY +enum netlistmgr.h NLM_NETWORK_CLASS +enum netlistmgr.h NLM_NETWORK_PROPERTY_CHANGE + +# Desktop App Technologies > Networking and Internet > Network List Manager > On-Demand Connection Routing Helper API reference +function ondemandconnroutehelper.h FreeInterfaceContextTable +function ondemandconnroutehelper.h GetInterfaceContextTableForHostName +function ondemandconnroutehelper.h OnDemandGetRoutingHint +function ondemandconnroutehelper.h OnDemandRegisterNotification +function ondemandconnroutehelper.h OnDemandUnregisterNotification +struct ondemandconnroutehelper.h NET_INTERFACE_CONTEXT +struct ondemandconnroutehelper.h NET_INTERFACE_CONTEXT_TABLE + +# Desktop App Technologies > Networking and Internet > Network Management > Network Management Reference > Network Management Enumerations +enum lmjoin.h DSREG_JOIN_TYPE + +# Desktop App Technologies > Networking and Internet > Network Management > Network Management Reference > Network Management Functions +function atacct.h GetNetScheduleAccountInformation +function atacct.h SetNetScheduleAccountInformation +function lmaccess.h NetAccessAdd +function lmaccess.h NetAccessDel +function lmaccess.h NetAccessEnum +function lmaccess.h NetAccessGetInfo +function lmaccess.h NetAccessGetUserPerms +function lmaccess.h NetAccessSetInfo +function lmaccess.h NetGetAnyDCName +function lmaccess.h NetGetDCName +function lmaccess.h NetGetDisplayInformationIndex +function lmaccess.h NetGroupAdd +function lmaccess.h NetGroupAddUser +function lmaccess.h NetGroupDel +function lmaccess.h NetGroupDelUser +function lmaccess.h NetGroupEnum +function lmaccess.h NetGroupGetInfo +function lmaccess.h NetGroupGetUsers +function lmaccess.h NetGroupSetInfo +function lmaccess.h NetGroupSetUsers +function lmaccess.h NetLocalGroupAdd +function lmaccess.h NetLocalGroupAddMember +function lmaccess.h NetLocalGroupAddMembers +function lmaccess.h NetLocalGroupDel +function lmaccess.h NetLocalGroupDelMember +function lmaccess.h NetLocalGroupDelMembers +function lmaccess.h NetLocalGroupEnum +function lmaccess.h NetLocalGroupGetInfo +function lmaccess.h NetLocalGroupGetMembers +function lmaccess.h NetLocalGroupSetInfo +function lmaccess.h NetLocalGroupSetMembers +function lmaccess.h NetQueryDisplayInformation +function lmaccess.h NetUserAdd +function lmaccess.h NetUserChangePassword +function lmaccess.h NetUserDel +function lmaccess.h NetUserEnum +function lmaccess.h NetUserGetGroups +function lmaccess.h NetUserGetInfo +function lmaccess.h NetUserGetLocalGroups +function lmaccess.h NetUserModalsGet +function lmaccess.h NetUserModalsSet +function lmaccess.h NetUserSetGroups +function lmaccess.h NetUserSetInfo +function lmaccess.h NetValidatePasswordPolicy +function lmaccess.h NetValidatePasswordPolicyFree +function lmalert.h NetAlertRaise +function lmalert.h NetAlertRaiseEx +function lmapibuf.h NetApiBufferAllocate +function lmapibuf.h NetApiBufferFree +function lmapibuf.h NetApiBufferReallocate +function lmapibuf.h NetApiBufferSize +function lmat.h NetScheduleJobAdd +function lmat.h NetScheduleJobDel +function lmat.h NetScheduleJobEnum +function lmat.h NetScheduleJobGetInfo +function lmjoin.h NetAddAlternateComputerName +function lmjoin.h NetCreateProvisioningPackage +function lmjoin.h NetEnumerateComputerNames +function lmjoin.h NetFreeAadJoinInformation +function lmjoin.h NetGetAadJoinInformation +function lmjoin.h NetGetJoinableOUs +function lmjoin.h NetGetJoinInformation +function lmjoin.h NetJoinDomain +function lmjoin.h NetProvisionComputerAccount +function lmjoin.h NetRemoveAlternateComputerName +function lmjoin.h NetRenameMachineInDomain +function lmjoin.h NetRequestOfflineDomainJoin +function lmjoin.h NetRequestProvisioningPackageInstall +function lmjoin.h NetSetPrimaryComputerName +function lmjoin.h NetUnjoinDomain +function lmjoin.h NetValidateName +function lmmsg.h NetMessageBufferSend +function lmmsg.h NetMessageNameAdd +function lmmsg.h NetMessageNameDel +function lmmsg.h NetMessageNameEnum +function lmmsg.h NetMessageNameGetInfo +function lmremutl.h NetRemoteComputerSupports +function lmremutl.h NetRemoteTOD +function lmserver.h NetServerComputerNameAdd +function lmserver.h NetServerComputerNameDel +function lmserver.h NetServerDiskEnum +function lmserver.h NetServerEnum +function lmserver.h NetServerGetInfo +function lmserver.h NetServerSetInfo +function lmserver.h NetServerTransportAdd +function lmserver.h NetServerTransportAddEx +function lmserver.h NetServerTransportDel +function lmserver.h NetServerTransportEnum +function lmuse.h NetUseAdd +function lmuse.h NetUseDel +function lmuse.h NetUseEnum +function lmuse.h NetUseGetInfo +function lmwksta.h NetWkstaGetInfo +function lmwksta.h NetWkstaSetInfo +function lmwksta.h NetWkstaTransportAdd +function lmwksta.h NetWkstaTransportDel +function lmwksta.h NetWkstaTransportEnum +function lmwksta.h NetWkstaUserEnum +function lmwksta.h NetWkstaUserGetInfo +function lmwksta.h NetWkstaUserSetInfo + +# Desktop App Technologies > Networking and Internet > Network Management > Network Management Reference > Network Management structures +struct lmaccess.h GROUP_INFO_0 +struct lmaccess.h GROUP_INFO_1 +struct lmaccess.h GROUP_INFO_1002 +struct lmaccess.h GROUP_INFO_1005 +struct lmaccess.h GROUP_INFO_2 +struct lmaccess.h GROUP_INFO_3 +struct lmaccess.h GROUP_USERS_INFO_0 +struct lmaccess.h GROUP_USERS_INFO_1 +struct lmaccess.h LOCALGROUP_INFO_0 +struct lmaccess.h LOCALGROUP_INFO_1 +struct lmaccess.h LOCALGROUP_INFO_1002 +struct lmaccess.h LOCALGROUP_MEMBERS_INFO_0 +struct lmaccess.h LOCALGROUP_MEMBERS_INFO_1 +struct lmaccess.h LOCALGROUP_MEMBERS_INFO_2 +struct lmaccess.h LOCALGROUP_MEMBERS_INFO_3 +struct lmaccess.h LOCALGROUP_USERS_INFO_0 +struct lmaccess.h NET_DISPLAY_GROUP +struct lmaccess.h NET_DISPLAY_MACHINE +struct lmaccess.h NET_DISPLAY_USER +struct lmaccess.h NET_VALIDATE_AUTHENTICATION_INPUT_ARG +struct lmaccess.h NET_VALIDATE_OUTPUT_ARG +struct lmaccess.h NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG +struct lmaccess.h NET_VALIDATE_PASSWORD_HASH +struct lmaccess.h NET_VALIDATE_PASSWORD_RESET_INPUT_ARG +struct lmaccess.h NET_VALIDATE_PERSISTED_FIELDS +struct lmaccess.h USER_INFO_0 +struct lmaccess.h USER_INFO_1 +struct lmaccess.h USER_INFO_10 +struct lmaccess.h USER_INFO_1003 +struct lmaccess.h USER_INFO_1005 +struct lmaccess.h USER_INFO_1006 +struct lmaccess.h USER_INFO_1007 +struct lmaccess.h USER_INFO_1008 +struct lmaccess.h USER_INFO_1009 +struct lmaccess.h USER_INFO_1010 +struct lmaccess.h USER_INFO_1011 +struct lmaccess.h USER_INFO_1012 +struct lmaccess.h USER_INFO_1013 +struct lmaccess.h USER_INFO_1014 +struct lmaccess.h USER_INFO_1017 +struct lmaccess.h USER_INFO_1018 +struct lmaccess.h USER_INFO_1020 +struct lmaccess.h USER_INFO_1023 +struct lmaccess.h USER_INFO_1024 +struct lmaccess.h USER_INFO_1025 +struct lmaccess.h USER_INFO_1051 +struct lmaccess.h USER_INFO_1052 +struct lmaccess.h USER_INFO_1053 +struct lmaccess.h USER_INFO_11 +struct lmaccess.h USER_INFO_2 +struct lmaccess.h USER_INFO_20 +struct lmaccess.h USER_INFO_21 +struct lmaccess.h USER_INFO_22 +struct lmaccess.h USER_INFO_23 +struct lmaccess.h USER_INFO_24 +struct lmaccess.h USER_INFO_3 +struct lmaccess.h USER_INFO_4 +struct lmaccess.h USER_MODALS_INFO_0 +struct lmaccess.h USER_MODALS_INFO_1 +struct lmaccess.h USER_MODALS_INFO_1001 +struct lmaccess.h USER_MODALS_INFO_1002 +struct lmaccess.h USER_MODALS_INFO_1003 +struct lmaccess.h USER_MODALS_INFO_1004 +struct lmaccess.h USER_MODALS_INFO_1005 +struct lmaccess.h USER_MODALS_INFO_1006 +struct lmaccess.h USER_MODALS_INFO_1007 +struct lmaccess.h USER_MODALS_INFO_2 +struct lmaccess.h USER_MODALS_INFO_3 +struct lmalert.h ADMIN_OTHER_INFO +struct lmalert.h ERRLOG_OTHER_INFO +struct lmalert.h PRINT_OTHER_INFO +struct lmalert.h STD_ALERT +struct lmalert.h USER_OTHER_INFO +struct lmat.h AT_ENUM +struct lmat.h AT_INFO +struct lmjoin.h DSREG_JOIN_INFO +struct lmjoin.h DSREG_USER_INFO +struct lmjoin.h NETSETUP_PROVISIONING_PARAMS +struct lmmsg.h MSG_INFO_0 +struct lmmsg.h MSG_INFO_1 +struct lmremutl.h TIME_OF_DAY_INFO +struct lmserver.h SERVER_INFO_100 +struct lmserver.h SERVER_INFO_1005 +struct lmserver.h SERVER_INFO_101 +struct lmserver.h SERVER_INFO_1010 +struct lmserver.h SERVER_INFO_1016 +struct lmserver.h SERVER_INFO_1017 +struct lmserver.h SERVER_INFO_1018 +struct lmserver.h SERVER_INFO_102 +struct lmserver.h SERVER_INFO_1107 +struct lmserver.h SERVER_INFO_1501 +struct lmserver.h SERVER_INFO_1502 +struct lmserver.h SERVER_INFO_1503 +struct lmserver.h SERVER_INFO_1506 +struct lmserver.h SERVER_INFO_1509 +struct lmserver.h SERVER_INFO_1510 +struct lmserver.h SERVER_INFO_1511 +struct lmserver.h SERVER_INFO_1512 +struct lmserver.h SERVER_INFO_1513 +struct lmserver.h SERVER_INFO_1515 +struct lmserver.h SERVER_INFO_1516 +struct lmserver.h SERVER_INFO_1518 +struct lmserver.h SERVER_INFO_1523 +struct lmserver.h SERVER_INFO_1528 +struct lmserver.h SERVER_INFO_1529 +struct lmserver.h SERVER_INFO_1530 +struct lmserver.h SERVER_INFO_1533 +struct lmserver.h SERVER_INFO_1536 +struct lmserver.h SERVER_INFO_1538 +struct lmserver.h SERVER_INFO_1539 +struct lmserver.h SERVER_INFO_1540 +struct lmserver.h SERVER_INFO_1541 +struct lmserver.h SERVER_INFO_1542 +struct lmserver.h SERVER_INFO_1544 +struct lmserver.h SERVER_INFO_1550 +struct lmserver.h SERVER_INFO_1552 +struct lmserver.h SERVER_INFO_402 +struct lmserver.h SERVER_INFO_403 +struct lmserver.h SERVER_INFO_502 +struct lmserver.h SERVER_INFO_503 +struct lmserver.h SERVER_TRANSPORT_INFO_0 +struct lmserver.h SERVER_TRANSPORT_INFO_1 +struct lmserver.h SERVER_TRANSPORT_INFO_2 +struct lmserver.h SERVER_TRANSPORT_INFO_3 +struct lmuse.h USE_INFO_0 +struct lmuse.h USE_INFO_1 +struct lmuse.h USE_INFO_2 +struct lmuse.h USE_INFO_3 +struct lmwksta.h WKSTA_INFO_100 +struct lmwksta.h WKSTA_INFO_101 +struct lmwksta.h WKSTA_INFO_102 +struct lmwksta.h WKSTA_INFO_502 +struct lmwksta.h WKSTA_TRANSPORT_INFO_0 +struct lmwksta.h WKSTA_USER_INFO_0 +struct lmwksta.h WKSTA_USER_INFO_1 +struct lmwksta.h WKSTA_USER_INFO_1101 + +# Desktop App Technologies > Networking and Internet > Network Management > Network Management Reference > Network Management Macros +function lmalert.h ALERT_OTHER_INFO +function lmalert.h ALERT_VAR_DATA + +# Desktop App Technologies > Networking and Internet > Network Share Management > Network Share Management Reference > Network Share Management Functions +function lmshare.h NetConnectionEnum +function lmshare.h NetFileClose +function lmshare.h NetFileEnum +function lmshare.h NetFileGetInfo +function lmshare.h NetSessionDel +function lmshare.h NetSessionEnum +function lmshare.h NetSessionGetInfo +function lmshare.h NetShareAdd +function lmshare.h NetShareCheck +function lmshare.h NetShareDel +function lmshare.h NetShareDelEx +function lmshare.h NetShareEnum +function lmshare.h NetShareGetInfo +function lmshare.h NetShareSetInfo +function lmstats.h NetStatisticsGet + +# Desktop App Technologies > Networking and Internet > Network Share Management > Network Share Management Reference > Network Share Management Structures +struct lmshare.h CONNECTION_INFO_0 +struct lmshare.h CONNECTION_INFO_1 +struct lmshare.h FILE_INFO_2 +struct lmshare.h FILE_INFO_3 +struct lmshare.h SESSION_INFO_0 +struct lmshare.h SESSION_INFO_1 +struct lmshare.h SESSION_INFO_10 +struct lmshare.h SESSION_INFO_2 +struct lmshare.h SESSION_INFO_502 +struct lmshare.h SHARE_INFO_0 +struct lmshare.h SHARE_INFO_1 +struct lmshare.h SHARE_INFO_1004 +struct lmshare.h SHARE_INFO_1005 +struct lmshare.h SHARE_INFO_1006 +struct lmshare.h SHARE_INFO_1501 +struct lmshare.h SHARE_INFO_2 +struct lmshare.h SHARE_INFO_501 +struct lmshare.h SHARE_INFO_502 +struct lmshare.h SHARE_INFO_503 +struct lmstats.h STAT_SERVER_0 +struct lmstats.h STAT_WORKSTATION_0 =stat_workstation_0-r1 + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Peer Infrastructure Common Documentation > Peer Infrastructure Common Reference > Common Functions +function p2p.h PeerEndEnumeration +function p2p.h PeerFreeData +function p2p.h PeerGetItemCount +function p2p.h PeerGetNextItem + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Peer Infrastructure Common Documentation > Peer Infrastructure Common Reference > Common Structures +struct p2p.h PEER_ADDRESS +struct p2p.h PEER_CONNECTION_INFO +struct p2p.h PEER_DATA +struct p2p.h PEER_EVENT_CONNECTION_CHANGE_DATA +struct p2p.h PEER_EVENT_INCOMING_DATA +struct p2p.h PEER_EVENT_RECORD_CHANGE_DATA +struct p2p.h PEER_RECORD +struct p2p.h PEER_VERSION_DATA + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Peer Infrastructure Common Documentation > Peer Infrastructure Common Reference > Common Constants +enum p2p.h PEER_CONNECTION_FLAGS +enum p2p.h PEER_CONNECTION_STATUS +enum p2p.h PEER_RECORD_CHANGE_TYPE +enum p2p.h PEER_RECORD_FLAGS + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Graphing API > Graphing API Reference > Graphing API Functions +function p2p.h PeerGraphAddRecord +function p2p.h PeerGraphClose +function p2p.h PeerGraphCloseDirectConnection +function p2p.h PeerGraphConnect +function p2p.h PeerGraphCreate +function p2p.h PeerGraphDelete +function p2p.h PeerGraphDeleteRecord +function p2p.h PeerGraphEndEnumeration +function p2p.h PeerGraphEnumConnections +function p2p.h PeerGraphEnumNodes +function p2p.h PeerGraphEnumRecords +function p2p.h PeerGraphExportDatabase +function p2p.h PeerGraphFreeData +function p2p.h PeerGraphGetEventData +function p2p.h PeerGraphGetItemCount +function p2p.h PeerGraphGetNextItem +function p2p.h PeerGraphGetNodeInfo +function p2p.h PeerGraphGetProperties +function p2p.h PeerGraphGetRecord +function p2p.h PeerGraphGetStatus +function p2p.h PeerGraphImportDatabase +function p2p.h PeerGraphListen +function p2p.h PeerGraphOpen +function p2p.h PeerGraphOpenDirectConnection +function p2p.h PeerGraphPeerTimeToUniversalTime +function p2p.h PeerGraphRegisterEvent +function p2p.h PeerGraphSearchRecords +function p2p.h PeerGraphSendData +function p2p.h PeerGraphSetNodeAttributes +function p2p.h PeerGraphSetPresence +function p2p.h PeerGraphSetProperties +function p2p.h PeerGraphShutdown +function p2p.h PeerGraphStartup +function p2p.h PeerGraphUniversalTimeToPeerTime +function p2p.h PeerGraphUnregisterEvent +function p2p.h PeerGraphUpdateRecord +function p2p.h PeerGraphValidateDeferredRecords + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Graphing API > Graphing API Reference > Graphing API Callbacks +callback p2p.h PFNPEER_FREE_SECURITY_DATA +callback p2p.h PFNPEER_SECURE_RECORD +callback p2p.h PFNPEER_VALIDATE_RECORD + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Graphing API > Graphing API Reference > Graphing API Structures +struct p2p.h PEER_EVENT_NODE_CHANGE_DATA +struct p2p.h PEER_EVENT_SYNCHRONIZED_DATA +struct p2p.h PEER_GRAPH_EVENT_DATA +struct p2p.h PEER_GRAPH_EVENT_REGISTRATION +struct p2p.h PEER_GRAPH_PROPERTIES +struct p2p.h PEER_NODE_INFO +struct p2p.h PEER_SECURITY_INTERFACE + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Graphing API > Graphing API Reference > Graphing API Constants +enum p2p.h PEER_GRAPH_EVENT_TYPE +enum p2p.h PEER_GRAPH_PROPERTY_FLAGS +enum p2p.h PEER_GRAPH_SCOPE +enum p2p.h PEER_GRAPH_STATUS_FLAGS +enum p2p.h PEER_NODE_CHANGE_TYPE + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Grouping API > Grouping API Reference > Grouping API Functions +function p2p.h PeerGroupAddRecord +function p2p.h PeerGroupClose +function p2p.h PeerGroupCloseDirectConnection +function p2p.h PeerGroupConnect +function p2p.h PeerGroupConnectByAddress +function p2p.h PeerGroupCreate +function p2p.h PeerGroupCreateInvitation +function p2p.h PeerGroupCreatePasswordInvitation +function p2p.h PeerGroupDelete +function p2p.h PeerGroupDeleteRecord +function p2p.h PeerGroupEnumConnections +function p2p.h PeerGroupEnumMembers +function p2p.h PeerGroupEnumRecords +function p2p.h PeerGroupExportConfig +function p2p.h PeerGroupExportDatabase +function p2p.h PeerGroupGetEventData +function p2p.h PeerGroupGetProperties +function p2p.h PeerGroupGetRecord +function p2p.h PeerGroupGetStatus +function p2p.h PeerGroupImportConfig +function p2p.h PeerGroupImportDatabase +function p2p.h PeerGroupIssueCredentials +function p2p.h PeerGroupJoin +function p2p.h PeerGroupOpen +function p2p.h PeerGroupOpenDirectConnection +function p2p.h PeerGroupParseInvitation +function p2p.h PeerGroupPasswordJoin +function p2p.h PeerGroupPeerTimeToUniversalTime +function p2p.h PeerGroupRegisterEvent +function p2p.h PeerGroupSearchRecords +function p2p.h PeerGroupSendData +function p2p.h PeerGroupSetProperties +function p2p.h PeerGroupShutdown +function p2p.h PeerGroupStartup +function p2p.h PeerGroupUniversalTimeToPeerTime +function p2p.h PeerGroupUnregisterEvent +function p2p.h PeerGroupUpdateRecord + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Grouping API > Grouping API Reference > Grouping API Structures +struct p2p.h PEER_CREDENTIAL_INFO +struct p2p.h PEER_EVENT_MEMBER_CHANGE_DATA +struct p2p.h PEER_GROUP_EVENT_DATA =peer_group_event_data-r1 +struct p2p.h PEER_GROUP_EVENT_REGISTRATION +struct p2p.h PEER_GROUP_PROPERTIES +struct p2p.h PEER_INVITATION_INFO +struct p2p.h PEER_MEMBER + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Grouping API > Grouping API Reference > Grouping API Enumerations +enum p2p.h PEER_GROUP_AUTHENTICATION_SCHEME +enum p2p.h PEER_GROUP_EVENT_TYPE +enum p2p.h PEER_GROUP_ISSUE_CREDENTIAL_FLAGS +enum p2p.h PEER_GROUP_PROPERTY_FLAGS +enum p2p.h PEER_GROUP_STATUS +enum p2p.h PEER_MEMBER_CHANGE_TYPE +enum p2p.h PEER_MEMBER_FLAGS + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Identity Manager API > Identity Manager Reference > Identity Manager Functions +function p2p.h PeerCreatePeerName +function p2p.h PeerEnumGroups +function p2p.h PeerEnumIdentities +function p2p.h PeerIdentityCreate +function p2p.h PeerIdentityDelete +function p2p.h PeerIdentityExport +function p2p.h PeerIdentityGetCryptKey +function p2p.h PeerIdentityGetDefault +function p2p.h PeerIdentityGetFriendlyName +function p2p.h PeerIdentityGetXML +function p2p.h PeerIdentityImport +function p2p.h PeerIdentitySetFriendlyName + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Identity Manager API > Identity Manager Reference > Identity Manager Structures +struct p2p.h PEER_NAME_PAIR + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Distributed Routing Table API > Distributed Routing Table API Reference > Distributed Routing Table Enumerations +enum drt.h DRT_ADDRESS_FLAGS +enum drt.h DRT_EVENT_TYPE +enum drt.h DRT_LEAFSET_KEY_CHANGE_TYPE +enum drt.h DRT_MATCH_TYPE +enum drt.h DRT_REGISTRATION_STATE +enum drt.h DRT_SCOPE +enum drt.h DRT_SECURITY_MODE +enum drt.h DRT_STATUS + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Distributed Routing Table API > Distributed Routing Table API Reference > Distributed Routing Table Structures +struct drt.h DRT_ADDRESS +struct drt.h DRT_ADDRESS_LIST +struct drt.h DRT_BOOTSTRAP_PROVIDER +struct drt.h DRT_DATA +struct drt.h DRT_EVENT_DATA +struct drt.h DRT_REGISTRATION +struct drt.h DRT_SEARCH_INFO +struct drt.h DRT_SEARCH_RESULT +struct drt.h DRT_SECURITY_PROVIDER +struct drt.h DRT_SETTINGS + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > Distributed Routing Table API > Distributed Routing Table API Reference > Distributed Routing Table Functions +function drt.h DrtClose +function drt.h DrtContinueSearch +function drt.h DrtCreateDerivedKey +function drt.h DrtCreateDerivedKeySecurityProvider +function drt.h DrtCreateDnsBootstrapResolver +function drt.h DrtCreateIpv6UdpTransport +function drt.h DrtCreateNullSecurityProvider +function drt.h DrtCreatePnrpBootstrapResolver +function drt.h DrtDeleteDerivedKeySecurityProvider +function drt.h DrtDeleteDnsBootstrapResolver +function drt.h DrtDeleteIpv6UdpTransport +function drt.h DrtDeleteNullSecurityProvider +function drt.h DrtDeletePnrpBootstrapResolver +function drt.h DrtEndSearch +function drt.h DrtGetEventData +function drt.h DrtGetEventDataSize +function drt.h DrtGetInstanceName +function drt.h DrtGetInstanceNameSize +function drt.h DrtGetSearchPath +function drt.h DrtGetSearchPathSize +function drt.h DrtGetSearchResult +function drt.h DrtGetSearchResultSize +function drt.h DrtOpen +function drt.h DrtRegisterKey +function drt.h DrtStartSearch +function drt.h DrtUnregisterKey +function drt.h DrtUpdateKey + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > PNRP Namespace Provider API > PNRP Reference > PNRP Functions +function p2p.h PeerHostNameToPeerName +function p2p.h PeerNameToPeerHostName +function p2p.h PeerPnrpEndResolve +function p2p.h PeerPnrpGetCloudInfo +function p2p.h PeerPnrpGetEndpoint +function p2p.h PeerPnrpRegister +function p2p.h PeerPnrpResolve +function p2p.h PeerPnrpShutdown +function p2p.h PeerPnrpStartResolve +function p2p.h PeerPnrpStartup +function p2p.h PeerPnrpUnregister +function p2p.h PeerPnrpUpdateRegistration + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > PNRP Namespace Provider API > PNRP Reference > PNRP Structures +struct p2p.h PEER_PNRP_CLOUD_INFO +struct p2p.h PEER_PNRP_ENDPOINT_INFO +struct p2p.h PEER_PNRP_REGISTRATION_INFO +struct pnrpdef.h PNRP_CLOUD_ID +struct pnrpns.h PNRPCLOUDINFO +struct pnrpns.h PNRPINFO_V1 + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Infrastructure > PNRP Namespace Provider API > PNRP Reference > PNRP Constants +enum pnrpdef.h PNRP_CLOUD_FLAGS +enum pnrpdef.h PNRP_CLOUD_STATE +enum pnrpdef.h PNRP_RESOLVE_CRITERIA + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Collaboration > Collaboration API Reference > Collaboration API Enumerations +enum p2p.h PEER_APPLICATION_REGISTRATION_TYPE +enum p2p.h PEER_CHANGE_TYPE +enum p2p.h PEER_COLLAB_EVENT_TYPE +enum p2p.h PEER_INVITATION_RESPONSE_TYPE +enum p2p.h PEER_PRESENCE_STATUS +enum p2p.h PEER_PUBLICATION_SCOPE +enum p2p.h PEER_SIGNIN_FLAGS +enum p2p.h PEER_WATCH_PERMISSION + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Collaboration > Collaboration API Reference > Collaboration API Functions +function p2p.h PeerCollabAddContact +function p2p.h PeerCollabAsyncInviteContact +function p2p.h PeerCollabAsyncInviteEndpoint +function p2p.h PeerCollabCancelInvitation +function p2p.h PeerCollabCloseHandle +function p2p.h PeerCollabDeleteContact +function p2p.h PeerCollabDeleteEndpointData +function p2p.h PeerCollabDeleteObject +function p2p.h PeerCollabEnumApplicationRegistrationInfo +function p2p.h PeerCollabEnumApplications +function p2p.h PeerCollabEnumContacts +function p2p.h PeerCollabEnumEndpoints +function p2p.h PeerCollabEnumObjects +function p2p.h PeerCollabEnumPeopleNearMe +function p2p.h PeerCollabExportContact +function p2p.h PeerCollabGetAppLaunchInfo +function p2p.h PeerCollabGetApplicationRegistrationInfo +function p2p.h PeerCollabGetContact +function p2p.h PeerCollabGetEndpointName +function p2p.h PeerCollabGetEventData +function p2p.h PeerCollabGetInvitationResponse +function p2p.h PeerCollabGetPresenceInfo +function p2p.h PeerCollabGetSigninOptions +function p2p.h PeerCollabInviteContact +function p2p.h PeerCollabInviteEndpoint +function p2p.h PeerCollabParseContact +function p2p.h PeerCollabQueryContactData +function p2p.h PeerCollabRefreshEndpointData +function p2p.h PeerCollabRegisterApplication +function p2p.h PeerCollabRegisterEvent +function p2p.h PeerCollabSetEndpointName +function p2p.h PeerCollabSetObject +function p2p.h PeerCollabSetPresenceInfo +function p2p.h PeerCollabShutdown +function p2p.h PeerCollabSignIn +function p2p.h PeerCollabSignOut +function p2p.h PeerCollabStartup +function p2p.h PeerCollabSubscribeEndpointData +function p2p.h PeerCollabUnregisterApplication +function p2p.h PeerCollabUnregisterEvent +function p2p.h PeerCollabUnsubscribeEndpointData +function p2p.h PeerCollabUpdateContact + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Collaboration > Collaboration API Reference > Collaboration API Structures +struct p2p.h PEER_APP_LAUNCH_INFO +struct p2p.h PEER_APPLICATION +struct p2p.h PEER_APPLICATION_REGISTRATION_INFO +struct p2p.h PEER_COLLAB_EVENT_DATA =peer_collab_event_data-r1 +struct p2p.h PEER_COLLAB_EVENT_REGISTRATION +struct p2p.h PEER_CONTACT +struct p2p.h PEER_ENDPOINT +struct p2p.h PEER_EVENT_APPLICATION_CHANGED_DATA +struct p2p.h PEER_EVENT_ENDPOINT_CHANGED_DATA +struct p2p.h PEER_EVENT_OBJECT_CHANGED_DATA +struct p2p.h PEER_EVENT_PEOPLE_NEAR_ME_CHANGED_DATA +struct p2p.h PEER_EVENT_PRESENCE_CHANGED_DATA +struct p2p.h PEER_EVENT_REQUEST_STATUS_CHANGED_DATA +struct p2p.h PEER_EVENT_WATCHLIST_CHANGED_DATA +struct p2p.h PEER_INVITATION +struct p2p.h PEER_INVITATION_RESPONSE +struct p2p.h PEER_OBJECT +struct p2p.h PEER_PEOPLE_NEAR_ME +struct p2p.h PEER_PRESENCE_INFO + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Distribution > Peer Distribution API Reference > Peer Distribution API Enumerations +enum peerdist.h PEERDIST_CLIENT_INFO_BY_HANDLE_CLASS +enum peerdist.h PEERDIST_STATUS + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Distribution > Peer Distribution API Reference > Peer Distribution API Structures +struct peerdist.h PEERDIST_CLIENT_BASIC_INFO +struct peerdist.h PEERDIST_CONTENT_TAG +struct peerdist.h PEERDIST_PUBLICATION_OPTIONS +struct peerdist.h PEERDIST_RETRIEVAL_OPTIONS +struct peerdist.h PEERDIST_STATUS_INFO + +# Desktop App Technologies > Networking and Internet > Peer-to-Peer > Peer Distribution > Peer Distribution API Reference > Peer Distribution API Functions +function peerdist.h PeerDistClientAddContentInformation +function peerdist.h PeerDistClientAddData +function peerdist.h PeerDistClientBlockRead +function peerdist.h PeerDistClientCancelAsyncOperation +function peerdist.h PeerDistClientCloseContent +function peerdist.h PeerDistClientCompleteContentInformation +function peerdist.h PeerDistClientFlushContent +function peerdist.h PeerDistClientGetInformationByHandle +function peerdist.h PeerDistClientOpenContent +function peerdist.h PeerDistClientStreamRead +function peerdist.h PeerDistGetOverlappedResult +function peerdist.h PeerDistGetStatus +function peerdist.h PeerDistGetStatusEx +function peerdist.h PeerDistRegisterForStatusChangeNotification +function peerdist.h PeerDistRegisterForStatusChangeNotificationEx +function peerdist.h PeerDistServerCancelAsyncOperation +function peerdist.h PeerDistServerCloseContentInformation +function peerdist.h PeerDistServerCloseStreamHandle +function peerdist.h PeerDistServerOpenContentInformation +function peerdist.h PeerDistServerOpenContentInformationEx +function peerdist.h PeerDistServerPublishAddToStream +function peerdist.h PeerDistServerPublishCompleteStream +function peerdist.h PeerDistServerPublishStream +function peerdist.h PeerDistServerRetrieveContentInformation +function peerdist.h PeerDistServerUnpublish +function peerdist.h PeerDistShutdown +function peerdist.h PeerDistStartup +function peerdist.h PeerDistUnregisterForStatusChangeNotification + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > Reference > RPC Data Types, Structures, and Constants > RPC Structures +struct guiddef.h GUID +struct rpcasync.h RPC_ASYNC_NOTIFICATION_INFO +struct rpcasync.h RPC_ASYNC_STATE +struct rpcasync.h RPC_CALL_ATTRIBUTES_V1 =rpc_call_attributes_v1_a +struct rpcasync.h RPC_CALL_ATTRIBUTES_V2 =rpc_call_attributes_v2_a +struct rpcasync.h RPC_CALL_LOCAL_ADDRESS_V1 +struct rpcasync.h RPC_EE_INFO_PARAM +struct rpcasync.h RPC_ERROR_ENUM_HANDLE +struct rpcasync.h RPC_EXTENDED_ERROR_INFO +struct rpcdce.h RPC_BINDING_HANDLE_OPTIONS_V1 +struct rpcdce.h RPC_BINDING_HANDLE_SECURITY_V1 =rpc_binding_handle_security_v1_a +struct rpcdce.h RPC_BINDING_HANDLE_TEMPLATE_V1 =rpc_binding_handle_template_v1_a +struct rpcdce.h RPC_BINDING_VECTOR +struct rpcdce.h RPC_ENDPOINT_TEMPLATE +struct rpcdce.h RPC_HTTP_TRANSPORT_CREDENTIALS =rpc_http_transport_credentials_a +struct rpcdce.h RPC_HTTP_TRANSPORT_CREDENTIALS_V2 =rpc_http_transport_credentials_v2_a +struct rpcdce.h RPC_HTTP_TRANSPORT_CREDENTIALS_V3 =rpc_http_transport_credentials_v3_a +struct rpcdce.h RPC_IF_ID +struct rpcdce.h RPC_IF_ID_VECTOR +struct rpcdce.h RPC_INTERFACE_TEMPLATE +struct rpcdce.h RPC_POLICY +struct rpcdce.h RPC_PROTSEQ_VECTOR +struct rpcdce.h RPC_SECURITY_QOS +struct rpcdce.h RPC_SECURITY_QOS_V2 =rpc_security_qos_v2_a +struct rpcdce.h RPC_SECURITY_QOS_V3 =rpc_security_qos_v3_a +struct rpcdce.h RPC_SECURITY_QOS_V4 =rpc_security_qos_v4_a +struct rpcdce.h RPC_SECURITY_QOS_V5 =rpc_security_qos_v5_a +struct rpcdce.h RPC_STATS_VECTOR +struct rpcdce.h SEC_WINNT_AUTH_IDENTITY =sec_winnt_auth_identity_a +struct rpcdce.h UUID_VECTOR +struct rpcdcep.h RPC_C_OPT_COOKIE_AUTH_DESCRIPTOR +struct rpcdcep.h RPC_CLIENT_INTERFACE +struct rpcdcep.h RPC_DISPATCH_TABLE +struct rpcndr.h NDR_USER_MARSHAL_INFO +struct rpcndr.h NDR_USER_MARSHAL_INFO_LEVEL1 + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > Reference > RPC Data Types, Structures, and Constants > RPC Enumerated Types +enum rpcasync.h RPC_ASYNC_EVENT +enum rpcasync.h RPC_NOTIFICATION_TYPES +enum rpcasync.h RPC_NOTIFICATIONS +enum rpcasync.h RpcCallClientLocality +enum rpcasync.h RpcCallType +enum rpcasync.h RpcLocalAddressFormat + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > Reference > Function Reference > RPC Functions +function midles.h MesBufferHandleReset +function midles.h MesDecodeBufferHandleCreate +function midles.h MesDecodeIncrementalHandleCreate +function midles.h MesEncodeDynBufferHandleCreate +function midles.h MesEncodeFixedBufferHandleCreate +function midles.h MesEncodeIncrementalHandleCreate +function midles.h MesHandleFree +function midles.h MesIncrementalHandleReset +function midles.h MesInqProcEncodingId +function rpcasync.h RpcAsyncAbortCall +function rpcasync.h RpcAsyncCancelCall +function rpcasync.h RpcAsyncCompleteCall +function rpcasync.h RpcAsyncGetCallStatus +function rpcasync.h RpcAsyncInitializeHandle +function rpcasync.h RpcAsyncRegisterInfo +function rpcasync.h RpcBindingBind +function rpcasync.h RpcBindingUnbind +function rpcasync.h RpcErrorAddRecord +function rpcasync.h RpcErrorClearInformation +function rpcasync.h RpcErrorEndEnumeration +function rpcasync.h RpcErrorGetNextRecord +function rpcasync.h RpcErrorGetNumberOfRecords +function rpcasync.h RpcErrorLoadErrorInfo +function rpcasync.h RpcErrorResetEnumeration +function rpcasync.h RpcErrorSaveErrorInfo +function rpcasync.h RpcErrorStartEnumeration +function rpcasync.h RpcFreeAuthorizationContext +function rpcasync.h RpcGetAuthorizationContextForClient +function rpcasync.h RpcServerInqCallAttributes =rpcserverinqcallattributesa +function rpcasync.h RpcServerSubscribeForNotification +function rpcasync.h RpcServerUnsubscribeForNotification +function rpcasync.h RpcSsContextLockExclusive +function rpcasync.h RpcSsContextLockShared +function rpcdce.h DceErrorInqText +function rpcdce.h RpcBindingCopy +function rpcdce.h RpcBindingCreate =rpcbindingcreatea +function rpcdce.h RpcBindingFree +function rpcdce.h RpcBindingFromStringBinding +function rpcdce.h RpcBindingInqAuthClient +function rpcdce.h RpcBindingInqAuthClientEx +function rpcdce.h RpcBindingInqAuthInfo +function rpcdce.h RpcBindingInqAuthInfoEx =rpcbindinginqauthinfoexa +function rpcdce.h RpcBindingInqObject +function rpcdce.h RpcBindingInqOption +function rpcdce.h RpcBindingReset +function rpcdce.h RpcBindingServerFromClient +function rpcdce.h RpcBindingSetAuthInfo +function rpcdce.h RpcBindingSetAuthInfoEx =rpcbindingsetauthinfoexa +function rpcdce.h RpcBindingSetObject +function rpcdce.h RpcBindingSetOption +function rpcdce.h RpcBindingToStringBinding +function rpcdce.h RpcBindingVectorFree +function rpcdce.h RpcCancelThread +function rpcdce.h RpcCancelThreadEx +function rpcdce.h RpcEpRegister +function rpcdce.h RpcEpRegisterNoReplace +function rpcdce.h RpcEpResolveBinding +function rpcdce.h RpcEpUnregister +function rpcdce.h RpcExceptionFilter +function rpcdce.h RpcIfIdVectorFree +function rpcdce.h RpcIfInqId +function rpcdce.h RpcImpersonateClient +function rpcdce.h RpcImpersonateClientContainer +function rpcdce.h RpcMgmtEnableIdleCleanup +function rpcdce.h RpcMgmtEpEltInqBegin +function rpcdce.h RpcMgmtEpEltInqDone +function rpcdce.h RpcMgmtEpEltInqNext +function rpcdce.h RpcMgmtEpUnregister +function rpcdce.h RpcMgmtInqComTimeout +function rpcdce.h RpcMgmtInqDefaultProtectLevel +function rpcdce.h RpcMgmtInqIfIds +function rpcdce.h RpcMgmtInqServerPrincName +function rpcdce.h RpcMgmtInqStats +function rpcdce.h RpcMgmtIsServerListening +function rpcdce.h RpcMgmtSetAuthorizationFn +function rpcdce.h RpcMgmtSetCancelTimeout +function rpcdce.h RpcMgmtSetComTimeout +function rpcdce.h RpcMgmtSetServerStackSize +function rpcdce.h RpcMgmtStatsVectorFree +function rpcdce.h RpcMgmtStopServerListening +function rpcdce.h RpcMgmtWaitServerListen +function rpcdce.h RpcNetworkInqProtseqs +function rpcdce.h RpcNetworkIsProtseqValid +function rpcdce.h RpcNsBindingInqEntryName +function rpcdce.h RpcObjectInqType +function rpcdce.h RpcObjectSetInqFn +function rpcdce.h RpcObjectSetType +function rpcdce.h RpcProtseqVectorFree +function rpcdce.h RpcRaiseException +function rpcdce.h RpcRevertContainerImpersonation +function rpcdce.h RpcRevertToSelf +function rpcdce.h RpcRevertToSelfEx +function rpcdce.h RpcServerCompleteSecurityCallback +function rpcdce.h RpcServerInqBindingHandle +function rpcdce.h RpcServerInqBindings +function rpcdce.h RpcServerInqDefaultPrincName +function rpcdce.h RpcServerInqIf +function rpcdce.h RpcServerInterfaceGroupActivate +function rpcdce.h RpcServerInterfaceGroupClose +function rpcdce.h RpcServerInterfaceGroupCreate +function rpcdce.h RpcServerInterfaceGroupDeactivate +function rpcdce.h RpcServerInterfaceGroupInqBindings +function rpcdce.h RpcServerListen +function rpcdce.h RpcServerRegisterAuthInfo +function rpcdce.h RpcServerRegisterIf +function rpcdce.h RpcServerRegisterIf2 +function rpcdce.h RpcServerRegisterIf3 +function rpcdce.h RpcServerRegisterIfEx +function rpcdce.h RpcServerTestCancel +function rpcdce.h RpcServerUnregisterIf +function rpcdce.h RpcServerUnregisterIfEx +function rpcdce.h RpcServerUseAllProtseqs +function rpcdce.h RpcServerUseAllProtseqsEx +function rpcdce.h RpcServerUseAllProtseqsIf +function rpcdce.h RpcServerUseAllProtseqsIfEx +function rpcdce.h RpcServerUseProtseq +function rpcdce.h RpcServerUseProtseqEp +function rpcdce.h RpcServerUseProtseqEpEx +function rpcdce.h RpcServerUseProtseqEx +function rpcdce.h RpcServerUseProtseqIf +function rpcdce.h RpcServerUseProtseqIfEx +function rpcdce.h RpcStringBindingCompose +function rpcdce.h RpcStringBindingParse +function rpcdce.h RpcStringFree +function rpcdce.h RpcTestCancel +function rpcdce.h UuidCompare +function rpcdce.h UuidCreate +function rpcdce.h UuidCreateNil +function rpcdce.h UuidCreateSequential +function rpcdce.h UuidEqual +function rpcdce.h UuidFromString +function rpcdce.h UuidHash +function rpcdce.h UuidIsNil +function rpcdce.h UuidToString +function rpcdcep.h I_RpcBindingInqLocalClientPID +function rpcndr.h RpcSmAllocate +function rpcndr.h RpcSmClientFree +function rpcndr.h RpcSmDestroyClientContext +function rpcndr.h RpcSmDisableAllocate +function rpcndr.h RpcSmEnableAllocate +function rpcndr.h RpcSmFree +function rpcndr.h RpcSmGetThreadHandle +function rpcndr.h RpcSmSetClientAllocFree +function rpcndr.h RpcSmSetThreadHandle +function rpcndr.h RpcSmSwapClientAllocFree +function rpcndr.h RpcSsAllocate +function rpcndr.h RpcSsDestroyClientContext +function rpcndr.h RpcSsDisableAllocate +function rpcndr.h RpcSsEnableAllocate +function rpcndr.h RpcSsFree +function rpcndr.h RpcSsGetThreadHandle +function rpcndr.h RpcSsSetClientAllocFree +function rpcndr.h RpcSsSetThreadHandle +function rpcndr.h RpcSsSwapClientAllocFree +function rpcndr.h RpcUserFree +function rpcnsi.h RpcNsBindingExport =rpcnsbindingexporta +function rpcnsi.h RpcNsBindingExportPnP =rpcnsbindingexportpnpa +function rpcnsi.h RpcNsBindingImportBegin =rpcnsbindingimportbegina +function rpcnsi.h RpcNsBindingImportDone +function rpcnsi.h RpcNsBindingImportNext +function rpcnsi.h RpcNsBindingLookupBegin =rpcnsbindinglookupbegina +function rpcnsi.h RpcNsBindingLookupDone +function rpcnsi.h RpcNsBindingLookupNext +function rpcnsi.h RpcNsBindingSelect +function rpcnsi.h RpcNsBindingUnexport =rpcnsbindingunexporta +function rpcnsi.h RpcNsBindingUnexportPnP =rpcnsbindingunexportpnpa +function rpcnsi.h RpcNsEntryExpandName =rpcnsentryexpandnamea +function rpcnsi.h RpcNsEntryObjectInqBegin =rpcnsentryobjectinqbegina +function rpcnsi.h RpcNsEntryObjectInqDone +function rpcnsi.h RpcNsEntryObjectInqNext +function rpcnsi.h RpcNsGroupDelete =rpcnsgroupdeletea +function rpcnsi.h RpcNsGroupMbrAdd =rpcnsgroupmbradda +function rpcnsi.h RpcNsGroupMbrInqBegin =rpcnsgroupmbrinqbegina +function rpcnsi.h RpcNsGroupMbrInqDone +function rpcnsi.h RpcNsGroupMbrInqNext =rpcnsgroupmbrinqnexta +function rpcnsi.h RpcNsGroupMbrRemove =rpcnsgroupmbrremovea +function rpcnsi.h RpcNsMgmtBindingUnexport =rpcnsmgmtbindingunexporta +function rpcnsi.h RpcNsMgmtEntryCreate =rpcnsmgmtentrycreatea +function rpcnsi.h RpcNsMgmtEntryDelete =rpcnsmgmtentrydeletea +function rpcnsi.h RpcNsMgmtEntryInqIfIds =rpcnsmgmtentryinqifidsa +function rpcnsi.h RpcNsMgmtHandleSetExpAge +function rpcnsi.h RpcNsMgmtInqExpAge +function rpcnsi.h RpcNsMgmtSetExpAge +function rpcnsi.h RpcNsProfileDelete =rpcnsprofiledeletea +function rpcnsi.h RpcNsProfileEltAdd =rpcnsprofileeltadda +function rpcnsi.h RpcNsProfileEltInqBegin =rpcnsprofileeltinqbegina +function rpcnsi.h RpcNsProfileEltInqDone +function rpcnsi.h RpcNsProfileEltInqNext =rpcnsprofileeltinqnexta +function rpcnsi.h RpcNsProfileEltRemove =rpcnsprofileeltremovea +function rpcssl.h RpcCertGeneratePrincipalName + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > Reference > Function Reference > RPC Callback and Notification Functions +callback rpcdce.h RPC_AUTH_KEY_RETRIEVAL_FN +callback rpcdce.h RPC_IF_CALLBACK_FN +callback rpcdce.h RPC_INTERFACE_GROUP_IDLE_CALLBACK_FN +callback rpcdce.h RPC_MGMT_AUTHORIZATION_FN +callback rpcdce.h RPC_OBJECT_INQ_FN + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > Reference > RPC Macros +function rpc.h RpcExcept +function rpcasync.h RpcAsyncGetCallHandle + +# Desktop App Technologies > Networking and Internet > Remote Procedure Call (RPC) > RPC NDR Engine > RPC NDR Interface Reference +function midles.h NdrMesProcEncodeDecode2 +function rpcndr.h Ndr64AsyncClientCall +function rpcndr.h Ndr64AsyncServerCallAll +function rpcndr.h NdrAsyncClientCall +function rpcndr.h NdrAsyncClientCall2 +function rpcndr.h NdrAsyncServerCall +function rpcndr.h NdrClearOutParameters +function rpcndr.h NdrClientCall +function rpcndr.h NdrClientCall2 +function rpcndr.h NdrClientCall3 +function rpcndr.h NdrClientCall4 +function rpcndr.h NdrConformantArrayUnmarshall +function rpcndr.h NdrConformantStringBufferSize +function rpcndr.h NdrConformantStringMarshall +function rpcndr.h NdrConformantStringUnmarshall +function rpcndr.h NdrContextHandleInitialize +function rpcndr.h NdrContextHandleMemorySize +function rpcndr.h NdrContextHandleSize +function rpcndr.h NdrConvert +function rpcndr.h NdrDcomAsyncClientCall +function rpcndr.h NdrDcomAsyncClientCall2 +function rpcndr.h NdrGetUserMarshalInfo +function rpcndr.h NdrInterfacePointerBufferSize +function rpcndr.h NdrInterfacePointerFree +function rpcndr.h NdrInterfacePointerMarshall +function rpcndr.h NdrInterfacePointerUnmarshall +function rpcndr.h NdrMesProcEncodeDecode4 +function rpcndr.h NdrOleAllocate +function rpcndr.h NdrOleFree +function rpcndr.h NdrPointerBufferSize +function rpcndr.h NdrPointerFree +function rpcndr.h NdrPointerMarshall +function rpcndr.h NdrPointerUnmarshall +function rpcndr.h NdrServerCall2 +function rpcndr.h NdrServerCallAll +function rpcndr.h NdrSimpleTypeMarshall +function rpcndr.h NdrSimpleTypeUnmarshall +function rpcndr.h NdrStubCall2 +function rpcndr.h NdrStubCall3 +function rpcndr.h NdrUserMarshalBufferSize +function rpcndr.h NdrUserMarshalFree +function rpcndr.h NdrUserMarshalMarshall +function rpcproxy.h CStdAsyncStubBuffer_AddRef +function rpcproxy.h CStdAsyncStubBuffer_Connect +function rpcproxy.h CStdAsyncStubBuffer_Disconnect +function rpcproxy.h CStdAsyncStubBuffer_Invoke +function rpcproxy.h CStdAsyncStubBuffer_QueryInterface +function rpcproxy.h CStdAsyncStubBuffer_Release +function rpcproxy.h CStdAsyncStubBuffer2_Connect +function rpcproxy.h CStdAsyncStubBuffer2_Disconnect +function rpcproxy.h CStdAsyncStubBuffer2_Release +function rpcproxy.h CStdStubBuffer_AddRef +function rpcproxy.h CStdStubBuffer_Connect +function rpcproxy.h CStdStubBuffer_CountRefs +function rpcproxy.h CStdStubBuffer_DebugServerQueryInterface +function rpcproxy.h CStdStubBuffer_DebugServerRelease +function rpcproxy.h CStdStubBuffer_Disconnect +function rpcproxy.h CStdStubBuffer_Invoke +function rpcproxy.h CStdStubBuffer_IsIIDSupported +function rpcproxy.h CStdStubBuffer_QueryInterface +function rpcproxy.h CStdStubBuffer2_Connect +function rpcproxy.h CStdStubBuffer2_CountRefs +function rpcproxy.h CStdStubBuffer2_Disconnect +function rpcproxy.h CStdStubBuffer2_QueryInterface +function rpcproxy.h NdrDllCanUnloadNow +function rpcproxy.h NdrDllGetClassObject +function rpcproxy.h NdrDllRegisterProxy +function rpcproxy.h NdrDllUnregisterProxy +function rpcproxy.h NdrProxyErrorHandler +function rpcproxy.h NdrProxyFreeBuffer +function rpcproxy.h NdrProxyGetBuffer +function rpcproxy.h NdrProxyInitialize +function rpcproxy.h NdrProxySendReceive +function rpcproxy.h NdrStubForwardingFunction +function rpcproxy.h NdrStubGetBuffer +function rpcproxy.h NdrStubInitialize +function unknwnbase.h IUnknown_AddRef_Proxy +function unknwnbase.h IUnknown_QueryInterface_Proxy +function unknwnbase.h IUnknown_Release_Proxy +struct rpcdcep.h RPC_MESSAGE +struct rpcndr.h MIDL_STUB_DESC +struct rpcndr.h MIDL_STUB_MESSAGE +struct rpcproxy.h ProxyFileInfo + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service > Remote Access Service Reference > Remote Access Service Functions > RAS Custom Scripting DLL Functions +callback ras.h PFNRASFREEBUFFER +callback ras.h PFNRASGETBUFFER +callback ras.h PFNRASRECEIVEBUFFER +callback ras.h PFNRASRETRIEVEBUFFER +callback ras.h PFNRASSENDBUFFER +callback ras.h RasCustomScriptExecuteFn + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service > Remote Access Service Reference > Remote Access Service Functions +callback ras.h ORASADFunc +callback ras.h PFNRASSETCOMMSETTINGS +callback ras.h RASADFunc =rasadfunca +callback ras.h RasCustomDeleteEntryNotifyFn +callback ras.h RasCustomDialFn +callback ras.h RasCustomHangUpFn +callback ras.h RasDialFunc +callback ras.h RasDialFunc1 +callback ras.h RasDialFunc2 +callback rasdlg.h RasCustomDialDlgFn +callback rasdlg.h RasCustomEntryDlgFn +callback rasdlg.h RasPBDlgFunc =raspbdlgfunca +function ras.h RasClearConnectionStatistics +function ras.h RasClearLinkStatistics +function ras.h RasConnectionNotification =rasconnectionnotificationa +function ras.h RasCreatePhonebookEntry =rascreatephonebookentrya +function ras.h RasDeleteEntry =rasdeleteentrya +function ras.h RasDeleteSubEntry =rasdeletesubentrya +function ras.h RasDial =rasdiala +function ras.h RasEditPhonebookEntry =raseditphonebookentrya +function ras.h RasEnumAutodialAddresses =rasenumautodialaddressesa +function ras.h RasEnumConnections =rasenumconnectionsa +function ras.h RasEnumDevices =rasenumdevicesa +function ras.h RasEnumEntries =rasenumentriesa +function ras.h RasFreeEapUserIdentity =rasfreeeapuseridentitya +function ras.h RasGetAutodialAddress =rasgetautodialaddressa +function ras.h RasGetAutodialEnable =rasgetautodialenablea +function ras.h RasGetAutodialParam =rasgetautodialparama +function ras.h RasGetConnectionStatistics +function ras.h RasGetConnectStatus =rasgetconnectstatusa +function ras.h RasGetCountryInfo =rasgetcountryinfoa +function ras.h RasGetCredentials =rasgetcredentialsa +function ras.h RasGetCustomAuthData =rasgetcustomauthdataa +function ras.h RasGetEapUserData =rasgeteapuserdataa +function ras.h RasGetEapUserIdentity =rasgeteapuseridentitya +function ras.h RasGetEntryDialParams =rasgetentrydialparamsa +function ras.h RasGetEntryProperties =rasgetentrypropertiesa +function ras.h RasGetErrorString =rasgeterrorstringa +function ras.h RasGetLinkStatistics +function ras.h RasGetNapStatus +function ras.h RasGetProjectionInfoEx +function ras.h RasGetSubEntryHandle =rasgetsubentryhandlea +function ras.h RasGetSubEntryProperties =rasgetsubentrypropertiesa +function ras.h RasHangUp =rashangupa +function ras.h RasInvokeEapUI +function ras.h RasRenameEntry =rasrenameentrya +function ras.h RasSetAutodialAddress =rassetautodialaddressa +function ras.h RasSetAutodialEnable =rassetautodialenablea +function ras.h RasSetAutodialParam =rassetautodialparama +function ras.h RasSetCredentials =rassetcredentialsa +function ras.h RasSetCustomAuthData =rassetcustomauthdataa +function ras.h RasSetEapUserData =rasseteapuserdataa +function ras.h RasSetEntryDialParams =rassetentrydialparamsa +function ras.h RasSetEntryProperties =rassetentrypropertiesa +function ras.h RasSetSubEntryProperties =rassetsubentrypropertiesa +function ras.h RasUpdateConnection +function ras.h RasValidateEntryName =rasvalidateentrynamea +function rasdlg.h RasDialDlg =rasdialdlga +function rasdlg.h RasEntryDlg =rasentrydlga +function rasdlg.h RasPhonebookDlg =rasphonebookdlga + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service > Remote Access Service Reference > Remote Access Service Structures +enum ras.h RASPROJECTION_INFO_TYPE +struct ras.h RAS_PROJECTION_INFO +struct ras.h RAS_STATS +struct ras.h RASDEVSPECIFICINFO +struct ras.h RASIKEV2_PROJECTION_INFO +struct ras.h RASNAPSTATE +struct ras.h RASPPP_PROJECTION_INFO + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > Remote Access Service Administration Reference > RAS Administration Functions > RAS Admininstration DLL Functions +function mprapi.h MprAdminAcceptNewConnection +function mprapi.h MprAdminAcceptNewConnection2 +function mprapi.h MprAdminAcceptNewConnection3 +function mprapi.h MprAdminAcceptNewConnectionEx +function mprapi.h MprAdminAcceptNewLink +function mprapi.h MprAdminAcceptReauthentication +function mprapi.h MprAdminAcceptReauthenticationEx +function mprapi.h MprAdminConnectionHangupNotification +function mprapi.h MprAdminConnectionHangupNotification2 +function mprapi.h MprAdminConnectionHangupNotification3 +function mprapi.h MprAdminConnectionHangupNotificationEx +function mprapi.h MprAdminGetIpAddressForUser +function mprapi.h MprAdminGetIpv6AddressForUser +function mprapi.h MprAdminInitializeDll +function mprapi.h MprAdminInitializeDllEx +function mprapi.h MprAdminLinkHangupNotification +function mprapi.h MprAdminReleaseIpAddress +function mprapi.h MprAdminReleaseIpv6AddressForUser +function mprapi.h MprAdminTerminateDll + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > Remote Access Service Administration Reference > RAS Administration Functions > RAS User Administration Functions +function mprapi.h MprAdminGetPDCServer +function mprapi.h MprAdminSendUserMessage +function mprapi.h MprAdminUserGetInfo +function mprapi.h MprAdminUserSetInfo + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > Remote Access Service Administration Reference > RAS Administration Functions +function mprapi.h MprAdminConnectionClearStats +function mprapi.h MprAdminConnectionEnum +function mprapi.h MprAdminConnectionEnumEx +function mprapi.h MprAdminConnectionGetInfo +function mprapi.h MprAdminConnectionGetInfoEx +function mprapi.h MprAdminConnectionRemoveQuarantine +function mprapi.h MprAdminPortClearStats +function mprapi.h MprAdminPortDisconnect +function mprapi.h MprAdminPortEnum +function mprapi.h MprAdminPortGetInfo +function mprapi.h MprAdminPortReset + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > Remote Access Service Administration Reference > RAS Administration Structures +struct mprapi.h MPRAPI_ADMIN_DLL_CALLBACKS +struct mprapi.h PPP_ATCP_INFO +struct mprapi.h PPP_CCP_INFO +struct mprapi.h PPP_INFO +struct mprapi.h PPP_INFO_2 +struct mprapi.h PPP_INFO_3 +struct mprapi.h PPP_IPCP_INFO +struct mprapi.h PPP_IPCP_INFO2 +struct mprapi.h PPP_IPV6_CP_INFO +struct mprapi.h PPP_IPXCP_INFO +struct mprapi.h PPP_LCP_INFO +struct mprapi.h PPP_NBFCP_INFO +struct mprapi.h RAS_CONNECTION_0 +struct mprapi.h RAS_CONNECTION_1 +struct mprapi.h RAS_CONNECTION_2 +struct mprapi.h RAS_CONNECTION_3 +struct mprapi.h RAS_CONNECTION_EX +struct mprapi.h RAS_PORT_0 +struct mprapi.h RAS_PORT_1 +struct mprapi.h RAS_USER_0 +struct mprapi.h RAS_USER_1 + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > Remote Access Service Administration Reference > RAS Administration Enumerations +enum mprapi.h RAS_HARDWARE_CONDITION +enum mprapi.h RAS_PORT_CONDITION +enum mprapi.h RAS_QUARANTINE_STATE + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > RAS Server Administration Reference > RAS Server Administration Functions > RAS Security DLL Functions +function rasshost.h RasSecurityDialogBegin +function rasshost.h RasSecurityDialogComplete +function rasshost.h RasSecurityDialogEnd +function rasshost.h RasSecurityDialogGetInfo +function rasshost.h RasSecurityDialogReceive +function rasshost.h RasSecurityDialogSend + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Remote Access > Remote Access Service Administration > RAS Server Administration Reference > RAS Server Administration Structures +struct rasshost.h RAS_SECURITY_INFO +struct rasshost.h SECURITY_MESSAGE + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Administration Reference > Router Administration Functions +function mprapi.h MprAdminBufferFree +function mprapi.h MprAdminDeregisterConnectionNotification +function mprapi.h MprAdminDeviceEnum +function mprapi.h MprAdminEstablishDomainRasServer +function mprapi.h MprAdminGetErrorString +function mprapi.h MprAdminInterfaceConnect +function mprapi.h MprAdminInterfaceCreate +function mprapi.h MprAdminInterfaceDelete +function mprapi.h MprAdminInterfaceDeviceGetInfo +function mprapi.h MprAdminInterfaceDeviceSetInfo +function mprapi.h MprAdminInterfaceDisconnect +function mprapi.h MprAdminInterfaceEnum +function mprapi.h MprAdminInterfaceGetCredentials +function mprapi.h MprAdminInterfaceGetCredentialsEx +function mprapi.h MprAdminInterfaceGetCustomInfoEx +function mprapi.h MprAdminInterfaceGetHandle +function mprapi.h MprAdminInterfaceGetInfo +function mprapi.h MprAdminInterfaceQueryUpdateResult +function mprapi.h MprAdminInterfaceSetCredentials +function mprapi.h MprAdminInterfaceSetCredentialsEx +function mprapi.h MprAdminInterfaceSetCustomInfoEx +function mprapi.h MprAdminInterfaceSetInfo +function mprapi.h MprAdminInterfaceTransportAdd +function mprapi.h MprAdminInterfaceTransportGetInfo +function mprapi.h MprAdminInterfaceTransportRemove +function mprapi.h MprAdminInterfaceTransportSetInfo +function mprapi.h MprAdminInterfaceUpdatePhonebookInfo +function mprapi.h MprAdminInterfaceUpdateRoutes +function mprapi.h MprAdminIsDomainRasServer +function mprapi.h MprAdminIsServiceInitialized +function mprapi.h MprAdminIsServiceRunning +function mprapi.h MprAdminRegisterConnectionNotification +function mprapi.h MprAdminServerConnect +function mprapi.h MprAdminServerDisconnect +function mprapi.h MprAdminServerGetCredentials +function mprapi.h MprAdminServerGetInfo +function mprapi.h MprAdminServerGetInfoEx +function mprapi.h MprAdminServerSetCredentials +function mprapi.h MprAdminServerSetInfo +function mprapi.h MprAdminServerSetInfoEx +function mprapi.h MprAdminTransportCreate +function mprapi.h MprAdminTransportGetInfo +function mprapi.h MprAdminTransportSetInfo + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Configuration Reference > Router Configuration Functions +function mprapi.h MprConfigBufferFree +function mprapi.h MprConfigFilterGetInfo +function mprapi.h MprConfigFilterSetInfo +function mprapi.h MprConfigGetFriendlyName +function mprapi.h MprConfigGetGuidName +function mprapi.h MprConfigInterfaceCreate +function mprapi.h MprConfigInterfaceDelete +function mprapi.h MprConfigInterfaceEnum +function mprapi.h MprConfigInterfaceGetCustomInfoEx +function mprapi.h MprConfigInterfaceGetHandle +function mprapi.h MprConfigInterfaceGetInfo +function mprapi.h MprConfigInterfaceSetCustomInfoEx +function mprapi.h MprConfigInterfaceSetInfo +function mprapi.h MprConfigInterfaceTransportAdd +function mprapi.h MprConfigInterfaceTransportEnum +function mprapi.h MprConfigInterfaceTransportGetHandle +function mprapi.h MprConfigInterfaceTransportGetInfo +function mprapi.h MprConfigInterfaceTransportRemove +function mprapi.h MprConfigInterfaceTransportSetInfo +function mprapi.h MprConfigServerBackup +function mprapi.h MprConfigServerConnect +function mprapi.h MprConfigServerDisconnect +function mprapi.h MprConfigServerGetInfo +function mprapi.h MprConfigServerGetInfoEx +function mprapi.h MprConfigServerInstall +function mprapi.h MprConfigServerRestore +function mprapi.h MprConfigServerSetInfo +function mprapi.h MprConfigServerSetInfoEx +function mprapi.h MprConfigTransportCreate +function mprapi.h MprConfigTransportDelete +function mprapi.h MprConfigTransportEnum +function mprapi.h MprConfigTransportGetHandle +function mprapi.h MprConfigTransportGetInfo +function mprapi.h MprConfigTransportSetInfo + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Information Reference > Router Information Functions +function mprapi.h MprInfoBlockAdd +function mprapi.h MprInfoBlockFind +function mprapi.h MprInfoBlockQuerySize +function mprapi.h MprInfoBlockRemove +function mprapi.h MprInfoBlockSet +function mprapi.h MprInfoCreate +function mprapi.h MprInfoDelete +function mprapi.h MprInfoDuplicate +function mprapi.h MprInfoRemoveAll + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Information Reference > Router Information Structures > IPX Information Structures +struct ipxrtdef.h IPX_IF_INFO +struct ipxrtdef.h IPXWAN_IF_INFO + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Management Structures +struct mprapi.h AUTH_VALIDATION_EX +struct mprapi.h IKEV2_CONFIG_PARAMS +struct mprapi.h IKEV2_PROJECTION_INFO +struct mprapi.h IKEV2_PROJECTION_INFO2 +struct mprapi.h IKEV2_TUNNEL_CONFIG_PARAMS1 +struct mprapi.h IKEV2_TUNNEL_CONFIG_PARAMS2 +struct mprapi.h L2TP_CONFIG_PARAMS =l2tp_config_params0 +struct mprapi.h MPR_CREDENTIALSEX_0 +struct mprapi.h MPR_CREDENTIALSEX_1 +struct mprapi.h MPR_DEVICE_0 +struct mprapi.h MPR_DEVICE_1 +struct mprapi.h MPR_FILTER_0 +struct mprapi.h MPR_IF_CUSTOMINFOEX0 +struct mprapi.h MPR_IFTRANSPORT_0 +struct mprapi.h MPR_INTERFACE_0 +struct mprapi.h MPR_INTERFACE_1 +struct mprapi.h MPR_INTERFACE_2 +struct mprapi.h MPR_INTERFACE_3 +struct mprapi.h MPR_SERVER_0 +struct mprapi.h MPR_SERVER_1 +struct mprapi.h MPR_SERVER_2 +struct mprapi.h MPR_SERVER_EX =mpr_server_ex0 +struct mprapi.h MPR_SERVER_SET_CONFIG_EX =mpr_server_set_config_ex0 +struct mprapi.h MPR_TRANSPORT_0 +struct mprapi.h MPRAPI_OBJECT_HEADER +struct mprapi.h MPRAPI_TUNNEL_CONFIG_PARAMS =mprapi_tunnel_config_params0 +struct mprapi.h PPP_PROJECTION_INFO +struct mprapi.h PPP_PROJECTION_INFO2 +struct mprapi.h PPTP_CONFIG_PARAMS +struct mprapi.h PROJECTION_INFO +struct mprapi.h PROJECTION_INFO2 +struct mprapi.h RAS_CONNECTION_4 +struct mprapi.h RAS_UPDATE_CONNECTION +struct mprapi.h ROUTER_CUSTOM_IKEV2_POLICY0 +struct mprapi.h ROUTER_IKEV2_IF_CUSTOM_CONFIG0 +struct mprapi.h SSTP_CERT_INFO +struct mprapi.h SSTP_CONFIG_PARAMS +struct routprot.h IP_ADAPTER_BINDING_INFO +struct routprot.h IP_LOCAL_BINDING +struct routprot.h IPV6_ADAPTER_BINDING_INFO +struct routprot.h IPV6_LOCAL_BINDING + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Management > Router Management Reference > Router Management Enumerations +enum mprapi.h MPRAPI_OBJECT_TYPE +enum mprapi.h ROUTER_CONNECTION_STATE +enum mprapi.h ROUTER_INTERFACE_TYPE + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing > Router Managers and Management Information Base > Router Management MIB Reference > Router Management MIB Functions +function mprapi.h MprAdminMIBBufferFree +function mprapi.h MprAdminMIBEntryCreate +function mprapi.h MprAdminMIBEntryDelete +function mprapi.h MprAdminMIBEntryGet +function mprapi.h MprAdminMIBEntryGetFirst +function mprapi.h MprAdminMIBEntryGetNext +function mprapi.h MprAdminMIBEntrySet +function mprapi.h MprAdminMIBGetTrapInfo +function mprapi.h MprAdminMIBServerConnect +function mprapi.h MprAdminMIBServerDisconnect +function mprapi.h MprAdminMIBSetTrapInfo + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Multicast Group Manager > Multicast Group Manager Reference > Multicast Group Manager Functions +function mgm.h MgmAddGroupMembershipEntry +function mgm.h MgmDeleteGroupMembershipEntry +function mgm.h MgmDeRegisterMProtocol +function mgm.h MgmGetFirstMfe +function mgm.h MgmGetFirstMfeStats +function mgm.h MgmGetMfe +function mgm.h MgmGetMfeStats +function mgm.h MgmGetNextMfe +function mgm.h MgmGetNextMfeStats +function mgm.h MgmGetProtocolOnInterface +function mgm.h MgmGroupEnumerationEnd +function mgm.h MgmGroupEnumerationGetNext +function mgm.h MgmGroupEnumerationStart +function mgm.h MgmRegisterMProtocol +function mgm.h MgmReleaseInterfaceOwnership +function mgm.h MgmSetMfe +function mgm.h MgmTakeInterfaceOwnership + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Multicast Group Manager > Multicast Group Manager Reference > Multicast Group Manager Callbacks +callback mgm.h PMGM_CREATION_ALERT_CALLBACK +callback mgm.h PMGM_DISABLE_IGMP_CALLBACK +callback mgm.h PMGM_ENABLE_IGMP_CALLBACK +callback mgm.h PMGM_JOIN_ALERT_CALLBACK +callback mgm.h PMGM_LOCAL_JOIN_CALLBACK +callback mgm.h PMGM_LOCAL_LEAVE_CALLBACK +callback mgm.h PMGM_PRUNE_ALERT_CALLBACK +callback mgm.h PMGM_RPF_CALLBACK +callback mgm.h PMGM_WRONG_IF_CALLBACK + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Multicast Group Manager > Multicast Group Manager Reference > Multicast Group Manager Structures +struct mgm.h MGM_IF_ENTRY +struct mgm.h ROUTING_PROTOCOL_CONFIG +struct mgm.h SOURCE_GROUP_ENTRY + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Multicast Group Manager > Multicast Group Manager Reference > Multicast Group Manager Enumerations +enum mgm.h MGM_ENUM_TYPES + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Protocol Interface > Routing Protocol Interface Reference > Routing Protocol Interface Functions +callback routprot.h PADD_INTERFACE +callback routprot.h PCONNECT_CLIENT +callback routprot.h PDELETE_INTERFACE +callback routprot.h PDISCONNECT_CLIENT +callback routprot.h PDO_UPDATE_ROUTES +callback routprot.h PGET_EVENT_MESSAGE +callback routprot.h PGET_GLOBAL_INFO +callback routprot.h PGET_INTERFACE_INFO +callback routprot.h PGET_MFE_STATUS +callback routprot.h PGET_NEIGHBORS +callback routprot.h PINTERFACE_STATUS +callback routprot.h PMIB_CREATE +callback routprot.h PMIB_DELETE +callback routprot.h PMIB_GET +callback routprot.h PMIB_GET_FIRST +callback routprot.h PMIB_GET_NEXT +callback routprot.h PMIB_GET_TRAP_INFO +callback routprot.h PMIB_SET +callback routprot.h PMIB_SET_TRAP_INFO +callback routprot.h PQUERY_POWER +callback routprot.h PREGISTER_PROTOCOL +callback routprot.h PSET_GLOBAL_INFO +callback routprot.h PSET_INTERFACE_INFO +callback routprot.h PSET_POWER +callback routprot.h PSTART_COMPLETE +callback routprot.h PSTART_PROTOCOL +callback routprot.h PSTOP_PROTOCOL + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Protocol Interface > Routing Protocol Interface Reference > Routing Protocol Interface Structures > Support Functions Reference +struct routprot.h SUPPORT_FUNCTIONS =support_functions_50 + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Protocol Interface > Routing Protocol Interface Reference > Routing Protocol Interface Structures +struct routprot.h MESSAGE +struct routprot.h MPR_ROUTING_CHARACTERISTICS =mpr50_routing_characteristics +struct routprot.h UPDATE_COMPLETE_MESSAGE +struct stm.h MPR_SERVICE_CHARACTERISTICS =mpr40_service_characteristics + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Protocol Interface > Routing Protocol Interface Reference > IPX Service Table Management > Service Table Management Functions +callback stm.h PBLOCK_CONVERT_SERVICES_TO_STATIC +callback stm.h PBLOCK_DELETE_STATIC_SERVICES +callback stm.h PCLOSE_SERVICE_ENUMERATION_HANDLE +callback stm.h PCREATE_SERVICE_ENUMERATION_HANDLE +callback stm.h PCREATE_STATIC_SERVICE +callback stm.h PDELETE_STATIC_SERVICE +callback stm.h PENUMERATE_GET_NEXT_SERVICE +callback stm.h PGET_FIRST_ORDERED_SERVICE +callback stm.h PGET_NEXT_ORDERED_SERVICE +callback stm.h PGET_SERVICE_COUNT +callback stm.h PIS_SERVICE + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Protocol Interface > Routing Protocol Interface Reference > IPX Service Table Management > Service Table Management Structures +struct stm.h IPX_SERVER_ENTRY +struct stm.h IPX_SERVICE + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Table Manager Version 2 > Routing Table Manager Version 2 Reference > Routing Table Manager Version 2 Functions +function rtmv2.h RtmAddNextHop +function rtmv2.h RtmAddRouteToDest +function rtmv2.h RtmBlockMethods +function rtmv2.h RtmCreateDestEnum +function rtmv2.h RtmCreateNextHopEnum +function rtmv2.h RtmCreateRouteEnum +function rtmv2.h RtmCreateRouteList +function rtmv2.h RtmCreateRouteListEnum +function rtmv2.h RtmDeleteEnumHandle +function rtmv2.h RtmDeleteNextHop +function rtmv2.h RtmDeleteRouteList +function rtmv2.h RtmDeleteRouteToDest +function rtmv2.h RtmDeregisterEntity +function rtmv2.h RtmDeregisterFromChangeNotification +function rtmv2.h RtmFindNextHop +function rtmv2.h RtmGetChangedDests +function rtmv2.h RtmGetChangeStatus +function rtmv2.h RtmGetDestInfo +function rtmv2.h RtmGetEntityInfo +function rtmv2.h RtmGetEntityMethods +function rtmv2.h RtmGetEnumDests +function rtmv2.h RtmGetEnumNextHops +function rtmv2.h RtmGetEnumRoutes +function rtmv2.h RtmGetExactMatchDestination +function rtmv2.h RtmGetExactMatchRoute +function rtmv2.h RtmGetLessSpecificDestination +function rtmv2.h RtmGetListEnumRoutes +function rtmv2.h RtmGetMostSpecificDestination +function rtmv2.h RtmGetNextHopInfo +function rtmv2.h RtmGetNextHopPointer +function rtmv2.h RtmGetOpaqueInformationPointer +function rtmv2.h RtmGetRegisteredEntities +function rtmv2.h RtmGetRouteInfo +function rtmv2.h RtmGetRoutePointer +function rtmv2.h RtmHoldDestination +function rtmv2.h RtmIgnoreChangedDests +function rtmv2.h RtmInsertInRouteList +function rtmv2.h RtmInvokeMethod +function rtmv2.h RtmIsBestRoute +function rtmv2.h RtmIsMarkedForChangeNotification +function rtmv2.h RtmLockDestination +function rtmv2.h RtmLockNextHop +function rtmv2.h RtmLockRoute +function rtmv2.h RtmMarkDestForChangeNotification +function rtmv2.h RtmReferenceHandles +function rtmv2.h RtmRegisterEntity +function rtmv2.h RtmRegisterForChangeNotification +function rtmv2.h RtmReleaseChangedDests +function rtmv2.h RtmReleaseDestInfo +function rtmv2.h RtmReleaseDests +function rtmv2.h RtmReleaseEntities +function rtmv2.h RtmReleaseEntityInfo +function rtmv2.h RtmReleaseNextHopInfo +function rtmv2.h RtmReleaseNextHops +function rtmv2.h RtmReleaseRouteInfo +function rtmv2.h RtmReleaseRoutes +function rtmv2.h RtmUpdateAndUnlockRoute + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Table Manager Version 2 > Routing Table Manager Version 2 Reference > Routing Table Manager Version 2 Callbacks +callback rtmv2.h _ENTITY_METHOD +callback rtmv2.h _EVENT_CALLBACK + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Table Manager Version 2 > Routing Table Manager Version 2 Reference > Routing Table Manager Version 2 Structures +struct rtmv2.h RTM_DEST_INFO +struct rtmv2.h RTM_ENTITY_EXPORT_METHODS +struct rtmv2.h RTM_ENTITY_ID +struct rtmv2.h RTM_ENTITY_INFO +struct rtmv2.h RTM_ENTITY_METHOD_INPUT +struct rtmv2.h RTM_ENTITY_METHOD_OUTPUT +struct rtmv2.h RTM_NET_ADDRESS +struct rtmv2.h RTM_NEXTHOP_INFO +struct rtmv2.h RTM_NEXTHOP_LIST +struct rtmv2.h RTM_PREF_INFO +struct rtmv2.h RTM_REGN_PROFILE +struct rtmv2.h RTM_ROUTE_INFO + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Table Manager Version 2 > Routing Table Manager Version 2 Reference > Routing Table Manager Version 2 Macros +function rtmv2.h RTM_IPV4_GET_ADDR_AND_LEN +function rtmv2.h RTM_IPV4_GET_ADDR_AND_MASK +function rtmv2.h RTM_IPV4_LEN_FROM_MASK +function rtmv2.h RTM_IPV4_MAKE_NET_ADDRESS +function rtmv2.h RTM_IPV4_MASK_FROM_LEN +function rtmv2.h RTM_IPV4_SET_ADDR_AND_LEN +function rtmv2.h RTM_IPV4_SET_ADDR_AND_MASK +function rtmv2.h RTM_SIZE_OF_DEST_INFO +function rtmv2.h RTM_SIZE_OF_ROUTE_INFO + +# Desktop App Technologies > Networking and Internet > Routing and Remote Access Service > Routing Protocols > Routing Table Manager Version 2 > Routing Table Manager Version 2 Reference > Routing Table Manager Version 2 Enumerations +enum rtmv2.h RTM_EVENT_TYPE + +# Desktop App Technologies > Networking and Internet > Simple Network Management Protocol > Simple Network Management Protocol (SNMP) > SNMP Reference > SNMP Functions +function mgmtapi.h SnmpMgrClose +function mgmtapi.h SnmpMgrCtl +function mgmtapi.h SnmpMgrGetTrap +function mgmtapi.h SnmpMgrGetTrapEx +function mgmtapi.h SnmpMgrOidToStr +function mgmtapi.h SnmpMgrOpen +function mgmtapi.h SnmpMgrRequest +function mgmtapi.h SnmpMgrStrToOid +function mgmtapi.h SnmpMgrTrapListen +function snmp.h SnmpExtensionClose +function snmp.h SnmpExtensionInit +function snmp.h SnmpExtensionInitEx +function snmp.h SnmpExtensionMonitor +function snmp.h SnmpExtensionQuery +function snmp.h SnmpExtensionQueryEx +function snmp.h SnmpExtensionTrap +function snmp.h SnmpSvcGetUptime +function snmp.h SnmpSvcSetLogLevel +function snmp.h SnmpSvcSetLogType +function snmp.h SnmpUtilAsnAnyCpy +function snmp.h SnmpUtilAsnAnyFree +function snmp.h SnmpUtilDbgPrint +function snmp.h SnmpUtilIdsToA +function snmp.h SnmpUtilMemAlloc +function snmp.h SnmpUtilMemFree +function snmp.h SnmpUtilMemReAlloc +function snmp.h SnmpUtilOctetsCmp +function snmp.h SnmpUtilOctetsCpy +function snmp.h SnmpUtilOctetsFree +function snmp.h SnmpUtilOctetsNCmp +function snmp.h SnmpUtilOidAppend +function snmp.h SnmpUtilOidCmp +function snmp.h SnmpUtilOidCpy +function snmp.h SnmpUtilOidFree +function snmp.h SnmpUtilOidNCmp +function snmp.h SnmpUtilOidToA +function snmp.h SnmpUtilPrintAsnAny +function snmp.h SnmpUtilPrintOid +function snmp.h SnmpUtilVarBindCpy +function snmp.h SnmpUtilVarBindFree +function snmp.h SnmpUtilVarBindListCpy +function snmp.h SnmpUtilVarBindListFree + +# Desktop App Technologies > Networking and Internet > Simple Network Management Protocol > Simple Network Management Protocol (SNMP) > SNMP Reference > SNMP Structures +struct snmp.h AsnAny +struct snmp.h AsnObjectIdentifier +struct snmp.h AsnOctetString +struct snmp.h SnmpVarBind +struct snmp.h SnmpVarBindList + +# Desktop App Technologies > Networking and Internet > Simple Network Management Protocol > WinSNMP API > WinSNMP API Reference > WinSNMP Functions +callback winsnmp.h SNMPAPI_CALLBACK +function winsnmp.h SnmpCancelMsg +function winsnmp.h SnmpCleanup +function winsnmp.h SnmpCleanupEx +function winsnmp.h SnmpClose +function winsnmp.h SnmpContextToStr +function winsnmp.h SnmpCountVbl +function winsnmp.h SnmpCreatePdu +function winsnmp.h SnmpCreateSession +function winsnmp.h SnmpCreateVbl +function winsnmp.h SnmpDecodeMsg +function winsnmp.h SnmpDeleteVb +function winsnmp.h SnmpDuplicatePdu +function winsnmp.h SnmpDuplicateVbl +function winsnmp.h SnmpEncodeMsg +function winsnmp.h SnmpEntityToStr +function winsnmp.h SnmpFreeContext +function winsnmp.h SnmpFreeDescriptor +function winsnmp.h SnmpFreeEntity +function winsnmp.h SnmpFreePdu +function winsnmp.h SnmpFreeVbl +function winsnmp.h SnmpGetLastError +function winsnmp.h SnmpGetPduData +function winsnmp.h SnmpGetRetransmitMode +function winsnmp.h SnmpGetRetry +function winsnmp.h SnmpGetTimeout +function winsnmp.h SnmpGetTranslateMode +function winsnmp.h SnmpGetVb +function winsnmp.h SnmpGetVendorInfo +function winsnmp.h SnmpListen +function winsnmp.h SnmpOidCompare +function winsnmp.h SnmpOidCopy +function winsnmp.h SnmpOidToStr +function winsnmp.h SnmpOpen +function winsnmp.h SnmpRecvMsg +function winsnmp.h SnmpRegister +function winsnmp.h SnmpSendMsg +function winsnmp.h SnmpSetPduData +function winsnmp.h SnmpSetPort +function winsnmp.h SnmpSetRetransmitMode +function winsnmp.h SnmpSetRetry +function winsnmp.h SnmpSetTimeout +function winsnmp.h SnmpSetTranslateMode +function winsnmp.h SnmpSetVb +function winsnmp.h SnmpStartup +function winsnmp.h SnmpStartupEx +function winsnmp.h SnmpStrToContext +function winsnmp.h SnmpStrToEntity +function winsnmp.h SnmpStrToOid + +# Desktop App Technologies > Networking and Internet > Simple Network Management Protocol > WinSNMP API > WinSNMP API Reference > WinSNMP Structures +struct winsnmp.h smiCNTR64 +struct winsnmp.h smiOCTETS +struct winsnmp.h smiOID +struct winsnmp.h smiVALUE +struct winsnmp.h smiVENDORINFO + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Functions > Assisted Telephony Functions +function tapi.h tapiGetLocationInfo +function tapi.h tapiRequestDrop +function tapi.h tapiRequestMakeCall + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Functions > Call Center Functions +function tapi.h lineAgentSpecific +function tapi.h lineCreateAgent =linecreateagenta +function tapi.h lineCreateAgentSession =linecreateagentsessiona +function tapi.h lineGetAgentActivityList =linegetagentactivitylista +function tapi.h lineGetAgentCaps =linegetagentcapsa +function tapi.h lineGetAgentGroupList =linegetagentgrouplista +function tapi.h lineGetAgentInfo +function tapi.h lineGetAgentSessionInfo +function tapi.h lineGetAgentSessionList +function tapi.h lineGetAgentStatus =linegetagentstatusa +function tapi.h lineGetGroupList =linegetgrouplista +function tapi.h lineGetProxyStatus +function tapi.h lineGetQueueInfo +function tapi.h lineGetQueueList =linegetqueuelista +function tapi.h lineProxyMessage +function tapi.h lineProxyResponse +function tapi.h lineSetAgentActivity +function tapi.h lineSetAgentGroup +function tapi.h lineSetAgentMeasurementPeriod +function tapi.h lineSetAgentSessionState +function tapi.h lineSetAgentState +function tapi.h lineSetAgentStateEx +function tapi.h lineSetQueueMeasurementPeriod + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Functions > Line Device Functions +callback tapi.h lineCallback +function tapi.h lineAccept +function tapi.h lineAddProvider +function tapi.h lineAddToConference +function tapi.h lineAnswer +function tapi.h lineBlindTransfer +function tapi.h lineClose +function tapi.h lineCompleteCall +function tapi.h lineCompleteTransfer +function tapi.h lineConfigDialog +function tapi.h lineConfigDialogEdit +function tapi.h lineConfigProvider +function tapi.h lineDeallocateCall +function tapi.h lineDevSpecific +function tapi.h lineDevSpecificFeature +function tapi.h lineDial +function tapi.h lineDrop +function tapi.h lineForward +function tapi.h lineGatherDigits +function tapi.h lineGenerateDigits +function tapi.h lineGenerateTone +function tapi.h lineGetAddressCaps +function tapi.h lineGetAddressID +function tapi.h lineGetAddressStatus +function tapi.h lineGetAppPriority +function tapi.h lineGetCallInfo +function tapi.h lineGetCallStatus +function tapi.h lineGetConfRelatedCalls +function tapi.h lineGetCountry +function tapi.h lineGetDevCaps +function tapi.h lineGetDevConfig +function tapi.h lineGetIcon +function tapi.h lineGetID +function tapi.h lineGetLineDevStatus +function tapi.h lineGetMessage +function tapi.h lineGetNewCalls +function tapi.h lineGetNumRings +function tapi.h lineGetProviderList +function tapi.h lineGetRequest +function tapi.h lineGetStatusMessages +function tapi.h lineGetTranslateCaps +function tapi.h lineHandoff +function tapi.h lineHold +function tapi.h lineInitialize +function tapi.h lineInitializeEx =lineinitializeexa +function tapi.h lineMakeCall +function tapi.h lineMonitorDigits +function tapi.h lineMonitorMedia +function tapi.h lineMonitorTones +function tapi.h lineNegotiateAPIVersion +function tapi.h lineNegotiateExtVersion +function tapi.h lineOpen +function tapi.h linePark +function tapi.h linePickup +function tapi.h linePrepareAddToConference +function tapi.h lineRedirect +function tapi.h lineRegisterRequestRecipient +function tapi.h lineReleaseUserUserInfo +function tapi.h lineRemoveFromConference +function tapi.h lineRemoveProvider +function tapi.h lineSecureCall +function tapi.h lineSendUserUserInfo +function tapi.h lineSetAppPriority +function tapi.h lineSetAppSpecific +function tapi.h lineSetCallData +function tapi.h lineSetCallParams +function tapi.h lineSetCallPrivilege +function tapi.h lineSetCallQualityOfService +function tapi.h lineSetCallTreatment +function tapi.h lineSetCurrentLocation +function tapi.h lineSetDevConfig +function tapi.h lineSetLineDevStatus +function tapi.h lineSetMediaControl +function tapi.h lineSetMediaMode +function tapi.h lineSetNumRings +function tapi.h lineSetStatusMessages +function tapi.h lineSetTerminal +function tapi.h lineSetTollList +function tapi.h lineSetupConference +function tapi.h lineSetupTransfer +function tapi.h lineShutdown +function tapi.h lineSwapHold +function tapi.h lineTranslateAddress +function tapi.h lineTranslateDialog +function tapi.h lineUncompleteCall +function tapi.h lineUnhold +function tapi.h lineUnpark + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Functions > Phone Device Functions +callback tapi.h phoneCallback +function tapi.h phoneClose +function tapi.h phoneConfigDialog +function tapi.h phoneDevSpecific +function tapi.h phoneGetButtonInfo +function tapi.h phoneGetData +function tapi.h phoneGetDevCaps +function tapi.h phoneGetDisplay +function tapi.h phoneGetGain +function tapi.h phoneGetHookSwitch +function tapi.h phoneGetIcon +function tapi.h phoneGetID +function tapi.h phoneGetLamp +function tapi.h phoneGetMessage +function tapi.h phoneGetRing +function tapi.h phoneGetStatus +function tapi.h phoneGetStatusMessages +function tapi.h phoneGetVolume +function tapi.h phoneInitialize +function tapi.h phoneInitializeEx =phoneinitializeexa +function tapi.h phoneNegotiateAPIVersion +function tapi.h phoneNegotiateExtVersion +function tapi.h phoneOpen +function tapi.h phoneSetButtonInfo +function tapi.h phoneSetData +function tapi.h phoneSetDisplay +function tapi.h phoneSetGain +function tapi.h phoneSetHookSwitch +function tapi.h phoneSetLamp +function tapi.h phoneSetRing +function tapi.h phoneSetStatusMessages +function tapi.h phoneSetVolume +function tapi.h phoneShutdown + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Structures > Call Center Structures +struct tapi.h LINEAGENTACTIVITYENTRY +struct tapi.h LINEAGENTACTIVITYLIST +struct tapi.h LINEAGENTCAPS +struct tapi.h LINEAGENTENTRY +struct tapi.h LINEAGENTGROUPENTRY +struct tapi.h LINEAGENTGROUPLIST +struct tapi.h LINEAGENTINFO +struct tapi.h LINEAGENTLIST +struct tapi.h LINEAGENTSESSIONENTRY +struct tapi.h LINEAGENTSESSIONINFO +struct tapi.h LINEAGENTSESSIONLIST +struct tapi.h LINEAGENTSTATUS +struct tapi.h LINEPROXYREQUEST +struct tapi.h LINEPROXYREQUESTLIST +struct tapi.h LINEQUEUEENTRY +struct tapi.h LINEQUEUEINFO +struct tapi.h LINEQUEUELIST + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Structures > Line Device Structures +struct tapi.h LINEADDRESSCAPS +struct tapi.h LINEADDRESSSTATUS +struct tapi.h LINEAPPINFO +struct tapi.h LINECALLHUBTRACKINGINFO +struct tapi.h LINECALLINFO +struct tapi.h LINECALLLIST +struct tapi.h LINECALLPARAMS +struct tapi.h LINECALLSTATUS +struct tapi.h LINECALLTREATMENTENTRY +struct tapi.h LINECARDENTRY +struct tapi.h LINECOUNTRYENTRY +struct tapi.h LINECOUNTRYLIST +struct tapi.h LINEDEVCAPS +struct tapi.h LINEDEVSTATUS +struct tapi.h LINEDIALPARAMS +struct tapi.h LINEEXTENSIONID +struct tapi.h LINEFORWARD +struct tapi.h LINEFORWARDLIST +struct tapi.h LINEGENERATETONE +struct tapi.h LINEINITIALIZEEXPARAMS +struct tapi.h LINELOCATIONENTRY +struct tapi.h LINEMEDIACONTROLCALLSTATE +struct tapi.h LINEMEDIACONTROLDIGIT +struct tapi.h LINEMEDIACONTROLMEDIA +struct tapi.h LINEMEDIACONTROLTONE +struct tapi.h LINEMESSAGE +struct tapi.h LINEMONITORTONE +struct tapi.h LINEPROVIDERENTRY +struct tapi.h LINEPROVIDERLIST +struct tapi.h LINEREQMAKECALL +struct tapi.h LINEREQMEDIACALL +struct tapi.h LINETERMCAPS +struct tapi.h LINETRANSLATECAPS +struct tapi.h LINETRANSLATEOUTPUT + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Structures > Phone Device Structures +struct tapi.h PHONEBUTTONINFO +struct tapi.h PHONECAPS +struct tapi.h PHONEEXTENSIONID +struct tapi.h PHONEINITIALIZEEXPARAMS +struct tapi.h PHONEMESSAGE +struct tapi.h PHONESTATUS +struct tapi.h VARSTRING + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 2.2 > TAPI 2.2 Reference > TAPI Messages > Formatted Error Messages +function tapi.h TAPIERROR_FORMATMESSAGE + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Call and Media Controls > Call and Media Controls Reference > Call and Media Controls Enumerations +callback tspi.h PHONE_EVENT +enum tapi3if.h ADDRESS_CAPABILITY +enum tapi3if.h ADDRESS_CAPABILITY_STRING +enum tapi3if.h ADDRESS_EVENT +enum tapi3if.h ADDRESS_STATE +enum tapi3if.h CALL_MEDIA_EVENT +enum tapi3if.h CALL_MEDIA_EVENT_CAUSE +enum tapi3if.h CALL_NOTIFICATION_EVENT +enum tapi3if.h CALL_PRIVILEGE +enum tapi3if.h CALL_STATE +enum tapi3if.h CALL_STATE_EVENT_CAUSE +enum tapi3if.h CALLHUB_EVENT +enum tapi3if.h CALLHUB_STATE +enum tapi3if.h CALLINFO_BUFFER +enum tapi3if.h CALLINFO_LONG +enum tapi3if.h CALLINFO_STRING +enum tapi3if.h CALLINFOCHANGE_CAUSE +enum tapi3if.h DISCONNECT_CODE +enum tapi3if.h FINISH_MODE +enum tapi3if.h FT_STATE_EVENT_CAUSE +enum tapi3if.h FULLDUPLEX_SUPPORT +enum tapi3if.h PHONE_BUTTON_FUNCTION +enum tapi3if.h PHONE_BUTTON_MODE +enum tapi3if.h PHONE_BUTTON_STATE +enum tapi3if.h PHONE_HOOK_SWITCH_DEVICE +enum tapi3if.h PHONE_HOOK_SWITCH_STATE +enum tapi3if.h PHONE_LAMP_MODE +enum tapi3if.h PHONE_PRIVILEGE +enum tapi3if.h PHONE_TONE +enum tapi3if.h PHONECAPS_BUFFER +enum tapi3if.h PHONECAPS_LONG +enum tapi3if.h PHONECAPS_STRING +enum tapi3if.h QOS_EVENT +enum tapi3if.h QOS_SERVICE_LEVEL +enum tapi3if.h TAPI_EVENT +enum tapi3if.h TAPI_GATHERTERM +enum tapi3if.h TAPI_OBJECT_TYPE +enum tapi3if.h TAPI_TONEMODE +enum tapi3if.h TAPIOBJECT_EVENT +enum tapi3if.h TERMINAL_DIRECTION +enum tapi3if.h TERMINAL_MEDIA_STATE +enum tapi3if.h TERMINAL_STATE +enum tapi3if.h TERMINAL_TYPE +enum termmgr.h TMGR_DIRECTION + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Call and Media Controls > Call and Media Controls Reference > Call and Media Controls Structures +struct tapi3if.h TAPI_CUSTOMTONE +struct tapi3if.h TAPI_DETECTTONE + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Call and Media Controls > Call and Media Controls Reference > Call and Media Control Interfaces +interface msp.h ITPluggableTerminalEventSink +interface msp.h ITPluggableTerminalEventSinkRegistration +interface tapi3.h ITAllocatorProperties +interface tapi3.h ITAMMediaFormat +interface tapi3cc.h ITTAPICallCenter +interface tapi3if.h IEnumAddress +interface tapi3if.h IEnumBstr +interface tapi3if.h IEnumCall +interface tapi3if.h IEnumCallHub +interface tapi3if.h IEnumCallingCard +interface tapi3if.h IEnumLocation +interface tapi3if.h IEnumPhone +interface tapi3if.h IEnumPluggableSuperclassInfo +interface tapi3if.h IEnumPluggableTerminalClassInfo +interface tapi3if.h IEnumTerminal +interface tapi3if.h IEnumTerminalClass +interface tapi3if.h ITAddress +interface tapi3if.h ITAddress2 +interface tapi3if.h ITAddressCapabilities +interface tapi3if.h ITAddressDeviceSpecificEvent +interface tapi3if.h ITAddressEvent +interface tapi3if.h ITAddressTranslation +interface tapi3if.h ITAddressTranslationInfo +interface tapi3if.h ITASRTerminalEvent +interface tapi3if.h ITAutomatedPhoneControl +interface tapi3if.h ITBasicAudioTerminal +interface tapi3if.h ITBasicCallControl +interface tapi3if.h ITBasicCallControl2 +interface tapi3if.h ITCallHub +interface tapi3if.h ITCallHubEvent +interface tapi3if.h ITCallInfo +interface tapi3if.h ITCallInfo2 +interface tapi3if.h ITCallInfoChangeEvent +interface tapi3if.h ITCallingCard +interface tapi3if.h ITCallMediaEvent +interface tapi3if.h ITCallNotificationEvent +interface tapi3if.h ITCallStateEvent +interface tapi3if.h ITCollection +interface tapi3if.h ITCollection2 +interface tapi3if.h ITCustomTone +interface tapi3if.h ITDetectTone +interface tapi3if.h ITDigitDetectionEvent +interface tapi3if.h ITDigitGenerationEvent +interface tapi3if.h ITDigitsGatheredEvent +interface tapi3if.h ITDispatchMapper +interface tapi3if.h ITFileTerminalEvent +interface tapi3if.h ITFileTrack +interface tapi3if.h ITForwardInformation +interface tapi3if.h ITForwardInformation2 +interface tapi3if.h ITLegacyAddressMediaControl +interface tapi3if.h ITLegacyAddressMediaControl2 +interface tapi3if.h ITLegacyCallMediaControl +interface tapi3if.h ITLegacyCallMediaControl2 +interface tapi3if.h ITLegacyWaveSupport +interface tapi3if.h ITLocationInfo +interface tapi3if.h ITMediaControl +interface tapi3if.h ITMediaPlayback +interface tapi3if.h ITMediaRecord +interface tapi3if.h ITMediaSupport +interface tapi3if.h ITMultiTrackTerminal +interface tapi3if.h ITPhone +interface tapi3if.h ITPhoneDeviceSpecificEvent +interface tapi3if.h ITPhoneEvent +interface tapi3if.h ITPluggableTerminalClassInfo +interface tapi3if.h ITPluggableTerminalSuperclassInfo +interface tapi3if.h ITPrivateEvent +interface tapi3if.h ITQOSEvent +interface tapi3if.h ITRequest +interface tapi3if.h ITRequestEvent +interface tapi3if.h ITScriptableAudioFormat +interface tapi3if.h ITStaticAudioTerminal +interface tapi3if.h ITTAPI +interface tapi3if.h ITTAPI2 +interface tapi3if.h ITTAPIEventNotification +interface tapi3if.h ITTAPIObjectEvent +interface tapi3if.h ITTAPIObjectEvent2 +interface tapi3if.h ITToneDetectionEvent +interface tapi3if.h ITToneTerminalEvent +interface tapi3if.h ITTTSTerminalEvent +interface termmgr.h ITPluggableTerminalClassRegistration +interface termmgr.h ITPluggableTerminalInitialization +interface termmgr.h ITPluggableTerminalSuperclassRegistration + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Call Center Controls > Call Center Controls Reference > Call Center Controls Interfaces +interface tapi3.h IEnumACDGroup +interface tapi3.h IEnumAgent +interface tapi3.h IEnumAgentHandler +interface tapi3.h IEnumAgentSession +interface tapi3.h IEnumQueue +interface tapi3.h ITACDGroup +interface tapi3.h ITACDGroupEvent +interface tapi3.h ITAgent +interface tapi3.h ITAgentEvent +interface tapi3.h ITAgentHandler +interface tapi3.h ITAgentHandlerEvent +interface tapi3.h ITAgentSession +interface tapi3.h ITAgentSessionEvent +interface tapi3cc.h ITQueue +interface tapi3cc.h ITQueueEvent + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Call Center Controls > Call Center Controls Reference > Call Center Controls Enumerations +enum tapi3.h ACDGROUP_EVENT +enum tapi3.h ACDQUEUE_EVENT +enum tapi3.h AGENT_EVENT +enum tapi3.h AGENT_SESSION_EVENT +enum tapi3.h AGENT_SESSION_STATE +enum tapi3.h AGENT_STATE +enum tapi3.h AGENTHANDLER_EVENT + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Rendezvous IP Telephony Conferencing > Rendezvous IP Telephony Conferencing Reference > Rendezvous COM Interfaces +interface mdhcp.h IEnumMcastScope +interface mdhcp.h IMcastAddressAllocation +interface mdhcp.h IMcastLeaseInfo +interface mdhcp.h IMcastScope +interface rend.h IEnumDialableAddrs +interface rend.h IEnumDirectory +interface rend.h IEnumDirectoryObject +interface rend.h ITDirectory +interface rend.h ITDirectoryObject +interface rend.h ITDirectoryObjectConference +interface rend.h ITDirectoryObjectUser +interface rend.h ITILSConfig +interface rend.h ITRendezvous + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI 3.1 > Rendezvous IP Telephony Conferencing > Rendezvous IP Telephony Conferencing Reference > Rendezvous Enumerations +enum rend.h DIRECTORY_OBJECT_TYPE +enum rend.h DIRECTORY_TYPE +enum rend.h RND_ADVERTISING_SCOPE + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TSPI Service Provider Functions +function tspi.h TSPI_providerConfig +function tspi.h TSPI_providerCreateLineDevice +function tspi.h TSPI_providerCreatePhoneDevice +function tspi.h TSPI_providerEnumDevices +function tspi.h TSPI_providerInit +function tspi.h TSPI_providerInstall +function tspi.h TSPI_providerRemove +function tspi.h TSPI_providerShutdown + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TSPI Line Device Functions +function tspi.h TSPI_lineAccept +function tspi.h TSPI_lineAddToConference +function tspi.h TSPI_lineAnswer +function tspi.h TSPI_lineBlindTransfer +function tspi.h TSPI_lineClose +function tspi.h TSPI_lineCloseCall +function tspi.h TSPI_lineCloseMSPInstance +function tspi.h TSPI_lineCompleteCall +function tspi.h TSPI_lineCompleteTransfer +function tspi.h TSPI_lineConditionalMediaDetection +function tspi.h TSPI_lineConfigDialog +function tspi.h TSPI_lineConfigDialogEdit +function tspi.h TSPI_lineCreateMSPInstance +function tspi.h TSPI_lineDevSpecific +function tspi.h TSPI_lineDevSpecificFeature +function tspi.h TSPI_lineDial +function tspi.h TSPI_lineDrop +function tspi.h TSPI_lineForward +function tspi.h TSPI_lineGatherDigits +function tspi.h TSPI_lineGenerateDigits +function tspi.h TSPI_lineGenerateTone +function tspi.h TSPI_lineGetAddressCaps +function tspi.h TSPI_lineGetAddressID +function tspi.h TSPI_lineGetAddressStatus +function tspi.h TSPI_lineGetCallAddressID +function tspi.h TSPI_lineGetCallHubTracking +function tspi.h TSPI_lineGetCallIDs +function tspi.h TSPI_lineGetCallInfo +function tspi.h TSPI_lineGetCallStatus +function tspi.h TSPI_lineGetDevCaps +function tspi.h TSPI_lineGetDevConfig +function tspi.h TSPI_lineGetExtensionID +function tspi.h TSPI_lineGetIcon +function tspi.h TSPI_lineGetID +function tspi.h TSPI_lineGetLineDevStatus +function tspi.h TSPI_lineGetNumAddressIDs +function tspi.h TSPI_lineHold +function tspi.h TSPI_lineMakeCall +function tspi.h TSPI_lineMonitorDigits +function tspi.h TSPI_lineMonitorMedia +function tspi.h TSPI_lineMonitorTones +function tspi.h TSPI_lineMSPIdentify +function tspi.h TSPI_lineNegotiateExtVersion +function tspi.h TSPI_lineNegotiateTSPIVersion +function tspi.h TSPI_lineOpen +function tspi.h TSPI_linePark +function tspi.h TSPI_linePickup +function tspi.h TSPI_linePrepareAddToConference +function tspi.h TSPI_lineReceiveMSPData +function tspi.h TSPI_lineRedirect +function tspi.h TSPI_lineReleaseUserUserInfo +function tspi.h TSPI_lineRemoveFromConference +function tspi.h TSPI_lineSecureCall +function tspi.h TSPI_lineSelectExtVersion +function tspi.h TSPI_lineSendUserUserInfo +function tspi.h TSPI_lineSetAppSpecific +function tspi.h TSPI_lineSetCallData +function tspi.h TSPI_lineSetCallHubTracking +function tspi.h TSPI_lineSetCallParams +function tspi.h TSPI_lineSetCallQualityOfService +function tspi.h TSPI_lineSetCallTreatment +function tspi.h TSPI_lineSetDefaultMediaDetection +function tspi.h TSPI_lineSetDevConfig +function tspi.h TSPI_lineSetLineDevStatus +function tspi.h TSPI_lineSetMediaControl +function tspi.h TSPI_lineSetMediaMode +function tspi.h TSPI_lineSetStatusMessages +function tspi.h TSPI_lineSetTerminal +function tspi.h TSPI_lineSetupConference +function tspi.h TSPI_lineSetupTransfer +function tspi.h TSPI_lineSwapHold +function tspi.h TSPI_lineUncompleteCall +function tspi.h TSPI_lineUnhold +function tspi.h TSPI_lineUnpark + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TSPI Phone Device Functions +function tspi.h TSPI_phoneClose +function tspi.h TSPI_phoneConfigDialog +function tspi.h TSPI_phoneDevSpecific +function tspi.h TSPI_phoneGetButtonInfo +function tspi.h TSPI_phoneGetData +function tspi.h TSPI_phoneGetDevCaps +function tspi.h TSPI_phoneGetDisplay +function tspi.h TSPI_phoneGetExtensionID +function tspi.h TSPI_phoneGetGain +function tspi.h TSPI_phoneGetHookSwitch +function tspi.h TSPI_phoneGetIcon +function tspi.h TSPI_phoneGetID +function tspi.h TSPI_phoneGetLamp +function tspi.h TSPI_phoneGetRing +function tspi.h TSPI_phoneGetStatus +function tspi.h TSPI_phoneGetVolume +function tspi.h TSPI_phoneNegotiateExtVersion +function tspi.h TSPI_phoneNegotiateTSPIVersion +function tspi.h TSPI_phoneOpen +function tspi.h TSPI_phoneSelectExtVersion +function tspi.h TSPI_phoneSetButtonInfo +function tspi.h TSPI_phoneSetData +function tspi.h TSPI_phoneSetDisplay +function tspi.h TSPI_phoneSetGain +function tspi.h TSPI_phoneSetHookSwitch +function tspi.h TSPI_phoneSetLamp +function tspi.h TSPI_phoneSetRing +function tspi.h TSPI_phoneSetStatusMessages +function tspi.h TSPI_phoneSetVolume + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TSPI Callback Functions +callback tspi.h Completion_Proc =async_completion +callback tspi.h Line_Event +enum tapi3if.h Phone_Event + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TSP UI DLL Functions +function tspi.h TSPI_providerFreeDialogInstance +function tspi.h TSPI_providerGenericDialogData +function tspi.h TSPI_providerUIIdentify + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TUISPI Service Provider Functions +function tspi.h TUISPI_providerConfig +function tspi.h TUISPI_providerGenericDialog +function tspi.h TUISPI_providerGenericDialogData +function tspi.h TUISPI_providerInstall +function tspi.h TUISPI_providerRemove + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TUISPI Line Device Functions +function tspi.h TUISPI_lineConfigDialog +function tspi.h TUISPI_lineConfigDialogEdit + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TUISPI Phone Device Functions +function tspi.h TUISPI_phoneConfigDialog + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Functions > TUISPI Callback Functions +callback tspi.h TUISPIDLLCALLBACK + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Telephony Service Providers > TSPI Reference > TSPI Structures > TUISPI Data Structures +struct tspi.h TUISPICREATEDIALOGINSTANCEPARAMS + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Media Service Providers > Writing a Media Service Provider > TAPI 3 MSP Base Classes Reference +class mspaddr.h CMSPAddress +class mspcall.h CMSPCallBase +class mspcall.h CMSPCallMultiGraph +class mspstrm.h CMSPStream + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Media Service Providers > Writing a Media Service Provider > TAPI 3 Terminal Manager > TAPI 3 Terminal Manager Reference +interface termmgr.h ITTerminalControl +interface termmgr.h ITTerminalManager +interface termmgr.h ITTerminalManager2 + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Media Service Providers > Media Service Provider Interface (MSPI) Reference > Media Service Provider Types +enum msp.h MSP_ADDRESS_EVENT +enum msp.h MSP_CALL_EVENT +enum msp.h MSP_CALL_EVENT_CAUSE +enum msp.h MSP_EVENT +struct msp.h MSP_EVENT_INFO + +# Desktop App Technologies > Networking and Internet > Telephony Application Programming Interfaces > TAPI Service Providers > Media Service Providers > Media Service Provider Interface (MSPI) Reference +interface msp.h ITMSPAddress +interface tapi3if.h IEnumStream +interface tapi3if.h IEnumSubStream +interface tapi3if.h ITStream +interface tapi3if.h ITStreamControl +interface tapi3if.h ITSubStream +interface tapi3if.h ITSubStreamControl +interface tapi3if.h ITTerminal +interface tapi3if.h ITTerminalSupport +interface tapi3if.h ITTerminalSupport2 + +# Desktop App Technologies > Networking and Internet > WebSocket Protocol Component API > WebSocket Protocol Component API Enumerations +enum websocket.h WEB_SOCKET_ACTION +enum websocket.h WEB_SOCKET_ACTION_QUEUE +enum websocket.h WEB_SOCKET_BUFFER_TYPE +enum websocket.h WEB_SOCKET_CLOSE_STATUS +enum websocket.h WEB_SOCKET_PROPERTY_TYPE + +# Desktop App Technologies > Networking and Internet > WebSocket Protocol Component API > WebSocket Protocol Component API Functions +function websocket.h WebSocketAbortHandle +function websocket.h WebSocketBeginClientHandshake +function websocket.h WebSocketBeginServerHandshake +function websocket.h WebSocketCompleteAction +function websocket.h WebSocketCreateClientHandle +function websocket.h WebSocketCreateServerHandle +function websocket.h WebSocketDeleteHandle +function websocket.h WebSocketEndClientHandshake +function websocket.h WebSocketEndServerHandshake +function websocket.h WebSocketGetAction +function websocket.h WebSocketGetGlobalProperty +function websocket.h WebSocketReceive +function websocket.h WebSocketSend + +# Desktop App Technologies > Networking and Internet > WebSocket Protocol Component API > WebSocket Protocol Component API Structures +struct websocket.h WEB_SOCKET_BUFFER +struct websocket.h WEB_SOCKET_HTTP_HEADER +struct websocket.h WEB_SOCKET_PROPERTY + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP enumerated types > IKE/AuthIP Types +enum iketypes.h IKEEXT_AUTHENTICATION_IMPERSONATION_TYPE +enum iketypes.h IKEEXT_AUTHENTICATION_METHOD_TYPE +enum iketypes.h IKEEXT_CERT_CONFIG_TYPE +enum iketypes.h IKEEXT_CERT_CRITERIA_NAME_TYPE +enum iketypes.h IKEEXT_CIPHER_TYPE +enum iketypes.h IKEEXT_DH_GROUP +enum iketypes.h IKEEXT_EM_SA_STATE +enum iketypes.h IKEEXT_INTEGRITY_TYPE +enum iketypes.h IKEEXT_KEY_MODULE_TYPE +enum iketypes.h IKEEXT_MM_SA_STATE +enum iketypes.h IKEEXT_QM_SA_STATE +enum iketypes.h IKEEXT_SA_ROLE + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP enumerated types > IPsec Types +enum ipsectypes.h IPSEC_AUTH_TYPE +enum ipsectypes.h IPSEC_CIPHER_TYPE +enum ipsectypes.h IPSEC_FAILURE_POINT +enum ipsectypes.h IPSEC_PFS_GROUP +enum ipsectypes.h IPSEC_SA_CONTEXT_EVENT_TYPE0 +enum ipsectypes.h IPSEC_TOKEN_MODE +enum ipsectypes.h IPSEC_TOKEN_PRINCIPAL +enum ipsectypes.h IPSEC_TOKEN_TYPE +enum ipsectypes.h IPSEC_TRAFFIC_TYPE +enum ipsectypes.h IPSEC_TRANSFORM_TYPE + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP enumerated types > Management Types +enum fwpmtypes.h DL_ADDRESS_TYPE +enum fwpmtypes.h FWPM_APPC_NETWORK_CAPABILITY_TYPE +enum fwpmtypes.h FWPM_CHANGE_TYPE +enum fwpmtypes.h FWPM_CONNECTION_EVENT_TYPE +enum fwpmtypes.h FWPM_ENGINE_OPTION +enum fwpmtypes.h FWPM_FIELD_TYPE +enum fwpmtypes.h FWPM_NET_EVENT_TYPE +enum fwpmtypes.h FWPM_PROVIDER_CONTEXT_TYPE +enum fwpmtypes.h FWPM_SERVICE_STATE +enum fwpmtypes.h FWPM_SYSTEM_PORT_TYPE +enum fwpmtypes.h FWPM_VSWITCH_EVENT_TYPE + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP enumerated types > Shared Types +enum fwptypes.h FWP_AF +enum fwptypes.h FWP_CLASSIFY_OPTION_TYPE +enum fwptypes.h FWP_DATA_TYPE +enum fwptypes.h FWP_DIRECTION +enum fwptypes.h FWP_ETHER_ENCAP_METHOD +enum fwptypes.h FWP_FILTER_ENUM_TYPE +enum fwptypes.h FWP_IP_VERSION +enum fwptypes.h FWP_MATCH_TYPE +enum fwptypes.h FWP_VSWITCH_NETWORK_TYPE + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > IKE/AuthIP Functions +function fwpmu.h IkeextGetStatistics0 +function fwpmu.h IkeextGetStatistics1 +function fwpmu.h IkeextSaCreateEnumHandle0 +function fwpmu.h IkeextSaDbGetSecurityInfo0 +function fwpmu.h IkeextSaDbSetSecurityInfo0 +function fwpmu.h IkeextSaDeleteById0 +function fwpmu.h IkeextSaDestroyEnumHandle0 +function fwpmu.h IkeextSaEnum0 +function fwpmu.h IkeextSaEnum1 +function fwpmu.h IkeextSaEnum2 +function fwpmu.h IkeextSaGetById0 +function fwpmu.h IkeextSaGetById1 +function fwpmu.h IkeextSaGetById2 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > IPsec Functions +callback fwpmu.h IPSEC_KEY_MANAGER_DICTATE_KEY0 +callback fwpmu.h IPSEC_KEY_MANAGER_KEY_DICTATION_CHECK0 +callback fwpmu.h IPSEC_KEY_MANAGER_NOTIFY_KEY0 +callback fwpmu.h IPSEC_SA_CONTEXT_CALLBACK0 +function fwpmu.h FwpmIPsecTunnelAdd0 +function fwpmu.h FwpmIPsecTunnelAdd1 +function fwpmu.h FwpmIPsecTunnelAdd2 +function fwpmu.h FwpmIPsecTunnelDeleteByKey0 +function fwpmu.h IPsecDospGetSecurityInfo0 +function fwpmu.h IPsecDospGetStatistics0 +function fwpmu.h IPsecDospSetSecurityInfo0 +function fwpmu.h IPsecDospStateCreateEnumHandle0 +function fwpmu.h IPsecDospStateDestroyEnumHandle0 +function fwpmu.h IPsecDospStateEnum0 +function fwpmu.h IPsecGetStatistics0 +function fwpmu.h IPsecGetStatistics1 +function fwpmu.h IPsecKeyManagerAddAndRegister0 +function fwpmu.h IPsecKeyManagerGetSecurityInfoByKey0 +function fwpmu.h IPsecKeyManagerSetSecurityInfoByKey0 +function fwpmu.h IPsecKeyManagersGet0 +function fwpmu.h IPsecKeyManagerUnregisterAndDelete0 +function fwpmu.h IPsecSaContextAddInbound0 +function fwpmu.h IPsecSaContextAddInbound1 +function fwpmu.h IPsecSaContextAddOutbound0 +function fwpmu.h IPsecSaContextAddOutbound1 +function fwpmu.h IPsecSaContextCreate0 +function fwpmu.h IPsecSaContextCreate1 +function fwpmu.h IPsecSaContextCreateEnumHandle0 +function fwpmu.h IPsecSaContextDeleteById0 +function fwpmu.h IPsecSaContextDestroyEnumHandle0 +function fwpmu.h IPsecSaContextEnum0 +function fwpmu.h IPsecSaContextEnum1 +function fwpmu.h IPsecSaContextExpire0 +function fwpmu.h IPsecSaContextGetById0 +function fwpmu.h IPsecSaContextGetById1 +function fwpmu.h IPsecSaContextGetSpi0 +function fwpmu.h IPsecSaContextGetSpi1 +function fwpmu.h IPsecSaContextSetSpi0 +function fwpmu.h IPsecSaContextSubscribe0 +function fwpmu.h IPsecSaContextSubscriptionsGet0 +function fwpmu.h IPsecSaContextUnsubscribe0 +function fwpmu.h IPsecSaContextUpdate0 +function fwpmu.h IPsecSaCreateEnumHandle0 +function fwpmu.h IPsecSaDbGetSecurityInfo0 +function fwpmu.h IPsecSaDbSetSecurityInfo0 +function fwpmu.h IPsecSaDestroyEnumHandle0 +function fwpmu.h IPsecSaEnum0 +function fwpmu.h IPsecSaEnum1 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Callout Management +callback fwpmu.h FWPM_CALLOUT_CHANGE_CALLBACK0 +function fwpmu.h FwpmCalloutAdd0 +function fwpmu.h FwpmCalloutCreateEnumHandle0 +function fwpmu.h FwpmCalloutDeleteById0 +function fwpmu.h FwpmCalloutDeleteByKey0 +function fwpmu.h FwpmCalloutDestroyEnumHandle0 +function fwpmu.h FwpmCalloutEnum0 +function fwpmu.h FwpmCalloutGetById0 +function fwpmu.h FwpmCalloutGetByKey0 +function fwpmu.h FwpmCalloutGetSecurityInfoByKey0 +function fwpmu.h FwpmCalloutSetSecurityInfoByKey0 +function fwpmu.h FwpmCalloutSubscribeChanges0 +function fwpmu.h FwpmCalloutSubscriptionsGet0 +function fwpmu.h FwpmCalloutUnsubscribeChanges0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Connection Object Management +callback fwpmu.h FWPM_CONNECTION_CALLBACK0 +function fwpmu.h FwpmConnectionCreateEnumHandle0 +function fwpmu.h FwpmConnectionDestroyEnumHandle0 +function fwpmu.h FwpmConnectionEnum0 +function fwpmu.h FwpmConnectionGetById0 +function fwpmu.h FwpmConnectionGetSecurityInfo0 +function fwpmu.h FwpmConnectionSetSecurityInfo0 +function fwpmu.h FwpmConnectionSubscribe0 +function fwpmu.h FwpmConnectionSubscriptionsGet0 +function fwpmu.h FwpmConnectionUnsubscribe0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Event Management +callback fwpmu.h FWPM_NET_EVENT_CALLBACK0 +callback fwpmu.h FWPM_NET_EVENT_CALLBACK1 +callback fwpmu.h FWPM_NET_EVENT_CALLBACK2 +function fwpmu.h FwpmNetEventCreateEnumHandle0 +function fwpmu.h FwpmNetEventDestroyEnumHandle0 +function fwpmu.h FwpmNetEventEnum0 +function fwpmu.h FwpmNetEventEnum1 +function fwpmu.h FwpmNetEventEnum2 +function fwpmu.h FwpmNetEventEnum3 +function fwpmu.h FwpmNetEventsGetSecurityInfo0 +function fwpmu.h FwpmNetEventsSetSecurityInfo0 +function fwpmu.h FwpmNetEventSubscribe0 +function fwpmu.h FwpmNetEventSubscribe1 +function fwpmu.h FwpmNetEventSubscribe2 +function fwpmu.h FwpmNetEventSubscriptionsGet0 +function fwpmu.h FwpmNetEventUnsubscribe0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Filter Management +callback fwpmu.h FWPM_FILTER_CHANGE_CALLBACK0 +function fwpmu.h FwpmFilterAdd0 +function fwpmu.h FwpmFilterCreateEnumHandle0 +function fwpmu.h FwpmFilterDeleteById0 +function fwpmu.h FwpmFilterDeleteByKey0 +function fwpmu.h FwpmFilterDestroyEnumHandle0 +function fwpmu.h FwpmFilterEnum0 +function fwpmu.h FwpmFilterGetById0 +function fwpmu.h FwpmFilterGetByKey0 +function fwpmu.h FwpmFilterGetSecurityInfoByKey0 +function fwpmu.h FwpmFilterSetSecurityInfoByKey0 +function fwpmu.h FwpmFilterSubscribeChanges0 +function fwpmu.h FwpmFilterSubscriptionsGet0 +function fwpmu.h FwpmFilterUnsubscribeChanges0 +function fwpmu.h FwpmGetAppIdFromFileName0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Layer Management +function fwpmu.h FwpmLayerCreateEnumHandle0 +function fwpmu.h FwpmLayerDestroyEnumHandle0 +function fwpmu.h FwpmLayerEnum0 +function fwpmu.h FwpmLayerGetById0 +function fwpmu.h FwpmLayerGetByKey0 +function fwpmu.h FwpmLayerGetSecurityInfoByKey0 +function fwpmu.h FwpmLayerSetSecurityInfoByKey0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Provider Context Management +callback fwpmu.h FWPM_PROVIDER_CONTEXT_CHANGE_CALLBACK0 +function fwpmu.h FwpmProviderContextAdd0 +function fwpmu.h FwpmProviderContextAdd1 +function fwpmu.h FwpmProviderContextAdd2 +function fwpmu.h FwpmProviderContextCreateEnumHandle0 +function fwpmu.h FwpmProviderContextDeleteById0 +function fwpmu.h FwpmProviderContextDeleteByKey0 +function fwpmu.h FwpmProviderContextDestroyEnumHandle0 +function fwpmu.h FwpmProviderContextEnum0 +function fwpmu.h FwpmProviderContextEnum1 +function fwpmu.h FwpmProviderContextEnum2 +function fwpmu.h FwpmProviderContextGetById0 +function fwpmu.h FwpmProviderContextGetById1 +function fwpmu.h FwpmProviderContextGetById2 +function fwpmu.h FwpmProviderContextGetByKey0 +function fwpmu.h FwpmProviderContextGetByKey1 +function fwpmu.h FwpmProviderContextGetByKey2 +function fwpmu.h FwpmProviderContextGetSecurityInfoByKey0 +function fwpmu.h FwpmProviderContextSetSecurityInfoByKey0 +function fwpmu.h FwpmProviderContextSubscribeChanges0 +function fwpmu.h FwpmProviderContextSubscriptionsGet0 +function fwpmu.h FwpmProviderContextUnsubscribeChanges0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Provider Management +callback fwpmu.h FWPM_PROVIDER_CHANGE_CALLBACK0 +function fwpmu.h FwpmProviderAdd0 +function fwpmu.h FwpmProviderCreateEnumHandle0 +function fwpmu.h FwpmProviderDeleteByKey0 +function fwpmu.h FwpmProviderDestroyEnumHandle0 +function fwpmu.h FwpmProviderEnum0 +function fwpmu.h FwpmProviderGetByKey0 +function fwpmu.h FwpmProviderGetSecurityInfoByKey0 +function fwpmu.h FwpmProviderSetSecurityInfoByKey0 +function fwpmu.h FwpmProviderSubscribeChanges0 +function fwpmu.h FwpmProviderSubscriptionsGet0 +function fwpmu.h FwpmProviderUnsubscribeChanges0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Session Management +function fwpmu.h FwpmEngineClose0 +function fwpmu.h FwpmEngineGetOption0 +function fwpmu.h FwpmEngineGetSecurityInfo0 +function fwpmu.h FwpmEngineOpen0 +function fwpmu.h FwpmEngineSetOption0 +function fwpmu.h FwpmEngineSetSecurityInfo0 +function fwpmu.h FwpmSessionCreateEnumHandle0 +function fwpmu.h FwpmSessionDestroyEnumHandle0 +function fwpmu.h FwpmSessionEnum0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Sub-Layer Management +callback fwpmu.h FWPM_SUBLAYER_CHANGE_CALLBACK0 +function fwpmu.h FwpmSubLayerAdd0 +function fwpmu.h FwpmSubLayerCreateEnumHandle0 +function fwpmu.h FwpmSubLayerDeleteByKey0 +function fwpmu.h FwpmSubLayerDestroyEnumHandle0 +function fwpmu.h FwpmSubLayerEnum0 +function fwpmu.h FwpmSubLayerGetByKey0 +function fwpmu.h FwpmSubLayerGetSecurityInfoByKey0 +function fwpmu.h FwpmSubLayerSetSecurityInfoByKey0 +function fwpmu.h FwpmSubLayerSubscribeChanges0 +function fwpmu.h FwpmSubLayerSubscriptionsGet0 +function fwpmu.h FwpmSubLayerUnsubscribeChanges0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > System Ports Management +callback fwpmu.h FWPM_SYSTEM_PORTS_CALLBACK0 +function fwpmu.h FwpmSystemPortsGet0 +function fwpmu.h FwpmSystemPortsSubscribe0 +function fwpmu.h FwpmSystemPortsUnsubscribe0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > Transaction Management +function fwpmu.h FwpmTransactionAbort0 +function fwpmu.h FwpmTransactionBegin0 +function fwpmu.h FwpmTransactionCommit0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Management Functions > vSwitch Management +callback fwpmu.h FWPM_VSWITCH_EVENT_CALLBACK0 +function fwpmu.h FwpmvSwitchEventsGetSecurityInfo0 +function fwpmu.h FwpmvSwitchEventsSetSecurityInfo0 +function fwpmu.h FwpmvSwitchEventSubscribe0 +function fwpmu.h FwpmvSwitchEventUnsubscribe0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP functions > Shared Functions +function fwpmu.h FwpmFreeMemory0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP structures > IKE/AuthIP Structures +struct iketypes.h IKEEXT_AUTHENTICATION_METHOD0 +struct iketypes.h IKEEXT_AUTHENTICATION_METHOD1 +struct iketypes.h IKEEXT_AUTHENTICATION_METHOD2 +struct iketypes.h IKEEXT_CERT_EKUS0 +struct iketypes.h IKEEXT_CERT_NAME0 +struct iketypes.h IKEEXT_CERT_ROOT_CONFIG0 +struct iketypes.h IKEEXT_CERTIFICATE_AUTHENTICATION0 +struct iketypes.h IKEEXT_CERTIFICATE_AUTHENTICATION1 +struct iketypes.h IKEEXT_CERTIFICATE_AUTHENTICATION2 +struct iketypes.h IKEEXT_CERTIFICATE_CREDENTIAL0 +struct iketypes.h IKEEXT_CERTIFICATE_CREDENTIAL1 +struct iketypes.h IKEEXT_CERTIFICATE_CRITERIA0 +struct iketypes.h IKEEXT_CIPHER_ALGORITHM0 +struct iketypes.h IKEEXT_COMMON_STATISTICS0 +struct iketypes.h IKEEXT_COMMON_STATISTICS1 +struct iketypes.h IKEEXT_COOKIE_PAIR0 +struct iketypes.h IKEEXT_CREDENTIAL_PAIR0 +struct iketypes.h IKEEXT_CREDENTIAL_PAIR1 +struct iketypes.h IKEEXT_CREDENTIAL_PAIR2 +struct iketypes.h IKEEXT_CREDENTIAL0 +struct iketypes.h IKEEXT_CREDENTIAL1 +struct iketypes.h IKEEXT_CREDENTIAL2 +struct iketypes.h IKEEXT_CREDENTIALS0 +struct iketypes.h IKEEXT_CREDENTIALS1 +struct iketypes.h IKEEXT_CREDENTIALS2 +struct iketypes.h IKEEXT_EAP_AUTHENTICATION0 +struct iketypes.h IKEEXT_EM_POLICY0 +struct iketypes.h IKEEXT_EM_POLICY1 +struct iketypes.h IKEEXT_EM_POLICY2 +struct iketypes.h IKEEXT_INTEGRITY_ALGORITHM0 +struct iketypes.h IKEEXT_IP_VERSION_SPECIFIC_COMMON_STATISTICS0 +struct iketypes.h IKEEXT_IP_VERSION_SPECIFIC_COMMON_STATISTICS1 +struct iketypes.h IKEEXT_IP_VERSION_SPECIFIC_KEYMODULE_STATISTICS0 +struct iketypes.h IKEEXT_IP_VERSION_SPECIFIC_KEYMODULE_STATISTICS1 +struct iketypes.h IKEEXT_IPV6_CGA_AUTHENTICATION0 +struct iketypes.h IKEEXT_KERBEROS_AUTHENTICATION0 +struct iketypes.h IKEEXT_KERBEROS_AUTHENTICATION1 +struct iketypes.h IKEEXT_KEYMODULE_STATISTICS0 +struct iketypes.h IKEEXT_KEYMODULE_STATISTICS1 +struct iketypes.h IKEEXT_NAME_CREDENTIAL0 +struct iketypes.h IKEEXT_NTLM_V2_AUTHENTICATION0 +struct iketypes.h IKEEXT_POLICY0 +struct iketypes.h IKEEXT_POLICY1 +struct iketypes.h IKEEXT_POLICY2 +struct iketypes.h IKEEXT_PRESHARED_KEY_AUTHENTICATION0 +struct iketypes.h IKEEXT_PRESHARED_KEY_AUTHENTICATION1 +struct iketypes.h IKEEXT_PROPOSAL0 +struct iketypes.h IKEEXT_RESERVED_AUTHENTICATION0 +struct iketypes.h IKEEXT_SA_DETAILS0 +struct iketypes.h IKEEXT_SA_DETAILS1 +struct iketypes.h IKEEXT_SA_DETAILS2 +struct iketypes.h IKEEXT_SA_ENUM_TEMPLATE0 +struct iketypes.h IKEEXT_STATISTICS0 +struct iketypes.h IKEEXT_STATISTICS1 +struct iketypes.h IKEEXT_TRAFFIC0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP structures > IPsec Structures +struct fwpmu.h IPSEC_KEY_MANAGER_CALLBACKS0 +struct fwptypes.h IPSEC_VIRTUAL_IF_TUNNEL_INFO0 +struct ipsectypes.h IPSEC_ADDRESS_INFO0 +struct ipsectypes.h IPSEC_AGGREGATE_DROP_PACKET_STATISTICS0 +struct ipsectypes.h IPSEC_AGGREGATE_DROP_PACKET_STATISTICS1 +struct ipsectypes.h IPSEC_AGGREGATE_SA_STATISTICS0 +struct ipsectypes.h IPSEC_AH_DROP_PACKET_STATISTICS0 +struct ipsectypes.h IPSEC_AUTH_AND_CIPHER_TRANSFORM0 +struct ipsectypes.h IPSEC_AUTH_TRANSFORM_ID0 +struct ipsectypes.h IPSEC_AUTH_TRANSFORM0 +struct ipsectypes.h IPSEC_CIPHER_TRANSFORM_ID0 +struct ipsectypes.h IPSEC_CIPHER_TRANSFORM0 +struct ipsectypes.h IPSEC_DOSP_OPTIONS0 +struct ipsectypes.h IPSEC_DOSP_STATE_ENUM_TEMPLATE0 +struct ipsectypes.h IPSEC_DOSP_STATE0 +struct ipsectypes.h IPSEC_DOSP_STATISTICS0 +struct ipsectypes.h IPSEC_ESP_DROP_PACKET_STATISTICS0 +struct ipsectypes.h IPSEC_GETSPI0 +struct ipsectypes.h IPSEC_GETSPI1 +struct ipsectypes.h IPSEC_ID0 +struct ipsectypes.h IPSEC_KEY_MANAGER0 +struct ipsectypes.h IPSEC_KEYING_POLICY0 +struct ipsectypes.h IPSEC_KEYING_POLICY1 +struct ipsectypes.h IPSEC_KEYMODULE_STATE0 +struct ipsectypes.h IPSEC_PROPOSAL0 +struct ipsectypes.h IPSEC_SA_AUTH_AND_CIPHER_INFORMATION0 +struct ipsectypes.h IPSEC_SA_AUTH_INFORMATION0 +struct ipsectypes.h IPSEC_SA_BUNDLE0 +struct ipsectypes.h IPSEC_SA_BUNDLE1 +struct ipsectypes.h IPSEC_SA_CIPHER_INFORMATION0 +struct ipsectypes.h IPSEC_SA_CONTEXT_CHANGE0 +struct ipsectypes.h IPSEC_SA_CONTEXT_ENUM_TEMPLATE0 +struct ipsectypes.h IPSEC_SA_CONTEXT_SUBSCRIPTION0 +struct ipsectypes.h IPSEC_SA_CONTEXT0 +struct ipsectypes.h IPSEC_SA_CONTEXT1 +struct ipsectypes.h IPSEC_SA_DETAILS0 +struct ipsectypes.h IPSEC_SA_DETAILS1 +struct ipsectypes.h IPSEC_SA_ENUM_TEMPLATE0 +struct ipsectypes.h IPSEC_SA_IDLE_TIMEOUT0 +struct ipsectypes.h IPSEC_SA_LIFETIME0 +struct ipsectypes.h IPSEC_SA_TRANSFORM0 +struct ipsectypes.h IPSEC_SA0 +struct ipsectypes.h IPSEC_STATISTICS0 +struct ipsectypes.h IPSEC_STATISTICS1 +struct ipsectypes.h IPSEC_TOKEN0 +struct ipsectypes.h IPSEC_TRAFFIC_STATISTICS0 +struct ipsectypes.h IPSEC_TRAFFIC_STATISTICS1 +struct ipsectypes.h IPSEC_TRAFFIC0 +struct ipsectypes.h IPSEC_TRAFFIC1 +struct ipsectypes.h IPSEC_TRANSPORT_POLICY0 +struct ipsectypes.h IPSEC_TRANSPORT_POLICY1 +struct ipsectypes.h IPSEC_TRANSPORT_POLICY2 +struct ipsectypes.h IPSEC_TUNNEL_ENDPOINT0 +struct ipsectypes.h IPSEC_TUNNEL_ENDPOINTS0 +struct ipsectypes.h IPSEC_TUNNEL_ENDPOINTS1 +struct ipsectypes.h IPSEC_TUNNEL_ENDPOINTS2 +struct ipsectypes.h IPSEC_TUNNEL_POLICY0 +struct ipsectypes.h IPSEC_TUNNEL_POLICY1 +struct ipsectypes.h IPSEC_TUNNEL_POLICY2 +struct ipsectypes.h IPSEC_V4_UDP_ENCAPSULATION0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP structures > Management Structures +struct fwpmtypes.h FWPM_ACTION0 +struct fwpmtypes.h FWPM_CALLOUT_CHANGE0 +struct fwpmtypes.h FWPM_CALLOUT_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_CALLOUT_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_CALLOUT0 +struct fwpmtypes.h FWPM_CLASSIFY_OPTION0 +struct fwpmtypes.h FWPM_CLASSIFY_OPTIONS0 +struct fwpmtypes.h FWPM_CONNECTION_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_CONNECTION_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_CONNECTION0 +struct fwpmtypes.h FWPM_FIELD0 +struct fwpmtypes.h FWPM_FILTER_CHANGE0 +struct fwpmtypes.h FWPM_FILTER_CONDITION0 +struct fwpmtypes.h FWPM_FILTER_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_FILTER_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_FILTER0 +struct fwpmtypes.h FWPM_LAYER_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_LAYER0 +struct fwpmtypes.h FWPM_NET_EVENT_CAPABILITY_ALLOW0 +struct fwpmtypes.h FWPM_NET_EVENT_CAPABILITY_DROP0 +struct fwpmtypes.h FWPM_NET_EVENT_CLASSIFY_ALLOW0 +struct fwpmtypes.h FWPM_NET_EVENT_CLASSIFY_DROP_MAC0 +struct fwpmtypes.h FWPM_NET_EVENT_CLASSIFY_DROP0 +struct fwpmtypes.h FWPM_NET_EVENT_CLASSIFY_DROP1 +struct fwpmtypes.h FWPM_NET_EVENT_CLASSIFY_DROP2 +struct fwpmtypes.h FWPM_NET_EVENT_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_NET_EVENT_HEADER0 +struct fwpmtypes.h FWPM_NET_EVENT_HEADER1 +struct fwpmtypes.h FWPM_NET_EVENT_HEADER2 +struct fwpmtypes.h FWPM_NET_EVENT_HEADER3 +struct fwpmtypes.h FWPM_NET_EVENT_IKEEXT_EM_FAILURE0 +struct fwpmtypes.h FWPM_NET_EVENT_IKEEXT_EM_FAILURE1 +struct fwpmtypes.h FWPM_NET_EVENT_IKEEXT_MM_FAILURE0 +struct fwpmtypes.h FWPM_NET_EVENT_IKEEXT_MM_FAILURE1 +struct fwpmtypes.h FWPM_NET_EVENT_IKEEXT_QM_FAILURE0 +struct fwpmtypes.h FWPM_NET_EVENT_IPSEC_DOSP_DROP0 +struct fwpmtypes.h FWPM_NET_EVENT_IPSEC_KERNEL_DROP0 +struct fwpmtypes.h FWPM_NET_EVENT_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_NET_EVENT0 +struct fwpmtypes.h FWPM_NET_EVENT1 +struct fwpmtypes.h FWPM_NET_EVENT2 +struct fwpmtypes.h FWPM_NET_EVENT3 +struct fwpmtypes.h FWPM_PROVIDER_CHANGE0 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT_CHANGE0 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT0 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT1 +struct fwpmtypes.h FWPM_PROVIDER_CONTEXT2 +struct fwpmtypes.h FWPM_PROVIDER_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_PROVIDER_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_PROVIDER0 +struct fwpmtypes.h FWPM_SESSION_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_SESSION0 +struct fwpmtypes.h FWPM_SUBLAYER_CHANGE0 +struct fwpmtypes.h FWPM_SUBLAYER_ENUM_TEMPLATE0 +struct fwpmtypes.h FWPM_SUBLAYER_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_SUBLAYER0 +struct fwpmtypes.h FWPM_SYSTEM_PORTS_BY_TYPE0 +struct fwpmtypes.h FWPM_SYSTEM_PORTS0 +struct fwpmtypes.h FWPM_VSWITCH_EVENT_SUBSCRIPTION0 +struct fwpmtypes.h FWPM_VSWITCH_EVENT0 +struct fwptypes.h FWPM_DISPLAY_DATA0 + +# Desktop App Technologies > Networking and Internet > Windows Filtering Platform > Windows Filtering Platform API reference > WFP structures > Shared Structures +struct fwptypes.h FWP_BYTE_ARRAY16 +struct fwptypes.h FWP_BYTE_ARRAY6 +struct fwptypes.h FWP_BYTE_BLOB +struct fwptypes.h FWP_CONDITION_VALUE0 +struct fwptypes.h FWP_RANGE0 +struct fwptypes.h FWP_TOKEN_INFORMATION +struct fwptypes.h FWP_V4_ADDR_AND_MASK +struct fwptypes.h FWP_V6_ADDR_AND_MASK +struct fwptypes.h FWP_VALUE0 + +# Desktop App Technologies > Networking and Internet > Windows Networking (WNet) > Windows Networking Reference > Windows Networking Functions +function winnetwk.h MultinetGetConnectionPerformance =multinetgetconnectionperformancea +function winnetwk.h WNetAddConnection =wnetaddconnectiona +function winnetwk.h WNetAddConnection2 =wnetaddconnection2a +function winnetwk.h WNetAddConnection3 =wnetaddconnection3a +function winnetwk.h WNetCancelConnection =wnetcancelconnectiona +function winnetwk.h WNetCancelConnection2 =wnetcancelconnection2a +function winnetwk.h WNetCloseEnum +function winnetwk.h WNetConnectionDialog +function winnetwk.h WNetConnectionDialog1 =wnetconnectiondialog1a +function winnetwk.h WNetDisconnectDialog +function winnetwk.h WNetDisconnectDialog1 =wnetdisconnectdialog1a +function winnetwk.h WNetEnumResource =wnetenumresourcea +function winnetwk.h WNetGetConnection =wnetgetconnectiona +function winnetwk.h WNetGetLastError =wnetgetlasterrora +function winnetwk.h WNetGetNetworkInformation =wnetgetnetworkinformationa +function winnetwk.h WNetGetProviderName =wnetgetprovidernamea +function winnetwk.h WNetGetResourceInformation =wnetgetresourceinformationa +function winnetwk.h WNetGetResourceParent =wnetgetresourceparenta +function winnetwk.h WNetGetUniversalName =wnetgetuniversalnamea +function winnetwk.h WNetGetUser =wnetgetusera +function winnetwk.h WNetOpenEnum =wnetopenenuma +function winnetwk.h WNetRestoreConnectionW +function winnetwk.h WNetUseConnection =wnetuseconnectiona + +# Desktop App Technologies > Networking and Internet > Windows Networking (WNet) > Windows Networking Reference > Windows Networking Structures +struct winnetwk.h CONNECTDLGSTRUCT =connectdlgstructa +struct winnetwk.h DISCDLGSTRUCT =discdlgstructa +struct winnetwk.h NETCONNECTINFOSTRUCT +struct winnetwk.h NETINFOSTRUCT +struct winnetwk.h NETRESOURCE =netresourcea +struct winnetwk.h REMOTE_NAME_INFO =remote_name_infoa +struct winnetwk.h UNIVERSAL_NAME_INFO =universal_name_infoa + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Annexes > Winsock ATM Annex > Winsock ATM Structures +struct ws2atm.h ATM_ADDRESS +struct ws2atm.h ATM_BHLI +struct ws2atm.h ATM_BLLI +struct ws2atm.h sockaddr_atm + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Annexes > Winsock IPX/SPX Annex > Winsock IPX/SPX Structures +struct wsnwlink.h IPX_ADDRESS_DATA +struct wsnwlink.h IPX_NETNUM_DATA +struct wsnwlink.h IPX_SPXCONNSTATUS_DATA + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Annexes > Winsock TCP/IP Annex > Winsock TCP/IP Structures +struct ws2ipdef.h INTERFACE_INFO +struct ws2ipdef.h INTERFACE_INFO_EX +struct ws2ipdef.h sockaddr_gen + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Enumerations +enum mstcpip.h CONTROL_CHANNEL_TRIGGER_STATUS +enum mstcpip.h SOCKET_SECURITY_PROTOCOL +enum mstcpip.h SOCKET_USAGE_TYPE +enum mstcpip.h TCPSTATE +enum mswsock.h RIO_NOTIFICATION_COMPLETION_TYPE +enum nsemail.h NAPI_PROVIDER_LEVEL +enum nsemail.h NAPI_PROVIDER_TYPE +enum winsock2.h WSAECOMPARATOR +enum ws2ipdef.h MULTICAST_MODE_TYPE +enum ws2spi.h WSC_PROVIDER_INFO_TYPE +enum wsrm.h eWINDOW_ADVANCE_METHOD + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Functions +callback mswsock.h DisconnectEx =lpfn_disconnectex +callback mswsock.h LPFN_CONNECTEX +callback mswsock.h LPFN_TRANSMITPACKETS +callback mswsock.h LPFN_WSARECVMSG (WSARecvMsg) =lpfn_wsarecvmsg +callback mswsock.h RIOCloseCompletionQueue =lpfn_rioclosecompletionqueue +callback mswsock.h RIOCreateCompletionQueue =lpfn_riodequeuecompletion +callback mswsock.h RIOCreateRequestQueue =lpfn_riocreaterequestqueue +callback mswsock.h RIODequeueCompletion =lpfn_riodequeuecompletion +callback mswsock.h RIODeregisterBuffer =lpfn_rioderegisterbuffer +callback mswsock.h RIONotify =lpfn_rionotify +callback mswsock.h RIOReceive =lpfn_rioreceive +callback mswsock.h RIOReceiveEx =lpfn_rioreceiveex +callback mswsock.h RIORegisterBuffer =lpfn_rioregisterbuffer +callback mswsock.h RIOResizeCompletionQueue =lpfn_rioresizecompletionqueue +callback mswsock.h RIOResizeRequestQueue =lpfn_rioresizerequestqueue +callback mswsock.h RIOSend =lpfn_riosend +callback mswsock.h RIOSendEx =lpfn_riosendex +function mswsock.h AcceptEx +function mswsock.h GetAcceptExSockaddrs +function mswsock.h TransmitFile +function mswsock.h WSARecvEx +function nspapi.h EnumProtocols =enumprotocolsa +function nspapi.h GetAddressByName =getaddressbynamea +function nspapi.h GetNameByType =getnamebytypea +function nspapi.h GetService =getservicea +function nspapi.h GetTypeByName =gettypebynamea +function nspapi.h SetService =setservicea +function socketapi.h SetSocketMediaStreamingMode +function winsock.h __WSAFDIsSet +function winsock.h bind +function winsock.h closesocket +function winsock.h gethostname +function winsock.h getpeername +function winsock.h getprotobyname +function winsock.h getprotobynumber +function winsock.h getservbyname +function winsock.h getservbyport +function winsock.h getsockname +function winsock.h getsockopt +function winsock.h htonl +function winsock.h htons +function winsock.h ioctlsocket +function winsock.h ntohl +function winsock.h ntohs +function winsock.h recv +function winsock.h recvfrom +function winsock.h sendto +function winsock.h setsockopt +function winsock.h shutdown +function winsock.h WSAAsyncGetProtoByName +function winsock.h WSAAsyncGetProtoByNumber +function winsock.h WSAAsyncGetServByName +function winsock.h WSAAsyncGetServByPort +function winsock.h WSAAsyncSelect +function winsock.h WSACancelAsyncRequest +function winsock.h WSACleanup +function winsock.h WSAGetLastError +function winsock.h WSASetLastError +function winsock.h WSAStartup +function winsock2.h accept +function winsock2.h connect +function winsock2.h GetHostNameW +function winsock2.h htond +function winsock2.h htonf +function winsock2.h htonll +function winsock2.h listen +function winsock2.h ntohd +function winsock2.h ntohf +function winsock2.h ntohll +function winsock2.h select +function winsock2.h send +function winsock2.h socket +function winsock2.h WSAAccept +function winsock2.h WSAAddressToString =wsaaddresstostringa +function winsock2.h WSACancelBlockingCall +function winsock2.h WSACloseEvent +function winsock2.h WSAConnect +function winsock2.h WSAConnectByList +function winsock2.h WSAConnectByName =wsaconnectbynamea +function winsock2.h WSACreateEvent +function winsock2.h WSADuplicateSocket =wsaduplicatesocketa +function winsock2.h WSAEnumNameSpaceProviders =wsaenumnamespaceprovidersa +function winsock2.h WSAEnumNameSpaceProvidersEx =wsaenumnamespaceprovidersexa +function winsock2.h WSAEnumNetworkEvents +function winsock2.h WSAEnumProtocols =wsaenumprotocolsa +function winsock2.h WSAEventSelect +function winsock2.h WSAGetOverlappedResult +function winsock2.h WSAGetQOSByName +function winsock2.h WSAGetServiceClassInfo =wsagetserviceclassinfoa +function winsock2.h WSAGetServiceClassNameByClassId =wsagetserviceclassnamebyclassida +function winsock2.h WSAHtonl +function winsock2.h WSAHtons +function winsock2.h WSAInstallServiceClass =wsainstallserviceclassa +function winsock2.h WSAIoctl +function winsock2.h WSAIsBlocking +function winsock2.h WSAJoinLeaf +function winsock2.h WSALookupServiceBegin =wsalookupservicebegina +function winsock2.h WSALookupServiceEnd +function winsock2.h WSALookupServiceNext =wsalookupservicenexta +function winsock2.h WSANSPIoctl +function winsock2.h WSANtohl +function winsock2.h WSANtohs +function winsock2.h WSAPoll +function winsock2.h WSAProviderConfigChange +function winsock2.h WSARecv +function winsock2.h WSARecvDisconnect +function winsock2.h WSARecvFrom +function winsock2.h WSARemoveServiceClass +function winsock2.h WSAResetEvent +function winsock2.h WSASend +function winsock2.h WSASendDisconnect +function winsock2.h WSASendMsg +function winsock2.h WSASendTo +function winsock2.h WSASetBlockingHook +function winsock2.h WSASetEvent +function winsock2.h WSASetService =wsasetservicea +function winsock2.h WSASocket =wsasocketa +function winsock2.h WSAStringToAddress =wsastringtoaddressa +function winsock2.h WSAUnhookBlockingHook +function winsock2.h WSAWaitForMultipleEvents +function ws2tcpip.h freeaddrinfo +function ws2tcpip.h FreeAddrInfoEx +function ws2tcpip.h FreeAddrInfoW +function ws2tcpip.h gai_strerror =gai_strerrora +function ws2tcpip.h getaddrinfo +function ws2tcpip.h GetAddrInfoEx =getaddrinfoexa +function ws2tcpip.h GetAddrInfoExCancel +function ws2tcpip.h GetAddrInfoExOverlappedResult +function ws2tcpip.h GetAddrInfoW +function ws2tcpip.h getipv4sourcefilter +function ws2tcpip.h getnameinfo +function ws2tcpip.h GetNameInfoW +function ws2tcpip.h getsourcefilter +function ws2tcpip.h InetNtop =inetntopw +function ws2tcpip.h InetPton =inetptonw +function ws2tcpip.h SetAddrInfoEx =setaddrinfoexa +function ws2tcpip.h setipv4sourcefilter +function ws2tcpip.h setsourcefilter +function ws2tcpip.h WSADeleteSocketPeerTargetName +function ws2tcpip.h WSAGetFailConnectOnIcmpError +function ws2tcpip.h WSAGetIcmpErrorInfo +function ws2tcpip.h WSAGetIPUserMtu +function ws2tcpip.h WSAGetUdpRecvMaxCoalescedSize +function ws2tcpip.h WSAGetUdpSendMessageSize +function ws2tcpip.h WSAImpersonateSocketPeer +function ws2tcpip.h WSAQuerySocketSecurity +function ws2tcpip.h WSARevertImpersonation +function ws2tcpip.h WSASetFailConnectOnIcmpError +function ws2tcpip.h WSASetIPUserMtu +function ws2tcpip.h WSASetSocketPeerTargetName +function ws2tcpip.h WSASetSocketSecurity +function ws2tcpip.h WSASetUdpRecvMaxCoalescedSize +function ws2tcpip.h WSASetUdpSendMessageSize +function wsipv6ok.h gethostbyaddr +function wsipv6ok.h gethostbyname +function wsipv6ok.h inet_addr +function wsipv6ok.h inet_ntoa +function wsipv6ok.h WSAAsyncGetHostByAddr +function wsipv6ok.h WSAAsyncGetHostByName + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock Structures +struct af_irda.h SOCKADDR_IRDA +struct in6addr.h in6_addr +struct mstcpip.h ASSOCIATE_NAMERES_CONTEXT_INPUT +struct mstcpip.h INET_PORT_RANGE +struct mstcpip.h INET_PORT_RESERVATION_INSTANCE +struct mstcpip.h INET_PORT_RESERVATION_TOKEN +struct mstcpip.h REAL_TIME_NOTIFICATION_SETTING_INPUT +struct mstcpip.h REAL_TIME_NOTIFICATION_SETTING_OUTPUT +struct mstcpip.h SOCKET_PEER_TARGET_NAME +struct mstcpip.h SOCKET_SECURITY_QUERY_INFO +struct mstcpip.h SOCKET_SECURITY_QUERY_TEMPLATE +struct mstcpip.h SOCKET_SECURITY_SETTINGS +struct mstcpip.h SOCKET_SECURITY_SETTINGS_IPSEC +struct mstcpip.h TCP_INFO_v0 +struct mstcpip.h TCP_INITIAL_RTO_PARAMETERS +struct mstcpip.h TRANSPORT_SETTING_ID +struct mswsock.h RIO_EXTENSION_FUNCTION_TABLE +struct mswsock.h RIO_NOTIFICATION_COMPLETION +struct mswsock.h TRANSMIT_FILE_BUFFERS +struct mswsock.h TRANSMIT_PACKETS_ELEMENT +struct mswsockdef.h RIO_BUF +struct mswsockdef.h RIORESULT +struct nsemail.h NAPI_DOMAIN_DESCRIPTION_BLOB +struct nsemail.h NAPI_PROVIDER_INSTALLATION_BLOB +struct nspapi.h BLOB +struct nspapi.h CSADDR_INFO +struct nspapi.h NS_SERVICE_INFO =ns_service_infoa +struct nspapi.h PROTOCOL_INFO =protocol_infoa +struct nspapi.h SERVICE_ADDRESS +struct nspapi.h SERVICE_ADDRESSES +struct nspapi.h SERVICE_INFO =service_infoa +struct nspapi.h SERVICE_TYPE_INFO_ABS =service_type_info_absa +struct nspapi.h SERVICE_TYPE_VALUE_ABS =service_type_value_absa +struct winsock.h hostent +struct winsock.h linger +struct winsock.h protoent +struct winsock.h servent +struct winsock.h timeval +struct winsock.h WSADATA +struct winsock2.h AFPROTOCOLS +struct winsock2.h fd_set +struct winsock2.h in_addr +struct winsock2.h WSACOMPLETION +struct winsock2.h WSANAMESPACE_INFO =wsanamespace_infow +struct winsock2.h WSANAMESPACE_INFOEX =wsanamespace_infoexw +struct winsock2.h WSANETWORKEVENTS +struct winsock2.h WSANSCLASSINFO =wsansclassinfow +struct winsock2.h WSAOVERLAPPED +struct winsock2.h WSAPOLLFD +struct winsock2.h WSAPROTOCOL_INFO =wsaprotocol_infoa +struct winsock2.h WSAPROTOCOL_INFOW +struct winsock2.h WSAPROTOCOLCHAIN +struct winsock2.h WSAQUERYSET =wsaquerysetw +struct winsock2.h WSAQUERYSET2 =wsaqueryset2w +struct winsock2.h WSASERVICECLASSINFO =wsaserviceclassinfow +struct winsock2.h WSAVERSION +struct ws2def.h addrinfo =addrinfoa +struct ws2def.h ADDRINFO_DNS_SERVER +struct ws2def.h addrinfoex =addrinfoexw +struct ws2def.h addrinfoex2 =addrinfoex2w +struct ws2def.h addrinfoex3 +struct ws2def.h addrinfoex4 +struct ws2def.h addrinfoex5 +struct ws2def.h addrinfoex6 +struct ws2def.h addrinfoW +struct ws2def.h SOCKADDR_STORAGE =sockaddr_storage_xp +struct ws2def.h SOCKET_ADDRESS +struct ws2def.h SOCKET_ADDRESS_LIST +struct ws2def.h SOCKET_PROCESSOR_AFFINITY +struct ws2def.h WSABUF +struct ws2def.h WSAMSG +struct ws2ipdef.h GROUP_FILTER +struct ws2ipdef.h GROUP_REQ +struct ws2ipdef.h GROUP_SOURCE_REQ +struct ws2ipdef.h in_pktinfo +struct ws2ipdef.h in6_pktinfo +struct ws2ipdef.h ip_mreq +struct ws2ipdef.h ip_mreq_source +struct ws2ipdef.h ip_msfilter +struct ws2ipdef.h ipv6_mreq +struct wsrm.h RM_FEC_INFO +struct wsrm.h RM_RECEIVER_STATS +struct wsrm.h RM_SEND_WINDOW +struct wsrm.h RM_SENDER_STATS + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock SPI > Winsock SPI Functions +callback ws2spi.h LPNSPCLEANUP +callback ws2spi.h LPNSPGETSERVICECLASSINFO +callback ws2spi.h LPNSPINSTALLSERVICECLASS +callback ws2spi.h LPNSPIOCTL +callback ws2spi.h LPNSPLOOKUPSERVICEBEGIN +callback ws2spi.h LPNSPLOOKUPSERVICEEND +callback ws2spi.h LPNSPLOOKUPSERVICENEXT +callback ws2spi.h LPNSPREMOVESERVICECLASS +callback ws2spi.h LPNSPSETSERVICE +callback ws2spi.h LPNSPV2CLEANUP +callback ws2spi.h LPNSPV2CLIENTSESSIONRUNDOWN +callback ws2spi.h LPNSPV2LOOKUPSERVICEBEGIN +callback ws2spi.h LPNSPV2LOOKUPSERVICEEND +callback ws2spi.h LPNSPV2LOOKUPSERVICENEXTEX +callback ws2spi.h LPNSPV2SETSERVICEEX +callback ws2spi.h LPNSPV2STARTUP +callback ws2spi.h LPWSPACCEPT +callback ws2spi.h LPWSPADDRESSTOSTRING +callback ws2spi.h LPWSPCANCELBLOCKINGCALL +callback ws2spi.h LPWSPGETOVERLAPPEDRESULT +callback ws2spi.h LPWSPGETPEERNAME +callback ws2spi.h LPWSPGETQOSBYNAME +callback ws2spi.h LPWSPGETSOCKNAME +callback ws2spi.h LPWSPJOINLEAF +callback ws2spi.h LPWSPRECVDISCONNECT +callback ws2spi.h LPWSPRECVFROM +callback ws2spi.h LPWSPSELECT +callback ws2spi.h LPWSPSENDDISCONNECT +callback ws2spi.h LPWSPSENDTO +callback ws2spi.h LPWSPSHUTDOWN +callback ws2spi.h LPWSPSOCKET +callback ws2spi.h LPWSPSTRINGTOADDRESS +function sporder.h WSCWriteNameSpaceOrder +function sporder.h WSCWriteNameSpaceOrder32 +function sporder.h WSCWriteProviderOrder +function sporder.h WSCWriteProviderOrder32 +function ws2spi.h NSPStartup +function ws2spi.h WPUCloseEvent +function ws2spi.h WPUCloseSocketHandle +function ws2spi.h WPUCloseThread +function ws2spi.h WPUCompleteOverlappedRequest +function ws2spi.h WPUCreateEvent +function ws2spi.h WPUCreateSocketHandle +function ws2spi.h WPUFDIsSet +function ws2spi.h WPUGetProviderPath +function ws2spi.h WPUModifyIFSHandle +function ws2spi.h WPUOpenCurrentThread +function ws2spi.h WPUPostMessage +function ws2spi.h WPUQueryBlockingCallback +function ws2spi.h WPUQuerySocketHandleContext +function ws2spi.h WPUQueueApc +function ws2spi.h WPUResetEvent +function ws2spi.h WPUSetEvent +function ws2spi.h WSAAdvertiseProvider +function ws2spi.h WSAProviderCompleteAsyncCall +function ws2spi.h WSAUnadvertiseProvider +function ws2spi.h WSCDeinstallProvider +function ws2spi.h WSCDeinstallProvider32 +function ws2spi.h WSCEnableNSProvider +function ws2spi.h WSCEnableNSProvider32 +function ws2spi.h WSCEnumNameSpaceProviders32 +function ws2spi.h WSCEnumNameSpaceProvidersEx32 +function ws2spi.h WSCEnumProtocols +function ws2spi.h WSCEnumProtocols32 +function ws2spi.h WSCGetApplicationCategory +function ws2spi.h WSCGetProviderInfo +function ws2spi.h WSCGetProviderInfo32 +function ws2spi.h WSCGetProviderPath +function ws2spi.h WSCGetProviderPath32 +function ws2spi.h WSCInstallNameSpace +function ws2spi.h WSCInstallNameSpace32 +function ws2spi.h WSCInstallNameSpaceEx +function ws2spi.h WSCInstallNameSpaceEx32 +function ws2spi.h WSCInstallProvider +function ws2spi.h WSCInstallProvider64_32 +function ws2spi.h WSCInstallProviderAndChains +function ws2spi.h WSCInstallProviderAndChains64_32 +function ws2spi.h WSCInstallQOSTemplate +function ws2spi.h WSCRemoveQOSTemplate +function ws2spi.h WSCSetApplicationCategory +function ws2spi.h WSCSetProviderInfo +function ws2spi.h WSCSetProviderInfo32 +function ws2spi.h WSCUnInstallNameSpace +function ws2spi.h WSCUnInstallNameSpace32 +function ws2spi.h WSCUpdateProvider +function ws2spi.h WSCUpdateProvider32 +function ws2spi.h WSPStartup + +# Desktop App Technologies > Networking and Internet > Windows Sockets 2 > Winsock Reference > Winsock SPI > Winsock SPI Structures +struct ws2spi.h NSP_ROUTINE +struct ws2spi.h NSPV2_ROUTINE +struct ws2spi.h WSATHREADID +struct ws2spi.h WSC_PROVIDER_AUDIT_INFO +struct ws2spi.h WSPDATA +struct ws2spi.h WSPPROC_TABLE +struct ws2spi.h WSPUPCALLTABLE + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Bluetooth > Bluetooth Reference > Bluetooth Callbacks +callback bluetoothapis.h PFN_AUTHENTICATION_CALLBACK +callback bluetoothapis.h PFN_AUTHENTICATION_CALLBACK_EX +callback bluetoothapis.h PFN_BLUETOOTH_ENUM_ATTRIBUTES_CALLBACK +callback bluetoothapis.h PFN_DEVICE_CALLBACK + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Bluetooth > Bluetooth Reference > Bluetooth Enumerations +enum bluetoothapis.h _AUTHENTICATION_REQUIREMENTS =bluetooth_authentication_requirements +enum bluetoothapis.h BLUETOOTH_AUTHENTICATION_METHOD +enum bluetoothapis.h BLUETOOTH_IO_CAPABILITY + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Bluetooth > Bluetooth Reference > Bluetooth Functions +function bluetoothapis.h BluetoothAuthenticateDevice +function bluetoothapis.h BluetoothAuthenticateDeviceEx +function bluetoothapis.h BluetoothAuthenticateMultipleDevices +function bluetoothapis.h BluetoothDisplayDeviceProperties +function bluetoothapis.h BluetoothEnableDiscovery +function bluetoothapis.h BluetoothEnableIncomingConnections +function bluetoothapis.h BluetoothEnumerateInstalledServices +function bluetoothapis.h BluetoothFindDeviceClose +function bluetoothapis.h BluetoothFindFirstDevice +function bluetoothapis.h BluetoothFindFirstRadio +function bluetoothapis.h BluetoothFindNextDevice +function bluetoothapis.h BluetoothFindNextRadio +function bluetoothapis.h BluetoothFindRadioClose +function bluetoothapis.h BluetoothGetDeviceInfo +function bluetoothapis.h BluetoothGetRadioInfo +function bluetoothapis.h BluetoothIsConnectable +function bluetoothapis.h BluetoothIsDiscoverable +function bluetoothapis.h BluetoothIsVersionAvailable +function bluetoothapis.h BluetoothRegisterForAuthentication +function bluetoothapis.h BluetoothRegisterForAuthenticationEx +function bluetoothapis.h BluetoothRemoveDevice +function bluetoothapis.h BluetoothSdpEnumAttributes +function bluetoothapis.h BluetoothSdpGetAttributeValue +function bluetoothapis.h BluetoothSdpGetContainerElementData +function bluetoothapis.h BluetoothSdpGetElementData +function bluetoothapis.h BluetoothSdpGetString +function bluetoothapis.h BluetoothSelectDevices +function bluetoothapis.h BluetoothSelectDevicesFree +function bluetoothapis.h BluetoothSendAuthenticationResponse +function bluetoothapis.h BluetoothSendAuthenticationResponseEx +function bluetoothapis.h BluetoothSetServiceState +function bluetoothapis.h BluetoothUnregisterAuthentication +function bluetoothapis.h BluetoothUpdateDeviceRecord + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Bluetooth > Bluetooth Reference > Bluetooth Structures +struct bluetoothapis.h BLUETOOTH_ADDRESS =bluetooth_address_struct +struct bluetoothapis.h BLUETOOTH_AUTHENTICATE_RESPONSE +struct bluetoothapis.h BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS +struct bluetoothapis.h BLUETOOTH_COD_PAIRS +struct bluetoothapis.h BLUETOOTH_DEVICE_INFO =bluetooth_device_info_struct +struct bluetoothapis.h BLUETOOTH_DEVICE_SEARCH_PARAMS +struct bluetoothapis.h BLUETOOTH_FIND_RADIO_PARAMS +struct bluetoothapis.h BLUETOOTH_LOCAL_SERVICE_INFO =bluetooth_local_service_info_struct +struct bluetoothapis.h BLUETOOTH_NUMERIC_COMPARISON_INFO +struct bluetoothapis.h BLUETOOTH_OOB_DATA_INFO +struct bluetoothapis.h BLUETOOTH_PASSKEY_INFO +struct bluetoothapis.h BLUETOOTH_PIN_INFO +struct bluetoothapis.h BLUETOOTH_RADIO_INFO +struct bluetoothapis.h BLUETOOTH_SELECT_DEVICE_PARAMS +struct bluetoothapis.h SDP_ELEMENT_DATA +struct bluetoothapis.h SDP_STRING_TYPE_DATA +struct bthdef.h BTH_DEVICE_INFO +struct bthdef.h BTH_HCI_EVENT_INFO +struct bthdef.h BTH_L2CAP_EVENT_INFO +struct bthdef.h BTH_RADIO_IN_RANGE +struct bthsdpdef.h SdpAttributeRange +struct bthsdpdef.h SdpQueryUuid +struct bthsdpdef.h SdpQueryUuidUnion +struct ws2bth.h BTH_QUERY_DEVICE +struct ws2bth.h BTH_QUERY_SERVICE +struct ws2bth.h BTH_SET_SERVICE +struct ws2bth.h SOCKADDR_BTH + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Mobile Broadband > Mobile Broadband API Reference > Mobile Broadband API Enumerations +enum mbnapi.h MBN_ACTIVATION_STATE +enum mbnapi.h MBN_AUTH_PROTOCOL +enum mbnapi.h MBN_BAND_CLASS +enum mbnapi.h MBN_CELLULAR_CLASS +enum mbnapi.h MBN_COMPRESSION +enum mbnapi.h MBN_CONNECTION_MODE +enum mbnapi.h MBN_CONTEXT_CONSTANTS +enum mbnapi.h MBN_CONTEXT_TYPE +enum mbnapi.h MBN_CTRL_CAPS +enum mbnapi.h MBN_DATA_CLASS +enum mbnapi.h MBN_DEVICE_SERVICES_INTERFACE_STATE +enum mbnapi.h MBN_INTERFACE_CAPS_CONSTANTS +enum mbnapi.h MBN_MSG_STATUS +enum mbnapi.h MBN_PIN_CONSTANTS +enum mbnapi.h MBN_PIN_FORMAT +enum mbnapi.h MBN_PIN_MODE +enum mbnapi.h MBN_PIN_STATE +enum mbnapi.h MBN_PIN_TYPE +enum mbnapi.h MBN_PROVIDER_CONSTANTS +enum mbnapi.h MBN_PROVIDER_STATE +enum mbnapi.h MBN_RADIO +enum mbnapi.h MBN_READY_STATE +enum mbnapi.h MBN_REGISTER_MODE +enum mbnapi.h MBN_REGISTER_STATE +enum mbnapi.h MBN_REGISTRATION_CONSTANTS +enum mbnapi.h MBN_SIGNAL_CONSTANTS +enum mbnapi.h MBN_SMS_CAPS +enum mbnapi.h MBN_SMS_CDMA_ENCODING +enum mbnapi.h MBN_SMS_CDMA_LANG +enum mbnapi.h MBN_SMS_CONSTANTS =wwaext_sms_constants +enum mbnapi.h MBN_SMS_FLAG +enum mbnapi.h MBN_SMS_FORMAT +enum mbnapi.h MBN_SMS_STATUS_FLAG +enum mbnapi.h MBN_VOICE_CALL_STATE +enum mbnapi.h MBN_VOICE_CLASS + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Mobile Broadband > Mobile Broadband API Reference > Mobile Broadband API Interfaces +interface mbnapi.h IMbnConnection +interface mbnapi.h IMbnConnectionContext +interface mbnapi.h IMbnConnectionContextEvents +interface mbnapi.h IMbnConnectionEvents +interface mbnapi.h IMbnConnectionManager +interface mbnapi.h IMbnConnectionManagerEvents +interface mbnapi.h IMbnConnectionProfile +interface mbnapi.h IMbnConnectionProfileEvents +interface mbnapi.h IMbnConnectionProfileManager +interface mbnapi.h IMbnConnectionProfileManagerEvents +interface mbnapi.h IMbnDeviceService +interface mbnapi.h IMbnDeviceServicesContext +interface mbnapi.h IMbnDeviceServicesEvents +interface mbnapi.h IMbnDeviceServicesManager +interface mbnapi.h IMbnInterface +interface mbnapi.h IMbnInterfaceEvents +interface mbnapi.h IMbnInterfaceManager +interface mbnapi.h IMbnInterfaceManagerEvents +interface mbnapi.h IMbnMultiCarrier +interface mbnapi.h IMbnMultiCarrierEvents +interface mbnapi.h IMbnPin +interface mbnapi.h IMbnPinEvents +interface mbnapi.h IMbnPinManager +interface mbnapi.h IMbnPinManagerEvents +interface mbnapi.h IMbnRadio +interface mbnapi.h IMbnRadioEvents +interface mbnapi.h IMbnRegistration +interface mbnapi.h IMbnRegistrationEvents +interface mbnapi.h IMbnServiceActivation +interface mbnapi.h IMbnServiceActivationEvents +interface mbnapi.h IMbnSignal +interface mbnapi.h IMbnSignalEvents +interface mbnapi.h IMbnSms +interface mbnapi.h IMbnSmsConfiguration +interface mbnapi.h IMbnSmsEvents +interface mbnapi.h IMbnSmsReadMsgPdu +interface mbnapi.h IMbnSmsReadMsgTextCdma +interface mbnapi.h IMbnSubscriberInformation +interface mbnapi.h IMbnVendorSpecificEvents +interface mbnapi.h IMbnVendorSpecificOperation + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Mobile Broadband > Mobile Broadband API Reference > Mobile Broadband API Structures +struct mbnapi.h MBN_CONTEXT +struct mbnapi.h MBN_DEVICE_SERVICE +struct mbnapi.h MBN_INTERFACE_CAPS +struct mbnapi.h MBN_PIN_INFO +struct mbnapi.h MBN_PROVIDER +struct mbnapi.h MBN_PROVIDER2 +struct mbnapi.h MBN_SMS_FILTER +struct mbnapi.h MBN_SMS_STATUS_INFO + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Native Wifi > Native Wifi reference > Native Wifi enumerations +enum dot1x.h ONEX_AUTH_IDENTITY +enum dot1x.h ONEX_AUTH_RESTART_REASON +enum dot1x.h ONEX_AUTH_STATUS +enum dot1x.h ONEX_EAP_METHOD_BACKEND_SUPPORT +enum dot1x.h ONEX_NOTIFICATION_TYPE +enum dot1x.h ONEX_REASON_CODE +enum wlanapi.h DOT11_RADIO_STATE =dot11_radio_state-r1 +enum wlanapi.h WL_DISPLAY_PAGES +enum wlanapi.h WLAN_ADHOC_NETWORK_STATE =wlan_adhoc_network_state-r1 +enum wlanapi.h WLAN_AUTOCONF_OPCODE =wlan_autoconf_opcode-r1 +enum wlanapi.h WLAN_CONNECTION_MODE +enum wlanapi.h WLAN_FILTER_LIST_TYPE +enum wlanapi.h WLAN_HOSTED_NETWORK_NOTIFICATION_CODE +enum wlanapi.h WLAN_HOSTED_NETWORK_OPCODE +enum wlanapi.h WLAN_HOSTED_NETWORK_PEER_AUTH_STATE +enum wlanapi.h WLAN_HOSTED_NETWORK_REASON +enum wlanapi.h WLAN_HOSTED_NETWORK_STATE +enum wlanapi.h WLAN_IHV_CONTROL_TYPE =wlan_ihv_control_type-r1 +enum wlanapi.h WLAN_INTERFACE_STATE =wlan_ihv_control_type-r1 +enum wlanapi.h WLAN_INTERFACE_TYPE +enum wlanapi.h WLAN_INTF_OPCODE +enum wlanapi.h WLAN_NOTIFICATION_ACM =wlan_notification_acm-r1 +enum wlanapi.h WLAN_NOTIFICATION_MSM =wlan_notification_msm-r1 +enum wlanapi.h WLAN_OPCODE_VALUE_TYPE =wlan_opcode_value_type-r1 +enum wlanapi.h WLAN_POWER_SETTING =wlan_power_setting-r1 +enum wlanapi.h WLAN_SECURABLE_OBJECT + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Native Wifi > Native Wifi reference > Native Wifi functions +callback wlanapi.h WFD_OPEN_SESSION_COMPLETE_CALLBACK +callback wlanapi.h WLAN_NOTIFICATION_CALLBACK +function wlanapi.h WFDCancelOpenSession +function wlanapi.h WFDCloseHandle +function wlanapi.h WFDCloseSession +function wlanapi.h WFDOpenHandle +function wlanapi.h WFDOpenLegacySession +function wlanapi.h WFDStartOpenSession +function wlanapi.h WFDUpdateDeviceVisibility +function wlanapi.h WlanAllocateMemory +function wlanapi.h WlanCloseHandle +function wlanapi.h WlanConnect +function wlanapi.h WlanDeleteProfile +function wlanapi.h WlanDeviceServiceCommand +function wlanapi.h WlanDisconnect +function wlanapi.h WlanEnumInterfaces +function wlanapi.h WlanExtractPsdIEDataList +function wlanapi.h WlanFreeMemory +function wlanapi.h WlanGetAvailableNetworkList +function wlanapi.h WlanGetFilterList +function wlanapi.h WlanGetInterfaceCapability +function wlanapi.h WlanGetNetworkBssList +function wlanapi.h WlanGetProfile +function wlanapi.h WlanGetProfileCustomUserData +function wlanapi.h WlanGetProfileList +function wlanapi.h WlanGetSecuritySettings +function wlanapi.h WlanGetSupportedDeviceServices +function wlanapi.h WlanHostedNetworkForceStart +function wlanapi.h WlanHostedNetworkForceStop +function wlanapi.h WlanHostedNetworkInitSettings +function wlanapi.h WlanHostedNetworkQueryProperty +function wlanapi.h WlanHostedNetworkQuerySecondaryKey +function wlanapi.h WlanHostedNetworkQueryStatus +function wlanapi.h WlanHostedNetworkRefreshSecuritySettings +function wlanapi.h WlanHostedNetworkSetProperty +function wlanapi.h WlanHostedNetworkSetSecondaryKey +function wlanapi.h WlanHostedNetworkStartUsing +function wlanapi.h WlanHostedNetworkStopUsing +function wlanapi.h WlanIhvControl +function wlanapi.h WlanOpenHandle +function wlanapi.h WlanQueryAutoConfigParameter +function wlanapi.h WlanQueryInterface +function wlanapi.h WlanReasonCodeToString +function wlanapi.h WlanRegisterDeviceServiceNotification +function wlanapi.h WlanRegisterNotification +function wlanapi.h WlanRegisterVirtualStationNotification +function wlanapi.h WlanRenameProfile +function wlanapi.h WlanSaveTemporaryProfile +function wlanapi.h WlanScan +function wlanapi.h WlanSetAutoConfigParameter +function wlanapi.h WlanSetFilterList +function wlanapi.h WlanSetInterface +function wlanapi.h WlanSetProfile +function wlanapi.h WlanSetProfileCustomUserData +function wlanapi.h WlanSetProfileEapUserData +function wlanapi.h WlanSetProfileEapXmlUserData +function wlanapi.h WlanSetProfileList +function wlanapi.h WlanSetProfilePosition +function wlanapi.h WlanSetPsdIEDataList +function wlanapi.h WlanSetSecuritySettings +function wlanapi.h WlanUIEditProfile + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Native Wifi > Native Wifi reference > Native Wifi structures +struct dot1x.h ONEX_AUTH_PARAMS +struct dot1x.h ONEX_EAP_ERROR +struct dot1x.h ONEX_RESULT_UPDATE_DATA +struct dot1x.h ONEX_STATUS +struct dot1x.h ONEX_VARIABLE_BLOB +struct wlanapi.h DOT11_NETWORK +struct wlanapi.h DOT11_NETWORK_LIST +struct wlanapi.h WLAN_ASSOCIATION_ATTRIBUTES +struct wlanapi.h WLAN_AUTH_CIPHER_PAIR_LIST +struct wlanapi.h WLAN_AVAILABLE_NETWORK +struct wlanapi.h WLAN_AVAILABLE_NETWORK_LIST +struct wlanapi.h WLAN_BSS_ENTRY +struct wlanapi.h WLAN_BSS_LIST +struct wlanapi.h WLAN_CONNECTION_ATTRIBUTES +struct wlanapi.h WLAN_CONNECTION_NOTIFICATION_DATA +struct wlanapi.h WLAN_CONNECTION_PARAMETERS +struct wlanapi.h WLAN_COUNTRY_OR_REGION_STRING_LIST +struct wlanapi.h WLAN_DEVICE_SERVICE_GUID_LIST +struct wlanapi.h WLAN_DEVICE_SERVICE_NOTIFICATION_DATA +struct wlanapi.h WLAN_HOSTED_NETWORK_CONNECTION_SETTINGS +struct wlanapi.h WLAN_HOSTED_NETWORK_DATA_PEER_STATE_CHANGE +struct wlanapi.h WLAN_HOSTED_NETWORK_PEER_STATE +struct wlanapi.h WLAN_HOSTED_NETWORK_RADIO_STATE +struct wlanapi.h WLAN_HOSTED_NETWORK_SECURITY_SETTINGS +struct wlanapi.h WLAN_HOSTED_NETWORK_STATE_CHANGE +struct wlanapi.h WLAN_HOSTED_NETWORK_STATUS +struct wlanapi.h WLAN_INTERFACE_CAPABILITY +struct wlanapi.h WLAN_INTERFACE_INFO +struct wlanapi.h WLAN_INTERFACE_INFO_LIST +struct wlanapi.h WLAN_MAC_FRAME_STATISTICS +struct wlanapi.h WLAN_MSM_NOTIFICATION_DATA +struct wlanapi.h WLAN_PHY_FRAME_STATISTICS +struct wlanapi.h WLAN_PHY_RADIO_STATE +struct wlanapi.h WLAN_PROFILE_INFO +struct wlanapi.h WLAN_PROFILE_INFO_LIST +struct wlanapi.h WLAN_RADIO_STATE +struct wlanapi.h WLAN_RATE_SET +struct wlanapi.h WLAN_RAW_DATA +struct wlanapi.h WLAN_RAW_DATA_LIST +struct wlanapi.h WLAN_SECURITY_ATTRIBUTES +struct wlanapi.h WLAN_STATISTICS + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Native Wifi > Wireless Ad Hoc Reference > Wireless Ad Hoc Enumerations +enum adhoc.h DOT11_ADHOC_AUTH_ALGORITHM +enum adhoc.h DOT11_ADHOC_CIPHER_ALGORITHM +enum adhoc.h DOT11_ADHOC_CONNECT_FAIL_REASON +enum adhoc.h DOT11_ADHOC_NETWORK_CONNECTION_STATUS + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Native Wifi > Wireless Ad Hoc Reference > Wireless Ad Hoc Interfaces +interface adhoc.h IDot11AdHocInterface +interface adhoc.h IDot11AdHocInterfaceNotificationSink +interface adhoc.h IDot11AdHocManager +interface adhoc.h IDot11AdHocManagerNotificationSink +interface adhoc.h IDot11AdHocNetwork +interface adhoc.h IDot11AdHocNetworkNotificationSink +interface adhoc.h IDot11AdHocSecuritySettings +interface adhoc.h IEnumDot11AdHocInterfaces +interface adhoc.h IEnumDot11AdHocNetworks +interface adhoc.h IEnumDot11AdHocSecuritySettings + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connect Now > Windows Connect Now Reference > Windows Connect Now Enumerations +enum wcndevice.h WCN_PASSWORD_TYPE +enum wcndevice.h WCN_SESSION_STATUS +enum wcntypes.h WCN_ATTRIBUTE_TYPE +enum wcntypes.h WCN_VALUE_TYPE_ASSOCIATION_STATE +enum wcntypes.h WCN_VALUE_TYPE_AUTHENTICATION_TYPE +enum wcntypes.h WCN_VALUE_TYPE_BOOLEAN +enum wcntypes.h WCN_VALUE_TYPE_CONFIG_METHODS +enum wcntypes.h WCN_VALUE_TYPE_CONFIGURATION_ERROR +enum wcntypes.h WCN_VALUE_TYPE_CONNECTION_TYPE +enum wcntypes.h WCN_VALUE_TYPE_DEVICE_PASSWORD_ID +enum wcntypes.h WCN_VALUE_TYPE_ENCRYPTION_TYPE +enum wcntypes.h WCN_VALUE_TYPE_RF_BANDS +enum wcntypes.h WCN_VALUE_TYPE_VERSION +enum wcntypes.h WCN_VALUE_TYPE_WI_FI_PROTECTED_SETUP_STATE + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connect Now > Windows Connect Now Reference > Windows Connect Now Structures +struct wcndevice.h WCN_VENDOR_EXTENSION_SPEC +struct wcntypes.h WCN_VALUE_TYPE_PRIMARY_DEVICE_TYPE + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connect Now > Windows Connect Now Reference > Windows Connect Now Interfaces +interface wcndevice.h IWCNConnectNotify +interface wcndevice.h IWCNDevice + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connection Manager > Windows Connection Manager Reference > Windows Connection Manager Enumerated Types +enum wcmapi.h WCM_CONNECTION_COST +enum wcmapi.h WCM_CONNECTION_COST_SOURCE +enum wcmapi.h WCM_MEDIA_TYPE +enum wcmapi.h WCM_PROPERTY + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connection Manager > Windows Connection Manager Reference > Windows Connection Manager Functions +function wcmapi.h WcmFreeMemory +function wcmapi.h WcmGetProfileList +function wcmapi.h WcmQueryProperty +function wcmapi.h WcmSetProfileList +function wcmapi.h WcmSetProperty + +# Desktop App Technologies > Networking and Internet > Wireless Networking > Windows Connection Manager > Windows Connection Manager Reference > Windows Connection Manager Structures +struct wcmapi.h WCM_BILLING_CYCLE_INFO +struct wcmapi.h WCM_CONNECTION_COST_DATA +struct wcmapi.h WCM_DATAPLAN_STATUS +struct wcmapi.h WCM_POLICY_VALUE +struct wcmapi.h WCM_PROFILE_INFO +struct wcmapi.h WCM_PROFILE_INFO_LIST +struct wcmapi.h WCM_TIME_INTERVAL +struct wcmapi.h WCM_USAGE_DATA + +# Desktop App Technologies > Networking and Internet > WebDAV > WebDAV API Reference > WebDAV API Enumerations +enum davclnt.h AUTHNEXTSTEP + +# Desktop App Technologies > Networking and Internet > WebDAV > WebDAV API Reference > WebDAV API Functions +callback davclnt.h PFNDAVAUTHCALLBACK +callback davclnt.h PFNDAVAUTHCALLBACK_FREECRED +function davclnt.h DavAddConnection +function davclnt.h DavCancelConnectionsToServer +function davclnt.h DavDeleteConnection +function davclnt.h DavFlushFile +function davclnt.h DavGetExtendedError +function davclnt.h DavGetHTTPFromUNCPath +function davclnt.h DavGetTheLockOwnerOfTheFile +function davclnt.h DavGetUNCFromHTTPPath +function davclnt.h DavInvalidateCache +function davclnt.h DavRegisterAuthCallback +function davclnt.h DavUnregisterAuthCallback + +# Desktop App Technologies > Networking and Internet > WebDAV > WebDAV API Reference > WebDAV API Structures +struct davclnt.h DAV_CALLBACK_AUTH_BLOB +struct davclnt.h DAV_CALLBACK_AUTH_UNP +struct davclnt.h DAV_CALLBACK_CRED + +# Desktop App Technologies > Networking and Internet > Windows HTTP Services (WinHTTP) > WinHTTP Reference > WinHTTP C/C++ API Reference > WinHTTP Functions +function winhttp.h WinHttpAddRequestHeaders +function winhttp.h WinHttpAddRequestHeadersEx +function winhttp.h WinHttpCheckPlatform +function winhttp.h WinHttpCloseHandle +function winhttp.h WinHttpConnect +function winhttp.h WinHttpCrackUrl +function winhttp.h WinHttpCreateProxyResolver +function winhttp.h WinHttpCreateUrl +function winhttp.h WinHttpDetectAutoProxyConfigUrl +function winhttp.h WinHttpFreeProxyResult +function winhttp.h WinHttpFreeProxySettingsEx +function winhttp.h WinHttpFreeQueryConnectionGroupResult +function winhttp.h WinHttpGetDefaultProxyConfiguration +function winhttp.h WinHttpGetIEProxyConfigForCurrentUser +function winhttp.h WinHttpGetProxyForUrl +function winhttp.h WinHttpGetProxyForUrlEx +function winhttp.h WinHttpGetProxyResult +function winhttp.h WinHttpGetProxySettingsEx +function winhttp.h WinHttpGetProxySettingsResultEx +function winhttp.h WinHttpOpen +function winhttp.h WinHttpOpenRequest +function winhttp.h WinHttpQueryAuthSchemes +function winhttp.h WinHttpQueryConnectionGroup +function winhttp.h WinHttpQueryDataAvailable +function winhttp.h WinHttpQueryHeaders +function winhttp.h WinHttpQueryHeadersEx +function winhttp.h WinHttpQueryOption +function winhttp.h WinHttpReadData +function winhttp.h WinHttpReadDataEx +function winhttp.h WinHttpReceiveResponse +function winhttp.h WinHttpRegisterProxyChangeNotification +function winhttp.h WinHttpResetAutoProxy +function winhttp.h WinHttpSendRequest +function winhttp.h WinHttpSetCredentials +function winhttp.h WinHttpSetDefaultProxyConfiguration +function winhttp.h WinHttpSetOption +function winhttp.h WinHttpSetStatusCallback +function winhttp.h WinHttpSetTimeouts +function winhttp.h WinHttpTimeFromSystemTime +function winhttp.h WinHttpTimeToSystemTime +function winhttp.h WinHttpUnregisterProxyChangeNotification +function winhttp.h WinHttpWebSocketClose +function winhttp.h WinHttpWebSocketCompleteUpgrade +function winhttp.h WinHttpWebSocketQueryCloseStatus +function winhttp.h WinHttpWebSocketReceive +function winhttp.h WinHttpWebSocketSend +function winhttp.h WinHttpWebSocketShutdown +function winhttp.h WinHttpWriteData + +# Desktop App Technologies > Networking and Internet > Windows HTTP Services (WinHTTP) > WinHTTP Reference > WinHTTP C/C++ API Reference > WinHTTP callbacks +callback winhttp.h WINHTTP_PROXY_CHANGE_CALLBACK +callback winhttp.h WINHTTP_STATUS_CALLBACK + +# Desktop App Technologies > Networking and Internet > Windows HTTP Services (WinHTTP) > WinHTTP Reference > WinHTTP C/C++ API Reference > WinHTTP structures +struct winhttp.h HTTP_VERSION_INFO +struct winhttp.h URL_COMPONENTS +struct winhttp.h WINHTTP_ASYNC_RESULT +struct winhttp.h WINHTTP_AUTOPROXY_OPTIONS +struct winhttp.h WINHTTP_CERTIFICATE_INFO +struct winhttp.h WINHTTP_CONNECTION_GROUP +struct winhttp.h WINHTTP_CONNECTION_INFO +struct winhttp.h WINHTTP_CREDS +struct winhttp.h WINHTTP_CREDS_EX +struct winhttp.h WINHTTP_CURRENT_USER_IE_PROXY_CONFIG +struct winhttp.h WINHTTP_EXTENDED_HEADER +struct winhttp.h WINHTTP_HEADER_NAME +struct winhttp.h WINHTTP_HOST_CONNECTION_GROUP +struct winhttp.h WINHTTP_MATCH_CONNECTION_GUID +struct winhttp.h WINHTTP_PROXY_INFO +struct winhttp.h WINHTTP_PROXY_RESULT +struct winhttp.h WINHTTP_PROXY_RESULT_ENTRY +struct winhttp.h WINHTTP_PROXY_SETTINGS_EX +struct winhttp.h WINHTTP_PROXY_SETTINGS_PARAM +struct winhttp.h WINHTTP_QUERY_CONNECTION_GROUP_RESULT +struct winhttp.h WINHTTP_REQUEST_STATS +struct winhttp.h WINHTTP_REQUEST_TIMES +struct winhttp.h WINHTTP_SECURITY_INFO +struct winhttp.h WINHTTP_WEB_SOCKET_ASYNC_RESULT +struct winhttp.h WINHTTP_WEB_SOCKET_STATUS + +# Desktop App Technologies > Networking and Internet > Windows HTTP Services (WinHTTP) > WinHTTP Reference > WinHTTP C/C++ API Reference > WinHTTP enumerations +enum winhttp.h WINHTTP_PROXY_SETTINGS_TYPE +enum winhttp.h WINHTTP_WEB_SOCKET_BUFFER_TYPE +enum winhttp.h WINHTTP_WEB_SOCKET_CLOSE_STATUS +enum winhttp.h WINHTTP_WEB_SOCKET_OPERATION + +# Desktop App Technologies > Networking and Internet > Windows Internet > WinINet Reference > WinINet Enumerations +enum wininet.h INTERNET_SCHEME +enum wininet.h InternetCookieState + +# Desktop App Technologies > Networking and Internet > Windows Internet > WinINet Reference > WinINet functions +callback wininet.h GopherAttributeEnumerator +callback wininet.h InternetStatusCallback +function wininet.h CommitUrlCacheEntryW +function wininet.h CreateMD5SSOHash +function wininet.h CreateUrlCacheEntry =createurlcacheentrya +function wininet.h CreateUrlCacheGroup +function wininet.h DeleteUrlCacheEntry +function wininet.h DeleteUrlCacheGroup +function wininet.h DetectAutoProxyUrl +function wininet.h FindCloseUrlCache +function wininet.h FindFirstUrlCacheEntry =findfirsturlcacheentrya +function wininet.h FindFirstUrlCacheEntryEx =findfirsturlcacheentryexa +function wininet.h FindFirstUrlCacheGroup +function wininet.h FindNextUrlCacheEntry =findnexturlcacheentrya +function wininet.h FindNextUrlCacheEntryEx =findnexturlcacheentryexa +function wininet.h FindNextUrlCacheGroup +function wininet.h FtpCommandW +function wininet.h FtpCreateDirectory =ftpcreatedirectorya +function wininet.h FtpDeleteFile =ftpdeletefilea +function wininet.h FtpFindFirstFile =ftpfindfirstfilea +function wininet.h FtpGetCurrentDirectory =ftpgetcurrentdirectorya +function wininet.h FtpGetFile =ftpgetfilea +function wininet.h FtpGetFileSize +function wininet.h FtpOpenFile =ftpopenfilea +function wininet.h FtpPutFile =ftpputfilea +function wininet.h FtpRemoveDirectory =ftpremovedirectorya +function wininet.h FtpRenameFile =ftprenamefilea +function wininet.h FtpSetCurrentDirectory =ftpsetcurrentdirectorya +function wininet.h GetUrlCacheEntryInfo =geturlcacheentryinfoa +function wininet.h GetUrlCacheEntryInfoEx =geturlcacheentryinfoexa +function wininet.h GetUrlCacheGroupAttribute =geturlcachegroupattributea +function wininet.h GopherCreateLocator =gophercreatelocatora +function wininet.h GopherFindFirstFile =gopherfindfirstfilea +function wininet.h GopherGetAttribute =gophergetattributea +function wininet.h GopherGetLocatorType =gophergetlocatortypea +function wininet.h GopherOpenFile =gopheropenfilea +function wininet.h HttpAddRequestHeaders =httpaddrequestheadersa +function wininet.h HttpEndRequest =httpendrequesta +function wininet.h HttpOpenRequest =httpopenrequesta +function wininet.h HttpQueryInfo =httpqueryinfoa +function wininet.h HttpSendRequest =httpsendrequesta +function wininet.h HttpSendRequestEx =httpsendrequestexa +function wininet.h InternetAttemptConnect +function wininet.h InternetAutodial +function wininet.h InternetAutodialHangup +function wininet.h InternetCanonicalizeUrl =internetcanonicalizeurla +function wininet.h InternetCheckConnection =internetcheckconnectiona +function wininet.h InternetClearAllPerSiteCookieDecisions +function wininet.h InternetCloseHandle +function wininet.h InternetCombineUrl =internetcombineurla +function wininet.h InternetConfirmZoneCrossing +function wininet.h InternetConnect =internetconnecta +function wininet.h InternetCrackUrl =internetcrackurla +function wininet.h InternetCreateUrl =internetcreateurla +function wininet.h InternetDial +function wininet.h InternetEnumPerSiteCookieDecision =internetenumpersitecookiedecisiona +function wininet.h InternetErrorDlg +function wininet.h InternetFindNextFile =internetfindnextfilea +function wininet.h InternetGetConnectedState +function wininet.h InternetGetConnectedStateEx +function wininet.h InternetGetCookie =internetgetcookiea +function wininet.h InternetGetCookieEx =internetgetcookieexa +function wininet.h InternetGetLastResponseInfo =internetgetlastresponseinfoa +function wininet.h InternetGetPerSiteCookieDecision =internetgetpersitecookiedecisiona +function wininet.h InternetGoOnline +function wininet.h InternetHangUp +function wininet.h InternetInitializeAutoProxyDll +function wininet.h InternetLockRequestFile +function wininet.h InternetOpen =internetopena +function wininet.h InternetOpenUrl =internetopenurla +function wininet.h InternetQueryDataAvailable +function wininet.h InternetQueryOption =internetqueryoptiona +function wininet.h InternetReadFile +function wininet.h InternetReadFileEx =internetreadfileexa +function wininet.h InternetSetCookie =internetsetcookiea +function wininet.h InternetSetCookieEx =internetsetcookieexa +function wininet.h InternetSetDialState +function wininet.h InternetSetFilePointer +function wininet.h InternetSetOption =internetsetoptiona +function wininet.h InternetSetOptionEx =internetsetoptionexa +function wininet.h InternetSetPerSiteCookieDecision =internetsetpersitecookiedecisiona +function wininet.h InternetSetStatusCallback +function wininet.h InternetTimeFromSystemTime +function wininet.h InternetTimeToSystemTime +function wininet.h InternetUnlockRequestFile +function wininet.h InternetWriteFile +function wininet.h PrivacyGetZonePreferenceW +function wininet.h PrivacySetZonePreferenceW +function wininet.h ReadUrlCacheEntryStream +function wininet.h ResumeSuspendedDownload +function wininet.h RetrieveUrlCacheEntryFile =retrieveurlcacheentryfilea +function wininet.h RetrieveUrlCacheEntryStream =retrieveurlcacheentrystreama +function wininet.h SetUrlCacheEntryGroup +function wininet.h SetUrlCacheEntryInfo =seturlcacheentryinfoa +function wininet.h SetUrlCacheGroupAttribute =seturlcachegroupattributea +function wininet.h UnlockUrlCacheEntryFile +function wininet.h UnlockUrlCacheEntryStream +function winineti.h CreateUrlCacheContainer =createurlcachecontainera +function winineti.h GetUrlCacheConfigInfoW + +# Desktop App Technologies > Networking and Internet > Windows Internet > WinINet Reference > WinINet Structures +struct wininet.h AUTO_PROXY_SCRIPT_BUFFER +struct wininet.h AutoProxyHelperFunctions +struct wininet.h AutoProxyHelperVtbl +struct wininet.h GOPHER_ATTRIBUTE_TYPE +struct wininet.h GOPHER_FIND_DATA =gopher_find_dataa +struct wininet.h HTTP_VERSION_INFO +struct wininet.h INTERNET_ASYNC_RESULT +struct wininet.h INTERNET_AUTH_NOTIFY_DATA +struct wininet.h INTERNET_BUFFERS =internet_buffersa +struct wininet.h INTERNET_CACHE_ENTRY_INFO =internet_cache_entry_infoa +struct wininet.h INTERNET_CACHE_GROUP_INFO =internet_cache_group_infoa +struct wininet.h INTERNET_CACHE_TIMESTAMPS +struct wininet.h INTERNET_CERTIFICATE_INFO +struct wininet.h INTERNET_CONNECTED_INFO +struct wininet.h INTERNET_DIAGNOSTIC_SOCKET_INFO +struct wininet.h INTERNET_PER_CONN_OPTION =internet_per_conn_optiona +struct wininet.h INTERNET_PER_CONN_OPTION_LIST =internet_per_conn_option_lista +struct wininet.h INTERNET_PROXY_INFO +struct wininet.h INTERNET_VERSION_INFO +struct wininet.h InternetCookieHistory +struct wininet.h URL_COMPONENTS =url_componentsa +struct winineti.h INTERNET_CACHE_CONFIG_INFO =internet_cache_config_infoa + +# Desktop App Technologies > Networking and Internet > Windows Internet > WinINet Reference > Proof of possession cookie API +function proofofpossessioncookieinfo.h FreeProofOfPossessionCookieInfoArray function =freeproofofpossessioncookieinfoarray +interface proofofpossessioncookieinfo.h IProofOfPossessionCookieInfoManager +interface proofofpossessioncookieinfo.h IProofOfPossessionCookieInfoManager2 +struct proofofpossessioncookieinfo.h ProofOfPossessionCookieInfo structure =proofofpossessioncookieinfo + +# Desktop App Technologies > Networking and Internet > Windows Web Services > Windows Web Services Reference > Windows Web Services Callback Functions +callback webservices.h WS_ABANDON_MESSAGE_CALLBACK +callback webservices.h WS_ABORT_CHANNEL_CALLBACK +callback webservices.h WS_ABORT_LISTENER_CALLBACK +callback webservices.h WS_ACCEPT_CHANNEL_CALLBACK +callback webservices.h WS_ASYNC_CALLBACK +callback webservices.h WS_ASYNC_FUNCTION +callback webservices.h WS_CERT_ISSUER_LIST_NOTIFICATION_CALLBACK +callback webservices.h WS_CERTIFICATE_VALIDATION_CALLBACK +callback webservices.h WS_CLOSE_CHANNEL_CALLBACK +callback webservices.h WS_CLOSE_LISTENER_CALLBACK +callback webservices.h WS_CREATE_CHANNEL_CALLBACK +callback webservices.h WS_CREATE_CHANNEL_FOR_LISTENER_CALLBACK +callback webservices.h WS_CREATE_DECODER_CALLBACK +callback webservices.h WS_CREATE_ENCODER_CALLBACK +callback webservices.h WS_CREATE_LISTENER_CALLBACK +callback webservices.h WS_DECODER_DECODE_CALLBACK +callback webservices.h WS_DECODER_END_CALLBACK +callback webservices.h WS_DECODER_GET_CONTENT_TYPE_CALLBACK +callback webservices.h WS_DECODER_START_CALLBACK +callback webservices.h WS_DURATION_COMPARISON_CALLBACK +callback webservices.h WS_DYNAMIC_STRING_CALLBACK +callback webservices.h WS_ENCODER_ENCODE_CALLBACK +callback webservices.h WS_ENCODER_END_CALLBACK +callback webservices.h WS_ENCODER_GET_CONTENT_TYPE_CALLBACK +callback webservices.h WS_ENCODER_START_CALLBACK +callback webservices.h WS_FREE_CHANNEL_CALLBACK +callback webservices.h WS_FREE_DECODER_CALLBACK +callback webservices.h WS_FREE_ENCODER_CALLBACK +callback webservices.h WS_FREE_LISTENER_CALLBACK +callback webservices.h WS_GET_CERT_CALLBACK +callback webservices.h WS_GET_CHANNEL_PROPERTY_CALLBACK +callback webservices.h WS_GET_LISTENER_PROPERTY_CALLBACK +callback webservices.h WS_HTTP_REDIRECT_CALLBACK +callback webservices.h WS_IS_DEFAULT_VALUE_CALLBACK +callback webservices.h WS_MESSAGE_DONE_CALLBACK +callback webservices.h WS_OPEN_CHANNEL_CALLBACK +callback webservices.h WS_OPEN_LISTENER_CALLBACK +callback webservices.h WS_OPERATION_CANCEL_CALLBACK +callback webservices.h WS_OPERATION_FREE_STATE_CALLBACK +callback webservices.h WS_PROXY_MESSAGE_CALLBACK +callback webservices.h WS_PULL_BYTES_CALLBACK +callback webservices.h WS_PUSH_BYTES_CALLBACK +callback webservices.h WS_READ_CALLBACK +callback webservices.h WS_READ_MESSAGE_END_CALLBACK +callback webservices.h WS_READ_MESSAGE_START_CALLBACK +callback webservices.h WS_READ_TYPE_CALLBACK +callback webservices.h WS_RESET_CHANNEL_CALLBACK +callback webservices.h WS_RESET_LISTENER_CALLBACK +callback webservices.h WS_SERVICE_ACCEPT_CHANNEL_CALLBACK +callback webservices.h WS_SERVICE_CLOSE_CHANNEL_CALLBACK +callback webservices.h WS_SERVICE_MESSAGE_RECEIVE_CALLBACK +callback webservices.h WS_SERVICE_SECURITY_CALLBACK +callback webservices.h WS_SERVICE_STUB_CALLBACK +callback webservices.h WS_SET_CHANNEL_PROPERTY_CALLBACK +callback webservices.h WS_SET_LISTENER_PROPERTY_CALLBACK +callback webservices.h WS_SHUTDOWN_SESSION_CHANNEL_CALLBACK +callback webservices.h WS_VALIDATE_PASSWORD_CALLBACK +callback webservices.h WS_VALIDATE_SAML_CALLBACK +callback webservices.h WS_WRITE_CALLBACK +callback webservices.h WS_WRITE_MESSAGE_END_CALLBACK +callback webservices.h WS_WRITE_MESSAGE_START_CALLBACK +callback webservices.h WS_WRITE_TYPE_CALLBACK + +# Desktop App Technologies > Networking and Internet > Windows Web Services > Windows Web Services Reference > Windows Web Services Enumerations +enum webservices.h WS_ADDRESSING_VERSION +enum webservices.h WS_BINDING_TEMPLATE_TYPE +enum webservices.h WS_CALL_PROPERTY_ID +enum webservices.h WS_CALLBACK_MODEL +enum webservices.h WS_CERT_CREDENTIAL_TYPE +enum webservices.h WS_CERT_FAILURE =ws_value_type +enum webservices.h WS_CHANNEL_BINDING +enum webservices.h WS_CHANNEL_PROPERTY_ID +enum webservices.h WS_CHANNEL_STATE +enum webservices.h WS_CHANNEL_TYPE +enum webservices.h WS_CHARSET +enum webservices.h WS_COOKIE_MODE +enum webservices.h WS_DATETIME_FORMAT +enum webservices.h WS_ENCODING +enum webservices.h WS_ENDPOINT_ADDRESS_EXTENSION_TYPE +enum webservices.h WS_ENDPOINT_IDENTITY_TYPE +enum webservices.h WS_ENVELOPE_VERSION +enum webservices.h WS_ERROR_PROPERTY_ID +enum webservices.h WS_EXCEPTION_CODE +enum webservices.h WS_EXTENDED_PROTECTION_POLICY +enum webservices.h WS_EXTENDED_PROTECTION_SCENARIO +enum webservices.h WS_FAULT_DISCLOSURE +enum webservices.h WS_FAULT_ERROR_PROPERTY_ID +enum webservices.h WS_FIELD_MAPPING +enum webservices.h WS_FIELD_OPTIONS =ws_xml_reader_encoding_type +enum webservices.h WS_HEADER_ATTRIBUTES =ws_xml_text_type +enum webservices.h WS_HEADER_TYPE +enum webservices.h WS_HEAP_PROPERTY_ID +enum webservices.h WS_HTTP_HEADER_AUTH_SCHEME =ws_xml_node_type +enum webservices.h WS_HTTP_HEADER_AUTH_TARGET +enum webservices.h WS_HTTP_HEADER_MAPPING_OPTIONS =ws_xml_canonicalization_algorithm +enum webservices.h WS_HTTP_PROXY_SETTING_MODE +enum webservices.h WS_HTTP_REQUEST_MAPPING_OPTIONS =ws_xml_writer_property_id +enum webservices.h WS_HTTP_RESPONSE_MAPPING_OPTIONS =ws_xml_canonicalization_property_id +enum webservices.h WS_IP_VERSION +enum webservices.h WS_LISTENER_PROPERTY_ID +enum webservices.h WS_LISTENER_STATE +enum webservices.h WS_MESSAGE_INITIALIZATION +enum webservices.h WS_MESSAGE_PROPERTY_ID +enum webservices.h WS_MESSAGE_SECURITY_USAGE +enum webservices.h WS_MESSAGE_STATE +enum webservices.h WS_METADATA_EXCHANGE_TYPE +enum webservices.h WS_METADATA_PROPERTY_ID +enum webservices.h WS_METADATA_STATE +enum webservices.h WS_MOVE_TO +enum webservices.h WS_OPERATION_CONTEXT_PROPERTY_ID +enum webservices.h WS_OPERATION_STYLE +enum webservices.h WS_PARAMETER_TYPE +enum webservices.h WS_POLICY_EXTENSION_TYPE +enum webservices.h WS_POLICY_PROPERTY_ID +enum webservices.h WS_POLICY_STATE +enum webservices.h WS_PROTECTION_LEVEL +enum webservices.h WS_PROXY_PROPERTY_ID +enum webservices.h WS_READ_OPTION +enum webservices.h WS_RECEIVE_OPTION +enum webservices.h WS_REPEATING_HEADER_OPTION +enum webservices.h WS_REQUEST_SECURITY_TOKEN_ACTION +enum webservices.h WS_REQUEST_SECURITY_TOKEN_PROPERTY_ID +enum webservices.h WS_SAML_AUTHENTICATOR_TYPE +enum webservices.h WS_SECURE_CONVERSATION_VERSION +enum webservices.h WS_SECURITY_ALGORITHM_ID +enum webservices.h WS_SECURITY_ALGORITHM_PROPERTY_ID +enum webservices.h WS_SECURITY_ALGORITHM_SUITE_NAME +enum webservices.h WS_SECURITY_BINDING_CONSTRAINT_TYPE +enum webservices.h WS_SECURITY_BINDING_PROPERTY_ID +enum webservices.h WS_SECURITY_BINDING_TYPE +enum webservices.h WS_SECURITY_CONTEXT_PROPERTY_ID +enum webservices.h WS_SECURITY_HEADER_LAYOUT +enum webservices.h WS_SECURITY_HEADER_VERSION +enum webservices.h WS_SECURITY_KEY_ENTROPY_MODE +enum webservices.h WS_SECURITY_KEY_HANDLE_TYPE +enum webservices.h WS_SECURITY_KEY_TYPE +enum webservices.h WS_SECURITY_PROPERTY_ID +enum webservices.h WS_SECURITY_TIMESTAMP_USAGE +enum webservices.h WS_SECURITY_TOKEN_PROPERTY_ID +enum webservices.h WS_SECURITY_TOKEN_REFERENCE_MODE +enum webservices.h WS_SERVICE_CANCEL_REASON +enum webservices.h WS_SERVICE_ENDPOINT_PROPERTY_ID +enum webservices.h WS_SERVICE_HOST_STATE +enum webservices.h WS_SERVICE_OPERATION_MESSAGE_OPTION =ws_charset +enum webservices.h WS_SERVICE_PROPERTY_ID +enum webservices.h WS_SERVICE_PROXY_STATE +enum webservices.h WS_TRACE_API +enum webservices.h WS_TRANSFER_MODE +enum webservices.h WS_TRUST_VERSION +enum webservices.h WS_TYPE +enum webservices.h WS_TYPE_MAPPING +enum webservices.h WS_URL_FLAGS =ws_xml_writer_encoding_type +enum webservices.h WS_URL_SCHEME_TYPE +enum webservices.h WS_USERNAME_CREDENTIAL_TYPE +enum webservices.h WS_VALUE_TYPE +enum webservices.h WS_WINDOWS_INTEGRATED_AUTH_CREDENTIAL_TYPE +enum webservices.h WS_WINDOWS_INTEGRATED_AUTH_PACKAGE +enum webservices.h WS_WRITE_OPTION +enum webservices.h WS_XML_BUFFER_PROPERTY_ID +enum webservices.h WS_XML_CANONICALIZATION_ALGORITHM +enum webservices.h WS_XML_CANONICALIZATION_PROPERTY_ID +enum webservices.h WS_XML_NODE_TYPE +enum webservices.h WS_XML_READER_ENCODING_TYPE +enum webservices.h WS_XML_READER_INPUT_TYPE +enum webservices.h WS_XML_READER_PROPERTY_ID +enum webservices.h WS_XML_SECURITY_TOKEN_PROPERTY_ID +enum webservices.h WS_XML_TEXT_TYPE +enum webservices.h WS_XML_WRITER_ENCODING_TYPE +enum webservices.h WS_XML_WRITER_OUTPUT_TYPE +enum webservices.h WS_XML_WRITER_PROPERTY_ID + +# Desktop App Technologies > Networking and Internet > Windows Web Services > Windows Web Services Reference > Windows Web Services Functions +function webservices.h WsAbandonCall +function webservices.h WsAbandonMessage +function webservices.h WsAbortChannel +function webservices.h WsAbortListener +function webservices.h WsAbortServiceHost +function webservices.h WsAbortServiceProxy +function webservices.h WsAcceptChannel +function webservices.h WsAddCustomHeader +function webservices.h WsAddErrorString +function webservices.h WsAddMappedHeader +function webservices.h WsAddressMessage +function webservices.h WsAlloc +function webservices.h WsAsyncExecute +function webservices.h WsCall +function webservices.h WsCheckMustUnderstandHeaders +function webservices.h WsCloseChannel +function webservices.h WsCloseListener +function webservices.h WsCloseServiceHost +function webservices.h WsCloseServiceProxy +function webservices.h WsCombineUrl +function webservices.h WsCopyError +function webservices.h WsCopyNode +function webservices.h WsCreateChannel +function webservices.h WsCreateChannelForListener +function webservices.h WsCreateError +function webservices.h WsCreateFaultFromError +function webservices.h WsCreateHeap +function webservices.h WsCreateListener +function webservices.h WsCreateMessage +function webservices.h WsCreateMessageForChannel +function webservices.h WsCreateMetadata +function webservices.h WsCreateReader +function webservices.h WsCreateServiceEndpointFromTemplate +function webservices.h WsCreateServiceHost +function webservices.h WsCreateServiceProxy +function webservices.h WsCreateServiceProxyFromTemplate +function webservices.h WsCreateWriter +function webservices.h WsCreateXmlBuffer +function webservices.h WsCreateXmlSecurityToken +function webservices.h WsDateTimeToFileTime +function webservices.h WsDecodeUrl +function webservices.h WsEncodeUrl +function webservices.h WsEndReaderCanonicalization +function webservices.h WsEndWriterCanonicalization +function webservices.h WsFileTimeToDateTime +function webservices.h WsFillBody +function webservices.h WsFillReader +function webservices.h WsFindAttribute +function webservices.h WsFlushBody +function webservices.h WsFlushWriter +function webservices.h WsFreeChannel +function webservices.h WsFreeError +function webservices.h WsFreeHeap +function webservices.h WsFreeListener +function webservices.h WsFreeMessage +function webservices.h WsFreeMetadata +function webservices.h WsFreeReader +function webservices.h WsFreeSecurityToken +function webservices.h WsFreeServiceHost +function webservices.h WsFreeServiceProxy +function webservices.h WsFreeWriter +function webservices.h WsGetChannelProperty +function webservices.h WsGetCustomHeader +function webservices.h WsGetDictionary +function webservices.h WsGetErrorProperty +function webservices.h WsGetErrorString +function webservices.h WsGetFaultErrorDetail +function webservices.h WsGetFaultErrorProperty +function webservices.h WsGetHeader +function webservices.h WsGetHeaderAttributes +function webservices.h WsGetHeapProperty +function webservices.h WsGetListenerProperty +function webservices.h WsGetMappedHeader +function webservices.h WsGetMessageProperty +function webservices.h WsGetMetadataEndpoints +function webservices.h WsGetMetadataProperty +function webservices.h WsGetMissingMetadataDocumentAddress +function webservices.h WsGetNamespaceFromPrefix +function webservices.h WsGetOperationContextProperty +function webservices.h WsGetPolicyAlternativeCount +function webservices.h WsGetPolicyProperty +function webservices.h WsGetPrefixFromNamespace +function webservices.h WsGetReaderNode +function webservices.h WsGetReaderPosition +function webservices.h WsGetReaderProperty +function webservices.h WsGetSecurityContextProperty +function webservices.h WsGetSecurityTokenProperty +function webservices.h WsGetServiceHostProperty +function webservices.h WsGetServiceProxyProperty +function webservices.h WsGetWriterPosition +function webservices.h WsGetWriterProperty +function webservices.h WsGetXmlAttribute +function webservices.h WsInitializeMessage +function webservices.h WsMarkHeaderAsUnderstood +function webservices.h WsMatchPolicyAlternative +function webservices.h WsMoveReader +function webservices.h WsMoveWriter +function webservices.h WsOffsetOf +function webservices.h WsOpenChannel +function webservices.h WsOpenListener +function webservices.h WsOpenServiceHost +function webservices.h WsOpenServiceProxy +function webservices.h WsPullBytes +function webservices.h WsPushBytes +function webservices.h WsReadArray +function webservices.h WsReadAttribute +function webservices.h WsReadBody +function webservices.h WsReadBytes +function webservices.h WsReadChars +function webservices.h WsReadCharsUtf8 +function webservices.h WsReadElement +function webservices.h WsReadEndAttribute +function webservices.h WsReadEndElement +function webservices.h WsReadEndpointAddressExtension +function webservices.h WsReadEnvelopeEnd +function webservices.h WsReadEnvelopeStart +function webservices.h WsReadMessageEnd +function webservices.h WsReadMessageStart +function webservices.h WsReadMetadata +function webservices.h WsReadNode +function webservices.h WsReadQualifiedName +function webservices.h WsReadStartAttribute +function webservices.h WsReadStartElement +function webservices.h WsReadToStartElement +function webservices.h WsReadType +function webservices.h WsReadValue +function webservices.h WsReadXmlBuffer +function webservices.h WsReadXmlBufferFromBytes +function webservices.h WsReceiveMessage +function webservices.h WsRegisterOperationForCancel +function webservices.h WsRemoveCustomHeader +function webservices.h WsRemoveHeader +function webservices.h WsRemoveMappedHeader +function webservices.h WsRemoveNode +function webservices.h WsRequestReply +function webservices.h WsRequestSecurityToken +function webservices.h WsResetChannel +function webservices.h WsResetError +function webservices.h WsResetHeap +function webservices.h WsResetListener +function webservices.h WsResetMessage +function webservices.h WsResetMetadata +function webservices.h WsResetServiceHost +function webservices.h WsResetServiceProxy +function webservices.h WsRevokeSecurityContext +function webservices.h WsSendFaultMessageForError +function webservices.h WsSendMessage +function webservices.h WsSendReplyMessage +function webservices.h WsSetChannelProperty +function webservices.h WsSetErrorProperty +function webservices.h WsSetFaultErrorDetail +function webservices.h WsSetFaultErrorProperty +function webservices.h WsSetHeader +function webservices.h WsSetInput +function webservices.h WsSetInputToBuffer +function webservices.h WsSetListenerProperty +function webservices.h WsSetMessageProperty +function webservices.h WsSetOutput +function webservices.h WsSetOutputToBuffer +function webservices.h WsSetReaderPosition +function webservices.h WsSetWriterPosition +function webservices.h WsShutdownSessionChannel +function webservices.h WsSkipNode +function webservices.h WsStartReaderCanonicalization +function webservices.h WsStartWriterCanonicalization +function webservices.h WsTrimXmlWhitespace +function webservices.h WsVerifyXmlNCName +function webservices.h WsWriteArray +function webservices.h WsWriteAttribute +function webservices.h WsWriteBody +function webservices.h WsWriteBytes +function webservices.h WsWriteChars +function webservices.h WsWriteCharsUtf8 +function webservices.h WsWriteElement +function webservices.h WsWriteEndAttribute +function webservices.h WsWriteEndCData +function webservices.h WsWriteEndElement +function webservices.h WsWriteEndStartElement +function webservices.h WsWriteEnvelopeEnd +function webservices.h WsWriteEnvelopeStart +function webservices.h WsWriteMessageEnd +function webservices.h WsWriteMessageStart +function webservices.h WsWriteNode +function webservices.h WsWriteQualifiedName +function webservices.h WsWriteStartAttribute +function webservices.h WsWriteStartCData +function webservices.h WsWriteStartElement +function webservices.h WsWriteText +function webservices.h WsWriteType +function webservices.h WsWriteValue +function webservices.h WsWriteXmlBuffer +function webservices.h WsWriteXmlBufferToBytes +function webservices.h WsWriteXmlnsAttribute +function webservices.h WsXmlStringEquals + +# Desktop App Technologies > Networking and Internet > Windows Web Services > Windows Web Services Reference > Windows Web Services Macros +function webservices.h WS_STRING_VALUE +function webservices.h WS_XML_STRING_DICTIONARY_VALUE +function webservices.h WS_XML_STRING_VALUE +function webservices.h WsCountOf + +# Desktop App Technologies > Networking and Internet > Windows Web Services > Windows Web Services Reference > Windows Web Services Structures +struct webservices.h WS_ANY_ATTRIBUTE +struct webservices.h WS_ANY_ATTRIBUTES +struct webservices.h WS_ASYNC_CONTEXT +struct webservices.h WS_ASYNC_OPERATION +struct webservices.h WS_ASYNC_STATE +struct webservices.h WS_ATTRIBUTE_DESCRIPTION +struct webservices.h WS_BOOL_DESCRIPTION +struct webservices.h WS_BUFFERS +struct webservices.h WS_BYTE_ARRAY_DESCRIPTION +struct webservices.h WS_BYTES +struct webservices.h WS_BYTES_DESCRIPTION +struct webservices.h WS_CALL_PROPERTY +struct webservices.h WS_CAPI_ASYMMETRIC_SECURITY_KEY_HANDLE +struct webservices.h WS_CERT_CREDENTIAL +struct webservices.h WS_CERT_ENDPOINT_IDENTITY +struct webservices.h WS_CERT_MESSAGE_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_CERT_SIGNED_SAML_AUTHENTICATOR +struct webservices.h WS_CERTIFICATE_VALIDATION_CALLBACK_CONTEXT +struct webservices.h WS_CHANNEL_DECODER +struct webservices.h WS_CHANNEL_ENCODER +struct webservices.h WS_CHANNEL_PROPERTIES +struct webservices.h WS_CHANNEL_PROPERTY +struct webservices.h WS_CHANNEL_PROPERTY_CONSTRAINT +struct webservices.h WS_CHAR_ARRAY_DESCRIPTION +struct webservices.h WS_CONTRACT_DESCRIPTION +struct webservices.h WS_CUSTOM_CERT_CREDENTIAL +struct webservices.h WS_CUSTOM_CHANNEL_CALLBACKS +struct webservices.h WS_CUSTOM_HTTP_PROXY +struct webservices.h WS_CUSTOM_LISTENER_CALLBACKS +struct webservices.h WS_CUSTOM_TYPE_DESCRIPTION +struct webservices.h WS_DATETIME +struct webservices.h WS_DATETIME_DESCRIPTION +struct webservices.h WS_DECIMAL_DESCRIPTION +struct webservices.h WS_DEFAULT_VALUE +struct webservices.h WS_DEFAULT_WINDOWS_INTEGRATED_AUTH_CREDENTIAL +struct webservices.h WS_DISALLOWED_USER_AGENT_SUBSTRINGS +struct webservices.h WS_DNS_ENDPOINT_IDENTITY +struct webservices.h WS_DOUBLE_DESCRIPTION +struct webservices.h WS_DURATION +struct webservices.h WS_DURATION_DESCRIPTION +struct webservices.h WS_ELEMENT_DESCRIPTION +struct webservices.h WS_ENDPOINT_ADDRESS +struct webservices.h WS_ENDPOINT_ADDRESS_DESCRIPTION +struct webservices.h WS_ENDPOINT_IDENTITY +struct webservices.h WS_ENDPOINT_POLICY_EXTENSION +struct webservices.h WS_ENUM_DESCRIPTION +struct webservices.h WS_ENUM_VALUE +struct webservices.h WS_ERROR_PROPERTY +struct webservices.h WS_FAULT +struct webservices.h WS_FAULT_CODE +struct webservices.h WS_FAULT_DESCRIPTION +struct webservices.h WS_FAULT_DETAIL_DESCRIPTION +struct webservices.h WS_FAULT_REASON +struct webservices.h WS_FIELD_DESCRIPTION +struct webservices.h WS_FLOAT_DESCRIPTION +struct webservices.h WS_GUID_DESCRIPTION +struct webservices.h WS_HEAP_PROPERTIES +struct webservices.h WS_HEAP_PROPERTY +struct webservices.h WS_HOST_NAMES +struct webservices.h WS_HTTP_BINDING_TEMPLATE +struct webservices.h WS_HTTP_HEADER_AUTH_BINDING_TEMPLATE +struct webservices.h WS_HTTP_HEADER_AUTH_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_HEADER_AUTH_SECURITY_BINDING +struct webservices.h WS_HTTP_HEADER_AUTH_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_HTTP_HEADER_AUTH_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_HEADER_AUTH_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_HTTP_HEADER_MAPPING +struct webservices.h WS_HTTP_MESSAGE_MAPPING +struct webservices.h WS_HTTP_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_REDIRECT_CALLBACK_CONTEXT +struct webservices.h WS_HTTP_SSL_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_HEADER_AUTH_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_HEADER_AUTH_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_SSL_KERBEROS_APREQ_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_KERBEROS_APREQ_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_SSL_KERBEROS_APREQ_SECURITY_CONTEXT_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_KERBEROS_APREQ_SECURITY_CONTEXT_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_SSL_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_SSL_USERNAME_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_USERNAME_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_SSL_USERNAME_SECURITY_CONTEXT_BINDING_TEMPLATE +struct webservices.h WS_HTTP_SSL_USERNAME_SECURITY_CONTEXT_POLICY_DESCRIPTION +struct webservices.h WS_HTTP_URL +struct webservices.h WS_HTTPS_URL +struct webservices.h WS_INT16_DESCRIPTION +struct webservices.h WS_INT32_DESCRIPTION +struct webservices.h WS_INT64_DESCRIPTION +struct webservices.h WS_INT8_DESCRIPTION +struct webservices.h WS_ISSUED_TOKEN_MESSAGE_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_ITEM_RANGE +struct webservices.h WS_KERBEROS_APREQ_MESSAGE_SECURITY_BINDING +struct webservices.h WS_KERBEROS_APREQ_MESSAGE_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_KERBEROS_APREQ_MESSAGE_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_KERBEROS_APREQ_MESSAGE_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_LISTENER_PROPERTIES +struct webservices.h WS_LISTENER_PROPERTY +struct webservices.h WS_MESSAGE_DESCRIPTION +struct webservices.h WS_MESSAGE_PROPERTIES +struct webservices.h WS_MESSAGE_PROPERTY +struct webservices.h WS_METADATA_ENDPOINT +struct webservices.h WS_METADATA_ENDPOINTS +struct webservices.h WS_METADATA_PROPERTY +struct webservices.h WS_NAMEDPIPE_SSPI_TRANSPORT_SECURITY_BINDING +struct webservices.h WS_NCRYPT_ASYMMETRIC_SECURITY_KEY_HANDLE +struct webservices.h WS_NETPIPE_URL +struct webservices.h WS_NETTCP_URL +struct webservices.h WS_OPAQUE_WINDOWS_INTEGRATED_AUTH_CREDENTIAL +struct webservices.h WS_OPERATION_DESCRIPTION +struct webservices.h WS_PARAMETER_DESCRIPTION +struct webservices.h WS_POLICY_CONSTRAINTS +struct webservices.h WS_POLICY_EXTENSION +struct webservices.h WS_POLICY_PROPERTIES +struct webservices.h WS_POLICY_PROPERTY +struct webservices.h WS_PROXY_MESSAGE_CALLBACK_CONTEXT +struct webservices.h WS_PROXY_PROPERTY +struct webservices.h WS_RAW_SYMMETRIC_SECURITY_KEY_HANDLE +struct webservices.h WS_REQUEST_SECURITY_TOKEN_PROPERTY +struct webservices.h WS_REQUEST_SECURITY_TOKEN_PROPERTY_CONSTRAINT +struct webservices.h WS_RSA_ENDPOINT_IDENTITY +struct webservices.h WS_SAML_AUTHENTICATOR +struct webservices.h WS_SAML_MESSAGE_SECURITY_BINDING +struct webservices.h WS_SECURITY_ALGORITHM_PROPERTY +struct webservices.h WS_SECURITY_ALGORITHM_SUITE +struct webservices.h WS_SECURITY_BINDING +struct webservices.h WS_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_SECURITY_BINDING_PROPERTIES +struct webservices.h WS_SECURITY_BINDING_PROPERTY +struct webservices.h WS_SECURITY_BINDING_PROPERTY_CONSTRAINT +struct webservices.h WS_SECURITY_CONSTRAINTS +struct webservices.h WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING +struct webservices.h WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_SECURITY_CONTEXT_PROPERTY +struct webservices.h WS_SECURITY_CONTEXT_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_SECURITY_CONTEXT_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_SECURITY_DESCRIPTION +struct webservices.h WS_SECURITY_KEY_HANDLE +struct webservices.h WS_SECURITY_PROPERTIES +struct webservices.h WS_SECURITY_PROPERTY +struct webservices.h WS_SECURITY_PROPERTY_CONSTRAINT +struct webservices.h WS_SERVICE_CONTRACT +struct webservices.h WS_SERVICE_ENDPOINT +struct webservices.h WS_SERVICE_ENDPOINT_METADATA +struct webservices.h WS_SERVICE_ENDPOINT_PROPERTY +struct webservices.h WS_SERVICE_METADATA +struct webservices.h WS_SERVICE_METADATA_DOCUMENT +struct webservices.h WS_SERVICE_PROPERTY +struct webservices.h WS_SERVICE_PROPERTY_ACCEPT_CALLBACK +struct webservices.h WS_SERVICE_PROPERTY_CLOSE_CALLBACK +struct webservices.h WS_SERVICE_SECURITY_IDENTITIES +struct webservices.h WS_SOAPUDP_URL +struct webservices.h WS_SPN_ENDPOINT_IDENTITY +struct webservices.h WS_SSL_TRANSPORT_SECURITY_BINDING +struct webservices.h WS_SSL_TRANSPORT_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_SSL_TRANSPORT_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_SSL_TRANSPORT_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_SSPI_TRANSPORT_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_STRING +struct webservices.h WS_STRING_DESCRIPTION +struct webservices.h WS_STRING_USERNAME_CREDENTIAL +struct webservices.h WS_STRING_WINDOWS_INTEGRATED_AUTH_CREDENTIAL +struct webservices.h WS_STRUCT_DESCRIPTION +struct webservices.h WS_SUBJECT_NAME_CERT_CREDENTIAL +struct webservices.h WS_TCP_BINDING_TEMPLATE +struct webservices.h WS_TCP_POLICY_DESCRIPTION +struct webservices.h WS_TCP_SSPI_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_KERBEROS_APREQ_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_KERBEROS_APREQ_POLICY_DESCRIPTION +struct webservices.h WS_TCP_SSPI_KERBEROS_APREQ_SECURITY_CONTEXT_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_KERBEROS_APREQ_SECURITY_CONTEXT_POLICY_DESCRIPTION +struct webservices.h WS_TCP_SSPI_POLICY_DESCRIPTION +struct webservices.h WS_TCP_SSPI_TRANSPORT_SECURITY_BINDING +struct webservices.h WS_TCP_SSPI_TRANSPORT_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_TCP_SSPI_TRANSPORT_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_USERNAME_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_USERNAME_POLICY_DESCRIPTION +struct webservices.h WS_TCP_SSPI_USERNAME_SECURITY_CONTEXT_BINDING_TEMPLATE +struct webservices.h WS_TCP_SSPI_USERNAME_SECURITY_CONTEXT_POLICY_DESCRIPTION +struct webservices.h WS_THUMBPRINT_CERT_CREDENTIAL +struct webservices.h WS_TIMESPAN +struct webservices.h WS_TIMESPAN_DESCRIPTION +struct webservices.h WS_UINT16_DESCRIPTION +struct webservices.h WS_UINT32_DESCRIPTION +struct webservices.h WS_UINT64_DESCRIPTION +struct webservices.h WS_UINT8_DESCRIPTION +struct webservices.h WS_UNION_DESCRIPTION +struct webservices.h WS_UNION_FIELD_DESCRIPTION +struct webservices.h WS_UNIQUE_ID +struct webservices.h WS_UNIQUE_ID_DESCRIPTION +struct webservices.h WS_UNKNOWN_ENDPOINT_IDENTITY +struct webservices.h WS_UPN_ENDPOINT_IDENTITY +struct webservices.h WS_URL +struct webservices.h WS_USERNAME_CREDENTIAL +struct webservices.h WS_USERNAME_MESSAGE_SECURITY_BINDING +struct webservices.h WS_USERNAME_MESSAGE_SECURITY_BINDING_CONSTRAINT +struct webservices.h WS_USERNAME_MESSAGE_SECURITY_BINDING_POLICY_DESCRIPTION +struct webservices.h WS_USERNAME_MESSAGE_SECURITY_BINDING_TEMPLATE +struct webservices.h WS_UTF8_ARRAY_DESCRIPTION +struct webservices.h WS_VOID_DESCRIPTION +struct webservices.h WS_WINDOWS_INTEGRATED_AUTH_CREDENTIAL +struct webservices.h WS_WSZ_DESCRIPTION +struct webservices.h WS_XML_ATTRIBUTE +struct webservices.h WS_XML_BASE64_TEXT +struct webservices.h WS_XML_BOOL_TEXT +struct webservices.h WS_XML_BUFFER_PROPERTY +struct webservices.h WS_XML_CANONICALIZATION_INCLUSIVE_PREFIXES +struct webservices.h WS_XML_CANONICALIZATION_PROPERTY +struct webservices.h WS_XML_COMMENT_NODE +struct webservices.h WS_XML_DATETIME_TEXT +struct webservices.h WS_XML_DECIMAL_TEXT +struct webservices.h WS_XML_DICTIONARY +struct webservices.h WS_XML_DOUBLE_TEXT +struct webservices.h WS_XML_ELEMENT_NODE +struct webservices.h WS_XML_FLOAT_TEXT +struct webservices.h WS_XML_GUID_TEXT +struct webservices.h WS_XML_INT32_TEXT +struct webservices.h WS_XML_INT64_TEXT +struct webservices.h WS_XML_LIST_TEXT +struct webservices.h WS_XML_NODE +struct webservices.h WS_XML_NODE_POSITION +struct webservices.h WS_XML_QNAME +struct webservices.h WS_XML_QNAME_DESCRIPTION +struct webservices.h WS_XML_QNAME_TEXT +struct webservices.h WS_XML_READER_BINARY_ENCODING +struct webservices.h WS_XML_READER_BUFFER_INPUT +struct webservices.h WS_XML_READER_ENCODING +struct webservices.h WS_XML_READER_INPUT +struct webservices.h WS_XML_READER_MTOM_ENCODING +struct webservices.h WS_XML_READER_PROPERTIES +struct webservices.h WS_XML_READER_PROPERTY +struct webservices.h WS_XML_READER_RAW_ENCODING +struct webservices.h WS_XML_READER_STREAM_INPUT +struct webservices.h WS_XML_READER_TEXT_ENCODING +struct webservices.h WS_XML_SECURITY_TOKEN_PROPERTY +struct webservices.h WS_XML_STRING +struct webservices.h WS_XML_STRING_DESCRIPTION +struct webservices.h WS_XML_TEXT +struct webservices.h WS_XML_TEXT_NODE +struct webservices.h WS_XML_TIMESPAN_TEXT +struct webservices.h WS_XML_TOKEN_MESSAGE_SECURITY_BINDING +struct webservices.h WS_XML_UINT64_TEXT +struct webservices.h WS_XML_UNIQUE_ID_TEXT +struct webservices.h WS_XML_UTF16_TEXT +struct webservices.h WS_XML_UTF8_TEXT +struct webservices.h WS_XML_WRITER_BINARY_ENCODING +struct webservices.h WS_XML_WRITER_BUFFER_OUTPUT +struct webservices.h WS_XML_WRITER_ENCODING +struct webservices.h WS_XML_WRITER_MTOM_ENCODING +struct webservices.h WS_XML_WRITER_OUTPUT +struct webservices.h WS_XML_WRITER_PROPERTIES +struct webservices.h WS_XML_WRITER_PROPERTY +struct webservices.h WS_XML_WRITER_RAW_ENCODING +struct webservices.h WS_XML_WRITER_STREAM_OUTPUT +struct webservices.h WS_XML_WRITER_TEXT_ENCODING + +# Desktop App Technologies > Networking and Internet > Prefetch debug APIs for Windows Store apps +interface icontentprefetchertasktrigger.h IContentPrefetcherTaskTrigger + +# Desktop App Technologies > Security and Identity > Antimalware Scan Interface (AMSI) > AMSI reference > AMSI enumerations +enum amsi.h AMSI_ATTRIBUTE +enum amsi.h AMSI_RESULT + +# Desktop App Technologies > Security and Identity > Antimalware Scan Interface (AMSI) > AMSI reference > AMSI functions +function amsi.h AmsiCloseSession +function amsi.h AmsiInitialize +function amsi.h AmsiNotifyOperation =amsiopensession +function amsi.h AmsiOpenSession =amsinotifyoperation +function amsi.h AmsiResultIsMalware +function amsi.h AmsiScanBuffer +function amsi.h AmsiScanString +function amsi.h AmsiUninitialize + +# Desktop App Technologies > Security and Identity > Antimalware Scan Interface (AMSI) > AMSI reference > AMSI interfaces +interface amsi.h IAmsiStream +interface amsi.h IAntimalware +interface amsi.h IAntimalware2 +interface amsi.h IAntimalwareProvider +interface amsi.h IAntimalwareProvider2 + +# Desktop App Technologies > Security and Identity > Authentication > About Authentication > Winlogon and Credential Providers > Winlogon and GINA > Winlogon Notification Packages > Winlogon Notification Package Reference > Event Handler Function Prototype +struct winwlx.h WLX_NOTIFICATION_INFO + +# Desktop App Technologies > Security and Identity > Authentication > Authentication Reference > Authentication Enumerations +enum credssp.h CREDSPP_SUBMIT_TYPE +enum identitycommon.h IDENTITY_TYPE +enum ntsecapi.h KERB_CERTIFICATE_INFO_TYPE +enum ntsecapi.h KERB_LOGON_SUBMIT_TYPE +enum ntsecapi.h KERB_PROFILE_BUFFER_TYPE +enum ntsecapi.h KERB_PROTOCOL_MESSAGE_TYPE +enum ntsecapi.h LSA_FOREST_TRUST_COLLISION_RECORD_TYPE +enum ntsecapi.h LSA_FOREST_TRUST_RECORD_TYPE +enum ntsecapi.h MSV1_0_LOGON_SUBMIT_TYPE +enum ntsecapi.h MSV1_0_PROFILE_BUFFER_TYPE +enum ntsecapi.h MSV1_0_PROTOCOL_MESSAGE_TYPE +enum ntsecapi.h PKU2U_LOGON_SUBMIT_TYPE +enum ntsecapi.h POLICY_DOMAIN_INFORMATION_CLASS +enum ntsecapi.h SECURITY_LOGON_TYPE +enum ntsecpkg.h LSA_TOKEN_INFORMATION_TYPE +enum ntsecpkg.h SECPKG_EXTENDED_INFORMATION_CLASS +enum ntsecpkg.h SECPKG_NAME_TYPE +enum ntsecpkg.h SECPKG_SESSIONINFO_TYPE +enum sspi.h SECPKG_ATTR_LCT_STATUS +enum sspi.h SECPKG_CRED_CLASS +enum tpmvscmgr.h TPMVSCMGR_ERROR +enum tpmvscmgr.h TPMVSCMGR_STATUS +enum wincred.h CRED_MARSHAL_TYPE +enum wincred.h CRED_PROTECTION_TYPE + +# Desktop App Technologies > Security and Identity > Authentication > Authentication Reference > Authentication Functions +callback ntlsa.h LSA_FREE_LSA_HEAP +callback ntsecpkg.h CreateThread =lsa_create_thread +callback ntsecpkg.h CredFreeCredentialsFn +callback ntsecpkg.h CrediUnmarshalandDecodeStringFn +callback ntsecpkg.h CredReadDomainCredentialsFn +callback ntsecpkg.h CredReadFn +callback ntsecpkg.h CredWriteFn +callback ntsecpkg.h DuplicateHandle =lsa_duplicate_handle +callback ntsecpkg.h KspDeleteContextFn +callback ntsecpkg.h KspMakeSignatureFn +callback ntsecpkg.h KspVerifySignatureFn +callback ntsecpkg.h LSA_ADD_CREDENTIAL +callback ntsecpkg.h LSA_ALLOCATE_CLIENT_BUFFER +callback ntsecpkg.h LSA_ALLOCATE_LSA_HEAP +callback ntsecpkg.h LSA_ALLOCATE_PRIVATE_HEAP +callback ntsecpkg.h LSA_ALLOCATE_SHARED_MEMORY +callback ntsecpkg.h LSA_AP_CALL_PACKAGE +callback ntsecpkg.h LSA_AP_CALL_PACKAGE_PASSTHROUGH +callback ntsecpkg.h LSA_AP_INITIALIZE_PACKAGE +callback ntsecpkg.h LSA_AP_LOGON_TERMINATED +callback ntsecpkg.h LSA_AP_LOGON_USER +callback ntsecpkg.h LSA_AP_LOGON_USER_EX +callback ntsecpkg.h LSA_AP_LOGON_USER_EX2 +callback ntsecpkg.h LSA_AUDIT_ACCOUNT_LOGON +callback ntsecpkg.h LSA_AUDIT_LOGON +callback ntsecpkg.h LSA_CALL_PACKAGE +callback ntsecpkg.h LSA_CALL_PACKAGE_PASSTHROUGH +callback ntsecpkg.h LSA_CALL_PACKAGEEX +callback ntsecpkg.h LSA_CANCEL_NOTIFICATION +callback ntsecpkg.h LSA_CLIENT_CALLBACK +callback ntsecpkg.h LSA_CLOSE_SAM_USER +callback ntsecpkg.h LSA_CONVERT_AUTH_DATA_TO_TOKEN +callback ntsecpkg.h LSA_COPY_FROM_CLIENT_BUFFER +callback ntsecpkg.h LSA_COPY_TO_CLIENT_BUFFER +callback ntsecpkg.h LSA_CRACK_SINGLE_NAME +callback ntsecpkg.h LSA_CREATE_LOGON_SESSION +callback ntsecpkg.h LSA_CREATE_SHARED_MEMORY +callback ntsecpkg.h LSA_CREATE_TOKEN +callback ntsecpkg.h LSA_CREATE_TOKEN_EX +callback ntsecpkg.h LSA_DELETE_CREDENTIAL +callback ntsecpkg.h LSA_DELETE_LOGON_SESSION +callback ntsecpkg.h LSA_DELETE_SHARED_MEMORY +callback ntsecpkg.h LSA_EXPAND_AUTH_DATA_FOR_DOMAIN +callback ntsecpkg.h LSA_FREE_CLIENT_BUFFER +callback ntsecpkg.h LSA_FREE_LSA_HEAP +callback ntsecpkg.h LSA_FREE_PRIVATE_HEAP +callback ntsecpkg.h LSA_FREE_SHARED_MEMORY +callback ntsecpkg.h LSA_GET_AUTH_DATA_FOR_USER +callback ntsecpkg.h LSA_GET_CALL_INFO +callback ntsecpkg.h LSA_GET_CLIENT_INFO +callback ntsecpkg.h LSA_GET_CREDENTIALS +callback ntsecpkg.h LSA_GET_USER_AUTH_DATA +callback ntsecpkg.h LSA_MAP_BUFFER +callback ntsecpkg.h LSA_OPEN_SAM_USER +callback ntsecpkg.h LSA_OPEN_TOKEN_BY_LOGON_ID +callback ntsecpkg.h LSA_PROTECT_MEMORY +callback ntsecpkg.h LSA_UPDATE_PRIMARY_CREDENTIALS +callback ntsecpkg.h PLSA_REGISTER_NOTIFICATION =lsa_register_notification +callback ntsecpkg.h SpAcceptCredentialsFn +callback ntsecpkg.h SpAcceptLsaModeContextFn +callback ntsecpkg.h SpAcquireCredentialsHandleFn +callback ntsecpkg.h SpAddCredentialsFn +callback ntsecpkg.h SpApplyControlTokenFn +callback ntsecpkg.h SpCompleteAuthTokenFn +callback ntsecpkg.h SpDeleteCredentialsFn +callback ntsecpkg.h SpExchangeMetaDataFn +callback ntsecpkg.h SpExportSecurityContextFn +callback ntsecpkg.h SpFormatCredentialsFn +callback ntsecpkg.h SpFreeCredentialsHandleFn +callback ntsecpkg.h SpGetContextTokenFn +callback ntsecpkg.h SpGetCredentialsFn +callback ntsecpkg.h SpGetCredUIContextFn +callback ntsecpkg.h SpGetExtendedInformationFn +callback ntsecpkg.h SpGetInfoFn +callback ntsecpkg.h SpGetUserInfoFn +callback ntsecpkg.h SpImportSecurityContextFn +callback ntsecpkg.h SpInitializeFn +callback ntsecpkg.h SpInitLsaModeContextFn +callback ntsecpkg.h SpInitUserModeContextFn +callback ntsecpkg.h SpInstanceInitFn +callback ntsecpkg.h SpLsaModeInitializeFn +callback ntsecpkg.h SpMarshallSupplementalCredsFn +callback ntsecpkg.h SpQueryContextAttributesFn +callback ntsecpkg.h SpQueryCredentialsAttributesFn +callback ntsecpkg.h SpQueryMetaDataFn +callback ntsecpkg.h SpSaveCredentialsFn +callback ntsecpkg.h SpSealMessageFn +callback ntsecpkg.h SpSetExtendedInformationFn +callback ntsecpkg.h SpUnsealMessageFn +callback ntsecpkg.h SpUpdateCredentialsFn +callback ntsecpkg.h SpUserModeInitializeFn +callback ntsecpkg.h SpValidateTargetInfoFn +callback winwlx.h WlxAssignShellProtection =pwlx_assign_shell_protection +callback winwlx.h WlxChangePasswordNotify =pwlx_change_password_notify +callback winwlx.h WlxChangePasswordNotifyEx =pwlx_change_password_notify_ex +callback winwlx.h WlxCloseUserDesktop =pwlx_close_user_desktop +callback winwlx.h WlxCreateUserDesktop =pwlx_create_user_desktop +callback winwlx.h WlxDialogBox =pwlx_dialog_box +callback winwlx.h WlxDialogBoxIndirect =pwlx_dialog_box_indirect +callback winwlx.h WlxDialogBoxIndirectParam =pwlx_dialog_box_indirect_param +callback winwlx.h WlxDialogBoxParam =pwlx_dialog_box_param +callback winwlx.h WlxDisconnect =pwlx_disconnect +callback winwlx.h WlxGetOption =pwlx_get_option +callback winwlx.h WlxGetSourceDesktop =pwlx_get_source_desktop +callback winwlx.h WlxMessageBox =pwlx_message_box +callback winwlx.h WlxQueryClientCredentials =pwlx_query_client_credentials +callback winwlx.h WlxQueryConsoleSwitchCredentials =pwlx_query_consoleswitch_credentials +callback winwlx.h WlxQueryInetConnectorCredentials =pwlx_query_ic_credentials +callback winwlx.h WlxQueryTerminalServicesData =pwlx_query_terminal_services_data +callback winwlx.h WlxQueryTsLogonCredentials =pwlx_query_ts_logon_credentials +callback winwlx.h WlxSasNotify =pwlx_sas_notify +callback winwlx.h WlxSetContextPointer =pwlx_set_context_pointer +callback winwlx.h WlxSetOption =pwlx_set_option +callback winwlx.h WlxSetReturnDesktop =pwlx_set_return_desktop +callback winwlx.h WlxSetTimeout =pwlx_set_timeout +callback winwlx.h WlxSwitchDesktopToUser =pwlx_switch_desktop_to_user +callback winwlx.h WlxSwitchDesktopToWinlogon =pwlx_switch_desktop_to_winlogon +callback winwlx.h WlxUseCtrlAltDel =pwlx_use_ctrl_alt_del +callback winwlx.h WlxWin31Migrate =pwlx_win31_migrate +function certpoleng.h PstAcquirePrivateKey +function certpoleng.h PstGetCertificates +function certpoleng.h PstGetTrustAnchors +function certpoleng.h PstGetUserNameForCertificate +function certpoleng.h PstMapCertificate +function certpoleng.h PstValidate +function npapi.h AddConnectNotify +function npapi.h CancelConnectNotify +function npapi.h NPAddConnection +function npapi.h NPAddConnection3 +function npapi.h NPCancelConnection +function npapi.h NPCloseEnum +function npapi.h NPDeviceMode +function npapi.h NPDirectoryNotify +function npapi.h NPEnumResource +function npapi.h NPFMXEditPerm +function npapi.h NPFMXGetPermCaps +function npapi.h NPFMXGetPermHelp +function npapi.h NPFormatNetworkName +function npapi.h NPGetCaps +function npapi.h NPGetConnection +function npapi.h NPGetConnection3 +function npapi.h NPGetConnectionPerformance +function npapi.h NPGetDirectoryType +function npapi.h NPGetPropertyText +function npapi.h NPGetResourceInformation +function npapi.h NPGetResourceParent +function npapi.h NPGetUniversalName +function npapi.h NPGetUser +function npapi.h NPLogonNotify +function npapi.h NPOpenEnum +function npapi.h NPPasswordChangeNotify +function npapi.h NPPropertyDialog +function npapi.h NPSearchDialog +function npapi.h WNetSetLastError =wnetsetlasterrora +function ntlsa.h LsaGetAppliedCAPIDs +function ntlsa.h LsaQueryCAPs +function ntsecapi.h LsaCallAuthenticationPackage +function ntsecapi.h LsaConnectUntrusted +function ntsecapi.h LsaDeregisterLogonProcess +function ntsecapi.h LsaEnumerateLogonSessions +function ntsecapi.h LsaFreeReturnBuffer +function ntsecapi.h LsaGetLogonSessionData +function ntsecapi.h LsaLogonUser +function ntsecapi.h LsaLookupAuthenticationPackage +function ntsecapi.h LsaOpenPolicy +function ntsecapi.h LsaQueryDomainInformationPolicy +function ntsecapi.h LsaQueryForestTrustInformation +function ntsecapi.h LsaQueryInformationPolicy +function ntsecapi.h LsaRegisterLogonProcess +function ntsecapi.h LsaSetDomainInformationPolicy +function ntsecapi.h LsaSetForestTrustInformation +function ntsecapi.h LsaSetInformationPolicy +function ntsecpkg.h CredMarshalTargetInfo +function sas.h SendSAS +function schannel.h SslCrackCertificate +function schannel.h SslEmptyCache =sslemptycachea +function schannel.h SslFreeCertificate +function schannel.h SslGetServerIdentity +function sspi.h AddSecurityPackage =addsecuritypackagea +function sspi.h ApplyControlToken +function sspi.h ChangeAccountPassword =changeaccountpassworda +function sspi.h CompleteAuthToken +function sspi.h DeleteSecurityContext +function sspi.h DeleteSecurityPackage =deletesecuritypackagea +function sspi.h EnumerateSecurityPackages =enumeratesecuritypackagesa +function sspi.h ExportSecurityContext +function sspi.h FreeContextBuffer +function sspi.h FreeCredentialsHandle +function sspi.h ImpersonateSecurityContext +function sspi.h ImportSecurityContext =importsecuritycontexta +function sspi.h InitSecurityInterface =initsecurityinterfacea +function sspi.h MakeSignature +function sspi.h QueryContextAttributesEx =querycontextattributesexa +function sspi.h QueryCredentialsAttributes =querycredentialsattributesa +function sspi.h QuerySecurityContextToken +function sspi.h QuerySecurityPackageInfo =querysecuritypackageinfoa +function sspi.h RevertSecurityContext +function sspi.h SaslAcceptSecurityContext +function sspi.h SaslEnumerateProfiles =saslenumerateprofilesa +function sspi.h SaslGetContextOption +function sspi.h SaslGetProfilePackage =saslgetprofilepackagea +function sspi.h SaslIdentifyPackage =saslidentifypackagea +function sspi.h SaslInitializeSecurityContext =saslinitializesecuritycontexta +function sspi.h SaslSetContextOption +function sspi.h SetContextAttributes =setcontextattributesa +function sspi.h SetCredentialsAttributes =setcredentialsattributesa +function sspi.h SspiCompareAuthIdentities +function sspi.h SspiCopyAuthIdentity +function sspi.h SspiDecryptAuthIdentity +function sspi.h SspiDecryptAuthIdentityEx +function sspi.h SspiEncodeAuthIdentityAsStrings +function sspi.h SspiEncodeStringsAsAuthIdentity +function sspi.h SspiEncryptAuthIdentity +function sspi.h SspiEncryptAuthIdentityEx +function sspi.h SspiExcludePackage +function sspi.h SspiFreeAuthIdentity +function sspi.h SspiGetCredUIContext +function sspi.h SspiGetTargetHostName +function sspi.h SspiIsAuthIdentityEncrypted +function sspi.h SspiIsPromptingNeeded +function sspi.h SspiLocalFree +function sspi.h SspiMarshalAuthIdentity +function sspi.h SspiPrepareForCredRead +function sspi.h SspiPrepareForCredWrite +function sspi.h SspiPromptForCredentials =sspipromptforcredentialsa +function sspi.h SspiUnmarshalAuthIdentity +function sspi.h SspiUnmarshalCredUIContext +function sspi.h SspiUpdateCredentials +function sspi.h SspiValidateAuthIdentity +function sspi.h SspiZeroAuthIdentity +function sspi.h VerifySignature +function subauth.h Msv1_0SubAuthenticationFilter +function subauth.h Msv1_0SubAuthenticationRoutine +function subauth.h Msv1_0SubAuthenticationRoutineEx +function subauth.h Msv1_0SubAuthenticationRoutineGeneric +function winbase.h LogonUser =logonusera +function winbase.h LogonUserEx =logonuserexa +function wincred.h CredDelete =creddeletea +function wincred.h CredEnumerate =credenumeratea +function wincred.h CredFindBestCredential =credfindbestcredentiala +function wincred.h CredFree +function wincred.h CredGetSessionTypes +function wincred.h CredGetTargetInfo =credgettargetinfoa +function wincred.h CredIsMarshaledCredential =credismarshaledcredentiala +function wincred.h CredIsProtected =credisprotecteda +function wincred.h CredMarshalCredential =credmarshalcredentiala +function wincred.h CredPackAuthenticationBuffer =credpackauthenticationbuffera +function wincred.h CredProtect =credprotecta +function wincred.h CredRead =credreada +function wincred.h CredReadDomainCredentials =credreaddomaincredentialsa +function wincred.h CredRename =credrenamea +function wincred.h CredUICmdLinePromptForCredentials =creduicmdlinepromptforcredentialsa +function wincred.h CredUIConfirmCredentials =creduiconfirmcredentialsa +function wincred.h CredUIParseUserName =creduiparseusernamea +function wincred.h CredUIPromptForCredentials =creduipromptforcredentialsa +function wincred.h CredUIPromptForWindowsCredentials =creduipromptforwindowscredentialsa +function wincred.h CredUIReadSSOCredW +function wincred.h CredUIStoreSSOCredW +function wincred.h CredUnmarshalCredential =credunmarshalcredentiala +function wincred.h CredUnPackAuthenticationBuffer =credunpackauthenticationbuffera +function wincred.h CredUnprotect =credunprotecta +function wincred.h CredWrite =credwritea +function wincred.h CredWriteDomainCredentials =credwritedomaincredentialsa +function winscard.h GetOpenCardName =getopencardnamea +function winscard.h SCardAccessStartedEvent +function winscard.h SCardAddReaderToGroup =scardaddreadertogroupa +function winscard.h SCardAudit +function winscard.h SCardBeginTransaction +function winscard.h SCardCancel +function winscard.h SCardConnect =scardconnecta +function winscard.h SCardControl +function winscard.h SCardDisconnect +function winscard.h SCardEndTransaction +function winscard.h SCardEstablishContext +function winscard.h SCardForgetCardType =scardforgetcardtypea +function winscard.h SCardForgetReader =scardforgetreadera +function winscard.h SCardForgetReaderGroup =scardforgetreadergroupa +function winscard.h SCardFreeMemory +function winscard.h SCardGetAttrib +function winscard.h SCardGetCardTypeProviderName =scardgetcardtypeprovidernamea +function winscard.h SCardGetDeviceTypeId =scardgetdevicetypeida +function winscard.h SCardGetProviderId =scardgetproviderida +function winscard.h SCardGetReaderDeviceInstanceId =scardgetreaderdeviceinstanceida +function winscard.h SCardGetReaderIcon =scardgetreadericona +function winscard.h SCardGetStatusChange =scardgetstatuschangea +function winscard.h SCardGetTransmitCount +function winscard.h SCardIntroduceCardType =scardintroducecardtypea +function winscard.h SCardIntroduceReader =scardintroducereadera +function winscard.h SCardIntroduceReaderGroup =scardintroducereadergroupa +function winscard.h SCardIsValidContext +function winscard.h SCardListCards =scardlistcardsa +function winscard.h SCardListInterfaces =scardlistinterfacesa +function winscard.h SCardListReaderGroups =scardlistreadergroupsa +function winscard.h SCardListReaders =scardlistreadersa +function winscard.h SCardListReadersWithDeviceInstanceId =scardlistreaderswithdeviceinstanceida +function winscard.h SCardLocateCards =scardlocatecardsa +function winscard.h SCardLocateCardsByATR =scardlocatecardsbyatra +function winscard.h SCardReadCache =scardreadcachea +function winscard.h SCardReconnect +function winscard.h SCardReleaseContext +function winscard.h SCardReleaseStartedEvent +function winscard.h SCardRemoveReaderFromGroup =scardremovereaderfromgroupa +function winscard.h SCardSetAttrib +function winscard.h SCardSetCardTypeProviderName =scardsetcardtypeprovidernamea +function winscard.h SCardStatus =scardstatusa +function winscard.h SCardTransmit +function winscard.h SCardUIDlgSelectCard =scarduidlgselectcarda +function winscard.h SCardWriteCache =scardwritecachea +function winwlx.h WlxActivateUserShell +function winwlx.h WlxDisplayLockedNotice +function winwlx.h WlxDisplaySASNotice +function winwlx.h WlxDisplayStatusMessage +function winwlx.h WlxGetConsoleSwitchCredentials +function winwlx.h WlxGetStatusMessage +function winwlx.h WlxInitialize +function winwlx.h WlxIsLockOk +function winwlx.h WlxIsLogoffOk +function winwlx.h WlxLoggedOnSAS +function winwlx.h WlxLoggedOutSAS +function winwlx.h WlxLogoff +function winwlx.h WlxNegotiate +function winwlx.h WlxNetworkProviderLoad +function winwlx.h WlxRemoveStatusMessage +function winwlx.h WlxScreenSaverNotify +function winwlx.h WlxShutdown +function winwlx.h WlxStartApplication +function winwlx.h WlxWkstaLockedSAS + +# Desktop App Technologies > Security and Identity > Authentication > Authentication Reference > Authentication Interfaces +interface identityprovider.h IAssociatedIdentityProvider +interface identityprovider.h IConnectedIdentityProvider +interface identityprovider.h IIdentityAdvise +interface identityprovider.h IIdentityProvider +interface identitystore.h IIdentityStore +interface tpmvscmgr.h ITpmVirtualSmartCardManager +interface tpmvscmgr.h ITpmVirtualSmartCardManagerStatusCallback + +# Desktop App Technologies > Security and Identity > Authentication > Authentication Reference > Authentication Structures +enum sspi.h SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS +struct credssp.h CREDSSP_CRED +struct credssp.h SecPkgContext_ClientCreds +struct lsalookup.h LSA_STRING +struct npapi.h NOTIFYADD +struct npapi.h NOTIFYCANCEL +struct npapi.h NOTIFYINFO +struct ntlsa.h CENTRAL_ACCESS_POLICY +struct ntlsa.h CENTRAL_ACCESS_POLICY_ENTRY +struct ntsecapi.h DOMAIN_PASSWORD_INFORMATION +struct ntsecapi.h KERB_ADD_BINDING_CACHE_ENTRY_EX_REQUEST +struct ntsecapi.h KERB_ADD_BINDING_CACHE_ENTRY_REQUEST +struct ntsecapi.h KERB_ADD_CREDENTIALS_REQUEST +struct ntsecapi.h KERB_ADD_CREDENTIALS_REQUEST_EX +struct ntsecapi.h KERB_BINDING_CACHE_ENTRY_DATA +struct ntsecapi.h KERB_CERTIFICATE_HASHINFO +struct ntsecapi.h KERB_CERTIFICATE_INFO +struct ntsecapi.h KERB_CERTIFICATE_LOGON +struct ntsecapi.h KERB_CERTIFICATE_S4U_LOGON +struct ntsecapi.h KERB_CERTIFICATE_UNLOCK_LOGON +struct ntsecapi.h KERB_CHANGEPASSWORD_REQUEST +struct ntsecapi.h KERB_CLEANUP_MACHINE_PKINIT_CREDS_REQUEST +struct ntsecapi.h KERB_CRYPTO_KEY +struct ntsecapi.h KERB_EXTERNAL_NAME +struct ntsecapi.h KERB_EXTERNAL_TICKET +struct ntsecapi.h KERB_INTERACTIVE_LOGON +struct ntsecapi.h KERB_INTERACTIVE_PROFILE +struct ntsecapi.h KERB_INTERACTIVE_UNLOCK_LOGON +struct ntsecapi.h KERB_PURGE_BINDING_CACHE_REQUEST +struct ntsecapi.h KERB_PURGE_TKT_CACHE_REQUEST +struct ntsecapi.h KERB_QUERY_BINDING_CACHE_REQUEST +struct ntsecapi.h KERB_QUERY_BINDING_CACHE_RESPONSE +struct ntsecapi.h KERB_QUERY_DOMAIN_EXTENDED_POLICIES_REQUEST +struct ntsecapi.h KERB_QUERY_DOMAIN_EXTENDED_POLICIES_RESPONSE +struct ntsecapi.h KERB_QUERY_TKT_CACHE_REQUEST +struct ntsecapi.h KERB_QUERY_TKT_CACHE_RESPONSE +struct ntsecapi.h KERB_RETRIEVE_TKT_REQUEST +struct ntsecapi.h KERB_RETRIEVE_TKT_RESPONSE +struct ntsecapi.h KERB_S4U_LOGON +struct ntsecapi.h KERB_SMART_CARD_LOGON +struct ntsecapi.h KERB_SMART_CARD_UNLOCK_LOGON +struct ntsecapi.h KERB_TICKET_CACHE_INFO +struct ntsecapi.h KERB_TICKET_LOGON +struct ntsecapi.h KERB_TICKET_PROFILE +struct ntsecapi.h KERB_TICKET_UNLOCK_LOGON +struct ntsecapi.h LSA_FOREST_TRUST_BINARY_DATA +struct ntsecapi.h LSA_FOREST_TRUST_COLLISION_INFORMATION +struct ntsecapi.h LSA_FOREST_TRUST_COLLISION_RECORD +struct ntsecapi.h LSA_FOREST_TRUST_DOMAIN_INFO +struct ntsecapi.h LSA_FOREST_TRUST_INFORMATION +struct ntsecapi.h LSA_FOREST_TRUST_RECORD +struct ntsecapi.h LSA_LAST_INTER_LOGON_INFO +struct ntsecapi.h MSV1_0_INTERACTIVE_LOGON +struct ntsecapi.h MSV1_0_INTERACTIVE_PROFILE +struct ntsecapi.h MSV1_0_LM20_LOGON +struct ntsecapi.h MSV1_0_LM20_LOGON_PROFILE +struct ntsecapi.h MSV1_0_SUBAUTH_LOGON +struct ntsecapi.h MSV1_0_SUBAUTH_REQUEST +struct ntsecapi.h MSV1_0_SUBAUTH_RESPONSE +struct ntsecapi.h MSV1_0_SUPPLEMENTAL_CREDENTIAL +struct ntsecapi.h PKU2U_CERT_BLOB +struct ntsecapi.h PKU2U_CERTIFICATE_S4U_LOGON +struct ntsecapi.h PKU2U_CREDUI_CONTEXT +struct ntsecapi.h SECURITY_LOGON_SESSION_DATA +struct ntsecpkg.h ENCRYPTED_CREDENTIALW +struct ntsecpkg.h LSA_DISPATCH_TABLE +struct ntsecpkg.h LSA_SECPKG_FUNCTION_TABLE +struct ntsecpkg.h LSA_TOKEN_INFORMATION_NULL +struct ntsecpkg.h LSA_TOKEN_INFORMATION_V2 =lsa_token_information_v1 +struct ntsecpkg.h LSA_TOKEN_INFORMATION_V3 +struct ntsecpkg.h SECPKG_BYTE_VECTOR +struct ntsecpkg.h SECPKG_CALL_INFO +struct ntsecpkg.h SECPKG_CLIENT_INFO +struct ntsecpkg.h SECPKG_CONTEXT_THUNKS +struct ntsecpkg.h SECPKG_CREDENTIAL +struct ntsecpkg.h SECPKG_DLL_FUNCTIONS +struct ntsecpkg.h SECPKG_EVENT_NOTIFY +struct ntsecpkg.h SECPKG_EVENT_PACKAGE_CHANGE +struct ntsecpkg.h SECPKG_EXTENDED_INFORMATION +struct ntsecpkg.h SECPKG_EXTRA_OIDS +struct ntsecpkg.h SECPKG_FUNCTION_TABLE +struct ntsecpkg.h SECPKG_GSS_INFO +struct ntsecpkg.h SECPKG_MUTUAL_AUTH_LEVEL +struct ntsecpkg.h SECPKG_NEGO2_INFO +struct ntsecpkg.h SECPKG_PARAMETERS +struct ntsecpkg.h SECPKG_PRIMARY_CRED +struct ntsecpkg.h SECPKG_SERIALIZED_OID +struct ntsecpkg.h SECPKG_SHORT_VECTOR +struct ntsecpkg.h SECPKG_SUPPLEMENTAL_CRED +struct ntsecpkg.h SECPKG_SUPPLEMENTAL_CRED_ARRAY +struct ntsecpkg.h SECPKG_SUPPLIED_CREDENTIAL +struct ntsecpkg.h SECPKG_TARGETINFO +struct ntsecpkg.h SECPKG_USER_FUNCTION_TABLE +struct ntsecpkg.h SECPKG_WOW_CLIENT_DLL +struct ntsecpkg.h SecurityUserData +struct schannel.h SCH_CRED_PUBLIC_CERTCHAIN +struct schannel.h SCH_CRED_SECRET_PRIVKEY +struct schannel.h SCHANNEL_ALERT_TOKEN +struct schannel.h SCHANNEL_CERT_HASH +struct schannel.h SCHANNEL_CERT_HASH_STORE +struct schannel.h SCHANNEL_CLIENT_SIGNATURE +struct schannel.h SCHANNEL_CRED +struct schannel.h SCHANNEL_SESSION_TOKEN +struct schannel.h SecPkgContext_CipherInfo +struct schannel.h SecPkgContext_ConnectionInfo +struct schannel.h SecPkgContext_EapKeyBlock +struct schannel.h SecPkgContext_EapPrfInfo +struct schannel.h SecPkgContext_EarlyStart +struct schannel.h SecPkgContext_IssuerListInfoEx +struct schannel.h SecPkgContext_KeyingMaterial +struct schannel.h SecPkgContext_KeyingMaterialInfo +struct schannel.h SecPkgContext_SessionAppData +struct schannel.h SecPkgContext_SessionInfo +struct schannel.h SecPkgContext_SupportedSignatures +struct schannel.h X509Certificate +struct sspi.h CREDUIWIN_MARSHALED_CONTEXT +struct sspi.h SEC_CHANNEL_BINDINGS +struct sspi.h SEC_WINNT_AUTH_BYTE_VECTOR +struct sspi.h SEC_WINNT_AUTH_CERTIFICATE_DATA +struct sspi.h SEC_WINNT_AUTH_DATA +struct sspi.h SEC_WINNT_AUTH_DATA_PASSWORD +struct sspi.h SEC_WINNT_AUTH_IDENTITY =sec_winnt_auth_identity_a +struct sspi.h SEC_WINNT_AUTH_IDENTITY_EX =sec_winnt_auth_identity_exa +struct sspi.h SEC_WINNT_AUTH_IDENTITY_EX2 +struct sspi.h SEC_WINNT_AUTH_PACKED_CREDENTIALS +struct sspi.h SEC_WINNT_AUTH_PACKED_CREDENTIALS_EX +struct sspi.h SEC_WINNT_AUTH_SHORT_VECTOR +struct sspi.h SEC_WINNT_CREDUI_CONTEXT +struct sspi.h SEC_WINNT_CREDUI_CONTEXT_VECTOR +struct sspi.h SecBuffer +struct sspi.h SecBufferDesc +struct sspi.h SecPkgContext_AccessToken +struct sspi.h SecPkgContext_Authority =secpkgcontext_authoritya +struct sspi.h SecPkgContext_Bindings +struct sspi.h SecPkgContext_ClientSpecifiedTarget +struct sspi.h SecPkgContext_CredentialName =_secpkgcontext_credentialnamea +struct sspi.h SecPkgContext_CredInfo +struct sspi.h SecPkgContext_DceInfo +struct sspi.h SecPkgContext_Flags +struct sspi.h SecPkgContext_KeyInfo =secpkgcontext_keyinfoa +struct sspi.h SecPkgContext_LastClientTokenStatus +struct sspi.h SecPkgContext_Lifespan +struct sspi.h SecPkgContext_Names =secpkgcontext_namesa +struct sspi.h SecPkgContext_NativeNames =_secpkgcontext_nativenamesa +struct sspi.h SecPkgContext_NegoStatus +struct sspi.h SecPkgContext_NegotiationInfo =secpkgcontext_negotiationinfoa +struct sspi.h SecPkgContext_PackageInfo =secpkginfoa +struct sspi.h SecPkgContext_PasswordExpiry +struct sspi.h SecPkgContext_ProtoInfo =secpkgcontext_protoinfoa +struct sspi.h SecPkgContext_SessionKey +struct sspi.h SecPkgContext_Sizes +struct sspi.h SecPkgContext_StreamSizes +struct sspi.h SecPkgContext_SubjectAttributes +struct sspi.h SecPkgContext_TargetInformation +struct sspi.h SecPkgCredentials_Cert +struct sspi.h SecPkgCredentials_KdcProxySettings =secpkgcredentials_kdcproxysettingsw +struct sspi.h SecPkgCredentials_Names =secpkgcredentials_namesa +struct sspi.h SecPkgCredentials_SSIProvider =secpkgcredentials_ssiprovidera +struct sspi.h SecPkgInfo =secpkginfoa +struct sspi.h SECURITY_INTEGER +struct sspi.h SECURITY_PACKAGE_OPTIONS +struct sspi.h SECURITY_STRING +struct sspi.h SecurityFunctionTable =securityfunctiontablea +struct subauth.h NETLOGON_LOGON_IDENTITY_INFO +struct subauth.h OLD_LARGE_INTEGER +struct subauth.h SR_SECURITY_DESCRIPTOR +struct subauth.h UNICODE_STRING +struct subauth.h USER_ALL_INFORMATION +struct wincred.h CERT_CREDENTIAL_INFO +struct wincred.h CREDENTIAL =credentiala +struct wincred.h CREDENTIAL_ATTRIBUTE =credential_attributea +struct wincred.h CREDENTIAL_TARGET_INFORMATION =credential_target_informationa +struct wincred.h CREDUI_INFO =credui_infoa +struct wincred.h USERNAME_TARGET_CREDENTIAL_INFO +struct winnetwk.h NETCONNECTINFOSTRUCT +struct winnetwk.h NETRESOURCE =netresourcea +struct winnetwk.h REMOTE_NAME_INFO =remote_name_infoa +struct winnetwk.h UNIVERSAL_NAME_INFO =universal_name_infoa +struct winnt.h QUOTA_LIMITS +struct winscard.h OPENCARD_SEARCH_CRITERIA =opencard_search_criteriaa +struct winscard.h OPENCARDNAME =opencardnamea +struct winscard.h OPENCARDNAME_EX =opencardname_exa +struct winscard.h SCARD_ATRMASK +struct winscard.h SCARD_READERSTATE =scard_readerstatea +struct winwlx.h WLX_CLIENT_CREDENTIALS_INFO_V1_0 +struct winwlx.h WLX_CLIENT_CREDENTIALS_INFO_V2_0 +struct winwlx.h WLX_CONSOLESWITCH_CREDENTIALS_INFO_V1_0 +struct winwlx.h WLX_DESKTOP +struct winwlx.h WLX_DISPATCH_VERSION_1_0 +struct winwlx.h WLX_DISPATCH_VERSION_1_1 +struct winwlx.h WLX_DISPATCH_VERSION_1_2 +struct winwlx.h WLX_DISPATCH_VERSION_1_3 +struct winwlx.h WLX_DISPATCH_VERSION_1_4 +struct winwlx.h WLX_MPR_NOTIFY_INFO +struct winwlx.h WLX_PROFILE_V1_0 +struct winwlx.h WLX_PROFILE_V2_0 +struct winwlx.h WLX_TERMINAL_SERVICES_DATA + +# Desktop App Technologies > Security and Identity > Authorization > Authorization Reference > Authorization Enumerations +enum accctrl.h ACCESS_MODE +enum accctrl.h MULTIPLE_TRUSTEE_OPERATION +enum accctrl.h PROG_INVOKE_SETTING +enum accctrl.h SE_OBJECT_TYPE +enum accctrl.h TRUSTEE_FORM +enum accctrl.h TRUSTEE_TYPE +enum aclui.h SI_PAGE_TYPE +enum adtgen.h AUDIT_PARAM_TYPE +enum authz.h AUTHZ_CONTEXT_INFORMATION_CLASS +enum authz.h AUTHZ_SECURITY_ATTRIBUTE_OPERATION +enum authz.h AUTHZ_SID_OPERATION +enum azroles.h AZ_PROP_CONSTANTS +enum winnt.h ACL_INFORMATION_CLASS +enum winnt.h AUDIT_EVENT_TYPE +enum winnt.h MANDATORY_LEVEL +enum winnt.h SECURITY_IMPERSONATION_LEVEL +enum winnt.h SID_NAME_USE +enum winnt.h TOKEN_ELEVATION_TYPE +enum winnt.h TOKEN_INFORMATION_CLASS +enum winnt.h TOKEN_TYPE +enum winnt.h WELL_KNOWN_SID_TYPE + +# Desktop App Technologies > Security and Identity > Authorization > Authorization Reference > Authorization Functions +function aclapi.h BuildExplicitAccessWithName =buildexplicitaccesswithnamea +function aclapi.h BuildSecurityDescriptor =buildsecuritydescriptora +function aclapi.h BuildTrusteeWithName =buildtrusteewithnamea +function aclapi.h BuildTrusteeWithObjectsAndName =buildtrusteewithobjectsandnamea +function aclapi.h BuildTrusteeWithObjectsAndSid =buildtrusteewithobjectsandsida +function aclapi.h BuildTrusteeWithSid =buildtrusteewithsida +function aclapi.h FreeInheritedFromArray +function aclapi.h GetAuditedPermissionsFromAcl =getauditedpermissionsfromacla +function aclapi.h GetEffectiveRightsFromAcl =geteffectiverightsfromacla +function aclapi.h GetExplicitEntriesFromAcl =getexplicitentriesfromacla +function aclapi.h GetInheritanceSource =getinheritancesourcea +function aclapi.h GetNamedSecurityInfo =getnamedsecurityinfoa +function aclapi.h GetSecurityInfo +function aclapi.h GetTrusteeForm =gettrusteeforma +function aclapi.h GetTrusteeName =gettrusteenamea +function aclapi.h GetTrusteeType =gettrusteetypea +function aclapi.h LookupSecurityDescriptorParts =lookupsecuritydescriptorpartsa +function aclapi.h SetEntriesInAcl =setentriesinacla +function aclapi.h SetNamedSecurityInfo =setnamedsecurityinfoa +function aclapi.h SetSecurityInfo +function aclapi.h TreeResetNamedSecurityInfo =treeresetnamedsecurityinfoa +function aclapi.h TreeSetNamedSecurityInfo =treesetnamedsecurityinfoa +function aclui.h CreateSecurityPage +function aclui.h EditSecurity +function aclui.h EditSecurityAdvanced +function authz.h AuthzAccessCheck +function authz.h AuthzAddSidsToContext +function authz.h AuthzCachedAccessCheck +function authz.h AuthzEnumerateSecurityEventSources +function authz.h AuthzFreeAuditEvent +function authz.h AuthzFreeCentralAccessPolicyCache +function authz.h AuthzFreeContext +function authz.h AuthzFreeHandle +function authz.h AuthzFreeResourceManager +function authz.h AuthzGetInformationFromContext +function authz.h AuthzInitializeCompoundContext +function authz.h AuthzInitializeContextFromAuthzContext +function authz.h AuthzInitializeContextFromSid +function authz.h AuthzInitializeContextFromToken +function authz.h AuthzInitializeObjectAccessAuditEvent +function authz.h AuthzInitializeObjectAccessAuditEvent2 +function authz.h AuthzInitializeRemoteResourceManager +function authz.h AuthzInitializeResourceManager +function authz.h AuthzInitializeResourceManagerEx +function authz.h AuthzInstallSecurityEventSource +function authz.h AuthzModifyClaims +function authz.h AuthzModifySecurityAttributes +function authz.h AuthzModifySids +function authz.h AuthzOpenObjectAudit +function authz.h AuthzRegisterCapChangeNotification +function authz.h AuthzRegisterSecurityEventSource +function authz.h AuthzReportSecurityEvent +function authz.h AuthzReportSecurityEventFromParams +function authz.h AuthzSetAppContainerInformation +function authz.h AuthzUninstallSecurityEventSource +function authz.h AuthzUnregisterCapChangeNotification +function authz.h AuthzUnregisterSecurityEventSource +function dssec.h DSCreateISecurityInfoObject +function dssec.h DSCreateISecurityInfoObjectEx +function dssec.h DSCreateSecurityPage +function dssec.h DSEditSecurity +function namedpipeapi.h ImpersonateNamedPipeClient +function ntsecapi.h AuditComputeEffectivePolicyBySid +function ntsecapi.h AuditComputeEffectivePolicyByToken +function ntsecapi.h AuditEnumerateCategories +function ntsecapi.h AuditEnumeratePerUserPolicy +function ntsecapi.h AuditEnumerateSubCategories +function ntsecapi.h AuditFree +function ntsecapi.h AuditLookupCategoryGuidFromCategoryId +function ntsecapi.h AuditLookupCategoryIdFromCategoryGuid +function ntsecapi.h AuditLookupCategoryName =auditlookupcategorynamea +function ntsecapi.h AuditLookupSubCategoryName =auditlookupsubcategorynamea +function ntsecapi.h AuditQueryGlobalSacl =auditqueryglobalsacla +function ntsecapi.h AuditQueryPerUserPolicy +function ntsecapi.h AuditQuerySecurity +function ntsecapi.h AuditQuerySystemPolicy +function ntsecapi.h AuditSetGlobalSacl =auditsetglobalsacla +function ntsecapi.h AuditSetPerUserPolicy +function ntsecapi.h AuditSetSecurity +function ntsecapi.h AuditSetSystemPolicy +function processthreadsapi.h GetCurrentProcessToken +function processthreadsapi.h GetCurrentThreadEffectiveToken +function processthreadsapi.h GetCurrentThreadToken +function processthreadsapi.h OpenProcessToken +function processthreadsapi.h OpenThreadToken +function processthreadsapi.h SetThreadToken +function sddl.h ConvertSecurityDescriptorToStringSecurityDescriptor =convertsecuritydescriptortostringsecuritydescriptora +function sddl.h ConvertSidToStringSid =convertsidtostringsida +function sddl.h ConvertStringSecurityDescriptorToSecurityDescriptor =convertstringsecuritydescriptortosecuritydescriptora +function sddl.h ConvertStringSidToSid =convertstringsidtosida +function securityappcontainer.h GetAppContainerNamedObjectPath +function securitybaseapi.h AccessCheck +function securitybaseapi.h AccessCheckByType +function securitybaseapi.h AccessCheckByTypeResultList +function securitybaseapi.h AddAccessAllowedAce +function securitybaseapi.h AddAccessAllowedAceEx +function securitybaseapi.h AddAccessAllowedObjectAce +function securitybaseapi.h AddAccessDeniedAce +function securitybaseapi.h AddAccessDeniedAceEx +function securitybaseapi.h AddAccessDeniedObjectAce +function securitybaseapi.h AddAce +function securitybaseapi.h AddAuditAccessAce +function securitybaseapi.h AddAuditAccessAceEx +function securitybaseapi.h AddAuditAccessObjectAce +function securitybaseapi.h AddMandatoryAce +function securitybaseapi.h AddResourceAttributeAce +function securitybaseapi.h AddScopedPolicyIDAce +function securitybaseapi.h AdjustTokenGroups +function securitybaseapi.h AdjustTokenPrivileges +function securitybaseapi.h AllocateAndInitializeSid +function securitybaseapi.h AllocateLocallyUniqueId +function securitybaseapi.h AreAllAccessesGranted +function securitybaseapi.h AreAnyAccessesGranted +function securitybaseapi.h CheckTokenCapability +function securitybaseapi.h CheckTokenMembership +function securitybaseapi.h CheckTokenMembershipEx +function securitybaseapi.h ConvertToAutoInheritPrivateObjectSecurity +function securitybaseapi.h CopySid +function securitybaseapi.h CreatePrivateObjectSecurity +function securitybaseapi.h CreatePrivateObjectSecurityEx +function securitybaseapi.h CreatePrivateObjectSecurityWithMultipleInheritance +function securitybaseapi.h CreateRestrictedToken +function securitybaseapi.h CreateWellKnownSid +function securitybaseapi.h DeleteAce +function securitybaseapi.h DeriveCapabilitySidsFromName +function securitybaseapi.h DestroyPrivateObjectSecurity +function securitybaseapi.h DuplicateToken +function securitybaseapi.h DuplicateTokenEx +function securitybaseapi.h EqualDomainSid +function securitybaseapi.h EqualPrefixSid +function securitybaseapi.h EqualSid +function securitybaseapi.h FindFirstFreeAce +function securitybaseapi.h FreeSid +function securitybaseapi.h GetAce +function securitybaseapi.h GetAclInformation +function securitybaseapi.h GetKernelObjectSecurity +function securitybaseapi.h GetLengthSid +function securitybaseapi.h GetPrivateObjectSecurity +function securitybaseapi.h GetSecurityDescriptorControl +function securitybaseapi.h GetSecurityDescriptorDacl +function securitybaseapi.h GetSecurityDescriptorGroup +function securitybaseapi.h GetSecurityDescriptorLength +function securitybaseapi.h GetSecurityDescriptorOwner +function securitybaseapi.h GetSecurityDescriptorRMControl +function securitybaseapi.h GetSecurityDescriptorSacl +function securitybaseapi.h GetSidIdentifierAuthority +function securitybaseapi.h GetSidLengthRequired +function securitybaseapi.h GetSidSubAuthority +function securitybaseapi.h GetSidSubAuthorityCount +function securitybaseapi.h GetTokenInformation +function securitybaseapi.h GetWindowsAccountDomainSid +function securitybaseapi.h ImpersonateAnonymousToken +function securitybaseapi.h ImpersonateLoggedOnUser +function securitybaseapi.h ImpersonateSelf +function securitybaseapi.h InitializeAcl +function securitybaseapi.h InitializeSecurityDescriptor +function securitybaseapi.h InitializeSid +function securitybaseapi.h IsTokenRestricted +function securitybaseapi.h IsValidAcl +function securitybaseapi.h IsValidSecurityDescriptor +function securitybaseapi.h IsValidSid +function securitybaseapi.h IsWellKnownSid +function securitybaseapi.h MakeAbsoluteSD +function securitybaseapi.h MakeSelfRelativeSD +function securitybaseapi.h MapGenericMask +function securitybaseapi.h PrivilegeCheck +function securitybaseapi.h QuerySecurityAccessMask +function securitybaseapi.h RevertToSelf +function securitybaseapi.h SetAclInformation +function securitybaseapi.h SetKernelObjectSecurity +function securitybaseapi.h SetPrivateObjectSecurity +function securitybaseapi.h SetPrivateObjectSecurityEx +function securitybaseapi.h SetSecurityAccessMask +function securitybaseapi.h SetSecurityDescriptorControl +function securitybaseapi.h SetSecurityDescriptorDacl +function securitybaseapi.h SetSecurityDescriptorGroup +function securitybaseapi.h SetSecurityDescriptorOwner +function securitybaseapi.h SetSecurityDescriptorRMControl +function securitybaseapi.h SetSecurityDescriptorSacl +function securitybaseapi.h SetTokenInformation +function winbase.h AccessCheckAndAuditAlarm =accesscheckandauditalarma +function winbase.h AccessCheckByTypeAndAuditAlarm =accesscheckbytypeandauditalarma +function winbase.h AccessCheckByTypeResultListAndAuditAlarm =accesscheckbytyperesultlistandauditalarma +function winbase.h AccessCheckByTypeResultListAndAuditAlarmByHandle =accesscheckbytyperesultlistandauditalarmbyhandlea +function winbase.h AddConditionalAce +function winbase.h GetFileSecurity =getfilesecuritya +function winbase.h LookupAccountName =lookupaccountnamea +function winbase.h LookupAccountSid =lookupaccountsida +function winbase.h LookupAccountSidLocal =lookupaccountsidlocala +function winbase.h LookupPrivilegeDisplayName =lookupprivilegedisplaynamea +function winbase.h LookupPrivilegeName =lookupprivilegenamea +function winbase.h LookupPrivilegeValue =lookupprivilegevaluea +function winbase.h ObjectCloseAuditAlarm =objectcloseauditalarma +function winbase.h ObjectDeleteAuditAlarm =objectdeleteauditalarma +function winbase.h ObjectOpenAuditAlarm =objectopenauditalarma +function winbase.h ObjectPrivilegeAuditAlarm =objectprivilegeauditalarma +function winbase.h PrivilegedServiceAuditAlarm =privilegedserviceauditalarma +function winbase.h SetFileSecurity =setfilesecuritya +function winreg.h RegGetKeySecurity +function winreg.h RegSetKeySecurity +function winsvc.h QueryServiceObjectSecurity +function winsvc.h SetServiceObjectSecurity +function winternl.h RtlConvertSidToUnicodeString +function winuser.h GetUserObjectSecurity +function winuser.h SetUserObjectSecurity + +# Desktop App Technologies > Security and Identity > Authorization > Authorization Reference > Authorization Interfaces +interface aclui.h IEffectivePermission +interface aclui.h IEffectivePermission2 +interface aclui.h ISecurityInformation +interface aclui.h ISecurityInformation2 +interface aclui.h ISecurityInformation3 +interface aclui.h ISecurityInformation4 +interface aclui.h ISecurityObjectTypeInfo +interface azroles.h IAzApplication +interface azroles.h IAzApplication2 +interface azroles.h IAzApplication3 +interface azroles.h IAzApplicationGroup +interface azroles.h IAzApplicationGroup2 +interface azroles.h IAzApplicationGroups +interface azroles.h IAzApplications +interface azroles.h IAzAuthorizationStore +interface azroles.h IAzAuthorizationStore2 +interface azroles.h IAzAuthorizationStore3 +interface azroles.h IAzBizRuleContext +interface azroles.h IAzBizRuleInterfaces +interface azroles.h IAzBizRuleParameters +interface azroles.h IAzClientContext +interface azroles.h IAzClientContext2 +interface azroles.h IAzClientContext3 +interface azroles.h IAzNameResolver +interface azroles.h IAzObjectPicker +interface azroles.h IAzOperation +interface azroles.h IAzOperation2 +interface azroles.h IAzOperations +interface azroles.h IAzPrincipalLocator +interface azroles.h IAzRole +interface azroles.h IAzRoleAssignment +interface azroles.h IAzRoleAssignments +interface azroles.h IAzRoleDefinition +interface azroles.h IAzRoleDefinitions +interface azroles.h IAzRoles +interface azroles.h IAzScope +interface azroles.h IAzScope2 +interface azroles.h IAzScopes +interface azroles.h IAzTask +interface azroles.h IAzTask2 +interface azroles.h IAzTasks + +# Desktop App Technologies > Security and Identity > Authorization > Authorization Reference > Authorization Structures +struct accctrl.h EXPLICIT_ACCESS =explicit_access_a +struct accctrl.h INHERITED_FROM =inherited_froma +struct accctrl.h OBJECTS_AND_NAME =objects_and_name_a +struct accctrl.h OBJECTS_AND_SID +struct accctrl.h TRUSTEE =trustee_a +struct aclui.h EFFPERM_RESULT_LIST +struct aclui.h SECURITY_OBJECT +struct aclui.h SI_ACCESS +struct aclui.h SI_INHERIT_TYPE +struct aclui.h SI_OBJECT_INFO +struct aclui.h SID_INFO +struct aclui.h SID_INFO_LIST +struct authz.h AUTHZ_ACCESS_REPLY +struct authz.h AUTHZ_ACCESS_REQUEST +struct authz.h AUTHZ_INIT_INFO +struct authz.h AUTHZ_REGISTRATION_OBJECT_TYPE_NAME_OFFSET +struct authz.h AUTHZ_RPC_INIT_INFO_CLIENT +struct authz.h AUTHZ_SECURITY_ATTRIBUTE_FQBN_VALUE +struct authz.h AUTHZ_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE +struct authz.h AUTHZ_SECURITY_ATTRIBUTE_V1 +struct authz.h AUTHZ_SECURITY_ATTRIBUTES_INFORMATION +struct authz.h AUTHZ_SOURCE_SCHEMA_REGISTRATION +struct ntsecapi.h AUDIT_POLICY_INFORMATION +struct ntsecapi.h POLICY_AUDIT_SID_ARRAY +struct winnt.h ACCESS_ALLOWED_ACE +struct winnt.h ACCESS_ALLOWED_CALLBACK_ACE +struct winnt.h ACCESS_ALLOWED_CALLBACK_OBJECT_ACE +struct winnt.h ACCESS_ALLOWED_OBJECT_ACE +struct winnt.h ACCESS_DENIED_ACE +struct winnt.h ACCESS_DENIED_CALLBACK_ACE +struct winnt.h ACCESS_DENIED_CALLBACK_OBJECT_ACE +struct winnt.h ACCESS_DENIED_OBJECT_ACE +struct winnt.h ACE_HEADER +struct winnt.h ACL +struct winnt.h ACL_REVISION_INFORMATION +struct winnt.h ACL_SIZE_INFORMATION +struct winnt.h CLAIM_SECURITY_ATTRIBUTE_FQBN_VALUE +struct winnt.h CLAIM_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE +struct winnt.h CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 +struct winnt.h CLAIM_SECURITY_ATTRIBUTE_V1 +struct winnt.h CLAIM_SECURITY_ATTRIBUTES_INFORMATION +struct winnt.h GENERIC_MAPPING +struct winnt.h LUID +struct winnt.h LUID_AND_ATTRIBUTES +struct winnt.h OBJECT_TYPE_LIST +struct winnt.h PRIVILEGE_SET +struct winnt.h SECURITY_CAPABILITIES +struct winnt.h SECURITY_DESCRIPTOR +struct winnt.h SECURITY_QUALITY_OF_SERVICE +struct winnt.h SID +struct winnt.h SID_AND_ATTRIBUTES +struct winnt.h SID_AND_ATTRIBUTES_HASH +struct winnt.h SID_IDENTIFIER_AUTHORITY +struct winnt.h SYSTEM_ALARM_ACE +struct winnt.h SYSTEM_ALARM_CALLBACK_ACE +struct winnt.h SYSTEM_ALARM_CALLBACK_OBJECT_ACE +struct winnt.h SYSTEM_ALARM_OBJECT_ACE +struct winnt.h SYSTEM_AUDIT_ACE +struct winnt.h SYSTEM_AUDIT_CALLBACK_ACE +struct winnt.h SYSTEM_AUDIT_CALLBACK_OBJECT_ACE +struct winnt.h SYSTEM_AUDIT_OBJECT_ACE =system_alarm_object_ace +struct winnt.h SYSTEM_MANDATORY_LABEL_ACE +struct winnt.h SYSTEM_RESOURCE_ATTRIBUTE_ACE +struct winnt.h SYSTEM_SCOPED_POLICY_ID_ACE +struct winnt.h TOKEN_ACCESS_INFORMATION +struct winnt.h TOKEN_APPCONTAINER_INFORMATION +struct winnt.h TOKEN_AUDIT_POLICY +struct winnt.h TOKEN_CONTROL +struct winnt.h TOKEN_DEFAULT_DACL +struct winnt.h TOKEN_DEVICE_CLAIMS +struct winnt.h TOKEN_ELEVATION +struct winnt.h TOKEN_GROUPS +struct winnt.h TOKEN_GROUPS_AND_PRIVILEGES +struct winnt.h TOKEN_LINKED_TOKEN +struct winnt.h TOKEN_MANDATORY_LABEL +struct winnt.h TOKEN_MANDATORY_POLICY +struct winnt.h TOKEN_ORIGIN +struct winnt.h TOKEN_OWNER +struct winnt.h TOKEN_PRIMARY_GROUP +struct winnt.h TOKEN_PRIVILEGES +struct winnt.h TOKEN_SOURCE +struct winnt.h TOKEN_STATISTICS +struct winnt.h TOKEN_USER +struct winnt.h TOKEN_USER_CLAIMS + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Request Interfaces +interface certenroll.h IX509CertificateRequest +interface certenroll.h IX509CertificateRequestCertificate +interface certenroll.h IX509CertificateRequestCertificate2 +interface certenroll.h IX509CertificateRequestCmc +interface certenroll.h IX509CertificateRequestCmc2 +interface certenroll.h IX509CertificateRequestPkcs10 +interface certenroll.h IX509CertificateRequestPkcs10V2 +interface certenroll.h IX509CertificateRequestPkcs10V3 +interface certenroll.h IX509CertificateRequestPkcs7 +interface certenroll.h IX509CertificateRequestPkcs7V2 + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Enrollment Interfaces +interface certenroll.h IX509Enrollment +interface certenroll.h IX509Enrollment2 +interface certenroll.h IX509EnrollmentHelper +interface certenroll.h IX509EnrollmentStatus +interface certenroll.h IX509SCEPEnrollment + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Enrollment Factory Interfaces +interface certenroll.h IX509EnrollmentWebClassFactory +interface certenroll.h IX509MachineEnrollmentFactory + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Extension Interfaces +interface certenroll.h IAlternativeName +interface certenroll.h IAlternativeNames +interface certenroll.h ICertificatePolicies +interface certenroll.h ICertificatePolicy +interface certenroll.h IPolicyQualifier +interface certenroll.h IPolicyQualifiers +interface certenroll.h ISmimeCapabilities +interface certenroll.h ISmimeCapability +interface certenroll.h IX509Extension +interface certenroll.h IX509ExtensionAlternativeNames +interface certenroll.h IX509ExtensionAuthorityKeyIdentifier +interface certenroll.h IX509ExtensionBasicConstraints +interface certenroll.h IX509ExtensionCertificatePolicies +interface certenroll.h IX509ExtensionEnhancedKeyUsage +interface certenroll.h IX509ExtensionKeyUsage +interface certenroll.h IX509ExtensionMSApplicationPolicies +interface certenroll.h IX509Extensions +interface certenroll.h IX509ExtensionSmimeCapabilities +interface certenroll.h IX509ExtensionSubjectKeyIdentifier +interface certenroll.h IX509ExtensionTemplate +interface certenroll.h IX509ExtensionTemplateName + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Attribute Interfaces +interface certenroll.h ICryptAttribute +interface certenroll.h ICryptAttributes +interface certenroll.h IX509Attribute +interface certenroll.h IX509AttributeArchiveKey +interface certenroll.h IX509AttributeArchiveKeyHash +interface certenroll.h IX509AttributeClientId +interface certenroll.h IX509AttributeCspProvider +interface certenroll.h IX509AttributeExtensions +interface certenroll.h IX509AttributeOSVersion +interface certenroll.h IX509AttributeRenewalCertificate +interface certenroll.h IX509Attributes +interface certenroll.h IX509NameValuePair +interface certenroll.h IX509NameValuePairs + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Property Interfaces +interface certenroll.h ICertProperties +interface certenroll.h ICertProperty +interface certenroll.h ICertPropertyArchived +interface certenroll.h ICertPropertyArchivedKeyHash +interface certenroll.h ICertPropertyAutoEnroll +interface certenroll.h ICertPropertyBackedUp +interface certenroll.h ICertPropertyDescription +interface certenroll.h ICertPropertyEnrollment +interface certenroll.h ICertPropertyEnrollmentPolicyServer +interface certenroll.h ICertPropertyFriendlyName +interface certenroll.h ICertPropertyKeyProvInfo +interface certenroll.h ICertPropertyRenewal +interface certenroll.h ICertPropertyRequestOriginator +interface certenroll.h ICertPropertySHA1Hash + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Template Interfaces +interface certenroll.h IX509CertificateTemplate +interface certenroll.h IX509CertificateTemplates +interface certenroll.h IX509CertificateTemplateWritable + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Policy Server Interfaces +interface certenroll.h IX509EnrollmentPolicyServer +interface certenroll.h IX509PolicyServerListManager +interface certenroll.h IX509PolicyServerUrl + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certificate Signature and Key Interfaces +interface certenroll.h ISignerCertificate +interface certenroll.h ISignerCertificates +interface certenroll.h IX509PrivateKey +interface certenroll.h IX509PublicKey +interface certenroll.h IX509SignatureInformation + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Certification Authority Interfaces +interface certenroll.h ICertificationAuthorities +interface certenroll.h ICertificationAuthority + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Cryptographic Provider Interfaces +interface certenroll.h ICspAlgorithm +interface certenroll.h ICspAlgorithms +interface certenroll.h ICspInformation +interface certenroll.h ICspInformations +interface certenroll.h ICspStatus +interface certenroll.h ICspStatuses + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Interfaces > Helper Interfaces +interface certenroll.h IBinaryConverter +interface certenroll.h ICertificateAttestationChallenge +interface certenroll.h IObjectId +interface certenroll.h IObjectIds +interface certenroll.h IX500DistinguishedName +interface certenroll.h IX509EndorsementKey + +# Desktop App Technologies > Security and Identity > Certificate Enrollment API > Certificate Enrollment API Reference > CertEnroll Enumerations +enum certenroll.h AlgorithmFlags +enum certenroll.h AlgorithmOperationFlags +enum certenroll.h AlgorithmType +enum certenroll.h AlternativeNameType +enum certenroll.h CERTENROLL_OBJECTID +enum certenroll.h CERTENROLL_PROPERTYID +enum certenroll.h CommitTemplateFlags +enum certenroll.h EncodingType +enum certenroll.h EnrollmentCAProperty +enum certenroll.h EnrollmentDisplayStatus +enum certenroll.h EnrollmentEnrollStatus +enum certenroll.h EnrollmentPolicyFlags +enum certenroll.h EnrollmentPolicyServerPropertyFlags +enum certenroll.h EnrollmentSelectionStatus +enum certenroll.h EnrollmentTemplateProperty +enum certenroll.h InnerRequestLevel +enum certenroll.h InstallResponseRestrictionFlags +enum certenroll.h KeyIdentifierHashAlgorithm +enum certenroll.h ObjectIdGroupId +enum certenroll.h ObjectIdPublicKeyFlags +enum certenroll.h PFXExportOptions +enum certenroll.h Pkcs10AllowedSignatureTypes +enum certenroll.h PolicyQualifierType +enum certenroll.h PolicyServerUrlFlags +enum certenroll.h PolicyServerUrlPropertyID +enum certenroll.h RequestClientInfoClientId +enum certenroll.h WebEnrollmentFlags +enum certenroll.h WebSecurityLevel +enum certenroll.h X500NameFlags +enum certenroll.h X509CertificateEnrollmentContext +enum certenroll.h X509CertificateTemplateEnrollmentFlag +enum certenroll.h X509CertificateTemplateGeneralFlag +enum certenroll.h X509CertificateTemplatePrivateKeyFlag +enum certenroll.h X509CertificateTemplateSubjectNameFlag +enum certenroll.h X509EnrollmentPolicyExportFlags +enum certenroll.h X509EnrollmentPolicyLoadOption +enum certenroll.h X509KeySpec +enum certenroll.h X509KeyUsageFlags +enum certenroll.h X509PrivateKeyExportFlags +enum certenroll.h X509PrivateKeyProtection +enum certenroll.h X509PrivateKeyUsageFlags +enum certenroll.h X509PrivateKeyVerify +enum certenroll.h X509ProviderType +enum certenroll.h X509RequestInheritOptions +enum certenroll.h X509RequestType +enum certpol.h X509SCEPDisposition +enum certpol.h X509SCEPFailInfo + +# Desktop App Technologies > Security and Identity > Cryptography > Cryptography Reference > Cryptography Enumerations +enum casetup.h CASetupProperty +enum casetup.h CEPSetupProperty +enum casetup.h CESSetupProperty +enum casetup.h MSCEPSetupProperty +enum celib.h ENUM_PERIOD +enum certcli.h X509EnrollmentAuthFlags +enum certsrv.h ENUM_CATYPES +enum cryptxml.h CRYPT_XML_CHARSET +enum cryptxml.h CRYPT_XML_KEYINFO_SPEC +enum cryptxml.h CRYPT_XML_PROPERTY_ID + +# Desktop App Technologies > Security and Identity > Cryptography > Cryptography Reference > Cryptography Functions +callback cryptdlg.h PFNCMFILTERPROC +callback cryptdlg.h PFNCMHOOKPROC +callback cryptuiapi.h PFNCFILTERPROC +callback cryptxml.h CryptXmlDllCloseDigest +callback cryptxml.h CryptXmlDllCreateDigest +callback cryptxml.h CryptXmlDllCreateKey +callback cryptxml.h CryptXmlDllDigestData +callback cryptxml.h CryptXmlDllEncodeAlgorithm +callback cryptxml.h CryptXmlDllEncodeKeyValue +callback cryptxml.h CryptXmlDllFinalizeDigest +callback cryptxml.h CryptXmlDllGetAlgorithmInfo +callback cryptxml.h CryptXmlDllGetInterface +callback cryptxml.h CryptXmlDllSignData +callback cryptxml.h CryptXmlDllVerifySignature +callback cryptxml.h PFN_CRYPT_XML_CREATE_TRANSFORM +callback cryptxml.h PFN_CRYPT_XML_DATA_PROVIDER_CLOSE +callback cryptxml.h PFN_CRYPT_XML_DATA_PROVIDER_READ +callback cryptxml.h PFN_CRYPT_XML_ENUM_ALG_INFO +callback cryptxml.h PFN_CRYPT_XML_WRITE_CALLBACK +callback mscat.h PFN_CDF_PARSE_ERROR_CALLBACK +callback mssip.h pCryptSIPGetCaps +callback mssip.h pfnIsFileSupported +callback mssip.h pfnIsFileSupportedName +callback wincrypt.h CertChainFindByIssuerCallback =pfn_cert_chain_find_by_issuer_callback +callback wincrypt.h CertDllOpenStoreProv =pfn_cert_dll_open_store_prov_func +callback wincrypt.h CertEnumPhysicalStoreCallback =pfn_cert_enum_physical_store +callback wincrypt.h CertEnumSystemStoreCallback =pfn_cert_enum_system_store +callback wincrypt.h CertStoreProvCloseCallback =pfn_cert_store_prov_close +callback wincrypt.h CertStoreProvDeleteCertCallback =pfn_cert_store_prov_delete_cert +callback wincrypt.h CertStoreProvDeleteCRLCallback =pfn_cert_store_prov_delete_crl +callback wincrypt.h CertStoreProvReadCertCallback =pfn_cert_store_prov_read_cert +callback wincrypt.h CertStoreProvReadCRLCallback =pfn_cert_store_prov_read_crl +callback wincrypt.h CertStoreProvSetCertPropertyCallback =pfn_cert_store_prov_set_cert_property +callback wincrypt.h CertStoreProvSetCRLPropertyCallback =pfn_cert_store_prov_set_crl_property +callback wincrypt.h CertStoreProvSetCTLProperty =pfn_cert_store_prov_set_ctl_property +callback wincrypt.h CertStoreProvWriteCertCallback =pfn_cert_store_prov_write_cert +callback wincrypt.h CertStoreProvWriteCRLCallback =pfn_cert_store_prov_write_crl +callback wincrypt.h CertStoreProvWriteCTL =pfn_cert_store_prov_write_ctl +callback wincrypt.h PCRYPT_DECRYPT_PRIVATE_KEY_FUNC +callback wincrypt.h PCRYPT_ENCRYPT_PRIVATE_KEY_FUNC +callback wincrypt.h PCRYPT_RESOLVE_HCRYPTPROV_FUNC +callback wincrypt.h PFN_CERT_CREATE_CONTEXT_SORT_FUNC +callback wincrypt.h PFN_CERT_ENUM_SYSTEM_STORE_LOCATION +callback wincrypt.h PFN_CERT_STORE_PROV_CONTROL +callback wincrypt.h PFN_CERT_STORE_PROV_READ_CERT =pfn_cert_store_prov_read_ctl +callback wincrypt.h PFN_CMSG_CNG_IMPORT_CONTENT_ENCRYPT_KEY +callback wincrypt.h PFN_CMSG_CNG_IMPORT_KEY_AGREE +callback wincrypt.h PFN_CMSG_CNG_IMPORT_KEY_TRANS +callback wincrypt.h PFN_CMSG_EXPORT_KEY_AGREE +callback wincrypt.h PFN_CMSG_EXPORT_KEY_TRANS +callback wincrypt.h PFN_CMSG_EXPORT_MAIL_LIST +callback wincrypt.h PFN_CMSG_GEN_CONTENT_ENCRYPT_KEY +callback wincrypt.h PFN_CMSG_IMPORT_KEY_AGREE +callback wincrypt.h PFN_CMSG_IMPORT_KEY_TRANS +callback wincrypt.h PFN_CMSG_IMPORT_MAIL_LIST +callback wincrypt.h PFN_CRYPT_ENUM_KEYID_PROP +callback wincrypt.h PFN_CRYPT_ENUM_OID_FUNC +callback wincrypt.h PFN_CRYPT_ENUM_OID_INFO +callback wincrypt.h PFN_CRYPT_EXPORT_PUBLIC_KEY_INFO_EX2_FUNC +callback wincrypt.h PFN_CRYPT_EXTRACT_ENCODED_SIGNATURE_PARAMETERS_FUNC +callback wincrypt.h PFN_CRYPT_GET_SIGNER_CERTIFICATE +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_FLUSH +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_FREE +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_FREE_IDENTIFIER +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_FREE_PASSWORD +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_GET +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_INITIALIZE +callback wincrypt.h PFN_CRYPT_OBJECT_LOCATOR_PROVIDER_RELEASE +callback wincrypt.h PFN_CRYPT_SIGN_AND_ENCODE_HASH_FUNC +callback wincrypt.h PFN_CRYPT_VERIFY_ENCODED_SIGNATURE_FUNC +callback wincrypt.h PFN_IMPORT_PUBLIC_KEY_INFO_EX2_FUNC +function certbcli.h CertSrvBackupClose +function certbcli.h CertSrvBackupEnd +function certbcli.h CertSrvBackupFree +function certbcli.h CertSrvBackupGetBackupLogs =certsrvbackupgetbackuplogsw +function certbcli.h CertSrvBackupGetDatabaseNames =certsrvbackupgetdatabasenamesw +function certbcli.h CertSrvBackupGetDynamicFileList =certsrvbackupgetdynamicfilelistw +function certbcli.h CertSrvBackupOpenFile =certsrvbackupopenfilew +function certbcli.h CertSrvBackupPrepare =certsrvbackuppreparew +function certbcli.h CertSrvBackupRead +function certbcli.h CertSrvBackupTruncateLogs +function certbcli.h CertSrvIsServerOnline =certsrvisserveronlinew +function certbcli.h CertSrvRestoreEnd +function certbcli.h CertSrvRestoreGetDatabaseLocations =certsrvrestoregetdatabaselocationsw +function certbcli.h CertSrvRestorePrepare =certsrvrestorepreparew +function certbcli.h CertSrvRestoreRegister =certsrvrestoreregisterw +function certbcli.h CertSrvRestoreRegisterComplete +function certbcli.h CertSrvRestoreRegisterThroughFile +function certbcli.h CertSrvServerControl =certsrvservercontrolw +function cryptdlg.h CertModifyCertificatesToTrust +function cryptdlg.h CertSelectCertificate =certselectcertificatea +function cryptdlg.h CertViewProperties =certviewpropertiesa +function cryptdlg.h GetFriendlyNameOfCert =getfriendlynameofcerta +function cryptuiapi.h CertSelectionGetSerializedBlob +function cryptuiapi.h CryptUIDlgCertMgr +function cryptuiapi.h CryptUIDlgSelectCertificateFromStore +function cryptuiapi.h CryptUIDlgViewCertificate =cryptuidlgviewcertificatea +function cryptuiapi.h CryptUIDlgViewContext +function cryptuiapi.h CryptUIWizDigitalSign +function cryptuiapi.h CryptUIWizExport +function cryptuiapi.h CryptUIWizFreeDigitalSignContext +function cryptuiapi.h CryptUIWizImport +function cryptxml.h CryptXmlAddObject +function cryptxml.h CryptXmlClose +function cryptxml.h CryptXmlCreateReference +function cryptxml.h CryptXmlDigestReference +function cryptxml.h CryptXmlEncode +function cryptxml.h CryptXmlGetAlgorithmInfo +function cryptxml.h CryptXmlGetDocContext +function cryptxml.h CryptXmlGetReference +function cryptxml.h CryptXmlGetSignature +function cryptxml.h CryptXmlGetStatus +function cryptxml.h CryptXmlGetTransforms +function cryptxml.h CryptXmlImportPublicKey +function cryptxml.h CryptXmlOpenToDecode +function cryptxml.h CryptXmlOpenToEncode +function cryptxml.h CryptXmlSetHMACSecret +function cryptxml.h CryptXmlSign +function cryptxml.h CryptXmlVerifySignature +function dpapi.h CryptProtectData +function dpapi.h CryptProtectMemory +function dpapi.h CryptUnprotectData +function dpapi.h CryptUnprotectMemory +function dpapi.h CryptUpdateProtectedState +function mscat.h CryptCATAdminAcquireContext +function mscat.h CryptCATAdminAcquireContext2 +function mscat.h CryptCATAdminAddCatalog +function mscat.h CryptCATAdminCalcHashFromFileHandle +function mscat.h CryptCATAdminCalcHashFromFileHandle2 +function mscat.h CryptCATAdminEnumCatalogFromHash +function mscat.h CryptCATAdminReleaseCatalogContext +function mscat.h CryptCATAdminReleaseContext +function mscat.h CryptCATAdminRemoveCatalog +function mscat.h CryptCATAdminResolveCatalogPath +function mscat.h CryptCATCatalogInfoFromContext +function mscat.h CryptCATCDFClose +function mscat.h CryptCATCDFEnumCatAttributes +function mscat.h CryptCATCDFOpen +function mscat.h CryptCATClose +function mscat.h CryptCATEnumerateAttr +function mscat.h CryptCATEnumerateCatAttr +function mscat.h CryptCATEnumerateMember +function mscat.h CryptCATGetAttrInfo +function mscat.h CryptCATGetMemberInfo +function mscat.h CryptCATHandleFromStore +function mscat.h CryptCATOpen +function mscat.h CryptCATPersistStore +function mscat.h CryptCATPutAttrInfo +function mscat.h CryptCATPutCatAttrInfo +function mscat.h CryptCATPutMemberInfo +function mscat.h CryptCATStoreFromHandle +function mscat.h IsCatalogFile +function mssip.h CryptSIPAddProvider +function mssip.h CryptSIPCreateIndirectData +function mssip.h CryptSIPGetCaps +function mssip.h CryptSIPGetSignedDataMsg +function mssip.h CryptSIPLoad +function mssip.h CryptSIPPutSignedDataMsg +function mssip.h CryptSIPRemoveProvider +function mssip.h CryptSIPRemoveSignedDataMsg +function mssip.h CryptSIPRetrieveSubjectGuid +function mssip.h CryptSIPRetrieveSubjectGuidForCatalogFile +function mssip.h CryptSIPVerifyIndirectData +function ntsecapi.h RtlDecryptMemory +function ntsecapi.h RtlEncryptMemory +function ntsecapi.h RtlGenRandom +function wincrypt.h CertAddCertificateContextToStore +function wincrypt.h CertAddCertificateLinkToStore +function wincrypt.h CertAddCRLContextToStore +function wincrypt.h CertAddCRLLinkToStore +function wincrypt.h CertAddCTLContextToStore +function wincrypt.h CertAddCTLLinkToStore +function wincrypt.h CertAddEncodedCertificateToStore +function wincrypt.h CertAddEncodedCertificateToSystemStore =certaddencodedcertificatetosystemstorea +function wincrypt.h CertAddEncodedCRLToStore +function wincrypt.h CertAddEncodedCTLToStore +function wincrypt.h CertAddEnhancedKeyUsageIdentifier +function wincrypt.h CertAddRefServerOcspResponse +function wincrypt.h CertAddRefServerOcspResponseContext +function wincrypt.h CertAddSerializedElementToStore +function wincrypt.h CertAddStoreToCollection +function wincrypt.h CertAlgIdToOID +function wincrypt.h CertCloseServerOcspResponse +function wincrypt.h CertCloseStore +function wincrypt.h CertCompareCertificate +function wincrypt.h CertCompareCertificateName +function wincrypt.h CertCompareIntegerBlob +function wincrypt.h CertComparePublicKeyInfo +function wincrypt.h CertControlStore +function wincrypt.h CertCreateCertificateChainEngine +function wincrypt.h CertCreateCertificateContext +function wincrypt.h CertCreateContext +function wincrypt.h CertCreateCRLContext +function wincrypt.h CertCreateCTLContext +function wincrypt.h CertCreateCTLEntryFromCertificateContextProperties +function wincrypt.h CertCreateSelfSignCertificate +function wincrypt.h CertDeleteCertificateFromStore +function wincrypt.h CertDeleteCRLFromStore +function wincrypt.h CertDeleteCTLFromStore +function wincrypt.h CertDuplicateCertificateChain +function wincrypt.h CertDuplicateCertificateContext +function wincrypt.h CertDuplicateCRLContext +function wincrypt.h CertDuplicateCTLContext +function wincrypt.h CertDuplicateStore +function wincrypt.h CertEnumCertificateContextProperties +function wincrypt.h CertEnumCertificatesInStore +function wincrypt.h CertEnumCRLContextProperties +function wincrypt.h CertEnumCRLsInStore +function wincrypt.h CertEnumCTLContextProperties +function wincrypt.h CertEnumCTLsInStore +function wincrypt.h CertEnumPhysicalStore +function wincrypt.h CertEnumSubjectInSortedCTL +function wincrypt.h CertEnumSystemStore +function wincrypt.h CertEnumSystemStoreLocation +function wincrypt.h CertFindAttribute +function wincrypt.h CertFindCertificateInCRL +function wincrypt.h CertFindCertificateInStore +function wincrypt.h CertFindChainInStore +function wincrypt.h CertFindCRLInStore +function wincrypt.h CertFindCTLInStore +function wincrypt.h CertFindExtension +function wincrypt.h CertFindRDNAttr +function wincrypt.h CertFindSubjectInCTL +function wincrypt.h CertFindSubjectInSortedCTL +function wincrypt.h CertFreeCertificateChain +function wincrypt.h CertFreeCertificateChainEngine +function wincrypt.h CertFreeCertificateChainList +function wincrypt.h CertFreeCertificateContext +function wincrypt.h CertFreeCRLContext +function wincrypt.h CertFreeCTLContext +function wincrypt.h CertFreeServerOcspResponseContext +function wincrypt.h CertGetCertificateChain +function wincrypt.h CertGetCertificateContextProperty +function wincrypt.h CertGetCRLContextProperty +function wincrypt.h CertGetCRLFromStore +function wincrypt.h CertGetCTLContextProperty +function wincrypt.h CertGetEnhancedKeyUsage +function wincrypt.h CertGetIntendedKeyUsage +function wincrypt.h CertGetIssuerCertificateFromStore +function wincrypt.h CertGetNameString =certgetnamestringa +function wincrypt.h CertGetPublicKeyLength +function wincrypt.h CertGetServerOcspResponseContext +function wincrypt.h CertGetStoreProperty +function wincrypt.h CertGetSubjectCertificateFromStore +function wincrypt.h CertGetValidUsages +function wincrypt.h CertIsRDNAttrsInCertificateName +function wincrypt.h CertIsStrongHashToSign +function wincrypt.h CertIsValidCRLForCertificate +function wincrypt.h CertNameToStr =certnametostra +function wincrypt.h CertOIDToAlgId +function wincrypt.h CertOpenServerOcspResponse +function wincrypt.h CertOpenStore +function wincrypt.h CertOpenSystemStore =certopensystemstorea +function wincrypt.h CertRDNValueToStr =certrdnvaluetostra +function wincrypt.h CertRegisterPhysicalStore +function wincrypt.h CertRegisterSystemStore +function wincrypt.h CertRemoveEnhancedKeyUsageIdentifier +function wincrypt.h CertRemoveStoreFromCollection +function wincrypt.h CertResyncCertificateChainEngine +function wincrypt.h CertRetrieveLogoOrBiometricInfo +function wincrypt.h CertSaveStore +function wincrypt.h CertSelectCertificateChains +function wincrypt.h CertSerializeCertificateStoreElement +function wincrypt.h CertSerializeCRLStoreElement +function wincrypt.h CertSerializeCTLStoreElement +function wincrypt.h CertSetCertificateContextPropertiesFromCTLEntry +function wincrypt.h CertSetCertificateContextProperty +function wincrypt.h CertSetCRLContextProperty +function wincrypt.h CertSetCTLContextProperty +function wincrypt.h CertSetEnhancedKeyUsage +function wincrypt.h CertSetStoreProperty +function wincrypt.h CertStrToName =certstrtonamea +function wincrypt.h CertUnregisterPhysicalStore +function wincrypt.h CertUnregisterSystemStore +function wincrypt.h CertVerifyCertificateChainPolicy +function wincrypt.h CertVerifyCRLRevocation +function wincrypt.h CertVerifyCRLTimeValidity +function wincrypt.h CertVerifyCTLUsage +function wincrypt.h CertVerifyRevocation +function wincrypt.h CertVerifySubjectCertificateContext +function wincrypt.h CertVerifyTimeValidity +function wincrypt.h CertVerifyValidityNesting +function wincrypt.h CryptAcquireCertificatePrivateKey +function wincrypt.h CryptAcquireContext =cryptacquirecontexta +function wincrypt.h CryptBinaryToString =cryptbinarytostringa +function wincrypt.h CryptContextAddRef +function wincrypt.h CryptCreateHash +function wincrypt.h CryptCreateKeyIdentifierFromCSP +function wincrypt.h CryptDecodeMessage +function wincrypt.h CryptDecodeObject +function wincrypt.h CryptDecodeObjectEx +function wincrypt.h CryptDecrypt +function wincrypt.h CryptDecryptAndVerifyMessageSignature +function wincrypt.h CryptDecryptMessage +function wincrypt.h CryptDeriveKey +function wincrypt.h CryptDestroyHash +function wincrypt.h CryptDestroyKey +function wincrypt.h CryptDuplicateHash +function wincrypt.h CryptDuplicateKey +function wincrypt.h CryptEncodeObject +function wincrypt.h CryptEncodeObjectEx +function wincrypt.h CryptEncrypt +function wincrypt.h CryptEncryptMessage +function wincrypt.h CryptEnumKeyIdentifierProperties +function wincrypt.h CryptEnumOIDFunction +function wincrypt.h CryptEnumOIDInfo +function wincrypt.h CryptEnumProviders =cryptenumprovidersa +function wincrypt.h CryptEnumProviderTypes =cryptenumprovidertypesa +function wincrypt.h CryptExportKey +function wincrypt.h CryptExportPKCS8 +function wincrypt.h CryptExportPKCS8Ex +function wincrypt.h CryptExportPublicKeyInfo +function wincrypt.h CryptExportPublicKeyInfoEx +function wincrypt.h CryptExportPublicKeyInfoFromBCryptKeyHandle +function wincrypt.h CryptFindCertificateKeyProvInfo +function wincrypt.h CryptFindLocalizedName +function wincrypt.h CryptFindOIDInfo +function wincrypt.h CryptFormatObject +function wincrypt.h CryptFreeOIDFunctionAddress +function wincrypt.h CryptGenKey +function wincrypt.h CryptGenRandom +function wincrypt.h CryptGetDefaultOIDDllList +function wincrypt.h CryptGetDefaultOIDFunctionAddress +function wincrypt.h CryptGetDefaultProvider =cryptgetdefaultprovidera +function wincrypt.h CryptGetHashParam +function wincrypt.h CryptGetKeyIdentifierProperty +function wincrypt.h CryptGetKeyParam +function wincrypt.h CryptGetMessageCertificates +function wincrypt.h CryptGetMessageSignerCount +function wincrypt.h CryptGetObjectUrl +function wincrypt.h CryptGetOIDFunctionAddress +function wincrypt.h CryptGetOIDFunctionValue +function wincrypt.h CryptGetProvParam +function wincrypt.h CryptGetTimeValidObject +function wincrypt.h CryptGetUserKey +function wincrypt.h CryptHashCertificate +function wincrypt.h CryptHashCertificate2 +function wincrypt.h CryptHashData +function wincrypt.h CryptHashMessage +function wincrypt.h CryptHashPublicKeyInfo +function wincrypt.h CryptHashSessionKey +function wincrypt.h CryptHashToBeSigned +function wincrypt.h CryptImportKey +function wincrypt.h CryptImportPKCS8 +function wincrypt.h CryptImportPublicKeyInfo +function wincrypt.h CryptImportPublicKeyInfoEx +function wincrypt.h CryptImportPublicKeyInfoEx2 +function wincrypt.h CryptInitOIDFunctionSet +function wincrypt.h CryptInstallDefaultContext +function wincrypt.h CryptInstallOIDFunctionAddress +function wincrypt.h CryptMemAlloc +function wincrypt.h CryptMemFree +function wincrypt.h CryptMemRealloc +function wincrypt.h CryptMsgCalculateEncodedLength +function wincrypt.h CryptMsgClose +function wincrypt.h CryptMsgControl +function wincrypt.h CryptMsgCountersign +function wincrypt.h CryptMsgCountersignEncoded +function wincrypt.h CryptMsgDuplicate +function wincrypt.h CryptMsgEncodeAndSignCTL +function wincrypt.h CryptMsgGetAndVerifySigner +function wincrypt.h CryptMsgGetParam +function wincrypt.h CryptMsgOpenToDecode +function wincrypt.h CryptMsgOpenToEncode +function wincrypt.h CryptMsgSignCTL +function wincrypt.h CryptMsgUpdate +function wincrypt.h CryptMsgVerifyCountersignatureEncoded +function wincrypt.h CryptMsgVerifyCountersignatureEncodedEx +function wincrypt.h CryptQueryObject +function wincrypt.h CryptRegisterDefaultOIDFunction +function wincrypt.h CryptRegisterOIDFunction +function wincrypt.h CryptRegisterOIDInfo +function wincrypt.h CryptReleaseContext +function wincrypt.h CryptRetrieveObjectByUrl =cryptretrieveobjectbyurla +function wincrypt.h CryptRetrieveTimeStamp +function wincrypt.h CryptSetHashParam +function wincrypt.h CryptSetKeyIdentifierProperty +function wincrypt.h CryptSetKeyParam +function wincrypt.h CryptSetOIDFunctionValue +function wincrypt.h CryptSetProvider =cryptsetprovidera +function wincrypt.h CryptSetProviderEx =cryptsetproviderexa +function wincrypt.h CryptSetProvParam +function wincrypt.h CryptSignAndEncodeCertificate +function wincrypt.h CryptSignAndEncryptMessage +function wincrypt.h CryptSignCertificate +function wincrypt.h CryptSignHash =cryptsignhasha +function wincrypt.h CryptSignMessage +function wincrypt.h CryptSignMessageWithKey +function wincrypt.h CryptStringToBinary =cryptstringtobinarya +function wincrypt.h CryptUninstallDefaultContext +function wincrypt.h CryptUnregisterDefaultOIDFunction +function wincrypt.h CryptUnregisterOIDFunction +function wincrypt.h CryptUnregisterOIDInfo +function wincrypt.h CryptVerifyCertificateSignature +function wincrypt.h CryptVerifyCertificateSignatureEx +function wincrypt.h CryptVerifyDetachedMessageHash +function wincrypt.h CryptVerifyDetachedMessageSignature +function wincrypt.h CryptVerifyMessageHash +function wincrypt.h CryptVerifyMessageSignature +function wincrypt.h CryptVerifyMessageSignatureWithKey +function wincrypt.h CryptVerifySignature =cryptverifysignaturea +function wincrypt.h CryptVerifyTimeStampSignature +function wincrypt.h GetEncSChannel +function wincrypt.h PFXExportCertStore +function wincrypt.h PFXExportCertStoreEx +function wincrypt.h PFXImportCertStore +function wincrypt.h PFXIsPFXBlob +function wincrypt.h PFXVerifyPassword +function wintrust.h OpenPersonalTrustDBDialog +function wintrust.h OpenPersonalTrustDBDialogEx +function wintrust.h WintrustAddActionID +function wintrust.h WintrustAddDefaultForUsage +function wintrust.h WintrustGetDefaultForUsage +function wintrust.h WintrustGetRegPolicyFlags +function wintrust.h WintrustLoadFunctionPointers +function wintrust.h WintrustRemoveActionID +function wintrust.h WintrustSetDefaultIncludePEPageHashes +function wintrust.h WintrustSetRegPolicyFlags +function wintrust.h WinVerifyTrust +function wintrust.h WinVerifyTrustEx +function wintrust.h WTHelperCertCheckValidSignature +function wintrust.h WTHelperCertIsSelfSigned +function wintrust.h WTHelperGetProvCertFromChain +function wintrust.h WTHelperGetProvPrivateDataFromChain +function wintrust.h WTHelperGetProvSignerFromChain +function wintrust.h WTHelperProvDataFromStateData + +# Desktop App Technologies > Security and Identity > Cryptography > Cryptography Reference > Cryptography Interfaces +interface casetup.h ICertificateEnrollmentPolicyServerSetup +interface casetup.h ICertificateEnrollmentServerSetup +interface casetup.h ICertSrvSetup +interface casetup.h ICertSrvSetupKeyInformation +interface casetup.h ICertSrvSetupKeyInformationCollection +interface casetup.h IMSCEPSetup +interface certadm.h ICertAdmin +interface certadm.h ICertAdmin2 +interface certadm.h IOCSPAdmin +interface certadm.h IOCSPCAConfiguration +interface certadm.h IOCSPCAConfigurationCollection +interface certadm.h IOCSPProperty +interface certadm.h IOCSPPropertyCollection +interface certcli.h ICertConfig +interface certcli.h ICertConfig2 +interface certcli.h ICertGetConfig +interface certcli.h ICertRequest +interface certcli.h ICertRequest2 +interface certcli.h ICertRequest3 +interface certenc.h ICertEncodeAltName +interface certenc.h ICertEncodeBitString +interface certenc.h ICertEncodeCRLDistInfo +interface certenc.h ICertEncodeDateArray +interface certenc.h ICertEncodeLongArray +interface certenc.h ICertEncodeStringArray +interface certexit.h ICertExit +interface certexit.h ICertExit2 +interface certif.h ICertServerExit +interface certif.h ICertServerPolicy +interface certmod.h ICertManageModule +interface certpol.h ICertPolicy +interface certpol.h ICertPolicy2 +interface certpol.h INDESPolicy +interface certview.h ICertView +interface certview.h ICertView2 +interface certview.h IEnumCERTVIEWATTRIBUTE +interface certview.h IEnumCERTVIEWCOLUMN +interface certview.h IEnumCERTVIEWEXTENSION +interface certview.h IEnumCERTVIEWROW +interface xenroll.h ICEnroll +interface xenroll.h ICEnroll2 +interface xenroll.h ICEnroll3 +interface xenroll.h ICEnroll4 +interface xenroll.h IEnroll +interface xenroll.h IEnroll2 +interface xenroll.h IEnroll4 + +# Desktop App Technologies > Security and Identity > Cryptography > Cryptography Reference > Cryptography Structures +struct cryptdlg.h CERT_SELECT_STRUCT =cert_select_struct_a +struct cryptdlg.h CERT_VIEWPROPERTIES_STRUCT =cert_viewproperties_struct_a +struct cryptdlg.h CTL_MODIFY_REQUEST +struct cryptuiapi.h CERT_SELECTUI_INPUT +struct cryptuiapi.h CRYPTUI_CERT_MGR_STRUCT +struct cryptuiapi.h CRYPTUI_INITDIALOG_STRUCT +struct cryptuiapi.h CRYPTUI_VIEWCERTIFICATE_STRUCT =cryptui_viewcertificate_structa +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_INFO +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_PVK_FILE_INFO +struct cryptuiapi.h CRYPTUI_WIZ_DIGITAL_SIGN_STORE_INFO +struct cryptuiapi.h CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO +struct cryptuiapi.h CRYPTUI_WIZ_EXPORT_INFO +struct cryptuiapi.h CRYPTUI_WIZ_IMPORT_SRC_INFO +struct cryptxml.h CRYPT_XML_ALGORITHM +struct cryptxml.h CRYPT_XML_ALGORITHM_INFO +struct cryptxml.h CRYPT_XML_BLOB +struct cryptxml.h CRYPT_XML_CRYPTOGRAPHIC_INTERFACE +struct cryptxml.h CRYPT_XML_DATA_BLOB +struct cryptxml.h CRYPT_XML_DATA_PROVIDER +struct cryptxml.h CRYPT_XML_DOC_CTXT +struct cryptxml.h CRYPT_XML_ISSUER_SERIAL +struct cryptxml.h CRYPT_XML_KEY_DSA_KEY_VALUE +struct cryptxml.h CRYPT_XML_KEY_ECDSA_KEY_VALUE +struct cryptxml.h CRYPT_XML_KEY_INFO +struct cryptxml.h CRYPT_XML_KEY_INFO_ITEM +struct cryptxml.h CRYPT_XML_KEY_RSA_KEY_VALUE +struct cryptxml.h CRYPT_XML_KEY_VALUE +struct cryptxml.h CRYPT_XML_KEYINFO_PARAM +struct cryptxml.h CRYPT_XML_OBJECT +struct cryptxml.h CRYPT_XML_PROPERTY +struct cryptxml.h CRYPT_XML_REFERENCE +struct cryptxml.h CRYPT_XML_REFERENCES +struct cryptxml.h CRYPT_XML_SIGNATURE +struct cryptxml.h CRYPT_XML_SIGNED_INFO +struct cryptxml.h CRYPT_XML_STATUS +struct cryptxml.h CRYPT_XML_TRANSFORM_CHAIN_CONFIG +struct cryptxml.h CRYPT_XML_TRANSFORM_INFO +struct cryptxml.h CRYPT_XML_X509DATA +struct cryptxml.h CRYPT_XML_X509DATA_ITEM +struct dpapi.h CRYPTPROTECT_PROMPTSTRUCT +struct mscat.h CATALOG_INFO +struct mscat.h CRYPTCATATTRIBUTE +struct mscat.h CRYPTCATCDF +struct mscat.h CRYPTCATMEMBER +struct mscat.h CRYPTCATSTORE +struct mssip.h MS_ADDINFO_BLOB +struct mssip.h MS_ADDINFO_CATALOGMEMBER +struct mssip.h MS_ADDINFO_FLAT +struct mssip.h SIP_ADD_NEWPROVIDER +struct mssip.h SIP_CAP_SET =sip_cap_set_v2 +struct mssip.h SIP_DISPATCH_INFO +struct mssip.h SIP_INDIRECT_DATA +struct mssip.h SIP_SUBJECTINFO +struct wincrypt.h AUTHENTICODE_EXTRA_CERT_CHAIN_POLICY_PARA +struct wincrypt.h AUTHENTICODE_EXTRA_CERT_CHAIN_POLICY_STATUS +struct wincrypt.h AUTHENTICODE_TS_EXTRA_CERT_CHAIN_POLICY_PARA +struct wincrypt.h CERT_ACCESS_DESCRIPTION +struct wincrypt.h CERT_ALT_NAME_ENTRY +struct wincrypt.h CERT_ALT_NAME_INFO +struct wincrypt.h CERT_AUTHORITY_INFO_ACCESS +struct wincrypt.h CERT_AUTHORITY_KEY_ID_INFO +struct wincrypt.h CERT_AUTHORITY_KEY_ID2_INFO +struct wincrypt.h CERT_BASIC_CONSTRAINTS_INFO +struct wincrypt.h CERT_BASIC_CONSTRAINTS2_INFO +struct wincrypt.h CERT_BIOMETRIC_DATA +struct wincrypt.h CERT_BIOMETRIC_EXT_INFO +struct wincrypt.h CERT_CHAIN_CONTEXT +struct wincrypt.h CERT_CHAIN_ELEMENT +struct wincrypt.h CERT_CHAIN_ENGINE_CONFIG +struct wincrypt.h CERT_CHAIN_FIND_BY_ISSUER_PARA +struct wincrypt.h CERT_CHAIN_PARA +struct wincrypt.h CERT_CHAIN_POLICY_PARA +struct wincrypt.h CERT_CHAIN_POLICY_STATUS +struct wincrypt.h CERT_CONTEXT +struct wincrypt.h CERT_CREATE_CONTEXT_PARA +struct wincrypt.h CERT_CRL_CONTEXT_PAIR +struct wincrypt.h CERT_DH_PARAMETERS +struct wincrypt.h CERT_DSS_PARAMETERS +struct wincrypt.h CERT_ECC_SIGNATURE +struct wincrypt.h CERT_EXTENSION +struct wincrypt.h CERT_EXTENSIONS +struct wincrypt.h CERT_GENERAL_SUBTREE +struct wincrypt.h CERT_HASHED_URL +struct wincrypt.h CERT_ID +struct wincrypt.h CERT_INFO +struct wincrypt.h CERT_ISSUER_SERIAL_NUMBER +struct wincrypt.h CERT_KEY_ATTRIBUTES_INFO +struct wincrypt.h CERT_KEY_CONTEXT +struct wincrypt.h CERT_KEY_USAGE_RESTRICTION_INFO +struct wincrypt.h CERT_KEYGEN_REQUEST_INFO +struct wincrypt.h CERT_LDAP_STORE_OPENED_PARA +struct wincrypt.h CERT_LOGOTYPE_AUDIO +struct wincrypt.h CERT_LOGOTYPE_AUDIO_INFO +struct wincrypt.h CERT_LOGOTYPE_DATA +struct wincrypt.h CERT_LOGOTYPE_DETAILS +struct wincrypt.h CERT_LOGOTYPE_EXT_INFO +struct wincrypt.h CERT_LOGOTYPE_IMAGE +struct wincrypt.h CERT_LOGOTYPE_IMAGE_INFO +struct wincrypt.h CERT_LOGOTYPE_INFO +struct wincrypt.h CERT_LOGOTYPE_REFERENCE +struct wincrypt.h CERT_NAME_CONSTRAINTS_INFO +struct wincrypt.h CERT_NAME_INFO +struct wincrypt.h CERT_NAME_VALUE +struct wincrypt.h CERT_OR_CRL_BLOB +struct wincrypt.h CERT_OR_CRL_BUNDLE +struct wincrypt.h CERT_OTHER_LOGOTYPE_INFO +struct wincrypt.h CERT_PAIR +struct wincrypt.h CERT_PHYSICAL_STORE_INFO +struct wincrypt.h CERT_POLICIES_INFO +struct wincrypt.h CERT_POLICY_CONSTRAINTS_INFO +struct wincrypt.h CERT_POLICY_ID +struct wincrypt.h CERT_POLICY_INFO +struct wincrypt.h CERT_POLICY_MAPPING +struct wincrypt.h CERT_POLICY_MAPPINGS_INFO +struct wincrypt.h CERT_POLICY_QUALIFIER_INFO +struct wincrypt.h CERT_PRIVATE_KEY_VALIDITY +struct wincrypt.h CERT_PUBLIC_KEY_INFO +struct wincrypt.h CERT_QC_STATEMENT +struct wincrypt.h CERT_QC_STATEMENTS_EXT_INFO +struct wincrypt.h CERT_RDN +struct wincrypt.h CERT_RDN_ATTR +struct wincrypt.h CERT_REQUEST_INFO +struct wincrypt.h CERT_REVOCATION_CHAIN_PARA +struct wincrypt.h CERT_REVOCATION_CRL_INFO +struct wincrypt.h CERT_REVOCATION_INFO +struct wincrypt.h CERT_REVOCATION_PARA +struct wincrypt.h CERT_REVOCATION_STATUS +struct wincrypt.h CERT_SELECT_CHAIN_PARA +struct wincrypt.h CERT_SELECT_CRITERIA +struct wincrypt.h CERT_SERVER_OCSP_RESPONSE_CONTEXT +struct wincrypt.h CERT_SIGNED_CONTENT_INFO +struct wincrypt.h CERT_SIMPLE_CHAIN +struct wincrypt.h CERT_STORE_PROV_FIND_INFO +struct wincrypt.h CERT_STORE_PROV_INFO +struct wincrypt.h CERT_STRONG_SIGN_PARA +struct wincrypt.h CERT_STRONG_SIGN_SERIALIZED_INFO +struct wincrypt.h CERT_SYSTEM_STORE_INFO +struct wincrypt.h CERT_SYSTEM_STORE_RELOCATE_PARA +struct wincrypt.h CERT_TEMPLATE_EXT +struct wincrypt.h CERT_TRUST_LIST_INFO +struct wincrypt.h CERT_TRUST_STATUS +struct wincrypt.h CERT_USAGE_MATCH +struct wincrypt.h CERT_X942_DH_PARAMETERS +struct wincrypt.h CERT_X942_DH_VALIDATION_PARAMS +struct wincrypt.h CMC_ADD_ATTRIBUTES_INFO +struct wincrypt.h CMC_ADD_EXTENSIONS_INFO +struct wincrypt.h CMC_DATA_INFO +struct wincrypt.h CMC_PEND_INFO +struct wincrypt.h CMC_RESPONSE_INFO +struct wincrypt.h CMC_STATUS_INFO +struct wincrypt.h CMC_TAGGED_ATTRIBUTE +struct wincrypt.h CMC_TAGGED_CERT_REQUEST +struct wincrypt.h CMC_TAGGED_CONTENT_INFO +struct wincrypt.h CMC_TAGGED_OTHER_MSG +struct wincrypt.h CMC_TAGGED_REQUEST +struct wincrypt.h CMS_DH_KEY_INFO +struct wincrypt.h CMS_KEY_INFO +struct wincrypt.h CMSG_CMS_RECIPIENT_INFO +struct wincrypt.h CMSG_CMS_SIGNER_INFO +struct wincrypt.h CMSG_CNG_CONTENT_DECRYPT_INFO +struct wincrypt.h CMSG_CONTENT_ENCRYPT_INFO +struct wincrypt.h CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA +struct wincrypt.h CMSG_CTRL_DECRYPT_PARA +struct wincrypt.h CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR_PARA +struct wincrypt.h CMSG_CTRL_KEY_AGREE_DECRYPT_PARA +struct wincrypt.h CMSG_CTRL_KEY_TRANS_DECRYPT_PARA +struct wincrypt.h CMSG_CTRL_MAIL_LIST_DECRYPT_PARA +struct wincrypt.h CMSG_CTRL_VERIFY_SIGNATURE_EX_PARA +struct wincrypt.h CMSG_ENVELOPED_ENCODE_INFO +struct wincrypt.h CMSG_HASHED_ENCODE_INFO +struct wincrypt.h CMSG_KEY_AGREE_ENCRYPT_INFO +struct wincrypt.h CMSG_KEY_AGREE_KEY_ENCRYPT_INFO +struct wincrypt.h CMSG_KEY_AGREE_RECIPIENT_ENCODE_INFO +struct wincrypt.h CMSG_KEY_AGREE_RECIPIENT_INFO +struct wincrypt.h CMSG_KEY_TRANS_ENCRYPT_INFO +struct wincrypt.h CMSG_KEY_TRANS_RECIPIENT_ENCODE_INFO +struct wincrypt.h CMSG_KEY_TRANS_RECIPIENT_INFO +struct wincrypt.h CMSG_MAIL_LIST_ENCRYPT_INFO +struct wincrypt.h CMSG_MAIL_LIST_RECIPIENT_ENCODE_INFO +struct wincrypt.h CMSG_MAIL_LIST_RECIPIENT_INFO +struct wincrypt.h CMSG_RC2_AUX_INFO +struct wincrypt.h CMSG_RC4_AUX_INFO +struct wincrypt.h CMSG_RECIPIENT_ENCODE_INFO +struct wincrypt.h CMSG_RECIPIENT_ENCRYPTED_KEY_ENCODE_INFO +struct wincrypt.h CMSG_RECIPIENT_ENCRYPTED_KEY_INFO +struct wincrypt.h CMSG_SIGNED_ENCODE_INFO +struct wincrypt.h CMSG_SIGNER_ENCODE_INFO +struct wincrypt.h CMSG_SIGNER_INFO +struct wincrypt.h CMSG_SP3_COMPATIBLE_AUX_INFO +struct wincrypt.h CMSG_STREAM_INFO +struct wincrypt.h CRL_CONTEXT +struct wincrypt.h CRL_DIST_POINT +struct wincrypt.h CRL_DIST_POINT_NAME +struct wincrypt.h CRL_DIST_POINTS_INFO +struct wincrypt.h CRL_ENTRY +struct wincrypt.h CRL_FIND_ISSUED_FOR_PARA +struct wincrypt.h CRL_INFO +struct wincrypt.h CRL_ISSUING_DIST_POINT +struct wincrypt.h CROSS_CERT_DIST_POINTS_INFO +struct wincrypt.h CRYPT_AES_128_KEY_STATE +struct wincrypt.h CRYPT_AES_256_KEY_STATE +struct wincrypt.h CRYPT_ALGORITHM_IDENTIFIER +struct wincrypt.h CRYPT_ATTRIBUTE +struct wincrypt.h CRYPT_ATTRIBUTE_TYPE_VALUE +struct wincrypt.h CRYPT_ATTRIBUTES +struct wincrypt.h CRYPT_BIT_BLOB +struct wincrypt.h CRYPT_BLOB_ARRAY +struct wincrypt.h CRYPT_CONTENT_INFO +struct wincrypt.h CRYPT_CONTENT_INFO_SEQUENCE_OF_ANY +struct wincrypt.h CRYPT_CREDENTIALS +struct wincrypt.h CRYPT_DECODE_PARA +struct wincrypt.h CRYPT_DECRYPT_MESSAGE_PARA +struct wincrypt.h CRYPT_DEFAULT_CONTEXT_MULTI_OID_PARA +struct wincrypt.h CRYPT_ECC_CMS_SHARED_INFO +struct wincrypt.h CRYPT_ENCODE_PARA +struct wincrypt.h CRYPT_ENCRYPT_MESSAGE_PARA +struct wincrypt.h CRYPT_ENCRYPTED_PRIVATE_KEY_INFO +struct wincrypt.h CRYPT_ENROLLMENT_NAME_VALUE_PAIR +struct wincrypt.h CRYPT_GET_TIME_VALID_OBJECT_EXTRA_INFO +struct wincrypt.h CRYPT_HASH_MESSAGE_PARA +struct wincrypt.h CRYPT_INTEGER_BLOB +struct wincrypt.h CRYPT_KEY_PROV_INFO +struct wincrypt.h CRYPT_KEY_PROV_PARAM +struct wincrypt.h CRYPT_KEY_SIGN_MESSAGE_PARA +struct wincrypt.h CRYPT_KEY_VERIFY_MESSAGE_PARA +struct wincrypt.h CRYPT_MASK_GEN_ALGORITHM +struct wincrypt.h CRYPT_OBJECT_LOCATOR_PROVIDER_TABLE +struct wincrypt.h CRYPT_OID_FUNC_ENTRY +struct wincrypt.h CRYPT_OID_INFO +struct wincrypt.h CRYPT_PASSWORD_CREDENTIALS =crypt_password_credentialsa +struct wincrypt.h CRYPT_PKCS12_PBE_PARAMS +struct wincrypt.h CRYPT_PKCS8_EXPORT_PARAMS +struct wincrypt.h CRYPT_PKCS8_IMPORT_PARAMS +struct wincrypt.h CRYPT_PRIVATE_KEY_INFO +struct wincrypt.h CRYPT_PSOURCE_ALGORITHM +struct wincrypt.h CRYPT_RC2_CBC_PARAMETERS +struct wincrypt.h CRYPT_RETRIEVE_AUX_INFO +struct wincrypt.h CRYPT_RSA_SSA_PSS_PARAMETERS +struct wincrypt.h CRYPT_RSAES_OAEP_PARAMETERS +struct wincrypt.h CRYPT_SEQUENCE_OF_ANY +struct wincrypt.h CRYPT_SIGN_MESSAGE_PARA +struct wincrypt.h CRYPT_SMART_CARD_ROOT_INFO +struct wincrypt.h CRYPT_SMIME_CAPABILITIES +struct wincrypt.h CRYPT_SMIME_CAPABILITY +struct wincrypt.h CRYPT_TIME_STAMP_REQUEST_INFO +struct wincrypt.h CRYPT_TIMESTAMP_ACCURACY +struct wincrypt.h CRYPT_TIMESTAMP_CONTEXT +struct wincrypt.h CRYPT_TIMESTAMP_INFO +struct wincrypt.h CRYPT_TIMESTAMP_PARA +struct wincrypt.h CRYPT_TIMESTAMP_REQUEST +struct wincrypt.h CRYPT_TIMESTAMP_RESPONSE +struct wincrypt.h CRYPT_URL_INFO +struct wincrypt.h CRYPT_VERIFY_CERT_SIGN_STRONG_PROPERTIES_INFO +struct wincrypt.h CRYPT_VERIFY_MESSAGE_PARA +struct wincrypt.h CRYPT_X942_OTHER_INFO +struct wincrypt.h CRYPTNET_URL_CACHE_FLUSH_INFO +struct wincrypt.h CRYPTNET_URL_CACHE_PRE_FETCH_INFO +struct wincrypt.h CRYPTNET_URL_CACHE_RESPONSE_INFO +struct wincrypt.h CTL_ANY_SUBJECT_INFO +struct wincrypt.h CTL_CONTEXT +struct wincrypt.h CTL_ENTRY +struct wincrypt.h CTL_FIND_SUBJECT_PARA +struct wincrypt.h CTL_FIND_USAGE_PARA +struct wincrypt.h CTL_INFO +struct wincrypt.h CTL_USAGE +struct wincrypt.h CTL_USAGE_MATCH +struct wincrypt.h CTL_VERIFY_USAGE_PARA +struct wincrypt.h CTL_VERIFY_USAGE_STATUS +struct wincrypt.h DHPRIVKEY_VER3 +struct wincrypt.h DHPUBKEY +struct wincrypt.h DHPUBKEY_VER3 +struct wincrypt.h DSSSEED +struct wincrypt.h EV_EXTRA_CERT_CHAIN_POLICY_PARA +struct wincrypt.h EV_EXTRA_CERT_CHAIN_POLICY_STATUS +struct wincrypt.h HMAC_INFO +struct wincrypt.h OCSP_BASIC_RESPONSE_ENTRY +struct wincrypt.h OCSP_BASIC_RESPONSE_INFO +struct wincrypt.h OCSP_BASIC_REVOKED_INFO +struct wincrypt.h OCSP_BASIC_SIGNED_RESPONSE_INFO +struct wincrypt.h OCSP_CERT_ID +struct wincrypt.h OCSP_REQUEST_ENTRY +struct wincrypt.h OCSP_REQUEST_INFO +struct wincrypt.h OCSP_RESPONSE_INFO +struct wincrypt.h OCSP_SIGNATURE_INFO +struct wincrypt.h OCSP_SIGNED_REQUEST_INFO +struct wincrypt.h PROV_ENUMALGS +struct wincrypt.h PROV_ENUMALGS_EX +struct wincrypt.h PUBLICKEYSTRUC +struct wincrypt.h ROOT_INFO_LUID +struct wincrypt.h RSAPUBKEY +struct wincrypt.h SCHANNEL_ALG +struct wincrypt.h SSL_EXTRA_CERT_CHAIN_POLICY_PARA =httpspolicycallbackdata +struct wincrypt.h SSL_F12_EXTRA_CERT_CHAIN_POLICY_STATUS +struct wintrust.h CRYPT_PROVIDER_CERT +struct wintrust.h CRYPT_PROVIDER_DATA +struct wintrust.h CRYPT_PROVIDER_DEFUSAGE +struct wintrust.h CRYPT_PROVIDER_FUNCTIONS +struct wintrust.h CRYPT_PROVIDER_PRIVDATA +struct wintrust.h CRYPT_PROVIDER_REGDEFUSAGE +struct wintrust.h CRYPT_PROVIDER_SGNR +struct wintrust.h CRYPT_PROVIDER_SIGSTATE +struct wintrust.h CRYPT_PROVUI_DATA +struct wintrust.h CRYPT_PROVUI_FUNCS +struct wintrust.h CRYPT_REGISTER_ACTIONID +struct wintrust.h CRYPT_TRUST_REG_ENTRY +struct wintrust.h SPC_INDIRECT_DATA_CONTENT +struct wintrust.h WIN_CERTIFICATE +struct wintrust.h WINTRUST_BLOB_INFO +struct wintrust.h WINTRUST_CATALOG_INFO +struct wintrust.h WINTRUST_CERT_INFO +struct wintrust.h WINTRUST_DATA +struct wintrust.h WINTRUST_FILE_INFO +struct wintrust.h WINTRUST_SGNR_INFO +struct wintrust.h WINTRUST_SIGNATURE_SETTINGS + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Cryptographic Configuration Functions +function bcrypt.h BCryptAddContextFunction +function bcrypt.h BCryptConfigureContext +function bcrypt.h BCryptConfigureContextFunction +function bcrypt.h BCryptCreateContext +function bcrypt.h BCryptDeleteContext +function bcrypt.h BCryptEnumAlgorithms +function bcrypt.h BCryptEnumContextFunctionProviders +function bcrypt.h BCryptEnumContextFunctions +function bcrypt.h BCryptEnumContexts +function bcrypt.h BCryptEnumProviders +function bcrypt.h BCryptEnumRegisteredProviders +function bcrypt.h BCryptGetFipsAlgorithmMode +function bcrypt.h BCryptQueryContextConfiguration +function bcrypt.h BCryptQueryContextFunctionConfiguration +function bcrypt.h BCryptQueryContextFunctionProperty +function bcrypt.h BCryptQueryProviderRegistration +function bcrypt.h BCryptRegisterConfigChangeNotify(HANDLE*) =bcryptregisterconfigchangenotify +function bcrypt.h BCryptRegisterConfigChangeNotify(PRKEVENT) =bcryptregisterconfigchangenotify +function bcrypt.h BCryptRemoveContextFunction +function bcrypt.h BCryptResolveProviders +function bcrypt.h BCryptSetContextFunctionProperty +function bcrypt.h BCryptUnregisterConfigChangeNotify(HANDLE) =bcryptunregisterconfigchangenotify +function bcrypt.h BCryptUnregisterConfigChangeNotify(PRKEVENT) =bcryptunregisterconfigchangenotify + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Cryptographic Primitive Functions +function bcrypt.h BCryptCloseAlgorithmProvider +function bcrypt.h BCryptCreateHash +function bcrypt.h BCryptCreateMultiHash +function bcrypt.h BCryptDecrypt +function bcrypt.h BCryptDeriveKey +function bcrypt.h BCryptDestroyHash +function bcrypt.h BCryptDestroyKey +function bcrypt.h BCryptDestroySecret +function bcrypt.h BCryptDuplicateHash +function bcrypt.h BCryptDuplicateKey +function bcrypt.h BCryptEncrypt +function bcrypt.h BCryptExportKey +function bcrypt.h BCryptFinalizeKeyPair +function bcrypt.h BCryptFinishHash +function bcrypt.h BCryptFreeBuffer +function bcrypt.h BCryptGenerateKeyPair +function bcrypt.h BCryptGenerateSymmetricKey +function bcrypt.h BCryptGenRandom +function bcrypt.h BCryptGetProperty +function bcrypt.h BCryptHash +function bcrypt.h BCryptHashData +function bcrypt.h BCryptImportKey +function bcrypt.h BCryptImportKeyPair +function bcrypt.h BCryptKeyDerivation +function bcrypt.h BCryptOpenAlgorithmProvider +function bcrypt.h BCryptProcessMultiOperations +function bcrypt.h BCryptSecretAgreement +function bcrypt.h BCryptSetProperty +function bcrypt.h BCryptSignHash +function bcrypt.h BCryptVerifySignature + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG DPAPI Functions +callback ncryptprotect.h PFNCryptStreamOutputCallback +function ncryptprotect.h NCryptCloseProtectionDescriptor +function ncryptprotect.h NCryptCreateProtectionDescriptor +function ncryptprotect.h NCryptGetProtectionDescriptorInfo +function ncryptprotect.h NCryptProtectSecret +function ncryptprotect.h NCryptQueryProtectionDescriptorName +function ncryptprotect.h NCryptRegisterProtectionDescriptorName +function ncryptprotect.h NCryptStreamClose +function ncryptprotect.h NCryptStreamOpenToProtect +function ncryptprotect.h NCryptStreamOpenToUnprotect +function ncryptprotect.h NCryptStreamOpenToUnprotectEx +function ncryptprotect.h NCryptStreamUpdate +function ncryptprotect.h NCryptUnprotectSecret + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Helper Functions +function bcrypt.h BCRYPT_INIT_AUTH_MODE_INFO +function bcrypt.h BCryptDeriveKeyCapi +function bcrypt.h BCryptDeriveKeyPBKDF2 + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Key Storage Functions +function ncrypt.h NCryptCreateClaim +function ncrypt.h NCryptCreatePersistedKey +function ncrypt.h NCryptDecrypt +function ncrypt.h NCryptDeleteKey +function ncrypt.h NCryptDeriveKey +function ncrypt.h NCryptEncrypt +function ncrypt.h NCryptEnumAlgorithms +function ncrypt.h NCryptEnumKeys +function ncrypt.h NCryptEnumStorageProviders +function ncrypt.h NCryptExportKey +function ncrypt.h NCryptFinalizeKey +function ncrypt.h NCryptFreeBuffer +function ncrypt.h NCryptFreeObject +function ncrypt.h NCryptGetProperty +function ncrypt.h NCryptImportKey +function ncrypt.h NCryptIsAlgSupported +function ncrypt.h NCryptIsKeyHandle +function ncrypt.h NCryptKeyDerivation +function ncrypt.h NCryptNotifyChangeKey +function ncrypt.h NCryptOpenKey +function ncrypt.h NCryptOpenStorageProvider +function ncrypt.h NCryptSecretAgreement +function ncrypt.h NCryptSetProperty +function ncrypt.h NCryptSignHash +function ncrypt.h NCryptTranslateHandle +function ncrypt.h NCryptVerifyClaim +function ncrypt.h NCryptVerifySignature + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Token Binding Functions +function tokenbinding.h TokenBindingDeleteAllBindings +function tokenbinding.h TokenBindingDeleteBinding +function tokenbinding.h TokenBindingGenerateBinding +function tokenbinding.h TokenBindingGenerateID +function tokenbinding.h TokenBindingGenerateMessage +function tokenbinding.h TokenBindingGetKeyTypesClient +function tokenbinding.h TokenBindingGetKeyTypesServer +function tokenbinding.h TokenBindingVerifyMessage + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Structures +struct bcrypt.h BCRYPT_ALGORITHM_IDENTIFIER +struct bcrypt.h BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO +struct bcrypt.h BCRYPT_DH_KEY_BLOB +struct bcrypt.h BCRYPT_DH_PARAMETER_HEADER +struct bcrypt.h BCRYPT_DSA_KEY_BLOB +struct bcrypt.h BCRYPT_DSA_KEY_BLOB_V2 +struct bcrypt.h BCRYPT_DSA_PARAMETER_HEADER +struct bcrypt.h BCRYPT_DSA_PARAMETER_HEADER_V2 +struct bcrypt.h BCRYPT_ECCKEY_BLOB +struct bcrypt.h BCRYPT_INTERFACE_VERSION +struct bcrypt.h BCRYPT_KEY_BLOB +struct bcrypt.h BCRYPT_KEY_DATA_BLOB_HEADER +struct bcrypt.h BCRYPT_KEY_LENGTHS_STRUCT +struct bcrypt.h BCRYPT_MULTI_HASH_OPERATION +struct bcrypt.h BCRYPT_MULTI_OBJECT_LENGTH_STRUCT +struct bcrypt.h BCRYPT_OAEP_PADDING_INFO +struct bcrypt.h BCRYPT_OID +struct bcrypt.h BCRYPT_OID_LIST +struct bcrypt.h BCRYPT_PKCS1_PADDING_INFO +struct bcrypt.h BCRYPT_PROVIDER_NAME +struct bcrypt.h BCRYPT_PSS_PADDING_INFO +struct bcrypt.h BCRYPT_RSAKEY_BLOB +struct bcrypt.h BCryptBuffer +struct bcrypt.h BCryptBufferDesc +struct bcrypt.h CRYPT_CONTEXT_CONFIG +struct bcrypt.h CRYPT_CONTEXT_FUNCTION_CONFIG +struct bcrypt.h CRYPT_CONTEXT_FUNCTION_PROVIDERS +struct bcrypt.h CRYPT_CONTEXT_FUNCTIONS +struct bcrypt.h CRYPT_CONTEXTS +struct bcrypt.h CRYPT_IMAGE_REF +struct bcrypt.h CRYPT_IMAGE_REG +struct bcrypt.h CRYPT_INTERFACE_REG +struct bcrypt.h CRYPT_PROPERTY_REF +struct bcrypt.h CRYPT_PROVIDER_REF +struct bcrypt.h CRYPT_PROVIDER_REFS +struct bcrypt.h CRYPT_PROVIDER_REG +struct bcrypt.h CRYPT_PROVIDERS +struct bcrypt.h NCryptBuffer =bcryptbuffer +struct bcrypt.h NCryptBufferDesc =bcryptbufferdesc +struct ncrypt.h NCRYPT_ALLOC_PARA +struct ncrypt.h NCRYPT_KEY_BLOB_HEADER +struct ncrypt.h NCRYPT_SUPPORTED_LENGTHS +struct ncrypt.h NCRYPT_UI_POLICY +struct ncrypt.h NCryptAlgorithmName +struct ncrypt.h NCryptKeyName +struct ncrypt.h NCryptProviderName +struct ncryptprotect.h NCRYPT_PROTECT_STREAM_INFO +struct tokenbinding.h TOKENBINDING_IDENTIFIER +struct tokenbinding.h TOKENBINDING_KEY_TYPES +struct tokenbinding.h TOKENBINDING_RESULT_DATA +struct tokenbinding.h TOKENBINDING_RESULT_LIST + +# Desktop App Technologies > Security and Identity > Cryptography API: Next Generation > CNG Reference > CNG Enumerations +enum bcrypt.h BCRYPT_HASH_OPERATION_TYPE +enum bcrypt.h BCRYPT_MULTI_OPERATION_TYPE +enum bcrypt.h DSAFIPSVERSION_ENUM +enum bcrypt.h HASHALGORITHM_ENUM +enum tokenbinding.h TOKENBINDING_EXTENSION_FORMAT +enum tokenbinding.h TOKENBINDING_TYPE + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > Admin Structures in Active Directory Domain Services +struct dsadmin.h DSA_NEWOBJ_DISPINFO + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > Display Structures in Active Directory Domain Services +struct cmnquery.h CQFORM +struct cmnquery.h CQPAGE +struct cmnquery.h OPENQUERYWINDOW +struct dsclient.h DOMAINDESC +struct dsclient.h DOMAINTREE +struct dsclient.h DSBITEM =dsbitema +struct dsclient.h DSBROWSEINFO =dsbrowseinfoa +struct dsclient.h DSCLASSCREATIONINFO +struct dsclient.h DSDISPLAYSPECOPTIONS +struct dsclient.h DSOBJECT +struct dsclient.h DSOBJECTNAMES +struct dsclient.h DSPROPERTYPAGEINFO +struct dsquery.h DSCOLUMN +struct dsquery.h DSQUERYCLASSLIST +struct dsquery.h DSQUERYINITPARAMS +struct dsquery.h DSQUERYPARAMS + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > MMC Property Page Structures in Active Directory Domain Services +struct adsprop.h ADSPROPERROR +struct adsprop.h ADSPROPINITPARAMS + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > Domain Controller and Replication Management Structures +struct ntdsapi.h DS_DOMAIN_CONTROLLER_INFO_1 =ds_domain_controller_info_1a +struct ntdsapi.h DS_DOMAIN_CONTROLLER_INFO_2 =ds_domain_controller_info_2a +struct ntdsapi.h DS_DOMAIN_CONTROLLER_INFO_3 =ds_domain_controller_info_3a +struct ntdsapi.h DS_NAME_RESULT =ds_name_resulta +struct ntdsapi.h DS_NAME_RESULT_ITEM =ds_name_result_itema +struct ntdsapi.h DS_REPL_ATTR_META_DATA +struct ntdsapi.h DS_REPL_ATTR_META_DATA_2 +struct ntdsapi.h DS_REPL_ATTR_META_DATA_BLOB +struct ntdsapi.h DS_REPL_ATTR_VALUE_META_DATA +struct ntdsapi.h DS_REPL_ATTR_VALUE_META_DATA_2 +struct ntdsapi.h DS_REPL_ATTR_VALUE_META_DATA_EXT +struct ntdsapi.h DS_REPL_CURSOR +struct ntdsapi.h DS_REPL_CURSOR_2 +struct ntdsapi.h DS_REPL_CURSOR_3 =ds_repl_cursor_3w +struct ntdsapi.h DS_REPL_CURSOR_BLOB +struct ntdsapi.h DS_REPL_CURSORS +struct ntdsapi.h DS_REPL_CURSORS_2 +struct ntdsapi.h DS_REPL_CURSORS_3 =ds_repl_cursors_3w +struct ntdsapi.h DS_REPL_KCC_DSA_FAILURE =ds_repl_kcc_dsa_failurew +struct ntdsapi.h DS_REPL_KCC_DSA_FAILURES =ds_repl_kcc_dsa_failuresw +struct ntdsapi.h DS_REPL_KCC_DSA_FAILUREW_BLOB +struct ntdsapi.h DS_REPL_NEIGHBOR =ds_repl_neighborw +struct ntdsapi.h DS_REPL_NEIGHBORS =ds_repl_neighborsw +struct ntdsapi.h DS_REPL_NEIGHBORW_BLOB +struct ntdsapi.h DS_REPL_OBJ_META_DATA +struct ntdsapi.h DS_REPL_OBJ_META_DATA_2 +struct ntdsapi.h DS_REPL_OP =ds_repl_opw +struct ntdsapi.h DS_REPL_OPW_BLOB +struct ntdsapi.h DS_REPL_PENDING_OPS =ds_repl_pending_opsw +struct ntdsapi.h DS_REPL_QUEUE_STATISTICSW +struct ntdsapi.h DS_REPL_VALUE_META_DATA +struct ntdsapi.h DS_REPL_VALUE_META_DATA_2 +struct ntdsapi.h DS_REPL_VALUE_META_DATA_BLOB +struct ntdsapi.h DS_REPL_VALUE_META_DATA_BLOB_EXT +struct ntdsapi.h DS_REPL_VALUE_META_DATA_EXT +struct ntdsapi.h DS_REPSYNCALL_ERRINFO =ds_repsyncall_errinfoa +struct ntdsapi.h DS_REPSYNCALL_SYNC =ds_repsyncall_synca +struct ntdsapi.h DS_REPSYNCALL_UPDATE =ds_repsyncall_updatea +struct ntdsapi.h DS_SCHEMA_GUID_MAP =ds_schema_guid_mapa +struct ntdsapi.h DS_SITE_COST_INFO +struct schedule.h SCHEDULE +struct schedule.h SCHEDULE_HEADER + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > Directory Service Structures +struct dsgetdc.h DOMAIN_CONTROLLER_INFO =domain_controller_infoa +struct dsgetdc.h DS_DOMAIN_TRUSTS =ds_domain_trustsa +struct dsrole.h DSROLE_OPERATION_STATE_INFO +struct dsrole.h DSROLE_PRIMARY_DOMAIN_INFO_BASIC +struct dsrole.h DSROLE_UPGRADE_STATUS_INFO + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Structures in Active Directory Domain Services > Object Picker Dialog Box Structures +struct objsel.h DS_SELECTION +struct objsel.h DS_SELECTION_LIST +struct objsel.h DSOP_FILTER_FLAGS +struct objsel.h DSOP_INIT_INFO +struct objsel.h DSOP_SCOPE_INIT_INFO +struct objsel.h DSOP_UPLEVEL_FILTER_FLAGS + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Enumerations in Active Directory Domain Services +enum dsparse.h DS_MANGLE_FOR +enum dsrole.h DSROLE_MACHINE_ROLE +enum dsrole.h DSROLE_OPERATION_STATE +enum dsrole.h DSROLE_PRIMARY_DOMAIN_INFO_LEVEL +enum dsrole.h DSROLE_SERVER_STATE +enum ntdsapi.h DS_KCC_TASKID +enum ntdsapi.h DS_NAME_ERROR +enum ntdsapi.h DS_NAME_FLAGS +enum ntdsapi.h DS_NAME_FORMAT +enum ntdsapi.h DS_REPL_INFO_TYPE +enum ntdsapi.h DS_REPL_OP_TYPE +enum ntdsapi.h DS_REPSYNCALL_ERROR +enum ntdsapi.h DS_REPSYNCALL_EVENT +enum ntdsapi.h DS_SPN_NAME_TYPE +enum ntdsapi.h DS_SPN_WRITE_OP + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Functions in Active Directory Domain Services > Display Functions in Active Directory Domain Services +callback cmnquery.h CQAddFormsProc =lpcqaddformsproc +callback cmnquery.h CQAddPagesProc =lpcqaddpagesproc +callback cmnquery.h CQPageProc =lpcqpageproc +callback dsclient.h DSEnumAttributesCallback =lpdsenumattributes +callback shlobj_core.h BFFCallBack +function dsclient.h DsBrowseForContainer =dsbrowseforcontainera +function dsclient.h DsGetFriendlyClassName +function dsclient.h DsGetIcon + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Functions in Active Directory Domain Services > MMC Property Page Functions in Active Directory Domain Services +function adsprop.h ADsPropCheckIfWritable +function adsprop.h ADsPropCreateNotifyObj +function adsprop.h ADsPropGetInitInfo +function adsprop.h ADsPropSendErrorMessage +function adsprop.h ADsPropSetHwnd +function adsprop.h ADsPropSetHwndWithTitle +function adsprop.h ADsPropShowErrorDialog + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Functions in Active Directory Domain Services > Domain Controller and Replication Management Functions +function dsparse.h DsCrackSpn =dscrackspna +function dsparse.h DsCrackUnquotedMangledRdn =dscrackunquotedmangledrdna +function dsparse.h DsGetRdnW +function dsparse.h DsIsMangledDn =dsismangleddna +function dsparse.h DsIsMangledRdnValue =dsismangledrdnvaluea +function dsparse.h DsMakeSpn =dsmakespna +function dsparse.h DsQuoteRdnValue =dsquoterdnvaluea +function dsparse.h DsUnquoteRdnValue =dsunquoterdnvaluea +function ntdsapi.h DsAddSidHistory =dsaddsidhistorya +function ntdsapi.h DsBind =dsbinda +function ntdsapi.h DsBindingSetTimeout +function ntdsapi.h DsBindToISTG =dsbindtoistga +function ntdsapi.h DsBindWithCred =dsbindwithcreda +function ntdsapi.h DsBindWithSpn =dsbindwithspna +function ntdsapi.h DsBindWithSpnEx =dsbindwithspnexa +function ntdsapi.h DsClientMakeSpnForTargetServer =dsclientmakespnfortargetservera +function ntdsapi.h DsCrackNames =dscracknamesa +function ntdsapi.h DsFreeDomainControllerInfo =dsfreedomaincontrollerinfoa +function ntdsapi.h DsFreeNameResult =dsfreenameresulta +function ntdsapi.h DsFreePasswordCredentials +function ntdsapi.h DsFreeSchemaGuidMap =dsfreeschemaguidmapa +function ntdsapi.h DsFreeSpnArray =dsfreespnarraya +function ntdsapi.h DsGetDomainControllerInfo =dsgetdomaincontrollerinfoa +function ntdsapi.h DsGetSpn =dsgetspna +function ntdsapi.h DsInheritSecurityIdentity =dsinheritsecurityidentitya +function ntdsapi.h DsListDomainsInSite =dslistdomainsinsitea +function ntdsapi.h DsListInfoForServer =dslistinfoforservera +function ntdsapi.h DsListRoles =dslistrolesa +function ntdsapi.h DsListServersForDomainInSite =dslistserversfordomaininsitea +function ntdsapi.h DsListServersInSite =dslistserversinsitea +function ntdsapi.h DsListSites =dslistsitesa +function ntdsapi.h DsMakePasswordCredentials =dsmakepasswordcredentialsa +function ntdsapi.h DsMapSchemaGuids =dsmapschemaguidsa +function ntdsapi.h DsQuerySitesByCost =dsquerysitesbycosta +function ntdsapi.h DsQuerySitesFree +function ntdsapi.h DsRemoveDsDomain =dsremovedsdomaina +function ntdsapi.h DsRemoveDsServer =dsremovedsservera +function ntdsapi.h DsReplicaAdd =dsreplicaadda +function ntdsapi.h DsReplicaConsistencyCheck +function ntdsapi.h DsReplicaDel =dsreplicadela +function ntdsapi.h DsReplicaFreeInfo +function ntdsapi.h DsReplicaGetInfo =dsreplicagetinfow +function ntdsapi.h DsReplicaGetInfo2 =dsreplicagetinfo2w +function ntdsapi.h DsReplicaModify =dsreplicamodifya +function ntdsapi.h DsReplicaSync =dsreplicasynca +function ntdsapi.h DsReplicaSyncAll =dsreplicasyncalla +function ntdsapi.h DsReplicaUpdateRefs =dsreplicaupdaterefsa +function ntdsapi.h DsReplicaVerifyObjects =dsreplicaverifyobjectsa +function ntdsapi.h DsServerRegisterSpn =dsserverregisterspna +function ntdsapi.h DsUnBind =dsunbinda +function ntdsapi.h DsWriteAccountSpn =dswriteaccountspna + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Functions in Active Directory Domain Services > Directory Service Functions +function dsgetdc.h DsAddressToSiteNames =dsaddresstositenamesa +function dsgetdc.h DsAddressToSiteNamesEx =dsaddresstositenamesexa +function dsgetdc.h DsDeregisterDnsHostRecords =dsderegisterdnshostrecordsa +function dsgetdc.h DsEnumerateDomainTrusts =dsenumeratedomaintrustsa +function dsgetdc.h DsGetDcClose =dsgetdcclosew +function dsgetdc.h DsGetDcName =dsgetdcnamea +function dsgetdc.h DsGetDcNext =dsgetdcnexta +function dsgetdc.h DsGetDcOpen =dsgetdcopena +function dsgetdc.h DsGetDcSiteCoverage =dsgetdcsitecoveragea +function dsgetdc.h DsGetForestTrustInformationW +function dsgetdc.h DsGetSiteName =dsgetsitenamea +function dsgetdc.h DsMergeForestTrustInformationW +function dsgetdc.h DsValidateSubnetName =dsvalidatesubnetnamea +function dsrole.h DsRoleFreeMemory +function dsrole.h DsRoleGetPrimaryDomainInformation + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Interfaces for Active Directory Domain Services > Admin Interfaces in Active Directory Domain Services +interface dsadmin.h IDsAdminCreateObj +interface dsadmin.h IDsAdminNewObj +interface dsadmin.h IDsAdminNewObjExt +interface dsadmin.h IDsAdminNewObjPrimarySite +interface dsadmin.h IDsAdminNotifyHandler + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Interfaces for Active Directory Domain Services > Display Interfaces in Active Directory Domain Services +interface cmnquery.h ICommonQuery +interface cmnquery.h IPersistQuery +interface cmnquery.h IQueryForm +interface dsclient.h IDsBrowseDomainTree +interface dsclient.h IDsDisplaySpecifier + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directories > Active Directory Domain Services > Active Directory Domain Services Reference > Interfaces for Active Directory Domain Services > Object Picker Dialog Box Interfaces +interface objsel.h IDsObjectPicker +interface objsel.h IDsObjectPickerCredentials + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Structures +struct iads.h ADS_ATTR_DEF +struct iads.h ADS_ATTR_INFO +struct iads.h ADS_BACKLINK +struct iads.h ADS_CASEIGNORE_LIST +struct iads.h ADS_CLASS_DEF +struct iads.h ADS_DN_WITH_BINARY +struct iads.h ADS_DN_WITH_STRING +struct iads.h ADS_EMAIL +struct iads.h ADS_FAXNUMBER +struct iads.h ADS_HOLD +struct iads.h ADS_NETADDRESS +struct iads.h ADS_NT_SECURITY_DESCRIPTOR +struct iads.h ADS_OBJECT_INFO +struct iads.h ADS_OCTET_LIST +struct iads.h ADS_OCTET_STRING +struct iads.h ADS_PATH +struct iads.h ADS_POSTALADDRESS +struct iads.h ADS_PROV_SPECIFIC +struct iads.h ADS_REPLICAPOINTER +struct iads.h ADS_SEARCH_COLUMN +struct iads.h ADS_SEARCHPREF_INFO +struct iads.h ADS_SORTKEY +struct iads.h ADS_TIMESTAMP +struct iads.h ADS_TYPEDNAME +struct iads.h ADS_VLV +struct iads.h ADSVALUE + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Enumerations +enum iads.h ADS_ACEFLAG_ENUM +enum iads.h ADS_ACETYPE_ENUM +enum iads.h ADS_AUTHENTICATION_ENUM +enum iads.h ADS_CHASE_REFERRALS_ENUM +enum iads.h ADS_DEREFENUM +enum iads.h ADS_DISPLAY_ENUM +enum iads.h ADS_ESCAPE_MODE_ENUM +enum iads.h ADS_FLAGTYPE_ENUM +enum iads.h ADS_FORMAT_ENUM +enum iads.h ADS_GROUP_TYPE_ENUM +enum iads.h ADS_NAME_INITTYPE_ENUM +enum iads.h ADS_NAME_TYPE_ENUM +enum iads.h ADS_OPTION_ENUM +enum iads.h ADS_PASSWORD_ENCODING_ENUM +enum iads.h ADS_PATHTYPE_ENUM +enum iads.h ADS_PREFERENCES_ENUM +enum iads.h ADS_PROPERTY_OPERATION_ENUM +enum iads.h ADS_RIGHTS_ENUM +enum iads.h ADS_SCOPEENUM +enum iads.h ADS_SD_CONTROL_ENUM +enum iads.h ADS_SD_FORMAT_ENUM +enum iads.h ADS_SD_REVISION_ENUM +enum iads.h ADS_SEARCHPREF_ENUM +enum iads.h ADS_SECURITY_INFO_ENUM +enum iads.h ADS_SETTYPE_ENUM +enum iads.h ADS_STATUSENUM +enum iads.h ADS_SYSTEMFLAG_ENUM +enum iads.h ADS_USER_FLAG_ENUM +enum iads.h ADSI_DIALECT_ENUM +enum iads.h ADSTYPEENUM + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Functions +function adshlp.h ADsBuildEnumerator +function adshlp.h ADsBuildVarArrayInt +function adshlp.h ADsBuildVarArrayStr +function adshlp.h ADsEncodeBinaryData +function adshlp.h ADsEnumerateNext +function adshlp.h ADsFreeEnumerator +function adshlp.h ADsGetLastError +function adshlp.h ADsGetObject +function adshlp.h ADsOpenObject +function adshlp.h ADsSetLastError +function adshlp.h AllocADsMem +function adshlp.h AllocADsStr +function adshlp.h BinarySDToSecurityDescriptor +function adshlp.h FreeADsMem +function adshlp.h FreeADsStr +function adshlp.h ReallocADsMem +function adshlp.h ReallocADsStr +function adshlp.h SecurityDescriptorToBinarySD + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Core Interfaces +interface iads.h IADs +interface iads.h IADsContainer +interface iads.h IADsNamespaces +interface iads.h IADsOpenDSObject + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Schema Interfaces +interface iads.h IADsClass +interface iads.h IADsProperty +interface iads.h IADsSyntax + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Property Cache Interfaces +interface iads.h IADsPropertyEntry +interface iads.h IADsPropertyList +interface iads.h IADsPropertyValue +interface iads.h IADsPropertyValue2 + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Persistent Object Interfaces +interface iads.h IADsCollection +interface iads.h IADsComputer +interface iads.h IADsDomain +interface iads.h IADsFileService +interface iads.h IADsFileShare +interface iads.h IADsGroup +interface iads.h IADsLocality +interface iads.h IADsMembers +interface iads.h IADsO +interface iads.h IADsOU +interface iads.h IADsPrintJob +interface iads.h IADsPrintQueue +interface iads.h IADsService +interface iads.h IADsUser + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Dynamic Object Interfaces +interface iads.h IADsComputerOperations +interface iads.h IADsFileServiceOperations +interface iads.h IADsPrintJobOperations +interface iads.h IADsPrintQueueOperations +interface iads.h IADsResource +interface iads.h IADsServiceOperations +interface iads.h IADsSession + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Security Interfaces +interface iads.h IADsAccessControlEntry +interface iads.h IADsAccessControlList +interface iads.h IADsSecurityDescriptor +interface iads.h IADsSecurityUtility + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Non-Automation Interfaces +interface iads.h IDirectoryObject +interface iads.h IDirectorySchemaMgmt +interface iads.h IDirectorySearch + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Extension Interfaces +interface iads.h IADsExtension + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Utility Interfaces +interface iads.h IADsADSystemInfo +interface iads.h IADsDeleteOps +interface iads.h IADsNameTranslate +interface iads.h IADsObjectOptions +interface iads.h IADsPathname +interface iads.h IADsWinNTSystemInfo + +# Desktop App Technologies > Security and Identity > Directory, Identity, and Access Services > Directory Services > Directory Access Technologies > Active Directory Service Interfaces > Active Directory Service Interfaces Reference > ADSI Interfaces > Data Type Interfaces +interface iads.h IADsAcl +interface iads.h IADsBackLink +interface iads.h IADsCaseIgnoreList +interface iads.h IADsDNWithBinary +interface iads.h IADsDNWithString +interface iads.h IADsEmail +interface iads.h IADsFaxNumber +interface iads.h IADsHold +interface iads.h IADsLargeInteger +interface iads.h IADsNetAddress +interface iads.h IADsOctetList +interface iads.h IADsPath +interface iads.h IADsPostalAddress +interface iads.h IADsReplicaPointer +interface iads.h IADsTimestamp +interface iads.h IADsTypedName + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol > Extensible Authentication Protocol Reference > EAP Enumerations +enum raseapif.h PPP_EAP_ACTION +enum raseapif.h RAS_AUTH_ATTRIBUTE_TYPE + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol > Extensible Authentication Protocol Reference > EAP Functions +function raseapif.h RasEapFreeMemory +function raseapif.h RasEapGetIdentity +function raseapif.h RasEapGetInfo +function raseapif.h RasEapInvokeConfigUI +function raseapif.h RasEapInvokeInteractiveUI + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol > Extensible Authentication Protocol Reference > EAP Interfaces +interface rrascfg.h IEAPProviderConfig + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol > Extensible Authentication Protocol Reference > EAP Structures +struct raseapif.h PPP_EAP_INFO +struct raseapif.h PPP_EAP_INPUT +struct raseapif.h PPP_EAP_OUTPUT +struct raseapif.h PPP_EAP_PACKET +struct raseapif.h RAS_AUTH_ATTRIBUTE + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > Common EAPHost API Enumerations +enum eapmethodtypes.h EapCode +enum eaptypes.h EAP_ATTRIBUTE_TYPE +enum eaptypes.h EAP_CONFIG_INPUT_FIELD_TYPE + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > Common EAPHost API Structures +struct eapmethodtypes.h EapPacket +struct eaptypes.h EAP_ATTRIBUTE +struct eaptypes.h EAP_ATTRIBUTES +struct eaptypes.h EAP_CONFIG_INPUT_FIELD_ARRAY +struct eaptypes.h EAP_CONFIG_INPUT_FIELD_DATA +struct eaptypes.h EAP_ERROR +struct eaptypes.h EAP_METHOD_INFO +struct eaptypes.h EAP_METHOD_INFO_ARRAY +struct eaptypes.h EAP_METHOD_INFO_ARRAY_EX +struct eaptypes.h EAP_METHOD_INFO_EX +struct eaptypes.h EAP_METHOD_TYPE +struct eaptypes.h EAP_TYPE + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Supplicant API Reference > EAPHost Supplicant Configuration Functions +function eaphostpeerconfigapis.h EapHostPeerConfigBlob2Xml +function eaphostpeerconfigapis.h EapHostPeerConfigXml2Blob +function eaphostpeerconfigapis.h EapHostPeerCredentialsXml2Blob +function eaphostpeerconfigapis.h EapHostPeerFreeErrorMemory +function eaphostpeerconfigapis.h EapHostPeerFreeMemory +function eaphostpeerconfigapis.h EapHostPeerGetMethodProperties +function eaphostpeerconfigapis.h EapHostPeerGetMethods +function eaphostpeerconfigapis.h EapHostPeerInvokeConfigUI +function eaphostpeerconfigapis.h EapHostPeerInvokeIdentityUI +function eaphostpeerconfigapis.h EapHostPeerInvokeInteractiveUI +function eaphostpeerconfigapis.h EapHostPeerQueryCredentialInputFields +function eaphostpeerconfigapis.h EapHostPeerQueryInteractiveUIInputFields +function eaphostpeerconfigapis.h EapHostPeerQueryUIBlobFromInteractiveUIInputFields +function eaphostpeerconfigapis.h EapHostPeerQueryUserBlobFromCredentialInputFields + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Supplicant API Reference > EAPHost Supplicant Run-time Functions +function eappapis.h EapHostPeerBeginSession +function eappapis.h EapHostPeerClearConnection +function eappapis.h EapHostPeerEndSession +function eappapis.h EapHostPeerFreeEapError +function eappapis.h EapHostPeerFreeRuntimeMemory +function eappapis.h EapHostPeerGetAuthStatus +function eappapis.h EapHostPeerGetDataToUnplumbCredentials +function eappapis.h EapHostPeerGetIdentity +function eappapis.h EapHostPeerGetResponseAttributes +function eappapis.h EapHostPeerGetResult +function eappapis.h EapHostPeerGetSendPacket +function eappapis.h EapHostPeerGetUIContext +function eappapis.h EapHostPeerInitialize +function eappapis.h EapHostPeerProcessReceivedPacket +function eappapis.h EapHostPeerSetResponseAttributes +function eappapis.h EapHostPeerSetUIContext +function eappapis.h EapHostPeerUninitialize + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Supplicant API Reference > EAPHost Supplicant Callbacks +callback eappapis.h NotificationHandler + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Supplicant API Reference > EAPHost Supplicant Enumerations +enum eaphostpeertypes.h EAPHOST_AUTH_STATUS +enum eaphostpeertypes.h EapHostPeerAuthParams +enum eaphostpeertypes.h EapHostPeerMethodResultReason +enum eaphostpeertypes.h EapHostPeerResponseAction +enum eaphostpeertypes.h ISOLATION_STATE +enum eaptypes.h EAP_INTERACTIVE_UI_DATA_TYPE +enum eaptypes.h EAP_METHOD_PROPERTY_TYPE +enum eaptypes.h EAP_METHOD_PROPERTY_VALUE_TYPE + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Supplicant API Reference > EAPHost Supplicant Structures +struct eaphostpeertypes.h EAPHOST_AUTH_INFO +struct eaphostpeertypes.h EapHostPeerMethodResult +struct eaptypes.h EAP_CRED_EXPIRY_REQ +struct eaptypes.h EAP_INTERACTIVE_UI_DATA +struct eaptypes.h EAP_METHOD_PROPERTY +struct eaptypes.h EAP_METHOD_PROPERTY_ARRAY +struct eaptypes.h EAP_METHOD_PROPERTY_VALUE +struct eaptypes.h EAP_METHOD_PROPERTY_VALUE_BOOL +struct eaptypes.h EAP_METHOD_PROPERTY_VALUE_DWORD +struct eaptypes.h EAP_METHOD_PROPERTY_VALUE_STRING +struct eaptypes.h EAP_UI_DATA_FORMAT + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Peer Method API Reference > EAPHost Peer Method Configuration Functions +function eapmethodpeerapis.h EapPeerConfigBlob2Xml +function eapmethodpeerapis.h EapPeerConfigXml2Blob +function eapmethodpeerapis.h EapPeerCredentialsXml2Blob +function eapmethodpeerapis.h EapPeerFreeErrorMemory +function eapmethodpeerapis.h EapPeerFreeMemory +function eapmethodpeerapis.h EapPeerGetMethodProperties +function eapmethodpeerapis.h EapPeerInvokeConfigUI +function eapmethodpeerapis.h EapPeerInvokeIdentityUI +function eapmethodpeerapis.h EapPeerInvokeInteractiveUI +function eapmethodpeerapis.h EapPeerQueryCredentialInputFields +function eapmethodpeerapis.h EapPeerQueryInteractiveUIInputFields +function eapmethodpeerapis.h EapPeerQueryUIBlobFromInteractiveUIInputFields +function eapmethodpeerapis.h EapPeerQueryUserBlobFromCredentialInputFields + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Peer Method API Reference > EAPHost Peer Method Run-Time Functions +function eapmethodpeerapis.h EapPeerBeginSession +function eapmethodpeerapis.h EapPeerEndSession +function eapmethodpeerapis.h EapPeerGetConfigBlobAndUserBlob +function eapmethodpeerapis.h EapPeerGetIdentity +function eapmethodpeerapis.h EapPeerGetInfo +function eapmethodpeerapis.h EapPeerGetResponseAttributes +function eapmethodpeerapis.h EapPeerGetResponsePacket +function eapmethodpeerapis.h EapPeerGetResult +function eapmethodpeerapis.h EapPeerGetUIContext +function eapmethodpeerapis.h EapPeerInitialize +function eapmethodpeerapis.h EapPeerProcessRequestPacket +function eapmethodpeerapis.h EapPeerSetCredentials +function eapmethodpeerapis.h EapPeerSetResponseAttributes +function eapmethodpeerapis.h EapPeerSetUIContext +function eapmethodpeerapis.h EapPeerShutdown + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Peer Method API Reference > EAPHost Peer Method Enumerations +enum eapauthenticatoractiondefine.h EapPeerMethodResponseAction +enum eapauthenticatoractiondefine.h EapPeerMethodResultReason +enum eaptypes.h EapCredentialType + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Peer Method API Reference > EAPHost Peer Method Structures +struct eapauthenticatoractiondefine.h EapPeerMethodOutput +struct eapmethodpeerapis.h EAP_PEER_METHOD_ROUTINES +struct eapmethodpeerapis.h EapPeerMethodResult +struct eaptypes.h EapCertificateCredential +struct eaptypes.h EapCredential +struct eaptypes.h EapSimCredential +struct eaptypes.h EapUsernamePasswordCredential + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Authenticator Method APIs > EAPHost Authenticator Method Functions +function eapmethodauthenticatorapis.h EapMethodAuthenticatorBeginSession +function eapmethodauthenticatorapis.h EapMethodAuthenticatorEndSession +function eapmethodauthenticatorapis.h EapMethodAuthenticatorFreeErrorMemory +function eapmethodauthenticatorapis.h EapMethodAuthenticatorFreeMemory +function eapmethodauthenticatorapis.h EapMethodAuthenticatorGetAttributes +function eapmethodauthenticatorapis.h EapMethodAuthenticatorGetInfo +function eapmethodauthenticatorapis.h EapMethodAuthenticatorGetResult +function eapmethodauthenticatorapis.h EapMethodAuthenticatorInitialize +function eapmethodauthenticatorapis.h EapMethodAuthenticatorInvokeConfigUI +function eapmethodauthenticatorapis.h EapMethodAuthenticatorReceivePacket +function eapmethodauthenticatorapis.h EapMethodAuthenticatorSendPacket +function eapmethodauthenticatorapis.h EapMethodAuthenticatorSetAttributes +function eapmethodauthenticatorapis.h EapMethodAuthenticatorShutdown +function eapmethodauthenticatorapis.h EapMethodAuthenticatorUpdateInnerMethodParams + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Authenticator Method APIs > EAPHost Authenticator Method Enumerations +enum eapauthenticatoractiondefine.h EAP_METHOD_AUTHENTICATOR_RESPONSE_ACTION +enum eapauthenticatortypes.h EAP_AUTHENTICATOR_SEND_TIMEOUT + +# Desktop App Technologies > Security and Identity > Extensible Authentication Protocol Host > EAPHost API Reference > EAPHost Authenticator Method APIs > EAPHost Authenticator Method Structures +struct eapauthenticatoractiondefine.h EAP_METHOD_AUTHENTICATOR_RESULT +struct eapmethodauthenticatorapis.h EAP_AUTHENTICATOR_METHOD_ROUTINES + +# Desktop App Technologies > Security and Identity > License Protection +enum licenseprotection.h LicenseProtectionStatus +function licenseprotection.h RegisterLicenseKeyWithExpiration +function licenseprotection.h ValidateLicenseKeyProtection + +# Desktop App Technologies > Security and Identity > Network Access Protection > NAP Reference > NAP Enumerated Types +enum naptypes.h ExtendedIsolationState +enum naptypes.h FailureCategory +enum naptypes.h FixupState +enum naptypes.h IsolationState +enum naptypes.h NapNotifyType +enum naptypes.h RemoteConfigurationType + +# Desktop App Technologies > Security and Identity > Network Access Protection > NAP Reference > NAP Structures +struct naptypes.h CorrelationId +struct naptypes.h CountedString +struct naptypes.h FailureCategoryMapping +struct naptypes.h FixupInfo +struct naptypes.h Ipv4Address +struct naptypes.h Ipv6Address +struct naptypes.h IsolationInfo +struct naptypes.h IsolationInfoEx +struct naptypes.h NapComponentRegistrationInfo +struct naptypes.h NetworkSoH +struct naptypes.h PrivateData +struct naptypes.h ResultCodes +struct naptypes.h SoH +struct naptypes.h SoHAttribute +struct naptypes.h SystemHealthAgentState + +# Desktop App Technologies > Security and Identity > Network Policy Server > Network Policy Server Extensions > NPS Extensions Reference > NPS Extensions Enumerations +enum authif.h RADIUS_ACTION +enum authif.h RADIUS_ATTRIBUTE_TYPE +enum authif.h RADIUS_AUTHENTICATION_PROVIDER +enum authif.h RADIUS_CODE +enum authif.h RADIUS_DATA_TYPE +enum authif.h RADIUS_EXTENSION_POINT +enum authif.h RADIUS_REJECT_REASON_CODE + +# Desktop App Technologies > Security and Identity > Network Policy Server > Network Policy Server Extensions > NPS Extensions Reference > NPS Extensions Functions > Application Defined +callback authif.h PRADIUS_EXTENSION_FREE_ATTRIBUTES +callback authif.h PRADIUS_EXTENSION_INIT +callback authif.h PRADIUS_EXTENSION_PROCESS +callback authif.h PRADIUS_EXTENSION_PROCESS_2 +callback authif.h PRADIUS_EXTENSION_PROCESS_EX +callback authif.h PRADIUS_EXTENSION_TERM + +# Desktop App Technologies > Security and Identity > Network Policy Server > Network Policy Server Extensions > NPS Extensions Reference > NPS Extensions Structures +struct authif.h RADIUS_ATTRIBUTE +struct authif.h RADIUS_ATTRIBUTE_ARRAY +struct authif.h RADIUS_EXTENSION_CONTROL_BLOCK +struct authif.h RADIUS_VSA_FORMAT + +# Desktop App Technologies > Security and Identity > Network Policy Server > Server Data Objects > Server Data Objects Reference > Server Data Objects Enumerations > Alphabetical Listing +enum sdoias.h ACCOUNTINGPROPERTIES +enum sdoias.h ATTRIBUTEFILTER +enum sdoias.h ATTRIBUTEID +enum sdoias.h ATTRIBUTEINFO +enum sdoias.h ATTRIBUTEPROPERTIES +enum sdoias.h ATTRIBUTERESTRICTIONS +enum sdoias.h ATTRIBUTESYNTAX +enum sdoias.h AUTHENTICATION_TYPE +enum sdoias.h CLIENTPROPERTIES +enum sdoias.h CONDITIONPROPERTIES +enum sdoias.h DICTIONARYPROPERTIES +enum sdoias.h IASCOMMONPROPERTIES +enum sdoias.h IASCOMPONENTPROPERTIES +enum sdoias.h IASDATASTORE +enum sdoias.h IASDOMAINTYPE +enum sdoias.h IASOSTYPE +enum sdoias.h IASPROPERTIES +enum sdoias.h IDENTITY_TYPE +enum sdoias.h NAMESPROPERTIES +enum sdoias.h NAPPROPERTIES +enum sdoias.h NEW_LOG_FILE_FREQUENCY +enum sdoias.h NTEVENTLOGPROPERTIES +enum sdoias.h NTSAMPROPERTIES +enum sdoias.h POLICYPROPERTIES +enum sdoias.h PROFILEPROPERTIES +enum sdoias.h PROTOCOLPROPERTIES +enum sdoias.h RADIUSPROPERTIES +enum sdoias.h RADIUSPROXYPROPERTIES +enum sdoias.h RADIUSSERVERGROUPPROPERTIES +enum sdoias.h RADIUSSERVERPROPERTIES +enum sdoias.h REMEDIATIONSERVERGROUPPROPERTIES +enum sdoias.h REMEDIATIONSERVERPROPERTIES +enum sdoias.h REMEDIATIONSERVERSPROPERTIES +enum sdoias.h SERVICE_TYPE +enum sdoias.h SHV_COMBINATION_TYPE +enum sdoias.h SHVTEMPLATEPROPERTIES +enum sdoias.h USERPROPERTIES +enum sdoias.h VENDORPROPERTIES + +# Desktop App Technologies > Security and Identity > Network Policy Server > Server Data Objects > Server Data Objects Reference > Server Data Objects Interfaces +interface sdoias.h ISdo +interface sdoias.h ISdoCollection +interface sdoias.h ISdoDictionaryOld +interface sdoias.h ISdoMachine +interface sdoias.h ISdoServiceControl + +# Desktop App Technologies > Security and Identity > Parental Controls > Parental Controls Reference > Parental Controls Logging > Parental Controls Logging Enumerations +enum wpcevent.h WPC_ARGS_CONVERSATIONINITEVENT +enum wpcevent.h WPC_ARGS_CONVERSATIONJOINEVENT +enum wpcevent.h WPC_ARGS_CONVERSATIONLEAVEEVENT +enum wpcevent.h WPC_ARGS_CUSTOMEVENT +enum wpcevent.h WPC_ARGS_EMAILCONTACTEVENT +enum wpcevent.h WPC_ARGS_EMAILRECEIVEDEVENT =wpc_args_emailreceievedevent +enum wpcevent.h WPC_ARGS_EMAILSENTEVENT +enum wpcevent.h WPC_ARGS_FILEDOWNLOADEVENT +enum wpcevent.h WPC_ARGS_GAMESTARTEVENT +enum wpcevent.h WPC_ARGS_IMCONTACTEVENT +enum wpcevent.h WPC_ARGS_IMFEATUREEVENT +enum wpcevent.h WPC_ARGS_MEDIADOWNLOADEVENT +enum wpcevent.h WPC_ARGS_MEDIAPLAYBACKEVENT +enum wpcevent.h WPC_ARGS_SAFERAPPBLOCKED +enum wpcevent.h WPC_ARGS_SETTINGSCHANGEEVENT +enum wpcevent.h WPC_ARGS_URLVISITEVENT +enum wpcevent.h WPC_MEDIA_EXPLICIT_TYPE =wpc_media_explicit +enum wpcevent.h WPC_MEDIA_TYPE +enum wpcevent.h WPCFLAG_IM_FEATURE +enum wpcevent.h WPCFLAG_IM_LEAVE_FLAG =wpcflag_im_leave +enum wpcevent.h WPCFLAG_ISBLOCKED +enum wpcevent.h WPCFLAG_LOGOFF_TYPE + +# Desktop App Technologies > Security and Identity > Parental Controls > Parental Controls Reference > Parental Controls Minimum Compliance API +interface wpcapi.h IWindowsParentalControls +interface wpcapi.h IWPCGamesSettings +interface wpcapi.h IWPCSettings +interface wpcapi.h IWPCWebSettings + +# Desktop App Technologies > Security and Identity > Parental Controls > Parental Controls Reference > Parental Controls WMI Provider API +interface wpcapi.h IWPCProviderConfig +interface wpcapi.h IWPCProviderState +interface wpcapi.h IWPCProviderSupport + +# Desktop App Technologies > Security and Identity > Security Management > Security Management Reference > Security Management Data Types +enum scesvc.h SCESVC_INFO_TYPE + +# Desktop App Technologies > Security and Identity > Security Management > Security Management Reference > Security Management Enumerations +enum lmaccess.h MSA_INFO_LEVEL +enum lmaccess.h MSA_INFO_STATE +enum ntsecapi.h POLICY_AUDIT_EVENT_TYPE +enum ntsecapi.h POLICY_INFORMATION_CLASS +enum ntsecapi.h POLICY_LSA_SERVER_ROLE +enum ntsecapi.h POLICY_NOTIFICATION_INFORMATION_CLASS +enum ntsecapi.h POLICY_SERVER_ENABLE_STATE +enum ntsecapi.h TRUSTED_INFORMATION_CLASS +enum winsafer.h SAFER_IDENTIFICATION_TYPES +enum winsafer.h SAFER_OBJECT_INFO_CLASS +enum winsafer.h SAFER_POLICY_INFO_CLASS + +# Desktop App Technologies > Security and Identity > Security Management > Security Management Reference > Security Management Functions +callback ntsecapi.h PSAM_INIT_NOTIFICATION_ROUTINE +callback ntsecapi.h PSAM_PASSWORD_FILTER_ROUTINE +callback ntsecapi.h PSAM_PASSWORD_NOTIFICATION_ROUTINE +callback scesvc.h PFSCE_FREE_INFO +callback scesvc.h PFSCE_LOG_INFO +callback scesvc.h PFSCE_QUERY_INFO +callback scesvc.h PFSCE_SET_INFO +function lmaccess.h NetAddServiceAccount +function lmaccess.h NetEnumerateServiceAccounts +function lmaccess.h NetIsServiceAccount +function lmaccess.h NetQueryServiceAccount +function lmaccess.h NetRemoveServiceAccount +function ntlsa.h LsaLookupPrivilegeValue +function ntsecapi.h LsaAddAccountRights +function ntsecapi.h LsaClose +function ntsecapi.h LsaCreateTrustedDomainEx +function ntsecapi.h LsaDeleteTrustedDomain +function ntsecapi.h LsaEnumerateAccountRights +function ntsecapi.h LsaEnumerateAccountsWithUserRight +function ntsecapi.h LsaEnumerateTrustedDomains +function ntsecapi.h LsaEnumerateTrustedDomainsEx +function ntsecapi.h LsaFreeMemory +function ntsecapi.h LsaLookupNames +function ntsecapi.h LsaLookupNames2 +function ntsecapi.h LsaLookupSids +function ntsecapi.h LsaLookupSids2 +function ntsecapi.h LsaNtStatusToWinError +function ntsecapi.h LsaOpenTrustedDomainByName +function ntsecapi.h LsaQueryTrustedDomainInfo +function ntsecapi.h LsaQueryTrustedDomainInfoByName +function ntsecapi.h LsaRegisterPolicyChangeNotification +function ntsecapi.h LsaRemoveAccountRights +function ntsecapi.h LsaRetrievePrivateData +function ntsecapi.h LsaSetTrustedDomainInfoByName +function ntsecapi.h LsaSetTrustedDomainInformation +function ntsecapi.h LsaStorePrivateData +function ntsecapi.h LsaUnregisterPolicyChangeNotification +function winsafer.h SaferCloseLevel +function winsafer.h SaferComputeTokenFromLevel +function winsafer.h SaferCreateLevel +function winsafer.h SaferGetLevelInformation +function winsafer.h SaferGetPolicyInformation +function winsafer.h SaferIdentifyLevel +function winsafer.h SaferiIsExecutableFileType +function winsafer.h SaferRecordEventLogEntry +function winsafer.h SaferSetLevelInformation +function winsafer.h SaferSetPolicyInformation + +# Desktop App Technologies > Security and Identity > Security Management > Security Management Reference > Security Management Interfaces +interface scesvc.h ISceSvcAttachmentData +interface scesvc.h ISceSvcAttachmentPersistInfo + +# Desktop App Technologies > Security and Identity > Security Management > Security Management Reference > Security Management Structures +struct lmaccess.h MSA_INFO_0 +struct lsalookup.h LSA_OBJECT_ATTRIBUTES +struct lsalookup.h LSA_REFERENCED_DOMAIN_LIST +struct lsalookup.h LSA_TRANSLATED_NAME +struct lsalookup.h LSA_TRANSLATED_SID2 +struct lsalookup.h LSA_TRUST_INFORMATION +struct lsalookup.h LSA_UNICODE_STRING +struct lsalookup.h POLICY_ACCOUNT_DOMAIN_INFO +struct lsalookup.h POLICY_DNS_DOMAIN_INFO +struct ntsecapi.h LSA_AUTH_INFORMATION +struct ntsecapi.h LSA_ENUMERATION_INFORMATION +struct ntsecapi.h LSA_TRANSLATED_SID +struct ntsecapi.h POLICY_AUDIT_EVENTS_INFO +struct ntsecapi.h POLICY_LSA_SERVER_ROLE_INFO +struct ntsecapi.h POLICY_MODIFICATION_INFO +struct ntsecapi.h POLICY_PRIMARY_DOMAIN_INFO +struct ntsecapi.h TRUSTED_DOMAIN_AUTH_INFORMATION +struct ntsecapi.h TRUSTED_DOMAIN_FULL_INFORMATION +struct ntsecapi.h TRUSTED_DOMAIN_INFORMATION_EX +struct ntsecapi.h TRUSTED_DOMAIN_NAME_INFO +struct ntsecapi.h TRUSTED_PASSWORD_INFO +struct ntsecapi.h TRUSTED_POSIX_OFFSET_INFO +struct scesvc.h SCESVC_ANALYSIS_INFO +struct scesvc.h SCESVC_ANALYSIS_LINE +struct scesvc.h SCESVC_CALLBACK_INFO +struct scesvc.h SCESVC_CONFIGURATION_INFO +struct scesvc.h SCESVC_CONFIGURATION_LINE +struct winsafer.h SAFER_CODE_PROPERTIES =safer_code_properties_v2 +struct winsafer.h SAFER_CODE_PROPERTIES_V1 +struct winsafer.h SAFER_HASH_IDENTIFICATION +struct winsafer.h SAFER_IDENTIFICATION_HEADER +struct winsafer.h SAFER_PATHNAME_IDENTIFICATION +struct winsafer.h SAFER_URLZONE_IDENTIFICATION + +# Desktop App Technologies > Security and Identity > TPM Base Services > TBS Reference > TBS Functions +function tbs.h Tbsi_Context_Create +function tbs.h Tbsi_Get_OwnerAuth +function tbs.h Tbsi_Get_TCG_Log +function tbs.h Tbsi_Get_TCG_Log_Ex +function tbs.h Tbsi_GetDeviceInfo +function tbs.h Tbsi_Physical_Presence_Command +function tbs.h Tbsi_Revoke_Attestation +function tbs.h Tbsip_Cancel_Commands +function tbs.h Tbsip_Context_Close +function tbs.h Tbsip_Submit_Command + +# Desktop App Technologies > Security and Identity > TPM Base Services > TBS Reference > TBS Structures +struct tbs.h TBS_CONTEXT_PARAMS +struct tbs.h TBS_CONTEXT_PARAMS2 +struct tbs.h TPM_DEVICE_INFO + +# Desktop App Technologies > Security and Identity > WebAuthN API > WebAuthN functions +function webauthn.h WebAuthNAuthenticatorGetAssertion +function webauthn.h WebAuthNAuthenticatorMakeCredential +function webauthn.h WebAuthNCancelCurrentOperation +function webauthn.h WebAuthNDeletePlatformCredential +function webauthn.h WebAuthNFreeAssertion +function webauthn.h WebAuthNFreeCredentialAttestation +function webauthn.h WebAuthNFreePlatformCredentialList +function webauthn.h WebAuthNGetApiVersionNumber +function webauthn.h WebAuthNGetCancellationId +function webauthn.h WebAuthNGetErrorName +function webauthn.h WebAuthNGetPlatformCredentialList +function webauthn.h WebAuthNGetW3CExceptionDomError +function webauthn.h WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable + +# Desktop App Technologies > Security and Identity > WebAuthN API > WebAuthN structures +struct webauthn.h WEBAUTHN_ASSERTION +struct webauthn.h WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS +struct webauthn.h WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS +struct webauthn.h WEBAUTHN_CLIENT_DATA +struct webauthn.h WEBAUTHN_COMMON_ATTESTATION +struct webauthn.h WEBAUTHN_COSE_CREDENTIAL_PARAMETER +struct webauthn.h WEBAUTHN_COSE_CREDENTIAL_PARAMETERS +struct webauthn.h WEBAUTHN_CRED_BLOB_EXTENSION +struct webauthn.h WEBAUTHN_CRED_PROTECT_EXTENSION_IN +struct webauthn.h WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT +struct webauthn.h WEBAUTHN_CREDENTIAL +struct webauthn.h WEBAUTHN_CREDENTIAL_ATTESTATION +struct webauthn.h WEBAUTHN_CREDENTIAL_DETAILS +struct webauthn.h WEBAUTHN_CREDENTIAL_DETAILS_LIST +struct webauthn.h WEBAUTHN_CREDENTIAL_EX +struct webauthn.h WEBAUTHN_CREDENTIAL_LIST +struct webauthn.h WEBAUTHN_CREDENTIALS +struct webauthn.h WEBAUTHN_EXTENSION +struct webauthn.h WEBAUTHN_EXTENSIONS +struct webauthn.h WEBAUTHN_GET_CREDENTIALS_OPTIONS +struct webauthn.h WEBAUTHN_HMAC_SECRET_SALT +struct webauthn.h WEBAUTHN_HMAC_SECRET_SALT_VALUES +struct webauthn.h WEBAUTHN_RP_ENTITY_INFORMATION +struct webauthn.h WEBAUTHN_USER_ENTITY_INFORMATION +struct webauthn.h WEBAUTHN_X5C + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Client Application Reference > Client Application Functions +function winbio.h WinBioAcquireFocus +function winbio.h WinBioAsyncEnumBiometricUnits +function winbio.h WinBioAsyncEnumDatabases +function winbio.h WinBioAsyncEnumServiceProviders +function winbio.h WinBioAsyncMonitorFrameworkChanges +function winbio.h WinBioAsyncOpenFramework +function winbio.h WinBioAsyncOpenSession +function winbio.h WinBioCancel +function winbio.h WinBioCaptureSample +function winbio.h WinBioCaptureSampleWithCallback +function winbio.h WinBioCloseFramework +function winbio.h WinBioCloseSession +function winbio.h WinBioControlUnit +function winbio.h WinBioControlUnitPrivileged +function winbio.h WinBioDeleteTemplate +function winbio.h WinBioEnrollBegin +function winbio.h WinBioEnrollCapture +function winbio.h WinBioEnrollCaptureWithCallback +function winbio.h WinBioEnrollCommit +function winbio.h WinBioEnrollDiscard +function winbio.h WinBioEnrollSelect +function winbio.h WinBioEnumBiometricUnits +function winbio.h WinBioEnumDatabases +function winbio.h WinBioEnumEnrollments +function winbio.h WinBioEnumServiceProviders +function winbio.h WinBioFree +function winbio.h WinBioGetCredentialState +function winbio.h WinBioGetDomainLogonSetting +function winbio.h WinBioGetEnabledSetting +function winbio.h WinBioGetEnrolledFactors +function winbio.h WinBioGetLogonSetting +function winbio.h WinBioGetProperty +function winbio.h WinBioIdentify +function winbio.h WinBioIdentifyWithCallback +function winbio.h WinBioLocateSensor +function winbio.h WinBioLocateSensorWithCallback +function winbio.h WinBioLockUnit +function winbio.h WinBioLogonIdentifiedUser +function winbio.h WinBioMonitorPresence +function winbio.h WinBioOpenSession +function winbio.h WinBioRegisterEventMonitor +function winbio.h WinBioReleaseFocus +function winbio.h WinBioRemoveAllCredentials +function winbio.h WinBioRemoveAllDomainCredentials +function winbio.h WinBioRemoveCredential +function winbio.h WinBioSetCredential +function winbio.h WinBioSetProperty +function winbio.h WinBioUnlockUnit +function winbio.h WinBioUnregisterEventMonitor +function winbio.h WinBioVerify +function winbio.h WinBioVerifyWithCallback +function winbio.h WinBioWait + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Client Application Reference > Client Application Callback Functions +callback winbio.h PWINBIO_ASYNC_COMPLETION_CALLBACK +callback winbio.h PWINBIO_CAPTURE_CALLBACK +callback winbio.h PWINBIO_ENROLL_CAPTURE_CALLBACK +callback winbio.h PWINBIO_EVENT_CALLBACK +callback winbio.h PWINBIO_IDENTIFY_CALLBACK +callback winbio.h PWINBIO_LOCATE_SENSOR_CALLBACK +callback winbio.h PWINBIO_VERIFY_CALLBACK + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Client Application Reference > Client Application Structures +struct winbio.h WINBIO_ASYNC_RESULT + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Client Application Reference > Client Application Enumerations +enum winbio.h WINBIO_ASYNC_NOTIFICATION_METHOD + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Plug-in Reference > Plug-in Functions > Engine Adapter Functions +callback winbio_adapter.h EngineAdapterAcceptSampleData =pibio_engine_accept_sample_data_fn +callback winbio_adapter.h EngineAdapterActivate =pibio_engine_activate_fn +callback winbio_adapter.h EngineAdapterAttach =pibio_engine_attach_fn +callback winbio_adapter.h EngineAdapterCheckForDuplicate =pibio_engine_check_for_duplicate_fn +callback winbio_adapter.h EngineAdapterClearContext =pibio_engine_clear_context_fn +callback winbio_adapter.h EngineAdapterCommitEnrollment =pibio_engine_commit_enrollment_fn +callback winbio_adapter.h EngineAdapterControlUnit =pibio_engine_control_unit_fn +callback winbio_adapter.h EngineAdapterControlUnitPrivileged =pibio_engine_control_unit_privileged_fn +callback winbio_adapter.h EngineAdapterCreateEnrollment =pibio_engine_create_enrollment_fn +callback winbio_adapter.h EngineAdapterCreateKey =pibio_engine_create_key_fn +callback winbio_adapter.h EngineAdapterDeactivate =pibio_engine_deactivate_fn +callback winbio_adapter.h EngineAdapterDetach =pibio_engine_detach_fn +callback winbio_adapter.h EngineAdapterDiscardEnrollment =pibio_engine_discard_enrollment_fn +callback winbio_adapter.h EngineAdapterExportEngineData =pibio_engine_export_engine_data_fn +callback winbio_adapter.h EngineAdapterGetEnrollmentHash =pibio_engine_get_enrollment_hash_fn +callback winbio_adapter.h EngineAdapterGetEnrollmentStatus =pibio_engine_get_enrollment_status_fn +callback winbio_adapter.h EngineAdapterIdentifyAll =pibio_engine_identify_all_fn +callback winbio_adapter.h EngineAdapterIdentifyFeatureSet =pibio_engine_identify_feature_set_fn +callback winbio_adapter.h EngineAdapterIdentifyFeatureSetSecure =pibio_engine_identify_feature_set_secure_fn +callback winbio_adapter.h EngineAdapterNotifyPowerChange =pibio_engine_notify_power_change_fn +callback winbio_adapter.h EngineAdapterPipelineCleanup =pibio_engine_pipeline_cleanup_fn +callback winbio_adapter.h EngineAdapterPipelineInit =pibio_engine_pipeline_init_fn +callback winbio_adapter.h EngineAdapterQueryCalibrationData =pibio_engine_query_calibration_data_fn +callback winbio_adapter.h EngineAdapterQueryExtendedEnrollmentStatus =pibio_engine_query_extended_enrollment_status_fn +callback winbio_adapter.h EngineAdapterQueryExtendedInfo =pibio_engine_query_extended_info_fn +callback winbio_adapter.h EngineAdapterQueryHashAlgorithms =pibio_engine_query_hash_algorithms_fn +callback winbio_adapter.h EngineAdapterQueryIndexVectorSize =pibio_engine_query_index_vector_size_fn +callback winbio_adapter.h EngineAdapterQueryPreferredFormat =pibio_engine_query_preferred_format_fn +callback winbio_adapter.h EngineAdapterQuerySampleHint =pibio_engine_query_sample_hint_fn +callback winbio_adapter.h EngineAdapterRefreshCache =pibio_engine_refresh_cache_fn +callback winbio_adapter.h EngineAdapterSelectCalibrationFormat =pibio_engine_select_calibration_format_fn +callback winbio_adapter.h EngineAdapterSetAccountPolicy =pibio_engine_set_account_policy_fn +callback winbio_adapter.h EngineAdapterSetEnrollmentParameters =pibio_engine_set_enrollment_parameters_fn +callback winbio_adapter.h EngineAdapterSetEnrollmentSelector =pibio_engine_set_enrollment_selector_fn +callback winbio_adapter.h EngineAdapterSetHashAlgorithm =pibio_engine_set_hash_algorithm_fn +callback winbio_adapter.h EngineAdapterUpdateEnrollment =pibio_engine_update_enrollment_fn +callback winbio_adapter.h EngineAdapterVerifyFeatureSet =pibio_engine_verify_feature_set_fn +function winbio_adapter.h WbioQueryEngineInterface + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Plug-in Reference > Plug-in Functions > Sensor Adapter Functions +callback winbio_adapter.h SensorAdapterAcceptCalibrationData =pibio_sensor_accept_calibration_data_fn +callback winbio_adapter.h SensorAdapterActivate =pibio_sensor_activate_fn +callback winbio_adapter.h SensorAdapterAttach =pibio_sensor_attach_fn +callback winbio_adapter.h SensorAdapterCancel =pibio_sensor_cancel_fn +callback winbio_adapter.h SensorAdapterClearContext =pibio_sensor_clear_context_fn +callback winbio_adapter.h SensorAdapterControlUnit =pibio_sensor_control_unit_fn +callback winbio_adapter.h SensorAdapterControlUnitPrivileged =pibio_sensor_control_unit_privileged_fn +callback winbio_adapter.h SensorAdapterDeactivate =pibio_sensor_deactivate_fn +callback winbio_adapter.h SensorAdapterDetach =pibio_sensor_detach_fn +callback winbio_adapter.h SensorAdapterExportSensorData =pibio_sensor_export_sensor_data_fn +callback winbio_adapter.h SensorAdapterFinishCapture =pibio_sensor_finish_capture_fn +callback winbio_adapter.h SensorAdapterGetIndicatorStatus =pibio_sensor_get_indicator_status_fn +callback winbio_adapter.h SensorAdapterNotifyPowerChange =pibio_sensor_notify_power_change_fn +callback winbio_adapter.h SensorAdapterPipelineCleanup =pibio_sensor_pipeline_cleanup_fn +callback winbio_adapter.h SensorAdapterPipelineInit =pibio_sensor_pipeline_init_fn +callback winbio_adapter.h SensorAdapterPushDataToEngine =pibio_sensor_push_data_to_engine_fn +callback winbio_adapter.h SensorAdapterQueryCalibrationFormats =pibio_sensor_query_calibration_formats_fn +callback winbio_adapter.h SensorAdapterQueryExtendedInfo =pibio_sensor_query_extended_info_fn +callback winbio_adapter.h SensorAdapterQueryStatus =pibio_sensor_query_status_fn +callback winbio_adapter.h SensorAdapterReset =pibio_sensor_reset_fn +callback winbio_adapter.h SensorAdapterSetCalibrationFormat =pibio_sensor_set_calibration_format_fn +callback winbio_adapter.h SensorAdapterSetIndicatorStatus =pibio_sensor_set_indicator_status_fn +callback winbio_adapter.h SensorAdapterSetMode =pibio_sensor_set_mode_fn +callback winbio_adapter.h SensorAdapterStartCapture =pibio_sensor_start_capture_fn +function winbio_adapter.h WbioQuerySensorInterface + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Plug-in Reference > Plug-in Functions > Storage Adapter Functions +callback winbio_adapter.h StorageAdapterActivate =pibio_storage_activate_fn +callback winbio_adapter.h StorageAdapterAddRecord =pibio_storage_add_record_fn +callback winbio_adapter.h StorageAdapterAttach =pibio_storage_attach_fn +callback winbio_adapter.h StorageAdapterClearContext =pibio_storage_clear_context_fn +callback winbio_adapter.h StorageAdapterCloseDatabase =pibio_storage_close_database_fn +callback winbio_adapter.h StorageAdapterControlUnit =pibio_storage_control_unit_fn +callback winbio_adapter.h StorageAdapterControlUnitPrivileged =pibio_storage_control_unit_privileged_fn +callback winbio_adapter.h StorageAdapterCreateDatabase =pibio_storage_create_database_fn +callback winbio_adapter.h StorageAdapterDeactivate =pibio_storage_deactivate_fn +callback winbio_adapter.h StorageAdapterDeleteRecord =pibio_storage_delete_record_fn +callback winbio_adapter.h StorageAdapterDetach =pibio_storage_detach_fn +callback winbio_adapter.h StorageAdapterEraseDatabase =pibio_storage_erase_database_fn +callback winbio_adapter.h StorageAdapterFirstRecord =pibio_storage_first_record_fn +callback winbio_adapter.h StorageAdapterGetCurrentRecord =pibio_storage_get_current_record_fn +callback winbio_adapter.h StorageAdapterGetDatabaseSize =pibio_storage_get_database_size_fn +callback winbio_adapter.h StorageAdapterGetDataFormat =pibio_storage_get_data_format_fn +callback winbio_adapter.h StorageAdapterGetRecordCount =pibio_storage_get_record_count_fn +callback winbio_adapter.h StorageAdapterNextRecord =pibio_storage_next_record_fn +callback winbio_adapter.h StorageAdapterNotifyPowerChange =pibio_storage_notify_power_change_fn +callback winbio_adapter.h StorageAdapterOpenDatabase =pibio_storage_open_database_fn +callback winbio_adapter.h StorageAdapterPipelineCleanup =pibio_storage_pipeline_cleanup_fn +callback winbio_adapter.h StorageAdapterPipelineInit =pibio_storage_pipeline_init_fn +callback winbio_adapter.h StorageAdapterQueryByContent =pibio_storage_query_by_content_fn +callback winbio_adapter.h StorageAdapterQueryBySubject =pibio_storage_query_by_subject_fn +callback winbio_adapter.h StorageAdapterQueryExtendedInfo =pibio_storage_query_extended_info_fn +function winbio_adapter.h WbioQueryStorageInterface + +# Desktop App Technologies > Security and Identity > Windows Biometric Framework > Windows Biometric Framework API Reference > Plug-in Reference > Plug-in Structures +struct winbio_adapter.h WINBIO_ADAPTER_INTERFACE_VERSION +struct winbio_adapter.h WINBIO_ENGINE_INTERFACE +struct winbio_adapter.h WINBIO_PIPELINE +struct winbio_adapter.h WINBIO_SENSOR_INTERFACE +struct winbio_adapter.h WINBIO_STORAGE_INTERFACE +struct winbio_adapter.h WINBIO_STORAGE_RECORD + +# Desktop App Technologies > System Services > Component Object Model (COM) > COM Fundamentals > Reference > Enumerations +enum callobj.h CALLFRAME_COPY +enum callobj.h CALLFRAME_FREE +enum callobj.h CALLFRAME_NULL +enum callobj.h CALLFRAME_WALK +enum combaseapi.h COWAIT_FLAGS +enum combaseapi.h CWMO_FLAGS +enum combaseapi.h REGCLS +enum eventsys.h EOC_ChangeType +enum objbase.h COINIT +enum objbase.h COMSD +enum objidl.h APTTYPE +enum objidl.h APTTYPEQUALIFIER +enum objidl.h BIND_FLAGS +enum objidl.h CALLTYPE +enum objidl.h CO_MARSHALING_CONTEXT_ATTRIBUTES +enum objidl.h EOLE_AUTHENTICATION_CAPABILITIES +enum objidl.h EXTCONN +enum objidl.h GLOBALOPT_PROPERTIES +enum objidl.h GLOBALOPT_UNMARSHALING_POLICY_VALUES +enum objidl.h MKRREDUCE +enum objidl.h MKSYS +enum objidl.h PENDINGMSG +enum objidl.h PENDINGTYPE +enum objidl.h SERVERCALL +enum objidl.h THDTYPE +enum oleidl.h BINDSPEED +enum txlogpub.h RECORD_READING_POLICY +enum wtypes.h TYSPEC +enum wtypesbase.h CLSCTX +enum wtypesbase.h MSHCTX +enum wtypesbase.h MSHLFLAGS + +# Desktop App Technologies > System Services > Component Object Model (COM) > COM Fundamentals > Reference > Interfaces +interface callobj.h ICallFrame +interface callobj.h ICallFrameEvents +interface callobj.h ICallFrameWalker +interface callobj.h ICallIndirect +interface callobj.h ICallInterceptor +interface callobj.h ICallUnmarshal +interface comcat.h ICatInformation +interface comcat.h ICatRegister +interface comcat.h IEnumCATEGORYINFO +interface comcat.h IEnumGUID +interface ctxtcall.h IContextCallback +interface docobj.h IContinueCallback +interface eventsys.h IEventProperty +interface eventsys.h IEventPublisher +interface iaccess.h IAccessControl +interface imessagedispatcher.h IMessageDispatcher +interface objidl.h IAgileObject +interface objidl.h IBindCtx +interface objidl.h IBlockingLock +interface objidl.h ICallFactory +interface objidl.h ICancelMethodCalls +interface objidl.h IClassActivator +interface objidl.h IClientSecurity +interface objidl.h IComThreadingInfo +interface objidl.h IContext +interface objidl.h IEnumContextProps +interface objidl.h IEnumString +interface objidl.h IEnumUnknown +interface objidl.h IExternalConnection +interface objidl.h IFastRundown +interface objidl.h IForegroundTransfer +interface objidl.h IGlobalInterfaceTable +interface objidl.h IGlobalOptions +interface objidl.h IInitializeSpy +interface objidl.h IInternalUnknown +interface objidl.h IMalloc +interface objidl.h IMallocSpy +interface objidl.h IMarshal +interface objidl.h IMarshalingStream +interface objidl.h IMessageFilter +interface objidl.h IMoniker +interface objidl.h IMultiQI +interface objidl.h INoMarshal +interface objidl.h IObjContext +interface objidl.h IPersist +interface objidl.h IPersistFile +interface objidl.h IPersistStorage +interface objidl.h IPersistStream +interface objidl.h IPipeByte +interface objidl.h IPipeDouble +interface objidl.h IPipeLong +interface objidl.h IProcessInitControl +interface objidl.h IProcessLock +interface objidl.h IProgressNotify +interface objidl.h IPSFactoryBuffer +interface objidl.h IROTData +interface objidl.h IRpcChannelBuffer +interface objidl.h IRpcOptions +interface objidl.h IRpcProxyBuffer +interface objidl.h IRpcStubBuffer +interface objidl.h IRunnableObject +interface objidl.h IRunningObjectTable +interface objidl.h IServerSecurity +interface objidl.h IStdMarshalInfo +interface objidl.h ISurrogate +interface objidl.h ISurrogateService +interface objidl.h ISynchronize +interface objidl.h ISynchronizeContainer +interface objidl.h ISynchronizeEvent +interface objidl.h ISynchronizeHandle +interface ocidl.h IClassFactory2 +interface ocidl.h IConnectionPoint +interface ocidl.h IConnectionPointContainer +interface ocidl.h IEnumConnectionPoints +interface ocidl.h IEnumConnections +interface ocidl.h IPersistStreamInit +interface ocidl.h IProvideClassInfo +interface ocidl.h IProvideClassInfo2 +interface ocidl.h IProvideMultipleClassInfo +interface oleidl.h IOleItemContainer +interface oleidl.h IParseDisplayName +interface shobjidl.h IAccessibilityDockingService +interface shobjidl.h IAccessibilityDockingServiceCallback +interface txlogpub.h IFileBasedLogInit +interface txlogpub.h ILog +interface unknwn.h IUnknown +interface unknwnbase.h IClassFactory + +# Desktop App Technologies > System Services > Component Object Model (COM) > COM Fundamentals > Reference > Macros +function dmerror.h MAKE_HRESULT +function winerror.h FAILED +function winerror.h HRESULT_CODE +function winerror.h HRESULT_FACILITY +function winerror.h HRESULT_FROM_NT +function winerror.h HRESULT_FROM_WIN32 +function winerror.h HRESULT_SEVERITY +function winerror.h IS_ERROR +function winerror.h MAKE_SCODE +function winerror.h SCODE_CODE +function winerror.h SCODE_FACILITY +function winerror.h SCODE_SEVERITY +function winerror.h SUCCEEDED +function wtypesbase.h OLESTR + +# Desktop App Technologies > System Services > Component Object Model (COM) > COM Fundamentals > Reference > Structures +struct accctrl.h ACTRL_ACCESS =actrl_accessa +struct accctrl.h ACTRL_ACCESS_ENTRY =actrl_access_entrya +struct accctrl.h ACTRL_ACCESS_ENTRY_LIST =actrl_access_entry_lista +struct accctrl.h ACTRL_PROPERTY_ENTRY =actrl_property_entrya +struct callobj.h CALLFRAME_MARSHALCONTEXT +struct callobj.h CALLFRAMEINFO +struct callobj.h CALLFRAMEPARAMINFO +struct comcat.h CATEGORYINFO +struct objidl.h BIND_OPTS +struct objidl.h BIND_OPTS2 =bind_opts2~r1 +struct objidl.h BIND_OPTS3 =bind_opts3~r1 +struct objidl.h COSERVERINFO +struct objidl.h INTERFACEINFO +struct objidl.h MULTI_QI +struct objidl.h RPCOLEMESSAGE +struct objidl.h SOLE_AUTHENTICATION_INFO +struct objidl.h SOLE_AUTHENTICATION_LIST +struct objidl.h SOLE_AUTHENTICATION_SERVICE +struct ocidl.h CONNECTDATA +struct wtypes.h CSPLATFORM +struct wtypes.h QUERYCONTEXT +struct wtypesbase.h COAUTHIDENTITY +struct wtypesbase.h COAUTHINFO + +# Desktop App Technologies > System Services > Component Object Model (COM) > COM Fundamentals > Reference > Functions +function callobj.h CoGetInterceptor +function combaseapi.h CLSIDFromProgID +function combaseapi.h CLSIDFromProgIDEx +function combaseapi.h CLSIDFromString +function combaseapi.h CoAddRefServerProcess +function combaseapi.h CoAllowUnmarshalerCLSID +function combaseapi.h CoCancelCall +function combaseapi.h CoCopyProxy +function combaseapi.h CoCreateFreeThreadedMarshaler +function combaseapi.h CoCreateGuid +function combaseapi.h CoCreateInstance +function combaseapi.h CoCreateInstanceEx +function combaseapi.h CoCreateInstanceFromApp +function combaseapi.h CoDecrementMTAUsage +function combaseapi.h CoDisableCallCancellation +function combaseapi.h CoDisconnectContext +function combaseapi.h CoDisconnectObject +function combaseapi.h CoEnableCallCancellation +function combaseapi.h CoFileTimeNow +function combaseapi.h CoFreeUnusedLibraries +function combaseapi.h CoFreeUnusedLibrariesEx +function combaseapi.h CoGetApartmentType +function combaseapi.h CoGetCallContext +function combaseapi.h CoGetCallerTID +function combaseapi.h CoGetCancelObject +function combaseapi.h CoGetClassObject +function combaseapi.h CoGetContextToken +function combaseapi.h CoGetCurrentLogicalThreadId +function combaseapi.h CoGetCurrentProcess +function combaseapi.h CoGetInterfaceAndReleaseStream +function combaseapi.h CoGetMalloc +function combaseapi.h CoGetMarshalSizeMax +function combaseapi.h CoGetObjectContext +function combaseapi.h CoGetPSClsid +function combaseapi.h CoGetStandardMarshal +function combaseapi.h CoGetStdMarshalEx +function combaseapi.h CoGetTreatAsClass +function combaseapi.h CoImpersonateClient +function combaseapi.h CoIncrementMTAUsage +function combaseapi.h CoInitializeEx +function combaseapi.h CoInitializeSecurity +function combaseapi.h CoInvalidateRemoteMachineBindings +function combaseapi.h CoIsHandlerConnected +function combaseapi.h CoLockObjectExternal +function combaseapi.h CoMarshalHresult +function combaseapi.h CoMarshalInterface +function combaseapi.h CoMarshalInterThreadInterfaceInStream +function combaseapi.h CoQueryAuthenticationServices +function combaseapi.h CoQueryClientBlanket +function combaseapi.h CoQueryProxyBlanket +function combaseapi.h CoRegisterActivationFilter +function combaseapi.h CoRegisterClassObject +function combaseapi.h CoRegisterPSClsid +function combaseapi.h CoRegisterSurrogate +function combaseapi.h CoReleaseMarshalData +function combaseapi.h CoReleaseServerProcess +function combaseapi.h CoResumeClassObjects +function combaseapi.h CoRevertToSelf +function combaseapi.h CoRevokeClassObject +function combaseapi.h CoSetCancelObject +function combaseapi.h CoSetProxyBlanket +function combaseapi.h CoSuspendClassObjects +function combaseapi.h CoSwitchCallContext +function combaseapi.h CoTaskMemAlloc +function combaseapi.h CoTaskMemFree +function combaseapi.h CoTaskMemRealloc +function combaseapi.h CoTestCancel +function combaseapi.h CoUninitialize +function combaseapi.h CoUnmarshalHresult +function combaseapi.h CoUnmarshalInterface +function combaseapi.h CoWaitForMultipleHandles +function combaseapi.h CoWaitForMultipleObjects +function combaseapi.h DllGetClassObject +function combaseapi.h IIDFromString +function combaseapi.h LPFNCANUNLOADNOW =dllcanunloadnow +function combaseapi.h ProgIDFromCLSID +function combaseapi.h StringFromCLSID +function combaseapi.h StringFromGUID2 +function combaseapi.h StringFromIID +function guiddef.h IsEqualCLSID +function guiddef.h IsEqualGUID +function guiddef.h IsEqualIID +function messagedispatcherapi.h CoHandlePriorityEventsFromMessagePump +function messagedispatcherapi.h CoSetMessageDispatcher +function objbase.h BindMoniker +function objbase.h CoAllowSetForegroundWindow +function objbase.h CoDosDateTimeToFileTime +function objbase.h CoFileTimeToDosDateTime +function objbase.h CoFreeAllLibraries +function objbase.h CoFreeLibrary +function objbase.h CoGetInstanceFromFile +function objbase.h CoGetInstanceFromIStorage +function objbase.h CoGetObject +function objbase.h CoGetSystemSecurityPermissions +function objbase.h CoInitialize +function objbase.h CoInstall +function objbase.h CoIsOle1Class +function objbase.h CoLoadLibrary +function objbase.h CoRegisterChannelHook +function objbase.h CoRegisterInitializeSpy +function objbase.h CoRegisterMallocSpy +function objbase.h CoRegisterMessageFilter +function objbase.h CoRevokeInitializeSpy +function objbase.h CoRevokeMallocSpy +function objbase.h CoTreatAsClass +function objbase.h CreateAntiMoniker +function objbase.h CreateBindCtx +function objbase.h CreateClassMoniker +function objbase.h CreateFileMoniker +function objbase.h CreateGenericComposite +function objbase.h CreateItemMoniker +function objbase.h CreateObjrefMoniker +function objbase.h CreatePointerMoniker +function objbase.h GetClassFile +function objbase.h GetRunningObjectTable +function objbase.h MkParseDisplayName +function objbase.h MonikerCommonPrefixWith +function objbase.h MonikerRelativePathTo +function ole2.h IsAccelerator +function ole2.h OleBuildVersion +function ole2.h OleDoAutoConvert +function ole2.h OleGetAutoConvert +function ole2.h OleGetIconOfClass +function ole2.h OleGetIconOfFile +function ole2.h OleRegGetMiscStatus +function ole2.h OleRegGetUserType +function ole2.h OleSetAutoConvert +function olectl.h DllRegisterServer +function olectl.h DllUnregisterServer +function olectl.h OleIconToCursor +function roapi.h Initialize +function urlmon.h CreateAsyncBindCtx + +# Desktop App Technologies > System Services > Component Object Model (COM) > OLE and Data Transfer > Reference > Enumerations +enum docobj.h DOCMISC +enum docobj.h OLECMDEXECOPT +enum docobj.h OLECMDF +enum docobj.h OLECMDID +enum docobj.h OLECMDID_WINDOWSTATE_FLAG +enum docobj.h OLECMDTEXTF +enum objidl.h ADVF +enum objidl.h DATADIR +enum objidl.h TYMED +enum ocidl.h ACTIVATEFLAGS +enum ocidl.h DVASPECT2 +enum ocidl.h DVASPECTINFOFLAG +enum ocidl.h DVEXTENTMODE +enum ocidl.h HITRESULT +enum ocidl.h OLEDCFLAGS +enum ocidl.h UASFLAGS +enum ocidl.h VIEWSTATUS +enum oledlg.h OLEUIPASTEFLAG +enum oleidl.h DISCARDCACHE +enum oleidl.h OLECLOSE +enum oleidl.h OLECONTF +enum oleidl.h OLEGETMONIKER +enum oleidl.h OLELINKBIND +enum oleidl.h OLEMISC +enum oleidl.h OLERENDER +enum oleidl.h OLEUPDATE +enum oleidl.h OLEVERBATTRIB +enum oleidl.h OLEWHICHMK +enum oleidl.h USERCLASSTYPE +enum wtypes.h DVASPECT + +# Desktop App Technologies > System Services > Component Object Model (COM) > OLE and Data Transfer > Reference > Functions +function objbase.h CreateDataCache +function ole.h OleCreate +function ole.h OleCreateFromFile +function ole.h OleDraw +function ole.h OleLoadFromStream +function ole.h OleSaveToStream +function ole2.h CreateDataAdviseHolder +function ole2.h CreateOleAdviseHolder +function ole2.h DoDragDrop +function ole2.h OleCreateDefaultHandler +function ole2.h OleCreateEmbeddingHelper +function ole2.h OleCreateEx +function ole2.h OleCreateFromData +function ole2.h OleCreateFromDataEx +function ole2.h OleCreateFromFileEx +function ole2.h OleCreateLink +function ole2.h OleCreateLinkEx +function ole2.h OleCreateLinkFromData +function ole2.h OleCreateLinkFromDataEx +function ole2.h OleCreateLinkToFile +function ole2.h OleCreateLinkToFileEx +function ole2.h OleCreateMenuDescriptor +function ole2.h OleCreateStaticFromData +function ole2.h OleDestroyMenuDescriptor +function ole2.h OleDuplicateData +function ole2.h OleFlushClipboard +function ole2.h OleGetClipboard +function ole2.h OleGetClipboardWithEnterpriseInfo +function ole2.h OleInitialize +function ole2.h OleIsCurrentClipboard +function ole2.h OleIsRunning +function ole2.h OleLoad +function ole2.h OleLockRunning +function ole2.h OleMetafilePictFromIconAndLabel +function ole2.h OleNoteObjectVisible +function ole2.h OleQueryCreateFromData +function ole2.h OleQueryLinkFromData +function ole2.h OleRegEnumFormatEtc +function ole2.h OleRegEnumVerbs +function ole2.h OleRun +function ole2.h OleSave +function ole2.h OleSetClipboard +function ole2.h OleSetContainedObject +function ole2.h OleSetMenuDescriptor +function ole2.h OleTranslateAccelerator +function ole2.h OleUninitialize +function ole2.h RegisterDragDrop +function ole2.h ReleaseStgMedium +function ole2.h RevokeDragDrop +function olectl.h OleCreateFontIndirect +function olectl.h OleCreatePictureIndirect +function olectl.h OleLoadPicture +function olectl.h OleLoadPictureEx +function oledlg.h OleUIAddVerbMenu =oleuiaddverbmenua +function oledlg.h OleUIBusy =oleuibusya +function oledlg.h OleUICanConvertOrActivateAs +function oledlg.h OleUIChangeIcon =oleuichangeicona +function oledlg.h OleUIChangeSource =oleuichangesourcea +function oledlg.h OleUIConvert =oleuiconverta +function oledlg.h OleUIEditLinks =oleuieditlinksa +function oledlg.h OleUIInsertObject =oleuiinsertobjecta +function oledlg.h OleUIObjectProperties =oleuiobjectpropertiesa +function oledlg.h OleUIPasteSpecial =oleuipastespeciala +function oledlg.h OleUIPromptUser =oleuipromptusera +function oledlg.h OleUIUpdateLinks =oleuiupdatelinksa +function urlmon.h CreateFormatEnumerator + +# Desktop App Technologies > System Services > Component Object Model (COM) > OLE and Data Transfer > Reference > Interfaces +interface docobj.h IEnumOleDocumentViews +interface docobj.h IOleCommandTarget +interface docobj.h IOleDocument +interface docobj.h IOleDocumentSite +interface docobj.h IOleDocumentView +interface objidl.h IAdviseSink +interface objidl.h IAdviseSink2 +interface objidl.h IDataAdviseHolder +interface objidl.h IDataObject +interface objidl.h IEnumFORMATETC +interface objidl.h IEnumMoniker +interface objidl.h IEnumSTATDATA +interface ocidl.h IAdviseSinkEx +interface ocidl.h IEnumOleUndoUnits +interface ocidl.h IObjectWithSite +interface ocidl.h IOleInPlaceObjectWindowless +interface ocidl.h IOleInPlaceSiteEx +interface ocidl.h IOleInPlaceSiteWindowless +interface ocidl.h IOleParentUndoUnit +interface ocidl.h IOleUndoManager +interface ocidl.h IOleUndoUnit +interface ocidl.h IViewObjectEx +interface oledlg.h IOleUILinkContainer =ioleuilinkcontainera +interface oledlg.h IOleUILinkInfo =ioleuilinkinfoa +interface oledlg.h IOleUIObjInfo =ioleuiobjinfoa +interface oleidl.h IDropSource +interface oleidl.h IDropSourceNotify +interface oleidl.h IDropTarget +interface oleidl.h IEnterpriseDropTarget +interface oleidl.h IEnumOLEVERB +interface oleidl.h IOleAdviseHolder +interface oleidl.h IOleCache +interface oleidl.h IOleCache2 +interface oleidl.h IOleCacheControl +interface oleidl.h IOleClientSite +interface oleidl.h IOleContainer +interface oleidl.h IOleInPlaceActiveObject +interface oleidl.h IOleInPlaceFrame +interface oleidl.h IOleInPlaceObject +interface oleidl.h IOleInPlaceSite +interface oleidl.h IOleInPlaceUIWindow +interface oleidl.h IOleLink +interface oleidl.h IOleObject +interface oleidl.h IOleWindow +interface oleidl.h IViewObject +interface oleidl.h IViewObject2 + +# Desktop App Technologies > System Services > Component Object Model (COM) > OLE and Data Transfer > Reference > Structures +struct docobj.h OLECMD +struct docobj.h OLECMDTEXT +struct objidl.h DVTARGETDEVICE +struct objidl.h FORMATETC +struct objidl.h STATDATA +struct objidl.h STGMEDIUM =ustgmedium-r1 +struct ocidl.h DVASPECTINFO +struct ocidl.h DVEXTENTINFO +struct ocidl.h POINTF +struct oledlg.h OLEUIBUSY =oleuibusya +struct oledlg.h OLEUICHANGEICON =oleuichangeicona +struct oledlg.h OLEUICHANGESOURCE =oleuichangesourcea +struct oledlg.h OLEUICONVERT =oleuiconverta +struct oledlg.h OLEUIEDITLINKS =oleuieditlinksa +struct oledlg.h OLEUIGNRLPROPS =oleuignrlpropsa +struct oledlg.h OLEUIINSERTOBJECT =oleuiinsertobjecta +struct oledlg.h OLEUILINKPROPS =oleuilinkpropsa +struct oledlg.h OLEUIOBJECTPROPS =oleuiobjectpropsa +struct oledlg.h OLEUIPASTEENTRY =oleuipasteentrya +struct oledlg.h OLEUIPASTESPECIAL =oleuipastespeciala +struct oledlg.h OLEUIVIEWPROPS =oleuiviewpropsa +struct oleidl.h OBJECTDESCRIPTOR +struct oleidl.h OLEINPLACEFRAMEINFO +struct oleidl.h OLEMENUGROUPWIDTHS +struct oleidl.h OLEVERB + +# Desktop App Technologies > System Services > Component Object Model (COM) > Controls and Property Pages > Guide > ActiveX Control and Control Container Guidelines > Component Categories > Visual Basic Private Interfaces +interface vbinterf.h IGetOleObject +interface vbinterf.h IGetVBAObject +interface vbinterf.h IVBFormat +interface vbinterf.h IVBGetControl + +# Desktop App Technologies > System Services > Component Object Model (COM) > Controls and Property Pages > Reference > Enumerations +enum ocidl.h GUIDKIND +enum ocidl.h PICTUREATTRIBUTES +enum ocidl.h POINTERINACTIVE +enum ocidl.h QACONTAINERFLAGS + +# Desktop App Technologies > System Services > Component Object Model (COM) > Controls and Property Pages > Reference > Functions +function olectl.h OleCreatePropertyFrame +function olectl.h OleCreatePropertyFrameIndirect +function olectl.h OleLoadPicturePath +function olectl.h OleTranslateColor + +# Desktop App Technologies > System Services > Component Object Model (COM) > Controls and Property Pages > Reference > Interfaces +interface docobj.h IPrint +interface ocidl.h IFont +interface ocidl.h IFontDisp +interface ocidl.h IOleControl +interface ocidl.h IOleControlSite +interface ocidl.h IPerPropertyBrowsing +interface ocidl.h IPicture +interface ocidl.h IPictureDisp +interface ocidl.h IPointerInactive +interface ocidl.h IPropertyNotifySink +interface ocidl.h IPropertyPage +interface ocidl.h IPropertyPage2 +interface ocidl.h IPropertyPageSite +interface ocidl.h IQuickActivate +interface ocidl.h ISimpleFrameSite +interface ocidl.h ISpecifyPropertyPages + +# Desktop App Technologies > System Services > Component Object Model (COM) > Controls and Property Pages > Reference > Structures +struct docobj.h PAGERANGE +struct docobj.h PAGESET +struct ocidl.h CADWORD +struct ocidl.h CALPOLESTR +struct ocidl.h CAUUID +struct ocidl.h CONTROLINFO +struct ocidl.h LICINFO +struct ocidl.h PROPPAGEINFO +struct ocidl.h QACONTAINER +struct ocidl.h QACONTROL +struct olectl.h FONTDESC +struct olectl.h OCPFIPARAMS +struct olectl.h PICTDESC + +# Desktop App Technologies > System Services > Structured Storage > Structured Storage Reference > Interfaces +interface objidl.h IDirectWriterLock +interface objidl.h IEnumSTATSTG +interface objidl.h IFillLockBytes +interface objidl.h ILayoutStorage +interface objidl.h ILockBytes +interface objidl.h IRootStorage +interface objidl.h ISequentialStream +interface objidl.h IStorage +interface objidl.h IStream +interface propidl.h IEnumSTATPROPSETSTG +interface propidl.h IEnumSTATPROPSTG +interface propidl.h IPropertySetStorage +interface propidl.h IPropertyStorage + +# Desktop App Technologies > System Services > Structured Storage > Structured Storage Reference > Functions +function combaseapi.h CreateStreamOnHGlobal +function combaseapi.h GetHGlobalFromStream +function coml2api.h CreateILockBytesOnHGlobal +function coml2api.h FmtIdToPropStgName +function coml2api.h GetConvertStg +function coml2api.h GetHGlobalFromILockBytes +function coml2api.h PropStgNameToFmtId +function coml2api.h ReadClassStg +function coml2api.h ReadClassStm +function coml2api.h StgCreateDocfile +function coml2api.h StgCreateDocfileOnILockBytes +function coml2api.h StgCreatePropSetStg +function coml2api.h StgCreatePropStg +function coml2api.h StgCreateStorageEx +function coml2api.h StgIsStorageFile +function coml2api.h StgIsStorageILockBytes +function coml2api.h StgOpenPropStg +function coml2api.h StgOpenStorage +function coml2api.h StgOpenStorageEx +function coml2api.h StgOpenStorageOnILockBytes +function coml2api.h StgSetTimes +function coml2api.h WriteClassStg +function coml2api.h WriteClassStm +function objbase.h StgGetIFillLockBytesOnFile +function objbase.h StgGetIFillLockBytesOnILockBytes +function objbase.h StgOpenAsyncDocfileOnIFillLockBytes +function objbase.h StgOpenLayoutDocfile +function ole2.h OleConvertIStorageToOLESTREAM +function ole2.h OleConvertIStorageToOLESTREAMEx +function ole2.h OleConvertOLESTREAMToIStorage +function ole2.h OleConvertOLESTREAMToIStorageEx +function ole2.h ReadFmtUserTypeStg +function ole2.h SetConvertStg +function ole2.h WriteFmtUserTypeStg +function propapi.h StgPropertyLengthAsVariant +function propidl.h FreePropVariantArray +function propidl.h PropVariantClear +function propidl.h PropVariantInit +function propidl.h StgConvertPropertyToVariant +function propidl.h StgConvertVariantToProperty +function propvarutil.h StgDeserializePropVariant +function propvarutil.h StgSerializePropVariant + +# Desktop App Technologies > System Services > Structured Storage > Structured Storage Reference > Structures +struct coml2api.h STGOPTIONS +struct objidl.h RemSNB +struct objidl.h STATSTG +struct objidl.h StorageLayout +struct propidl.h PROPSPEC +struct propidl.h PROPVARIANT +struct propidl.h STATPROPSETSTG +struct propidl.h STATPROPSTG + +# Desktop App Technologies > System Services > Structured Storage > Structured Storage Reference > Enumerations +enum objidl.h LOCKTYPE +enum objidl.h STGTY +enum objidl.h STREAM_SEEK +enum wtypes.h STATFLAG +enum wtypes.h STGC +enum wtypes.h STGMOVE + +# Desktop App Technologies > System Services > Activity Coordinator API > Activity Coordinator API functions +function activitycoordinator.h CreateActivityCoordinatorPolicy +function activitycoordinator.h DestroyActivityCoordinatorPolicy +function activitycoordinator.h GetActivityCoordinatorPolicyResourceCondition +function activitycoordinator.h IsActivityCoordinatorResourceSupported +function activitycoordinator.h SetActivityCoordinatorPolicyResourceCondition +function activitycoordinator.h SubscribeActivityCoordinatorPolicy +function activitycoordinator.h UnsubscribeActivityCoordinatorPolicy + +# Desktop App Technologies > System Services > Activity Coordinator API > Activity Coordinator API enumerations +enum activitycoordinatortypes.h ACTIVITY_COORDINATOR_CONDITION +enum activitycoordinatortypes.h ACTIVITY_COORDINATOR_NOTIFICATION +enum activitycoordinatortypes.h ACTIVITY_COORDINATOR_POLICY_TEMPLATE +enum activitycoordinatortypes.h ACTIVITY_COORDINATOR_RESOURCE + +# Desktop App Technologies > System Services > COM+ (Component Services) > COM+ Reference > COM+ Administration Reference > COM+ Administration Interfaces +interface comadmin.h ICatalogCollection +interface comadmin.h ICatalogObject +interface comadmin.h ICOMAdminCatalog +interface comadmin.h ICOMAdminCatalog2 + +# Desktop App Technologies > System Services > COM+ (Component Services) > COM+ Reference > COM+ Programming Reference > COM+ Enumerations +enum comadmin.h COMAdminTxIsolationLevelOptions +enum comsvcs.h COMPLUS_APPTYPE +enum comsvcs.h CRMFLAGS +enum comsvcs.h CRMREGFLAGS +enum comsvcs.h CrmTransactionState +enum comsvcs.h CSC_Binding +enum comsvcs.h CSC_COMTIIntrinsicsConfig +enum comsvcs.h CSC_IISIntrinsicsConfig +enum comsvcs.h CSC_InheritanceConfig +enum comsvcs.h CSC_PartitionConfig +enum comsvcs.h CSC_SxsConfig +enum comsvcs.h CSC_SynchronizationConfig +enum comsvcs.h CSC_ThreadPool +enum comsvcs.h CSC_TrackerConfig +enum comsvcs.h CSC_TransactionConfig +enum comsvcs.h GetAppTrackerDataFlags +enum comsvcs.h TRACKING_COLL_TYPE +enum comsvcs.h TransactionVote +enum eventsys.h EOC_ChangeType + +# Desktop App Technologies > System Services > COM+ (Component Services) > COM+ Reference > COM+ Programming Reference > COM+ Functions +function combaseapi.h CoGetDefaultContext +function comsvcs.h CoCreateActivity +function comsvcs.h CoEnterServiceDomain +function comsvcs.h CoLeaveServiceDomain +function comsvcs.h GetManagedExtensions +function comsvcs.h GetObjectContext +function comsvcs.h MTSCreateActivity +function comsvcs.h RecycleSurrogate +function comsvcs.h SafeRef +function mtxdm.h GetDispenserManager + +# Desktop App Technologies > System Services > COM+ (Component Services) > COM+ Reference > COM+ Programming Reference > COM+ Interfaces +interface comsvcs.h ContextInfo +interface comsvcs.h ContextInfo2 +interface comsvcs.h IAppDomainHelper +interface comsvcs.h IAssemblyLocator +interface comsvcs.h IAsyncErrorNotify +interface comsvcs.h ICheckSxSConfig +interface comsvcs.h IComActivityEvents +interface comsvcs.h IComApp2Events +interface comsvcs.h IComAppEvents +interface comsvcs.h IComCRMEvents +interface comsvcs.h IComExceptionEvents +interface comsvcs.h IComIdentityEvents +interface comsvcs.h IComInstance2Events +interface comsvcs.h IComInstanceEvents +interface comsvcs.h ICOMLBArguments +interface comsvcs.h IComLTxEvents +interface comsvcs.h IComMethod2Events +interface comsvcs.h IComMethodEvents +interface comsvcs.h IComObjectConstruction2Events +interface comsvcs.h IComObjectConstructionEvents +interface comsvcs.h IComObjectEvents +interface comsvcs.h IComObjectPool2Events +interface comsvcs.h IComObjectPoolEvents +interface comsvcs.h IComObjectPoolEvents2 +interface comsvcs.h IComQCEvents +interface comsvcs.h IComResourceEvents +interface comsvcs.h IComSecurityEvents +interface comsvcs.h IComThreadEvents +interface comsvcs.h IComTrackingInfoCollection +interface comsvcs.h IComTrackingInfoEvents +interface comsvcs.h IComTrackingInfoObject +interface comsvcs.h IComTrackingInfoProperties +interface comsvcs.h IComTransaction2Events +interface comsvcs.h IComTransactionEvents +interface comsvcs.h IComUserEvent +interface comsvcs.h IContextProperties +interface comsvcs.h IContextState +interface comsvcs.h ICreateWithLocalTransaction +interface comsvcs.h ICreateWithTipTransactionEx +interface comsvcs.h ICreateWithTransactionEx +interface comsvcs.h ICrmCompensator +interface comsvcs.h ICrmCompensatorVariants +interface comsvcs.h ICrmFormatLogRecords +interface comsvcs.h ICrmLogControl +interface comsvcs.h ICrmMonitor +interface comsvcs.h ICrmMonitorClerks +interface comsvcs.h ICrmMonitorLogRecords +interface comsvcs.h IDispenserDriver +interface comsvcs.h IDispenserManager +interface comsvcs.h IEnumNames +interface comsvcs.h IGetAppTrackerData +interface comsvcs.h IGetContextProperties +interface comsvcs.h IGetSecurityCallContext +interface comsvcs.h IHolder +interface comsvcs.h IManagedActivationEvents +interface comsvcs.h IManagedObjectInfo +interface comsvcs.h IManagedPoolAction +interface comsvcs.h IManagedPooledObj +interface comsvcs.h IMessageMover +interface comsvcs.h IMTSActivity +interface comsvcs.h IMTSCall +interface comsvcs.h IMtsEventInfo +interface comsvcs.h IMtsEvents +interface comsvcs.h IMtsGrp +interface comsvcs.h IMTSLocator +interface comsvcs.h IObjectConstruct +interface comsvcs.h IObjectConstructString +interface comsvcs.h IObjectContext +interface comsvcs.h IObjectContextActivity +interface comsvcs.h IObjectContextInfo +interface comsvcs.h IObjectContextInfo2 +interface comsvcs.h IObjectContextTip +interface comsvcs.h IObjectControl +interface comsvcs.h IObjPool +interface comsvcs.h IPlaybackControl +interface comsvcs.h IPoolManager +interface comsvcs.h IProcessInitializer +interface comsvcs.h ISecurityCallContext +interface comsvcs.h ISecurityCallersColl +interface comsvcs.h ISecurityIdentityColl +interface comsvcs.h ISecurityProperty +interface comsvcs.h ISelectCOMLBServer +interface comsvcs.h ISendMethodEvents +interface comsvcs.h IServiceActivity +interface comsvcs.h IServiceCall +interface comsvcs.h IServiceComTIIntrinsicsConfig +interface comsvcs.h IServiceIISIntrinsicsConfig +interface comsvcs.h IServiceInheritanceConfig +interface comsvcs.h IServicePartitionConfig +interface comsvcs.h IServicePool +interface comsvcs.h IServicePoolConfig +interface comsvcs.h IServiceSxSConfig +interface comsvcs.h IServiceSynchronizationConfig +interface comsvcs.h IServiceSysTxnConfig +interface comsvcs.h IServiceThreadPoolConfig +interface comsvcs.h IServiceTrackerConfig +interface comsvcs.h IServiceTransactionConfig +interface comsvcs.h IServiceTransactionConfigBase +interface comsvcs.h ISharedProperty +interface comsvcs.h ISharedPropertyGroup +interface comsvcs.h ISharedPropertyGroupManager +interface comsvcs.h ISystemAppEventData +interface comsvcs.h IThreadPoolKnobs +interface comsvcs.h ITransactionContext +interface comsvcs.h ITransactionContextEx +interface comsvcs.h ITransactionProperty +interface comsvcs.h ITransactionProxy +interface comsvcs.h ITransactionResourcePool +interface comsvcs.h ITransactionStatus +interface comsvcs.h ObjectContext +interface comsvcs.h ObjectControl +interface comsvcs.h SecurityProperty +interface eventsys.h IEnumEventObject +interface eventsys.h IEventClass +interface eventsys.h IEventClass2 +interface eventsys.h IEventControl +interface eventsys.h IEventObjectChange +interface eventsys.h IEventObjectChange2 +interface eventsys.h IEventObjectCollection +interface eventsys.h IEventProperty +interface eventsys.h IEventSubscription +interface eventsys.h IEventSystem +interface eventsys.h IFiringControl +interface eventsys.h IMultiInterfaceEventControl +interface eventsys.h IMultiInterfacePublisherFilter +interface eventsys.h IPublisherFilter + +# Desktop App Technologies > System Services > COM+ (Component Services) > COM+ Reference > COM+ Programming Reference > COM+ Structures +struct comsvcs.h ApplicationProcessRecycleInfo +struct comsvcs.h ApplicationProcessStatistics +struct comsvcs.h ApplicationProcessSummary +struct comsvcs.h ApplicationSummary +struct comsvcs.h ComponentHangMonitorInfo +struct comsvcs.h ComponentStatistics +struct comsvcs.h ComponentSummary +struct comsvcs.h COMSVCSEVENTINFO +struct comsvcs.h CrmLogRecordRead +struct eventsys.h COMEVENTSYSCHANGEINFO + +# Desktop App Technologies > System Services > Compression API > Compression API Functions +function compressapi.h CloseCompressor +function compressapi.h CloseDecompressor +function compressapi.h Compress +function compressapi.h CreateCompressor +function compressapi.h CreateDecompressor +function compressapi.h Decompress +function compressapi.h QueryCompressorInformation +function compressapi.h QueryDecompressorInformation +function compressapi.h ResetCompressor +function compressapi.h ResetDecompressor +function compressapi.h SetCompressorInformation +function compressapi.h SetDecompressorInformation + +# Desktop App Technologies > System Services > Compression API > Compression API Structures +struct compressapi.h COMPRESS_ALLOCATION_ROUTINES + +# Desktop App Technologies > System Services > Compression API > Compression API Enumerations +enum compressapi.h COMPRESS_INFORMATION_CLASS + +# Desktop App Technologies > System Services > Dynamic-link libraries > Dynamic-link library reference > Dynamic-link library functions +function libloaderapi.h AddDllDirectory +function libloaderapi.h DisableThreadLibraryCalls +function libloaderapi.h FreeLibrary +function libloaderapi.h FreeLibraryAndExitThread +function libloaderapi.h GetModuleFileName =getmodulefilenamea +function libloaderapi.h GetModuleHandle =getmodulehandlea +function libloaderapi.h GetModuleHandleEx =getmodulehandleexa +function libloaderapi.h GetProcAddress +function libloaderapi.h LoadLibrary =loadlibrarya +function libloaderapi.h LoadLibraryEx =loadlibraryexa +function libloaderapi.h RemoveDllDirectory +function libloaderapi.h SetDefaultDllDirectories +function libloaderapi2.h QueryOptionalDelayLoadedAPI +function winbase.h GetDllDirectory =getdlldirectorya +function winbase.h LoadModule +function winbase.h LoadPackagedLibrary +function winbase.h SetDllDirectory =setdlldirectorya + +# Desktop App Technologies > System Services > Enclaves > Enclaves reference > Enclaves enumerations +enum ntenclv.h ENCLAVE_SEALING_IDENTITY_POLICY + +# Desktop App Technologies > System Services > Enclaves > Enclaves reference > Enclaves functions +function enclaveapi.h CallEnclave +function enclaveapi.h CreateEnclave +function enclaveapi.h DeleteEnclave +function enclaveapi.h InitializeEnclave +function enclaveapi.h IsEnclaveTypeSupported +function enclaveapi.h LoadEnclaveData +function enclaveapi.h LoadEnclaveImage =loadenclaveimagew +function enclaveapi.h TerminateEnclave +function winenclaveapi.h EnclaveCopyIntoEnclave +function winenclaveapi.h EnclaveCopyOutOfEnclave +function winenclaveapi.h EnclaveGetAttestationReport +function winenclaveapi.h EnclaveGetEnclaveInformation +function winenclaveapi.h EnclaveRestrictContainingProcessAccess +function winenclaveapi.h EnclaveSealData +function winenclaveapi.h EnclaveUnsealData +function winenclaveapi.h EnclaveVerifyAttestationReport + +# Desktop App Technologies > System Services > Enclaves > Enclaves reference > Enclaves structures +struct ntenclv.h ENCLAVE_IDENTITY +struct ntenclv.h ENCLAVE_INFORMATION +struct ntenclv.h VBS_ENCLAVE_REPORT +struct ntenclv.h VBS_ENCLAVE_REPORT_MODULE +struct ntenclv.h VBS_ENCLAVE_REPORT_PKG_HEADER +struct ntenclv.h VBS_ENCLAVE_REPORT_VARDATA_HEADER +struct winnt.h ENCLAVE_CREATE_INFO_SGX +struct winnt.h ENCLAVE_CREATE_INFO_VBS +struct winnt.h ENCLAVE_INIT_INFO_SGX +struct winnt.h ENCLAVE_INIT_INFO_VBS +struct winnt.h IMAGE_ENCLAVE_CONFIG32 +struct winnt.h IMAGE_ENCLAVE_CONFIG64 +struct winnt.h IMAGE_ENCLAVE_IMPORT + +# Desktop App Technologies > System Services > HWREQCHK API > HWREQCHK API functions +function hwreqchkapi.h EvaluateHardwareRequirement +function hwreqchkapi.h GetHardwareRequirements +function hwreqchkapi.h GetHardwareRequirementSystemInfo +function hwreqchkapi.h GetLatestHardwareRequirement + +# Desktop App Technologies > System Services > HWREQCHK API > HWREQCHK API structures +struct hwreqchkapi.h HWREQCHK_DEVICE_HARDWARE_EVALUATION +struct hwreqchkapi.h HWREQCHK_DEVICE_HARDWARE_REQUIREMENT +struct hwreqchkapi.h HWREQCHK_DEVICE_HARDWARE_SYSINFO + +# Desktop App Technologies > System Services > HWREQCHK API > HWREQCHK API enums +enum hwreqchkapi.h HWREQCHK_CPU_VENDOR +enum hwreqchkapi.h HWREQCHK_PRODUCT_TYPE +enum hwreqchkapi.h HWREQCHK_TARGET_RELEASE + +# Desktop App Technologies > System Services > Interprocess Communications > Mailslots > Mailslot Reference > Mailslot Functions +function winbase.h CreateMailslot =createmailslota +function winbase.h GetMailslotInfo +function winbase.h SetMailslotInfo + +# Desktop App Technologies > System Services > Interprocess Communications > Pipes > Pipe Reference > Pipe Functions +function namedpipeapi.h ConnectNamedPipe +function namedpipeapi.h CreatePipe +function namedpipeapi.h DisconnectNamedPipe +function namedpipeapi.h GetNamedPipeInfo +function namedpipeapi.h PeekNamedPipe +function namedpipeapi.h SetNamedPipeHandleState +function namedpipeapi.h TransactNamedPipe +function winbase.h CallNamedPipe =callnamedpipea +function winbase.h CreateNamedPipe =createnamedpipea +function winbase.h GetNamedPipeClientComputerName =getnamedpipeclientcomputernamea +function winbase.h GetNamedPipeClientProcessId +function winbase.h GetNamedPipeClientSessionId +function winbase.h GetNamedPipeHandleState =getnamedpipehandlestatea +function winbase.h GetNamedPipeServerProcessId +function winbase.h GetNamedPipeServerSessionId +function winbase.h WaitNamedPipe =waitnamedpipea + +# Desktop App Technologies > System Services > Kernel Transaction Manager > KTM Reference > Kernel Transaction Manager Enumerations +enum winnt.h TRANSACTION_OUTCOME + +# Desktop App Technologies > System Services > Kernel Transaction Manager > KTM Reference > Kernel Transaction Manager Functions +function ktmw32.h CommitComplete +function ktmw32.h CommitEnlistment +function ktmw32.h CommitTransaction +function ktmw32.h CommitTransactionAsync +function ktmw32.h CreateEnlistment +function ktmw32.h CreateResourceManager +function ktmw32.h CreateTransaction +function ktmw32.h CreateTransactionManager +function ktmw32.h GetCurrentClockTransactionManager +function ktmw32.h GetEnlistmentId +function ktmw32.h GetEnlistmentRecoveryInformation +function ktmw32.h GetNotificationResourceManager +function ktmw32.h GetNotificationResourceManagerAsync +function ktmw32.h GetTransactionId +function ktmw32.h GetTransactionInformation +function ktmw32.h GetTransactionManagerId +function ktmw32.h OpenEnlistment +function ktmw32.h OpenResourceManager +function ktmw32.h OpenTransaction +function ktmw32.h OpenTransactionManager +function ktmw32.h OpenTransactionManagerById +function ktmw32.h PrepareComplete +function ktmw32.h PrepareEnlistment +function ktmw32.h PrePrepareComplete +function ktmw32.h PrePrepareEnlistment +function ktmw32.h ReadOnlyEnlistment +function ktmw32.h RecoverEnlistment +function ktmw32.h RecoverResourceManager +function ktmw32.h RecoverTransactionManager +function ktmw32.h RenameTransactionManager +function ktmw32.h RollbackComplete +function ktmw32.h RollbackEnlistment +function ktmw32.h RollbackTransaction +function ktmw32.h RollbackTransactionAsync +function ktmw32.h RollforwardTransactionManager +function ktmw32.h SetEnlistmentRecoveryInformation +function ktmw32.h SetResourceManagerCompletionPort +function ktmw32.h SetTransactionInformation +function ktmw32.h SinglePhaseReject + +# Desktop App Technologies > System Services > Kernel Transaction Manager > KTM Reference > Kernel Transaction Manager Structures +struct ktmtypes.h TRANSACTION_NOTIFICATION +struct ktmtypes.h TRANSACTION_NOTIFICATION_RECOVERY_ARGUMENT + +# Desktop App Technologies > System Services > Memory Management > Memory Management Reference > Memory Management Enumerations +enum winnt.h HEAP_INFORMATION_CLASS +enum winnt.h MEM_EXTENDED_PARAMETER_TYPE + +# Desktop App Technologies > System Services > Memory Management > Memory Management Reference > Memory Management Functions +callback winnt.h SecureMemoryCacheCallback =psecure_memory_cache_callback +function atlthunk.h AtlThunk_AllocateData +function atlthunk.h AtlThunk_DataToCode +function atlthunk.h AtlThunk_FreeData +function atlthunk.h AtlThunk_InitData +function enclaveapi.h CreateEnclave +function enclaveapi.h InitializeEnclave +function enclaveapi.h IsEnclaveTypeSupported +function enclaveapi.h LoadEnclaveData +function heapapi.h GetProcessHeap +function heapapi.h GetProcessHeaps +function heapapi.h HeapAlloc +function heapapi.h HeapCompact +function heapapi.h HeapCreate +function heapapi.h HeapDestroy +function heapapi.h HeapFree +function heapapi.h HeapLock +function heapapi.h HeapQueryInformation +function heapapi.h HeapReAlloc +function heapapi.h HeapSetInformation +function heapapi.h HeapSize +function heapapi.h HeapUnlock +function heapapi.h HeapValidate +function heapapi.h HeapWalk +function memoryapi.h AllocateUserPhysicalPages +function memoryapi.h AllocateUserPhysicalPagesNuma +function memoryapi.h CreateFileMapping2 +function memoryapi.h CreateFileMappingFromApp +function memoryapi.h CreateFileMappingW +function memoryapi.h CreateMemoryResourceNotification +function memoryapi.h DiscardVirtualMemory +function memoryapi.h FlushViewOfFile +function memoryapi.h FreeUserPhysicalPages +function memoryapi.h GetLargePageMinimum +function memoryapi.h GetMemoryErrorHandlingCapabilities +function memoryapi.h GetSystemFileCacheSize +function memoryapi.h GetWriteWatch +function memoryapi.h MapUserPhysicalPages +function memoryapi.h MapViewOfFile +function memoryapi.h MapViewOfFile2 +function memoryapi.h MapViewOfFile3 +function memoryapi.h MapViewOfFile3FromApp +function memoryapi.h MapViewOfFileEx +function memoryapi.h MapViewOfFileFromApp +function memoryapi.h MapViewOfFileNuma2 +function memoryapi.h OfferVirtualMemory +function memoryapi.h OpenFileMappingFromApp +function memoryapi.h PrefetchVirtualMemory +function memoryapi.h QueryMemoryResourceNotification +function memoryapi.h QueryVirtualMemoryInformation +function memoryapi.h ReadProcessMemory +function memoryapi.h ReclaimVirtualMemory +function memoryapi.h RegisterBadMemoryNotification +function memoryapi.h ResetWriteWatch +function memoryapi.h SetProcessValidCallTargets +function memoryapi.h SetSystemFileCacheSize +function memoryapi.h UnmapViewOfFile +function memoryapi.h UnmapViewOfFile2 +function memoryapi.h UnmapViewOfFileEx +function memoryapi.h UnregisterBadMemoryNotification +function memoryapi.h VirtualAlloc +function memoryapi.h VirtualAlloc2 +function memoryapi.h VirtualAlloc2FromApp =virtualallocfromapp +function memoryapi.h VirtualAllocEx +function memoryapi.h VirtualAllocExNuma +function memoryapi.h VirtualAllocFromApp +function memoryapi.h VirtualFree +function memoryapi.h VirtualFreeEx +function memoryapi.h VirtualLock +function memoryapi.h VirtualProtect +function memoryapi.h VirtualProtectEx +function memoryapi.h VirtualProtectFromApp +function memoryapi.h VirtualQuery +function memoryapi.h VirtualQueryEx +function memoryapi.h VirtualUnlock +function memoryapi.h WriteProcessMemory +function minwinbase.h LocalDiscard +function psapi.h GetMappedFileName =getmappedfilenamea +function sysinfoapi.h GetPhysicallyInstalledSystemMemory +function sysinfoapi.h GlobalMemoryStatusEx +function winbase.h AddSecureMemoryCacheCallback +function winbase.h CreateFileMappingA +function winbase.h CreateFileMappingNuma =createfilemappingnumaa +function winbase.h GetProcessDEPPolicy +function winbase.h GetSystemDEPPolicy +function winbase.h GlobalAlloc +function winbase.h GlobalDiscard +function winbase.h GlobalFlags +function winbase.h GlobalFree +function winbase.h GlobalHandle +function winbase.h GlobalLock +function winbase.h GlobalMemoryStatus +function winbase.h GlobalReAlloc +function winbase.h GlobalSize +function winbase.h GlobalUnlock +function winbase.h IsBadCodePtr +function winbase.h IsBadReadPtr +function winbase.h IsBadStringPtr =isbadstringptra +function winbase.h IsBadWritePtr +function winbase.h LocalAlloc +function winbase.h LocalFlags +function winbase.h LocalFree +function winbase.h LocalHandle +function winbase.h LocalLock +function winbase.h LocalReAlloc +function winbase.h LocalSize +function winbase.h LocalUnlock +function winbase.h MapUserPhysicalPagesScatter +function winbase.h MapViewOfFileExNuma +function winbase.h OpenFileMapping =openfilemappinga +function winbase.h RemoveSecureMemoryCacheCallback +function winbase.h SetProcessDEPPolicy + +# Desktop App Technologies > System Services > Memory Management > Memory Management Reference > Memory Management Structures +struct memoryapi.h WIN32_MEMORY_RANGE_ENTRY +struct memoryapi.h WIN32_MEMORY_REGION_INFORMATION +struct minwinbase.h PROCESS_HEAP_ENTRY +struct sysinfoapi.h MEMORYSTATUSEX +struct winbase.h MEMORYSTATUS +struct winnt.h HEAP_OPTIMIZE_RESOURCES_INFORMATION +struct winnt.h MEM_ADDRESS_REQUIREMENTS +struct winnt.h MEM_EXTENDED_PARAMETER +struct winnt.h MEMORY_BASIC_INFORMATION + +# Desktop App Technologies > System Services > Power Management > Power Management Reference > Power Management Enumeration Types +enum powersetting.h EFFECTIVE_POWER_MODE +enum powrprof.h POWER_DATA_ACCESSOR +enum winnt.h POWER_ACTION +enum winnt.h POWER_PLATFORM_ROLE +enum winnt.h SYSTEM_POWER_CONDITION +enum winnt.h SYSTEM_POWER_STATE + +# Desktop App Technologies > System Services > Power Management > Power Management Reference > Power Management Functions +callback powrprof.h DeviceNotifyCallbackRoutine +function powerbase.h CallNtPowerInformation +function powerbase.h GetPwrCapabilities +function powerbase.h PowerDeterminePlatformRoleEx +function powerbase.h PowerRegisterSuspendResumeNotification +function powerbase.h PowerUnregisterSuspendResumeNotification +function powersetting.h EFFECTIVE_POWER_MODE_CALLBACK +function powersetting.h PowerGetActiveScheme +function powersetting.h PowerReadACValue +function powersetting.h PowerReadDCValue +function powersetting.h PowerRegisterForEffectivePowerModeNotifications +function powersetting.h PowerSetActiveScheme +function powersetting.h PowerSettingRegisterNotification +function powersetting.h PowerSettingUnregisterNotification +function powersetting.h PowerUnregisterFromEffectivePowerModeNotifications =powersettingunregisternotification +function powersetting.h PowerWriteACValueIndex +function powersetting.h PowerWriteDCValueIndex +function powrprof.h CanUserWritePwrScheme +function powrprof.h DeletePwrScheme +function powrprof.h DevicePowerClose +function powrprof.h DevicePowerEnumDevices +function powrprof.h DevicePowerOpen +function powrprof.h DevicePowerSetDeviceState +function powrprof.h EnumPwrSchemes +function powrprof.h GetActivePwrScheme +function powrprof.h GetCurrentPowerPolicies +function powrprof.h GetPwrDiskSpindownRange +function powrprof.h IsPwrHibernateAllowed +function powrprof.h IsPwrShutdownAllowed +function powrprof.h IsPwrSuspendAllowed +function powrprof.h PowerCanRestoreIndividualDefaultPowerScheme +function powrprof.h PowerCreatePossibleSetting +function powrprof.h PowerCreateSetting +function powrprof.h PowerDeleteScheme +function powrprof.h PowerDeterminePlatformRole +function powrprof.h PowerDuplicateScheme +function powrprof.h PowerEnumerate +function powrprof.h PowerImportPowerScheme +function powrprof.h PowerIsSettingRangeDefined +function powrprof.h PowerReadACDefaultIndex +function powrprof.h PowerReadACValueIndex +function powrprof.h PowerReadDCDefaultIndex +function powrprof.h PowerReadDCValueIndex +function powrprof.h PowerReadDescription +function powrprof.h PowerReadFriendlyName +function powrprof.h PowerReadIconResourceSpecifier +function powrprof.h PowerReadPossibleDescription +function powrprof.h PowerReadPossibleFriendlyName +function powrprof.h PowerReadPossibleValue +function powrprof.h PowerReadSettingAttributes +function powrprof.h PowerReadValueIncrement +function powrprof.h PowerReadValueMax +function powrprof.h PowerReadValueMin +function powrprof.h PowerReadValueUnitsSpecifier +function powrprof.h PowerRemovePowerSetting +function powrprof.h PowerReplaceDefaultPowerSchemes +function powrprof.h PowerReportThermalEvent +function powrprof.h PowerRestoreDefaultPowerSchemes +function powrprof.h PowerRestoreIndividualDefaultPowerScheme +function powrprof.h PowerSettingAccessCheck +function powrprof.h PowerSettingAccessCheckEx +function powrprof.h PowerWriteACDefaultIndex +function powrprof.h PowerWriteDCDefaultIndex +function powrprof.h PowerWriteDescription +function powrprof.h PowerWriteFriendlyName +function powrprof.h PowerWriteIconResourceSpecifier +function powrprof.h PowerWritePossibleDescription +function powrprof.h PowerWritePossibleFriendlyName +function powrprof.h PowerWritePossibleValue +function powrprof.h PowerWriteSettingAttributes +function powrprof.h PowerWriteValueIncrement +function powrprof.h PowerWriteValueMax +function powrprof.h PowerWriteValueMin +function powrprof.h PowerWriteValueUnitsSpecifier +function powrprof.h ReadGlobalPwrPolicy +function powrprof.h ReadProcessorPwrScheme +function powrprof.h ReadPwrScheme +function powrprof.h SetActivePwrScheme +function powrprof.h SetSuspendState +function powrprof.h WriteGlobalPwrPolicy +function powrprof.h WriteProcessorPwrScheme +function powrprof.h WritePwrScheme +function winbase.h GetDevicePowerState +function winbase.h GetSystemPowerStatus +function winbase.h IsSystemResumeAutomatic +function winbase.h PowerClearRequest +function winbase.h PowerCreateRequest +function winbase.h PowerSetRequest +function winbase.h RequestWakeupLatency +function winbase.h SetSystemPowerState +function winbase.h SetThreadExecutionState +function winuser.h RegisterPowerSettingNotification +function winuser.h RegisterSuspendResumeNotification +function winuser.h UnregisterPowerSettingNotification +function winuser.h UnregisterSuspendResumeNotification + +# Desktop App Technologies > System Services > Power Management > Power Management Reference > Power Management Structures +struct minwinbase.h REASON_CONTEXT +struct powrprof.h DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS +struct powrprof.h GLOBAL_MACHINE_POWER_POLICY +struct powrprof.h GLOBAL_POWER_POLICY +struct powrprof.h GLOBAL_USER_POWER_POLICY +struct powrprof.h MACHINE_POWER_POLICY +struct powrprof.h MACHINE_PROCESSOR_POWER_POLICY +struct powrprof.h POWER_POLICY +struct powrprof.h THERMAL_EVENT +struct powrprof.h USER_POWER_POLICY +struct winbase.h SYSTEM_POWER_STATUS +struct winnt.h ADMINISTRATOR_POWER_POLICY +struct winnt.h BATTERY_REPORTING_SCALE +struct winnt.h POWER_ACTION_POLICY +struct winnt.h PROCESSOR_POWER_POLICY +struct winnt.h PROCESSOR_POWER_POLICY_INFO +struct winnt.h SYSTEM_BATTERY_STATE +struct winnt.h SYSTEM_POWER_CAPABILITIES +struct winnt.h SYSTEM_POWER_LEVEL +struct winnt.h SYSTEM_POWER_POLICY +struct winuser.h POWERBROADCAST_SETTING + +# Desktop App Technologies > System Services > Processes and Threads > About Processes and Threads > Scheduling > Multimedia Class Scheduler Service > Real-Time Work Queue API +enum rtworkq.h RTWQ_WORKQUEUE_TYPE +function rtworkq.h RtwqAddPeriodicCallback +function rtworkq.h RtwqAllocateSerialWorkQueue +function rtworkq.h RtwqAllocateWorkQueue +function rtworkq.h RtwqBeginRegisterWorkQueueWithMMCSS +function rtworkq.h RtwqBeginUnregisterWorkQueueWithMMCSS +function rtworkq.h RtwqCancelDeadline +function rtworkq.h RtwqCancelWorkItem +function rtworkq.h RtwqCreateAsyncResult +function rtworkq.h RtwqEndRegisterWorkQueueWithMMCSS +function rtworkq.h RtwqGetWorkQueueMMCSSClass +function rtworkq.h RtwqGetWorkQueueMMCSSPriority +function rtworkq.h RtwqGetWorkQueueMMCSSTaskId +function rtworkq.h RtwqInvokeCallback +function rtworkq.h RtwqJoinWorkQueue +function rtworkq.h RtwqLockPlatform +function rtworkq.h RtwqLockSharedWorkQueue +function rtworkq.h RtwqLockWorkQueue +function rtworkq.h RtwqPutWaitingWorkItem +function rtworkq.h RtwqPutWorkItem +function rtworkq.h RtwqRegisterPlatformEvents +function rtworkq.h RtwqRegisterPlatformWithMMCSS +function rtworkq.h RtwqRemovePeriodicCallback +function rtworkq.h RtwqScheduleWorkItem +function rtworkq.h RtwqSetDeadline +function rtworkq.h RtwqSetDeadline2 +function rtworkq.h RtwqSetLongRunning +function rtworkq.h RtwqShutdown +function rtworkq.h RtwqStartup +function rtworkq.h RtwqUnjoinWorkQueue +function rtworkq.h RtwqUnlockPlatform +function rtworkq.h RtwqUnlockWorkQueue +function rtworkq.h RtwqUnregisterPlatformEvents +function rtworkq.h RtwqUnregisterPlatformFromMMCSS +interface rtworkq.h IRtwqAsyncCallback +interface rtworkq.h IRtwqAsyncResult +interface rtworkq.h IRtwqPlatformEvents + +# Desktop App Technologies > System Services > Processes and Threads > Process and Thread Reference > Process and Thread Enumerations +enum dispatcherqueue.h DISPATCHERQUEUE_THREAD_APARTMENTTYPE +enum dispatcherqueue.h DISPATCHERQUEUE_THREAD_TYPE +enum processthreadsapi.h PROCESS_MEMORY_EXHAUSTION_TYPE +enum winnt.h JOB_OBJECT_NET_RATE_CONTROL_FLAGS +enum winnt.h LOGICAL_PROCESSOR_RELATIONSHIP +enum winnt.h PROCESS_MITIGATION_POLICY +enum winnt.h PROCESSOR_CACHE_TYPE +struct winbase.h UMS_THREAD_INFO_CLASS =ums_system_thread_information + +# Desktop App Technologies > System Services > Processes and Threads > Process and Thread Reference > Process and Thread Functions +callback winbase.h FiberProc =pfiber_start_routine +callback winnt.h PFLS_CALLBACK_FUNCTION +callback winnt.h UmsSchedulerProc =rtl_ums_scheduler_entry_point +function avrt.h AvQuerySystemResponsiveness +function avrt.h AvRevertMmThreadCharacteristics +function avrt.h AvRtCreateThreadOrderingGroup +function avrt.h AvRtCreateThreadOrderingGroupEx =avrtcreatethreadorderinggroupexa +function avrt.h AvRtDeleteThreadOrderingGroup +function avrt.h AvRtJoinThreadOrderingGroup +function avrt.h AvRtLeaveThreadOrderingGroup +function avrt.h AvRtWaitOnThreadOrderingGroup +function avrt.h AvSetMmMaxThreadCharacteristics =avsetmmmaxthreadcharacteristicsa +function avrt.h AvSetMmThreadCharacteristics =avsetmmthreadcharacteristicsa +function avrt.h AvSetMmThreadPriority +function dispatcherqueue.h CreateDispatcherQueueController +function fibersapi.h FlsAlloc +function fibersapi.h FlsFree +function fibersapi.h FlsGetValue +function fibersapi.h FlsSetValue +function fibersapi.h IsThreadAFiber +function jobapi.h IsProcessInJob +function jobapi2.h AssignProcessToJobObject +function jobapi2.h FreeMemoryJobObject +function jobapi2.h QueryInformationJobObject +function jobapi2.h QueryIoRateControlInformationJobObject +function jobapi2.h SetInformationJobObject +function jobapi2.h SetIoRateControlInformationJobObject +function jobapi2.h TerminateJobObject +function memoryapi.h GetProcessWorkingSetSize +function memoryapi.h GetProcessWorkingSetSizeEx +function memoryapi.h SetProcessWorkingSetSize +function memoryapi.h SetProcessWorkingSetSizeEx +function processenv.h FreeEnvironmentStrings =freeenvironmentstringsa +function processenv.h GetCommandLine =getcommandlinea +function processenv.h NeedCurrentDirectoryForExePath =needcurrentdirectoryforexepatha +function processthreadsapi.h CreateProcess =createprocessa +function processthreadsapi.h CreateProcessAsUser =createprocessasusera +function processthreadsapi.h CreateRemoteThread +function processthreadsapi.h CreateRemoteThreadEx +function processthreadsapi.h CreateThread +function processthreadsapi.h DeleteProcThreadAttributeList +function processthreadsapi.h ExitProcess +function processthreadsapi.h ExitThread +function processthreadsapi.h FlushProcessWriteBuffers +function processthreadsapi.h GetCurrentProcess +function processthreadsapi.h GetCurrentProcessId +function processthreadsapi.h GetCurrentProcessorNumber +function processthreadsapi.h GetCurrentProcessorNumberEx +function processthreadsapi.h GetCurrentThread +function processthreadsapi.h GetCurrentThreadId +function processthreadsapi.h GetCurrentThreadStackLimits +function processthreadsapi.h GetExitCodeProcess +function processthreadsapi.h GetExitCodeThread +function processthreadsapi.h GetPriorityClass +function processthreadsapi.h GetProcessDefaultCpuSets =setprocessdefaultcpusets +function processthreadsapi.h GetProcessHandleCount +function processthreadsapi.h GetProcessId +function processthreadsapi.h GetProcessIdOfThread +function processthreadsapi.h GetProcessInformation +function processthreadsapi.h GetProcessMitigationPolicy +function processthreadsapi.h GetProcessPriorityBoost +function processthreadsapi.h GetProcessShutdownParameters +function processthreadsapi.h GetProcessTimes +function processthreadsapi.h GetProcessVersion +function processthreadsapi.h GetStartupInfo =getstartupinfow +function processthreadsapi.h GetThreadDescription +function processthreadsapi.h GetThreadId +function processthreadsapi.h GetThreadIdealProcessorEx +function processthreadsapi.h GetThreadInformation +function processthreadsapi.h GetThreadIOPendingFlag +function processthreadsapi.h GetThreadPriority +function processthreadsapi.h GetThreadPriorityBoost +function processthreadsapi.h GetThreadSelectedCpuSets +function processthreadsapi.h GetThreadTimes +function processthreadsapi.h InitializeProcThreadAttributeList +function processthreadsapi.h IsProcessCritical +function processthreadsapi.h OpenProcess +function processthreadsapi.h OpenThread +function processthreadsapi.h QueryProcessAffinityUpdateMode +function processthreadsapi.h QueryProtectedPolicy +function processthreadsapi.h ResumeThread +function processthreadsapi.h SetPriorityClass +function processthreadsapi.h SetProcessAffinityUpdateMode +function processthreadsapi.h SetProcessDefaultCpuSets +function processthreadsapi.h SetProcessInformation +function processthreadsapi.h SetProcessMitigationPolicy +function processthreadsapi.h SetProcessPriorityBoost +function processthreadsapi.h SetProcessShutdownParameters +function processthreadsapi.h SetProtectedPolicy +function processthreadsapi.h SetThreadDescription +function processthreadsapi.h SetThreadIdealProcessor +function processthreadsapi.h SetThreadIdealProcessorEx +function processthreadsapi.h SetThreadInformation +function processthreadsapi.h SetThreadPriority +function processthreadsapi.h SetThreadPriorityBoost +function processthreadsapi.h SetThreadSelectedCpuSets +function processthreadsapi.h SetThreadStackGuarantee +function processthreadsapi.h SuspendThread +function processthreadsapi.h SwitchToThread +function processthreadsapi.h TerminateProcess +function processthreadsapi.h TerminateThread +function processthreadsapi.h TlsAlloc +function processthreadsapi.h TlsFree +function processthreadsapi.h TlsGetValue +function processthreadsapi.h TlsSetValue +function processthreadsapi.h UpdateProcThreadAttribute +function processtopologyapi.h GetProcessGroupAffinity +function processtopologyapi.h GetThreadGroupAffinity +function processtopologyapi.h SetThreadGroupAffinity +function realtimeapiset.h QueryIdleProcessorCycleTime +function realtimeapiset.h QueryIdleProcessorCycleTimeEx +function realtimeapiset.h QueryProcessCycleTime +function realtimeapiset.h QueryThreadCycleTime +function synchapi.h Sleep +function synchapi.h SleepEx +function sysinfoapi.h GetLogicalProcessorInformation +function sysinfoapi.h GetLogicalProcessorInformationEx +function sysinfoapi.h GetProcessorSystemCycleTime +function systemtopologyapi.h GetNumaHighestNodeNumber +function systemtopologyapi.h GetNumaNodeProcessorMaskEx +function systemtopologyapi.h GetNumaProximityNodeEx +function threadpoolapiset.h CallbackMayRunLong +function threadpoolapiset.h CancelThreadpoolIo +function threadpoolapiset.h CloseThreadpool +function threadpoolapiset.h CloseThreadpoolCleanupGroup +function threadpoolapiset.h CloseThreadpoolCleanupGroupMembers +function threadpoolapiset.h CloseThreadpoolIo +function threadpoolapiset.h CloseThreadpoolTimer +function threadpoolapiset.h CloseThreadpoolWait +function threadpoolapiset.h CloseThreadpoolWork +function threadpoolapiset.h CreateThreadpool +function threadpoolapiset.h CreateThreadpoolCleanupGroup +function threadpoolapiset.h CreateThreadpoolIo +function threadpoolapiset.h CreateThreadpoolTimer +function threadpoolapiset.h CreateThreadpoolWait +function threadpoolapiset.h CreateThreadpoolWork +function threadpoolapiset.h DisassociateCurrentThreadFromCallback +function threadpoolapiset.h FreeLibraryWhenCallbackReturns +function threadpoolapiset.h IsThreadpoolTimerSet +function threadpoolapiset.h LeaveCriticalSectionWhenCallbackReturns +function threadpoolapiset.h QueryThreadpoolStackInformation +function threadpoolapiset.h ReleaseMutexWhenCallbackReturns +function threadpoolapiset.h ReleaseSemaphoreWhenCallbackReturns +function threadpoolapiset.h SetEventWhenCallbackReturns +function threadpoolapiset.h SetThreadpoolStackInformation +function threadpoolapiset.h SetThreadpoolThreadMaximum +function threadpoolapiset.h SetThreadpoolThreadMinimum +function threadpoolapiset.h SetThreadpoolTimer +function threadpoolapiset.h SetThreadpoolTimerEx +function threadpoolapiset.h SetThreadpoolWait +function threadpoolapiset.h SetThreadpoolWaitEx +function threadpoolapiset.h StartThreadpoolIo +function threadpoolapiset.h SubmitThreadpoolWork +function threadpoolapiset.h TrySubmitThreadpoolCallback +function threadpoolapiset.h WaitForThreadpoolIoCallbacks +function threadpoolapiset.h WaitForThreadpoolTimerCallbacks +function threadpoolapiset.h WaitForThreadpoolWaitCallbacks +function threadpoolapiset.h WaitForThreadpoolWorkCallbacks +function threadpoollegacyapiset.h QueueUserWorkItem +function winbase.h BindIoCompletionCallback +function winbase.h ConvertFiberToThread +function winbase.h ConvertThreadToFiber +function winbase.h ConvertThreadToFiberEx +function winbase.h CreateFiber +function winbase.h CreateFiberEx +function winbase.h CreateJobObject =createjobobjecta +function winbase.h CreateProcessWithLogonW +function winbase.h CreateProcessWithTokenW +function winbase.h CreateUmsCompletionList +function winbase.h CreateUmsThreadContext +function winbase.h DeleteFiber +function winbase.h DeleteUmsCompletionList +function winbase.h DeleteUmsThreadContext +function winbase.h DequeueUmsCompletionListItems +function winbase.h DestroyThreadpoolEnvironment +function winbase.h EnterUmsSchedulingMode +function winbase.h ExecuteUmsThread +function winbase.h GetActiveProcessorCount +function winbase.h GetActiveProcessorGroupCount +function winbase.h GetCurrentUmsThread +function winbase.h GetEnvironmentVariable +function winbase.h GetMaximumProcessorCount +function winbase.h GetMaximumProcessorGroupCount +function winbase.h GetNextUmsListItem +function winbase.h GetNumaAvailableMemoryNode +function winbase.h GetNumaAvailableMemoryNodeEx +function winbase.h GetNumaNodeNumberFromHandle +function winbase.h GetNumaNodeProcessorMask +function winbase.h GetNumaProcessorNode +function winbase.h GetNumaProcessorNodeEx +function winbase.h GetNumaProximityNode +function winbase.h GetProcessAffinityMask +function winbase.h GetProcessIoCounters +function winbase.h GetUmsCompletionListEvent +function winbase.h GetUmsSystemThreadInformation +function winbase.h InitializeThreadpoolEnvironment +function winbase.h OpenJobObject =openjobobjecta +function winbase.h QueryFullProcessImageName =queryfullprocessimagenamea +function winbase.h QueryUmsThreadInformation +function winbase.h SetEnvironmentVariable +function winbase.h SetProcessAffinityMask +function winbase.h SetThreadAffinityMask +function winbase.h SetThreadpoolCallbackCleanupGroup +function winbase.h SetThreadpoolCallbackLibrary +function winbase.h SetThreadpoolCallbackPersistent +function winbase.h SetThreadpoolCallbackPool +function winbase.h SetThreadpoolCallbackPriority +function winbase.h SetThreadpoolCallbackRunsLong +function winbase.h SetUmsThreadInformation +function winbase.h SwitchToFiber +function winbase.h UmsThreadYield +function winbase.h WinExec +function winnt.h TpDestroyCallbackEnviron +function winnt.h TpInitializeCallbackEnviron +function winnt.h TpSetCallbackActivationContext +function winnt.h TpSetCallbackCleanupGroup +function winnt.h TpSetCallbackFinalizationCallback +function winnt.h TpSetCallbackLongFunction +function winnt.h TpSetCallbackNoActivationContext +function winnt.h TpSetCallbackPersistent +function winnt.h TpSetCallbackPriority +function winnt.h TpSetCallbackRaceWithDll +function winnt.h TpSetCallbackThreadpool +function winternl.h NtQueryInformationProcess +function winternl.h NtQueryInformationThread +function winuser.h AttachThreadInput +function winuser.h GetAutoRotationState +function winuser.h GetDisplayAutoRotationPreferences +function winuser.h GetDisplayAutoRotationPreferencesByProcessId +function winuser.h GetGuiResources +function winuser.h IsImmersiveProcess +function winuser.h IsWow64Message +function winuser.h SetDisplayAutoRotationPreferences +function winuser.h SetProcessRestrictionExemption +function winuser.h UserHandleGrantAccess +function winuser.h WaitForInputIdle +function wow64apiset.h IsWow64Process +function wow64apiset.h IsWow64Process2 +function wow64apiset.h Wow64SuspendThread + +# Desktop App Technologies > System Services > Processes and Threads > Process and Thread Reference > Process and Thread Macros +function winnt.h GetCurrentFiber +function winnt.h GetFiberData + +# Desktop App Technologies > System Services > Processes and Threads > Process and Thread Reference > Process and Thread Structures +enum winuser.h AR_STATE +enum winuser.h ORIENTATION_PREFERENCE +function processthreadsapi.h GetProcessMitigationPolicy +struct dispatcherqueue.h DispatcherQueueOptions +struct jobapi2.h JOBOBJECT_IO_RATE_CONTROL_INFORMATION +struct processthreadsapi.h APP_MEMORY_INFORMATION +struct processthreadsapi.h MEMORY_PRIORITY_INFORMATION +struct processthreadsapi.h PROCESS_INFORMATION +struct processthreadsapi.h PROCESS_MEMORY_EXHAUSTION_INFO +struct processthreadsapi.h PROCESS_POWER_THROTTLING_STATE +struct processthreadsapi.h PROCESS_PROTECTION_LEVEL_INFORMATION +struct processthreadsapi.h STARTUPINFO =startupinfoa +struct processthreadsapi.h THREAD_POWER_THROTTLING_STATE +struct winbase.h STARTUPINFOEX =startupinfoexa +struct winbase.h UMS_SCHEDULER_STARTUP_INFO +struct winbase.h UMS_SYSTEM_THREAD_INFORMATION +struct winnt.h CACHE_DESCRIPTOR +struct winnt.h CACHE_RELATIONSHIP +struct winnt.h GROUP_AFFINITY +struct winnt.h GROUP_RELATIONSHIP +struct winnt.h IO_COUNTERS +struct winnt.h JOBOBJECT_ASSOCIATE_COMPLETION_PORT +struct winnt.h JOBOBJECT_BASIC_ACCOUNTING_INFORMATION +struct winnt.h JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION +struct winnt.h JOBOBJECT_BASIC_LIMIT_INFORMATION +struct winnt.h JOBOBJECT_BASIC_PROCESS_ID_LIST +struct winnt.h JOBOBJECT_BASIC_UI_RESTRICTIONS +struct winnt.h JOBOBJECT_CPU_RATE_CONTROL_INFORMATION +struct winnt.h JOBOBJECT_END_OF_JOB_TIME_INFORMATION +struct winnt.h JOBOBJECT_EXTENDED_LIMIT_INFORMATION +struct winnt.h JOBOBJECT_LIMIT_VIOLATION_INFORMATION +struct winnt.h JOBOBJECT_LIMIT_VIOLATION_INFORMATION_2 +struct winnt.h JOBOBJECT_NET_RATE_CONTROL_INFORMATION +struct winnt.h JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION +struct winnt.h JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 +struct winnt.h JOBOBJECT_SECURITY_LIMIT_INFORMATION +struct winnt.h NUMA_NODE_RELATIONSHIP +struct winnt.h PROCESS_MITIGATION_ASLR_POLICY +struct winnt.h PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY +struct winnt.h PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY +struct winnt.h PROCESS_MITIGATION_DEP_POLICY +struct winnt.h PROCESS_MITIGATION_DYNAMIC_CODE_POLICY +struct winnt.h PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY +struct winnt.h PROCESS_MITIGATION_FONT_DISABLE_POLICY +struct winnt.h PROCESS_MITIGATION_IMAGE_LOAD_POLICY +struct winnt.h PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY +struct winnt.h PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY +struct winnt.h PROCESSOR_GROUP_INFO +struct winnt.h PROCESSOR_NUMBER +struct winnt.h PROCESSOR_RELATIONSHIP +struct winnt.h SYSTEM_CPU_SET_INFORMATION +struct winnt.h SYSTEM_LOGICAL_PROCESSOR_INFORMATION +struct winnt.h SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX +struct winnt.h UMS_CREATE_THREAD_ATTRIBUTES +struct winternl.h PEB +struct winternl.h PEB_LDR_DATA +struct winternl.h RTL_USER_PROCESS_PARAMETERS +struct winternl.h TEB + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop ActiveX control > Remote Desktop ActiveX control reference > Remote Desktop ActiveX control interfaces +interface rdpappcontainerclient.h IRemoteDesktopClient +interface rdpappcontainerclient.h IRemoteDesktopClientActions +interface rdpappcontainerclient.h IRemoteDesktopClientSettings +interface rdpappcontainerclient.h IRemoteDesktopClientTouchPointer + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop ActiveX control > Remote Desktop ActiveX control reference > Remote Desktop ActiveX control enumerations +enum rdpappcontainerclient.h RemoteActionType +enum rdpappcontainerclient.h SnapshotEncodingType +enum rdpappcontainerclient.h SnapshotFormatType + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Enumerations +enum wtsdefs.h WRDS_CONNECTION_SETTING_LEVEL +enum wtsdefs.h WRDS_LISTENER_SETTING_LEVEL +enum wtsdefs.h WRDS_SETTING_LEVEL +enum wtsdefs.h WRDS_SETTING_STATUS +enum wtsdefs.h WRDS_SETTING_TYPE +enum wtsdefs.h WTS_CERT_TYPE +enum wtsdefs.h WTS_LOGON_ERROR_REDIRECTOR_RESPONSE +enum wtsdefs.h WTS_RCM_DRAIN_STATE +enum wtsdefs.h WTS_RCM_SERVICE_STATE + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Interfaces > IWRdsRemoteFXGraphicsConnection +interface wrdsgraphicschannels.h GetVirtualChannelTransport method =iwrdsgraphicschannelmanager + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Interfaces > Deprecated Desktop Protocol Provider Interfaces +interface wtsprotocol.h IWTSProtocolConnection +interface wtsprotocol.h IWTSProtocolConnectionCallback +interface wtsprotocol.h IWTSProtocolLicenseConnection +interface wtsprotocol.h IWTSProtocolListener +interface wtsprotocol.h IWTSProtocolListenerCallback +interface wtsprotocol.h IWTSProtocolLogonErrorRedirector +interface wtsprotocol.h IWTSProtocolManager +interface wtsprotocol.h IWTSProtocolShadowCallback +interface wtsprotocol.h IWTSProtocolShadowConnection + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Interfaces +interface wtsprotocol.h IWRdsProtocolConnection +interface wtsprotocol.h IWRdsProtocolConnectionCallback +interface wtsprotocol.h IWRdsProtocolLicenseConnection +interface wtsprotocol.h IWRdsProtocolListener +interface wtsprotocol.h IWRdsProtocolListenerCallback +interface wtsprotocol.h IWRdsProtocolLogonErrorRedirector +interface wtsprotocol.h IWRdsProtocolManager +interface wtsprotocol.h IWRdsProtocolSettings +interface wtsprotocol.h IWRdsProtocolShadowCallback +interface wtsprotocol.h IWRdsProtocolShadowConnection + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Structures +struct wtsdefs.h WRDS_CONNECTION_SETTINGS +struct wtsdefs.h WRDS_CONNECTION_SETTINGS_1 +struct wtsdefs.h WRDS_DYNAMIC_TIME_ZONE_INFORMATION +struct wtsdefs.h WRDS_LISTENER_SETTINGS +struct wtsdefs.h WRDS_LISTENER_SETTINGS_1 +struct wtsdefs.h WRDS_SETTINGS +struct wtsdefs.h WRDS_SETTINGS_1 +struct wtsdefs.h WTS_CACHE_STATS +struct wtsdefs.h WTS_CLIENT_DATA +struct wtsdefs.h WTS_DISPLAY_IOCTL +struct wtsdefs.h WTS_LICENSE_CAPABILITIES +struct wtsdefs.h WTS_POLICY_DATA +struct wtsdefs.h WTS_PROPERTY_VALUE +struct wtsdefs.h WTS_PROTOCOL_CACHE +struct wtsdefs.h WTS_PROTOCOL_COUNTERS +struct wtsdefs.h WTS_PROTOCOL_STATUS +struct wtsdefs.h WTS_SERVICE_STATE +struct wtsdefs.h WTS_SESSION_ID +struct wtsdefs.h WTS_SMALL_RECT +struct wtsdefs.h WTS_SOCKADDR +struct wtsdefs.h WTS_SYSTEMTIME +struct wtsdefs.h WTS_TIME_ZONE_INFORMATION +struct wtsdefs.h WTS_USER_CREDENTIAL +struct wtsdefs.h WTS_USER_DATA + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Protocol Provider API > Remote Desktop Protocol Provider reference > Remote Desktop Protocol Provider Unions +struct wtsdefs.h WRDS_CONNECTION_SETTING +struct wtsdefs.h WRDS_LISTENER_SETTING +struct wtsdefs.h WRDS_SETTING +struct wtsdefs.h WTS_CACHE_STATS_UN + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services virtual channels > Dynamic virtual channels reference > DVC Client APIs +interface tsvirtualchannels.h IWTSListener +interface tsvirtualchannels.h IWTSListenerCallback +interface tsvirtualchannels.h IWTSPlugin +interface tsvirtualchannels.h IWTSVirtualChannel +interface tsvirtualchannels.h IWTSVirtualChannelCallback +interface tsvirtualchannels.h IWTSVirtualChannelManager + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services virtual channels > Graphics virtual channels reference > Graphics virtual channels interfaces +interface wrdsgraphicschannels.h IWRdsGraphicsChannel +interface wrdsgraphicschannels.h IWRdsGraphicsChannelEvents +interface wrdsgraphicschannels.h IWRdsGraphicsChannelManager + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services virtual channels > Graphics virtual channels reference > Graphics virtual channels enumerations +enum wrdsgraphicschannels.h WRdsGraphicsChannelType + +# Desktop App Technologies > System Services > Remote Desktop Services > RemoteFX Media Redirection API > RemoteFX Media Redirection API reference > RemoteFX Media Redirection API functions +function wtshintapi.h WTSSetRenderHint + +# Desktop App Technologies > System Services > Remote Desktop Services > RemoteFX Media Redirection API > RemoteFX Media Redirection API reference > RemoteFX Media Redirection API interfaces +interface tsvirtualchannels.h IWTSBitmapRenderer +interface tsvirtualchannels.h IWTSBitmapRendererCallback +interface tsvirtualchannels.h IWTSBitmapRenderService +interface tsvirtualchannels.h IWTSPluginServiceProvider + +# Desktop App Technologies > System Services > Remote Desktop Services > RemoteFX Media Redirection API > RemoteFX Media Redirection API reference > RemoteFX Media Redirection API structures +struct tsvirtualchannels.h BITMAP_RENDERER_STATISTICS + +# Desktop App Technologies > System Services > Remote Desktop Services > Personal desktop task agent API reference > Personal desktop task agent API enumerations +enum sessdirpublictypes.h RDV_TASK_STATUS + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Reference for the ADSI Extension for Remote Desktop Services User Configuration +interface tsuserex.h IADsTSUserEx + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Gateway API reference > Remote Desktop Gateway Enumeration Types +enum tsgpolicyengine.h AAAccountingDataType +enum tsgpolicyengine.h AAAuthSchemes +enum tsgpolicyengine.h PolicyAttributeType + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Gateway API reference > Remote Desktop Gateway Interfaces +interface tsgauthenticationengine.h ITSGAuthenticateUserSink +interface tsgauthenticationengine.h ITSGAuthenticationEngine +interface tsgpolicyengine.h ITSGAccountingEngine +interface tsgpolicyengine.h ITSGAuthorizeConnectionSink +interface tsgpolicyengine.h ITSGAuthorizeResourceSink +interface tsgpolicyengine.h ITSGPolicyEngine + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Gateway API reference > Remote Desktop Gateway Structures +struct tsgpolicyengine.h AAAccountingData + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services API reference > Remote Desktop Services API Enumeration Types +enum tssbx.h WTSSBX_ADDRESS_FAMILY +enum tssbx.h WTSSBX_MACHINE_DRAIN +enum tssbx.h WTSSBX_MACHINE_SESSION_MODE +enum tssbx.h WTSSBX_MACHINE_STATE +enum tssbx.h WTSSBX_NOTIFICATION_TYPE +enum tssbx.h WTSSBX_SESSION_STATE +enum wtsapi32.h WTS_CONFIG_CLASS +enum wtsapi32.h WTS_CONFIG_SOURCE +enum wtsapi32.h WTS_CONNECTSTATE_CLASS +enum wtsapi32.h WTS_INFO_CLASS +enum wtsapi32.h WTS_TYPE_CLASS +enum wtsapi32.h WTS_VIRTUAL_CLASS + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services API reference > Remote Desktop Services API Functions +callback cchannel.h VirtualChannelClose +callback cchannel.h VirtualChannelEntry +callback cchannel.h VirtualChannelInit +callback cchannel.h VirtualChannelInitEvent =channel_init_event_fn +callback cchannel.h VirtualChannelOpen +callback cchannel.h VirtualChannelOpenEvent =channel_open_event_fn +callback cchannel.h VirtualChannelWrite +function processthreadsapi.h ProcessIdToSessionId +function winbase.h WTSGetActiveConsoleSessionId +function wtsapi32.h WTSCloseServer +function wtsapi32.h WTSConnectSession =wtsconnectsessiona +function wtsapi32.h WTSCreateListener =wtscreatelistenera +function wtsapi32.h WTSDisconnectSession +function wtsapi32.h WTSEnableChildSessions +function wtsapi32.h WTSEnumerateListeners =wtsenumeratelistenersa +function wtsapi32.h WTSEnumerateProcesses =wtsenumerateprocessesa +function wtsapi32.h WTSEnumerateProcessesEx =wtsenumerateprocessesexa +function wtsapi32.h WTSEnumerateServers =wtsenumerateserversa +function wtsapi32.h WTSEnumerateSessions =wtsenumeratesessionsa +function wtsapi32.h WTSEnumerateSessionsEx =wtsenumeratesessionsexa +function wtsapi32.h WTSFreeMemory +function wtsapi32.h WTSFreeMemoryEx =wtsfreememoryexa +function wtsapi32.h WTSGetChildSessionId +function wtsapi32.h WTSGetListenerSecurity =wtsgetlistenersecuritya +function wtsapi32.h WTSIsChildSessionsEnabled +function wtsapi32.h WTSLogoffSession +function wtsapi32.h WTSOpenServer =wtsopenservera +function wtsapi32.h WTSOpenServerEx =wtsopenserverexa +function wtsapi32.h WTSQueryListenerConfig =wtsquerylistenerconfiga +function wtsapi32.h WTSQuerySessionInformation =wtsquerysessioninformationa +function wtsapi32.h WTSQueryUserConfig =wtsqueryuserconfiga +function wtsapi32.h WTSQueryUserToken +function wtsapi32.h WTSRegisterSessionNotification +function wtsapi32.h WTSRegisterSessionNotificationEx +function wtsapi32.h WTSSendMessage =wtssendmessagea +function wtsapi32.h WTSSetListenerSecurity =wtssetlistenersecuritya +function wtsapi32.h WTSSetUserConfig =wtssetuserconfiga +function wtsapi32.h WTSShutdownSystem +function wtsapi32.h WTSStartRemoteControlSession =wtsstartremotecontrolsessiona +function wtsapi32.h WTSStopRemoteControlSession +function wtsapi32.h WTSTerminateProcess +function wtsapi32.h WTSUnRegisterSessionNotification +function wtsapi32.h WTSUnRegisterSessionNotificationEx +function wtsapi32.h WTSVirtualChannelClose +function wtsapi32.h WTSVirtualChannelOpen +function wtsapi32.h WTSVirtualChannelOpenEx +function wtsapi32.h WTSVirtualChannelPurgeInput +function wtsapi32.h WTSVirtualChannelPurgeOutput +function wtsapi32.h WTSVirtualChannelQuery +function wtsapi32.h WTSVirtualChannelRead +function wtsapi32.h WTSVirtualChannelWrite +function wtsapi32.h WTSWaitSystemEvent + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services API reference > Remote Desktop Services API Structures +struct cchannel.h CHANNEL_ENTRY_POINTS +struct pchannel.h CHANNEL_PDU_HEADER +struct tssbx.h WTSSBX_IP_ADDRESS +struct tssbx.h WTSSBX_MACHINE_CONNECT_INFO +struct tssbx.h WTSSBX_MACHINE_INFO +struct tssbx.h WTSSBX_SESSION_INFO +struct winuser.h WTSSESSION_NOTIFICATION +struct wtsapi32.h WTS_CLIENT_ADDRESS +struct wtsapi32.h WTS_CLIENT_DISPLAY +struct wtsapi32.h WTS_PROCESS_INFO =wts_process_infoa +struct wtsapi32.h WTS_PROCESS_INFO_EX =wts_process_info_exa +struct wtsapi32.h WTS_SERVER_INFO =wts_server_infoa +struct wtsapi32.h WTS_SESSION_ADDRESS +struct wtsapi32.h WTS_SESSION_INFO =wts_session_infoa +struct wtsapi32.h WTS_SESSION_INFO_1 =wts_session_info_1a +struct wtsapi32.h WTSCLIENT =wtsclienta +struct wtsapi32.h WTSCONFIGINFO =wtsconfiginfoa +struct wtsapi32.h WTSINFO =wtsinfoa +struct wtsapi32.h WTSINFOEX =wtsinfoexa +struct wtsapi32.h WTSINFOEX_LEVEL1 =wtsinfoex_level1_a +struct wtsapi32.h WTSLISTENERCONFIG =wtslistenerconfiga +struct wtsapi32.h WTSUSERCONFIG =wtsuserconfiga + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services API reference > Remote Desktop Services API Unions +struct wtsapi32.h WTSINFOEX_LEVEL =wtsinfoex_level_a + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services AudioEndpoint API reference > Remote Desktop Services AudioEndpoint Enumeration Types +enum audioapotypes.h APO_BUFFER_FLAGS +enum audioengineendpoint.h AE_POSITION_FLAGS + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services AudioEndpoint API reference > Remote Desktop Services AudioEndpoint Interfaces +interface audioengineendpoint.h IAudioDeviceEndpoint +interface audioengineendpoint.h IAudioEndpoint +interface audioengineendpoint.h IAudioEndpointControl +interface audioengineendpoint.h IAudioEndpointRT +interface audioengineendpoint.h IAudioInputEndpointRT +interface audioengineendpoint.h IAudioOutputEndpointRT + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Services AudioEndpoint API reference > Remote Desktop Services AudioEndpoint Structures +struct audioapotypes.h APO_CONNECTION_PROPERTY +struct audioengineendpoint.h AE_CURRENT_POSITION + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Virtualization API reference > Remote Desktop Virtualization Enumerations +enum sbtsv.h TS_SB_SORT_BY +enum sessdirpublictypes.h TARGET_CHANGE_TYPE +enum sessdirpublictypes.h TARGET_STATE +enum sessdirpublictypes.h TARGET_TYPE +enum sessdirpublictypes.h TSSD_AddrV46Type +enum sessdirpublictypes.h TSSESSION_STATE + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Virtualization API reference > Remote Desktop Virtualization Interfaces +interface sbtsv.h ITsSbBaseNotifySink +interface sbtsv.h ITsSbClientConnection +interface sbtsv.h ITsSbClientConnectionPropertySet +interface sbtsv.h ITsSbEnvironment +interface sbtsv.h ITsSbEnvironmentPropertySet +interface sbtsv.h ITsSbFilterPluginStore +interface sbtsv.h ITsSbGenericNotifySink +interface sbtsv.h ITsSbGlobalStore +interface sbtsv.h ITsSbLoadBalanceResult +interface sbtsv.h ITsSbLoadBalancing +interface sbtsv.h ITsSbLoadBalancingNotifySink +interface sbtsv.h ITsSbOrchestration +interface sbtsv.h ITsSbOrchestrationNotifySink +interface sbtsv.h ITsSbPlacement +interface sbtsv.h ITsSbPlacementNotifySink +interface sbtsv.h ITsSbPlugin +interface sbtsv.h ITsSbPluginNotifySink +interface sbtsv.h ITsSbPluginPropertySet +interface sbtsv.h ITsSbPropertySet +interface sbtsv.h ITsSbProvider +interface sbtsv.h ITsSbProvisioning +interface sbtsv.h ITsSbProvisioningPluginNotifySink +interface sbtsv.h ITsSbResourceNotification +interface sbtsv.h ITsSbResourceNotificationEx +interface sbtsv.h ITsSbResourcePlugin +interface sbtsv.h ITsSbResourcePluginStore +interface sbtsv.h ITsSbServiceNotification +interface sbtsv.h ITsSbSession +interface sbtsv.h ITsSbTarget +interface sbtsv.h ITsSbTargetPropertySet +interface sbtsv.h ITsSbTaskInfo +interface sbtsv.h ITsSbTaskPlugin +interface sbtsv.h ITsSbTaskPluginNotifySink +interface tssbx.h IWTSSBPlugin + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > Remote Desktop Virtualization API reference > Remote Desktop Virtualization Structures +struct sessdirpublictypes.h CLIENT_DISPLAY +struct sessdirpublictypes.h TSSD_ConnectionPoint + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > RemoteApp and Desktop Connection Management Service API reference > RemoteApp and Desktop Connection Management Service Interfaces +interface tspubplugin2com.h ItsPubPlugin2 +interface tspubplugincom.h ItsPubPlugin + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > RemoteApp and Desktop Connection Management Service API reference > RemoteApp and Desktop Connection Management Service Structures +struct tspubplugin2com.h pluginResource2 +struct tspubplugin2com.h pluginResource2FileAssociation +struct tspubplugincom.h pluginResource + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > RemoteApp and Desktop Connection Management Service API reference > RemoteApp and Desktop Connection Management Service Enumerations +enum tspubplugin2com.h TSPUB_PLUGIN_PD_ASSIGNMENT_TYPE +enum tspubplugin2com.h TSPUB_PLUGIN_PD_RESOLUTION_TYPE + +# Desktop App Technologies > System Services > Remote Desktop Services > Remote Desktop Services reference > RemoteApp and Desktop Connection Runtime interfaces +interface workspaceax.h IWorkspaceResTypeRegistry +interface workspaceruntime.h IWorkspace +interface workspaceruntime.h IWorkspace2 +interface workspaceruntime.h IWorkspace3 +interface workspaceruntime.h IWorkspaceRegistration +interface workspaceruntime.h IWorkspaceRegistration2 +interface workspaceruntime.h IWorkspaceReportMessage +interface workspaceruntime.h IWorkspaceScriptable +interface workspaceruntime.h IWorkspaceScriptable2 +interface workspaceruntime.h IWorkspaceScriptable3 +interface workspaceruntimeclientext.h IWorkspaceClientExt + +# Desktop App Technologies > System Services > Services > Service Reference > Service Functions +callback winsvc.h Handler =lphandler_function +callback winsvc.h HandlerEx =lphandler_function_ex +callback winsvc.h LPSERVICE_MAIN_FUNCTION =lpservice_main_functiona +function lmserver.h SetServiceBits +function sysinfoapi.h InstallELAMCertificateInfo +function winsvc.h ChangeServiceConfig =changeserviceconfiga +function winsvc.h ChangeServiceConfig2 =changeserviceconfig2a +function winsvc.h CloseServiceHandle +function winsvc.h ControlService +function winsvc.h ControlServiceEx =controlserviceexa +function winsvc.h CreateService =createservicea +function winsvc.h DeleteService +function winsvc.h EnumDependentServices =enumdependentservicesa +function winsvc.h EnumServicesStatus =enumservicesstatusa +function winsvc.h EnumServicesStatusEx =enumservicesstatusexa +function winsvc.h GetServiceDisplayName =getservicedisplaynamea +function winsvc.h GetServiceKeyName =getservicekeynamea +function winsvc.h LockServiceDatabase +function winsvc.h NotifyBootConfigStatus +function winsvc.h NotifyServiceStatusChange =notifyservicestatuschangea +function winsvc.h OpenSCManager =openscmanagera +function winsvc.h OpenService =openservicea +function winsvc.h QueryServiceConfig =queryserviceconfiga +function winsvc.h QueryServiceConfig2 =queryserviceconfig2a +function winsvc.h QueryServiceDynamicInformation +function winsvc.h QueryServiceLockStatus =queryservicelockstatusa +function winsvc.h QueryServiceStatus +function winsvc.h QueryServiceStatusEx +function winsvc.h RegisterServiceCtrlHandler =registerservicectrlhandlera +function winsvc.h RegisterServiceCtrlHandlerEx =registerservicectrlhandlerexa +function winsvc.h SetServiceStatus +function winsvc.h StartService =startservicea +function winsvc.h StartServiceCtrlDispatcher =startservicectrldispatchera +function winsvc.h UnlockServiceDatabase + +# Desktop App Technologies > System Services > Services > Service Reference > Service Structures +struct winsvc.h ENUM_SERVICE_STATUS =enum_service_statusa +struct winsvc.h ENUM_SERVICE_STATUS_PROCESS =enum_service_status_processa +struct winsvc.h QUERY_SERVICE_CONFIG =query_service_configa +struct winsvc.h QUERY_SERVICE_LOCK_STATUS =query_service_lock_statusa +struct winsvc.h SC_ACTION +struct winsvc.h SERVICE_CONTROL_STATUS_REASON_PARAMS =service_control_status_reason_paramsa +struct winsvc.h SERVICE_DELAYED_AUTO_START_INFO +struct winsvc.h SERVICE_DESCRIPTION =service_descriptiona +struct winsvc.h SERVICE_FAILURE_ACTIONS =service_failure_actionsa +struct winsvc.h SERVICE_FAILURE_ACTIONS_FLAG +struct winsvc.h SERVICE_LAUNCH_PROTECTED_INFO +struct winsvc.h SERVICE_NOTIFY =service_notify_2a +struct winsvc.h SERVICE_PREFERRED_NODE_INFO +struct winsvc.h SERVICE_PRESHUTDOWN_INFO +struct winsvc.h SERVICE_REQUIRED_PRIVILEGES_INFO =service_required_privileges_infoa +struct winsvc.h SERVICE_SID_INFO +struct winsvc.h SERVICE_STATUS +struct winsvc.h SERVICE_STATUS_PROCESS +struct winsvc.h SERVICE_TABLE_ENTRY =service_table_entrya +struct winsvc.h SERVICE_TIMECHANGE_INFO +struct winsvc.h SERVICE_TRIGGER +struct winsvc.h SERVICE_TRIGGER_INFO +struct winsvc.h SERVICE_TRIGGER_SPECIFIC_DATA_ITEM + +# Desktop App Technologies > System Services > Setup API > Reference > Functions +callback setupapi.h FileCallback =psp_file_callback_a +function setupapi.h InstallHinfSection =installhinfsectiona +function setupapi.h SetupAddInstallSectionToDiskSpaceList =setupaddinstallsectiontodiskspacelista +function setupapi.h SetupAddSectionToDiskSpaceList =setupaddsectiontodiskspacelista +function setupapi.h SetupAddToDiskSpaceList =setupaddtodiskspacelista +function setupapi.h SetupAddToSourceList =setupaddtosourcelista +function setupapi.h SetupAdjustDiskSpaceList =setupadjustdiskspacelista +function setupapi.h SetupBackupError =setupbackuperrora +function setupapi.h SetupCancelTemporarySourceList +function setupapi.h SetupCloseFileQueue +function setupapi.h SetupCloseInfFile +function setupapi.h SetupCloseLog +function setupapi.h SetupCommitFileQueue =setupcommitfilequeuea +function setupapi.h SetupConfigureWmiFromInfSection =setupconfigurewmifrominfsectiona +function setupapi.h SetupCopyError =setupcopyerrora +function setupapi.h SetupCopyOEMInf =setupcopyoeminfa +function setupapi.h SetupCreateDiskSpaceList =setupcreatediskspacelista +function setupapi.h SetupDecompressOrCopyFile =setupdecompressorcopyfilea +function setupapi.h SetupDefaultQueueCallback =setupdefaultqueuecallbacka +function setupapi.h SetupDeleteError =setupdeleteerrora +function setupapi.h SetupDestroyDiskSpaceList +function setupapi.h SetupDuplicateDiskSpaceList =setupduplicatediskspacelista +function setupapi.h SetupEnumInfSections =setupenuminfsectionsa +function setupapi.h SetupFindFirstLine =setupfindfirstlinea +function setupapi.h SetupFindNextLine +function setupapi.h SetupFindNextMatchLine =setupfindnextmatchlinea +function setupapi.h SetupFreeSourceList =setupfreesourcelista +function setupapi.h SetupGetBinaryField +function setupapi.h SetupGetFieldCount +function setupapi.h SetupGetFileCompressionInfo =setupgetfilecompressioninfoa +function setupapi.h SetupGetFileCompressionInfoEx =setupgetfilecompressioninfoexa +function setupapi.h SetupGetFileQueueCount +function setupapi.h SetupGetFileQueueFlags +function setupapi.h SetupGetInfFileList =setupgetinffilelista +function setupapi.h SetupGetInfInformation =setupgetinfinformationa +function setupapi.h SetupGetIntField +function setupapi.h SetupGetLineByIndex =setupgetlinebyindexa +function setupapi.h SetupGetLineCount =setupgetlinecounta +function setupapi.h SetupGetLineText =setupgetlinetexta +function setupapi.h SetupGetMultiSzField =setupgetmultiszfielda +function setupapi.h SetupGetSourceFileLocation =setupgetsourcefilelocationa +function setupapi.h SetupGetSourceFileSize =setupgetsourcefilesizea +function setupapi.h SetupGetSourceInfo =setupgetsourceinfoa +function setupapi.h SetupGetStringField =setupgetstringfielda +function setupapi.h SetupGetTargetPath =setupgettargetpatha +function setupapi.h SetupInitDefaultQueueCallback +function setupapi.h SetupInitDefaultQueueCallbackEx +function setupapi.h SetupInitializeFileLog =setupinitializefileloga +function setupapi.h SetupInstallFile =setupinstallfilea +function setupapi.h SetupInstallFileEx =setupinstallfileexa +function setupapi.h SetupInstallFilesFromInfSection =setupinstallfilesfrominfsectiona +function setupapi.h SetupInstallFromInfSection =setupinstallfrominfsectiona +function setupapi.h SetupInstallServicesFromInfSection =setupinstallservicesfrominfsectiona +function setupapi.h SetupInstallServicesFromInfSectionEx =setupinstallservicesfrominfsectionexa +function setupapi.h SetupIterateCabinet =setupiteratecabineta +function setupapi.h SetupLogError =setuplogerrora +function setupapi.h SetupLogFile =setuplogfilea +function setupapi.h SetupOpenAppendInfFile =setupopenappendinffilea +function setupapi.h SetupOpenFileQueue +function setupapi.h SetupOpenInfFile =setupopeninffilea +function setupapi.h SetupOpenLog +function setupapi.h SetupOpenMasterInf +function setupapi.h SetupPromptForDisk =setuppromptfordiska +function setupapi.h SetupPromptReboot +function setupapi.h SetupQueryDrivesInDiskSpaceList =setupquerydrivesindiskspacelista +function setupapi.h SetupQueryFileLog =setupqueryfileloga +function setupapi.h SetupQueryInfFileInformation =setupqueryinffileinformationa +function setupapi.h SetupQueryInfOriginalFileInformation =setupqueryinforiginalfileinformationa +function setupapi.h SetupQueryInfVersionInformation =setupqueryinfversioninformationa +function setupapi.h SetupQuerySourceList =setupquerysourcelista +function setupapi.h SetupQuerySpaceRequiredOnDrive =setupqueryspacerequiredondrivea +function setupapi.h SetupQueueCopy =setupqueuecopya +function setupapi.h SetupQueueCopyIndirect =setupqueuecopyindirecta +function setupapi.h SetupQueueCopySection =setupqueuecopysectiona +function setupapi.h SetupQueueDefaultCopy =setupqueuedefaultcopya +function setupapi.h SetupQueueDelete =setupqueuedeletea +function setupapi.h SetupQueueDeleteSection =setupqueuedeletesectiona +function setupapi.h SetupQueueRename =setupqueuerenamea +function setupapi.h SetupQueueRenameSection =setupqueuerenamesectiona +function setupapi.h SetupRemoveFileLogEntry =setupremovefilelogentrya +function setupapi.h SetupRemoveFromDiskSpaceList =setupremovefromdiskspacelista +function setupapi.h SetupRemoveFromSourceList =setupremovefromsourcelista +function setupapi.h SetupRemoveInstallSectionFromDiskSpaceList =setupremoveinstallsectionfromdiskspacelista +function setupapi.h SetupRemoveSectionFromDiskSpaceList =setupremovesectionfromdiskspacelista +function setupapi.h SetupRenameError =setuprenameerrora +function setupapi.h SetupScanFileQueue =setupscanfilequeuea +function setupapi.h SetupSetDirectoryId =setupsetdirectoryida +function setupapi.h SetupSetDirectoryIdEx =setupsetdirectoryidexa +function setupapi.h SetupSetFileQueueAlternatePlatform =setupsetfilequeuealternateplatforma +function setupapi.h SetupSetFileQueueFlags +function setupapi.h SetupSetPlatformPathOverride =setupsetplatformpathoverridea +function setupapi.h SetupSetSourceList =setupsetsourcelista +function setupapi.h SetupTermDefaultQueueCallback +function setupapi.h SetupTerminateFileLog +function setupapi.h SetupUninstallNewlyCopiedInfs +function setupapi.h SetupUninstallOEMInf =setupuninstalloeminfa +function setupapi.h SetupVerifyInfFile =setupverifyinffilea + +# Desktop App Technologies > System Services > Setup API > Reference > Structures (Setup API) +struct setupapi.h CABINET_INFO =cabinet_info_a +struct setupapi.h FILE_IN_CABINET_INFO =file_in_cabinet_info_a +struct setupapi.h FILEPATHS =filepaths_a +struct setupapi.h FILEPATHS_SIGNERINFO =filepaths_signerinfo_a +struct setupapi.h INFCONTEXT +struct setupapi.h SOURCE_MEDIA =source_media_a +struct setupapi.h SP_ALTPLATFORM_INFO_V1 +struct setupapi.h SP_ALTPLATFORM_INFO_V2 +struct setupapi.h SP_FILE_COPY_PARAMS =sp_file_copy_params_a +struct setupapi.h SP_INF_INFORMATION +struct setupapi.h SP_INF_SIGNER_INFO =sp_inf_signer_info_v1_a +struct setupapi.h SP_ORIGINAL_FILE_INFO =sp_original_file_info_a +struct setupapi.h SP_REGISTER_CONTROL_STATUS =sp_register_control_statusa + +# Desktop App Technologies > System Services > Synchronization > Synchronization Reference > Synchronization Functions +callback synchapi.h PINIT_ONCE_FN +callback synchapi.h PTIMERAPCROUTINE +callback winnt.h APCProc =papcfunc +function interlockedapi.h InitializeSListHead +function interlockedapi.h InterlockedFlushSList +function interlockedapi.h InterlockedPopEntrySList +function interlockedapi.h InterlockedPushEntrySList +function interlockedapi.h InterlockedPushListSListEx +function interlockedapi.h QueryDepthSList +function ioapiset.h GetOverlappedResult +function ioapiset.h GetOverlappedResultEx +function namespaceapi.h AddSIDToBoundaryDescriptor +function namespaceapi.h ClosePrivateNamespace +function namespaceapi.h DeleteBoundaryDescriptor +function processthreadsapi.h QueueUserAPC +function synchapi.h AcquireSRWLockExclusive +function synchapi.h AcquireSRWLockShared +function synchapi.h CancelWaitableTimer +function synchapi.h CreateEvent =createeventa +function synchapi.h CreateEventEx =createeventexa +function synchapi.h CreateMutex =createmutexa +function synchapi.h CreateMutexEx =createmutexexa +function synchapi.h CreateWaitableTimer =createwaitabletimerw +function synchapi.h CreateWaitableTimerEx =createwaitabletimerexw +function synchapi.h DeleteCriticalSection +function synchapi.h DeleteSynchronizationBarrier +function synchapi.h EnterCriticalSection +function synchapi.h EnterSynchronizationBarrier +function synchapi.h InitializeConditionVariable +function synchapi.h InitializeCriticalSection +function synchapi.h InitializeCriticalSectionAndSpinCount +function synchapi.h InitializeCriticalSectionEx +function synchapi.h InitializeSRWLock +function synchapi.h InitializeSynchronizationBarrier +function synchapi.h InitOnceBeginInitialize +function synchapi.h InitOnceComplete +function synchapi.h InitOnceExecuteOnce +function synchapi.h InitOnceInitialize +function synchapi.h LeaveCriticalSection +function synchapi.h OpenEvent =openeventa +function synchapi.h OpenMutex =openmutexw +function synchapi.h OpenSemaphore =opensemaphorew +function synchapi.h OpenWaitableTimer =openwaitabletimerw +function synchapi.h ReleaseMutex +function synchapi.h ReleaseSemaphore +function synchapi.h ReleaseSRWLockExclusive +function synchapi.h ReleaseSRWLockShared +function synchapi.h ResetEvent +function synchapi.h SetCriticalSectionSpinCount +function synchapi.h SetEvent +function synchapi.h SetWaitableTimer +function synchapi.h SetWaitableTimerEx +function synchapi.h SignalObjectAndWait +function synchapi.h SleepConditionVariableCS +function synchapi.h SleepConditionVariableSRW +function synchapi.h TryAcquireSRWLockExclusive +function synchapi.h TryAcquireSRWLockShared +function synchapi.h TryEnterCriticalSection +function synchapi.h WaitForMultipleObjects +function synchapi.h WaitForMultipleObjectsEx +function synchapi.h WaitForSingleObject +function synchapi.h WaitForSingleObjectEx +function synchapi.h WaitOnAddress +function synchapi.h WakeAllConditionVariable +function synchapi.h WakeByAddressAll +function synchapi.h WakeByAddressSingle +function synchapi.h WakeConditionVariable +function threadpoollegacyapiset.h ChangeTimerQueueTimer +function threadpoollegacyapiset.h CreateTimerQueue +function threadpoollegacyapiset.h CreateTimerQueueTimer +function threadpoollegacyapiset.h DeleteTimerQueue +function threadpoollegacyapiset.h DeleteTimerQueueEx +function threadpoollegacyapiset.h DeleteTimerQueueTimer +function winbase.h AddIntegrityLabelToBoundaryDescriptor +function winbase.h CreateBoundaryDescriptor =createboundarydescriptora +function winbase.h CreatePrivateNamespace =createprivatenamespacea +function winbase.h CreateSemaphore =createsemaphorea +function winbase.h CreateSemaphoreEx =createsemaphoreexa +function winbase.h InterlockedExchangeSubtract +function winbase.h OpenPrivateNamespace =openprivatenamespacea +function winbase.h PulseEvent +function winbase.h RegisterWaitForSingleObject +function winbase.h UnregisterWait +function winnt.h InterlockedAdd +function winnt.h InterlockedAdd64 =_inlineinterlockedadd64 +function winnt.h InterlockedAddAcquire =_inlineinterlockedadd +function winnt.h InterlockedAnd +function winnt.h InterlockedAnd16 +function winnt.h InterlockedAnd64 +function winnt.h InterlockedAnd8 +function winnt.h InterlockedBitTestAndReset +function winnt.h InterlockedBitTestAndReset64 +function winnt.h InterlockedBitTestAndSet +function winnt.h InterlockedBitTestAndSet64 +function winnt.h InterlockedCompareExchange128 +function winnt.h InterlockedCompareExchange16 +function winnt.h InterlockedCompareExchange64 +function winnt.h InterlockedCompareExchangePointer +function winnt.h InterlockedDecrement16 +function winnt.h InterlockedDecrement64 +function winnt.h InterlockedExchange16 +function winnt.h InterlockedExchange64 +function winnt.h InterlockedExchange8 +function winnt.h InterlockedExchangeAdd64 +function winnt.h InterlockedExchangePointer +function winnt.h InterlockedIncrement16 +function winnt.h InterlockedIncrement64 +function winnt.h InterlockedOr +function winnt.h InterlockedOr16 +function winnt.h InterlockedOr8 +function winnt.h InterlockedXor +function winnt.h InterlockedXor16 +function winnt.h InterlockedXor64 +function winnt.h InterlockedXor8 +function winnt.h RtlFirstEntrySList +function winnt.h RtlInitializeSListHead +function winnt.h RtlInterlockedFlushSList +function winnt.h RtlInterlockedPopEntrySList +function winnt.h RtlInterlockedPushEntrySList +function winnt.h RtlQueryDepthSList +function winuser.h MsgWaitForMultipleObjects +function winuser.h MsgWaitForMultipleObjectsEx + +# Desktop App Technologies > System Services > Synchronization > Synchronization Reference > Synchronization Macros +function winbase.h HasOverlappedIoCompleted +function winnt.h MemoryBarrier +function winnt.h PreFetchCacheLine +function winnt.h YieldProcessor + +# Desktop App Technologies > System Services > Synchronization > Synchronization Reference > Synchronization Structures +struct minwinbase.h OVERLAPPED +struct winnt.h SLIST_ENTRY + +# Desktop App Technologies > System Services > Windows System Information > Handles and Objects > Handle and Object Reference > Handle and Object Functions +function handleapi.h CloseHandle +function handleapi.h CompareObjectHandles +function handleapi.h DuplicateHandle +function handleapi.h GetHandleInformation +function handleapi.h SetHandleInformation + +# Desktop App Technologies > System Services > Windows System Information > Registry > Registry Reference > Registry Functions +function winbase.h GetPrivateProfileInt +function winbase.h GetPrivateProfileSection +function winbase.h GetPrivateProfileSectionNames +function winbase.h GetPrivateProfileString +function winbase.h GetPrivateProfileStruct +function winbase.h GetProfileInt =getprofileinta +function winbase.h GetProfileSection =getprofilesectiona +function winbase.h GetProfileString =getprofilestringa +function winbase.h WritePrivateProfileSection =writeprivateprofilesectiona +function winbase.h WritePrivateProfileString =writeprivateprofilestringa +function winbase.h WritePrivateProfileStruct =writeprivateprofilestructa +function winbase.h WriteProfileSection =writeprofilesectiona +function winbase.h WriteProfileString =writeprofilestringa +function winreg.h RegCloseKey +function winreg.h RegConnectRegistry =regconnectregistrya +function winreg.h RegCopyTree =regcopytreea +function winreg.h RegCreateKey =regcreatekeya +function winreg.h RegCreateKeyEx =regcreatekeyexa +function winreg.h RegCreateKeyTransacted =regcreatekeytransacteda +function winreg.h RegDeleteKey =regdeletekeya +function winreg.h RegDeleteKeyEx =regdeletekeyexa +function winreg.h RegDeleteKeyTransacted =regdeletekeytransacteda +function winreg.h RegDeleteKeyValue =regdeletekeyvaluea +function winreg.h RegDeleteTree =regdeletetreea +function winreg.h RegDeleteValue =regdeletevaluea +function winreg.h RegDisablePredefinedCache +function winreg.h RegDisablePredefinedCacheEx +function winreg.h RegDisableReflectionKey +function winreg.h RegEnableReflectionKey +function winreg.h RegEnumKey =regenumkeya +function winreg.h RegEnumKeyEx =regenumkeyexa +function winreg.h RegEnumValue =regenumvaluea +function winreg.h RegFlushKey +function winreg.h RegGetValue =reggetvaluea +function winreg.h RegLoadAppKey =regloadappkeya +function winreg.h RegLoadKey =regloadkeya +function winreg.h RegLoadMUIString =regloadmuistringa +function winreg.h RegNotifyChangeKeyValue +function winreg.h RegOpenCurrentUser +function winreg.h RegOpenKey =regopenkeya +function winreg.h RegOpenKeyEx =regopenkeyexa +function winreg.h RegOpenKeyTransacted =regopenkeytransacteda +function winreg.h RegOpenUserClassesRoot +function winreg.h RegOverridePredefKey +function winreg.h RegQueryInfoKey =regqueryinfokeya +function winreg.h RegQueryMultipleValues =regquerymultiplevaluesa +function winreg.h RegQueryReflectionKey +function winreg.h RegQueryValue =regqueryvaluea +function winreg.h RegQueryValueEx =regqueryvalueexa +function winreg.h RegRenameKey +function winreg.h RegReplaceKey =regreplacekeya +function winreg.h RegRestoreKey =regrestorekeya +function winreg.h RegSaveKey =regsavekeya +function winreg.h RegSaveKeyEx =regsavekeyexa +function winreg.h RegSetKeyValue =regsetkeyvaluea +function winreg.h RegSetValue =regsetvaluea +function winreg.h RegSetValueEx =regsetvalueexa +function winreg.h RegUnLoadKey =regunloadkeya + +# Desktop App Technologies > System Services > Windows System Information > Registry > Registry Reference > Registry Structures +struct winreg.h VALENT =valenta + +# Desktop App Technologies > System Services > Windows System Information > System Information > System Information Reference > System Information Functions > Version Helper functions +function versionhelpers.h IsWindows10OrGreater +function versionhelpers.h IsWindows7OrGreater +function versionhelpers.h IsWindows7SP1OrGreater +function versionhelpers.h IsWindows8OrGreater +function versionhelpers.h IsWindows8Point1OrGreater +function versionhelpers.h IsWindowsServer +function versionhelpers.h IsWindowsVersionOrGreater +function versionhelpers.h IsWindowsVistaOrGreater +function versionhelpers.h IsWindowsVistaSP1OrGreater +function versionhelpers.h IsWindowsVistaSP2OrGreater +function versionhelpers.h IsWindowsXPOrGreater +function versionhelpers.h IsWindowsXPSP1OrGreater +function versionhelpers.h IsWindowsXPSP2OrGreater +function versionhelpers.h IsWindowsXPSP3OrGreater + +# Desktop App Technologies > System Services > Windows System Information > System Information > System Information Reference > System Information Functions +function processthreadsapi.h IsProcessorFeaturePresent +function profileapi.h QueryPerformanceCounter +function profileapi.h QueryPerformanceFrequency +function secext.h GetComputerObjectName =getcomputerobjectnamea +function secext.h GetUserNameEx =getusernameexa +function secext.h TranslateName =translatenamea +function sysinfoapi.h EnumSystemFirmwareTables +function sysinfoapi.h GetComputerNameEx =getcomputernameexa +function sysinfoapi.h GetIntegratedDisplaySize +function sysinfoapi.h GetNativeSystemInfo +function sysinfoapi.h GetProductInfo +function sysinfoapi.h GetSystemDirectory =getsystemdirectorya +function sysinfoapi.h GetSystemFirmwareTable +function sysinfoapi.h GetSystemInfo +function sysinfoapi.h GetSystemWindowsDirectory =getsystemwindowsdirectorya +function sysinfoapi.h GetVersion +function sysinfoapi.h GetVersionEx =getversionexa +function sysinfoapi.h GetWindowsDirectory =getwindowsdirectorya +function sysinfoapi.h SetComputerName =setcomputernamea +function sysinfoapi.h SetComputerNameEx =setcomputernameexa +function winbase.h DnsHostnameToComputerName =dnshostnametocomputernamea +function winbase.h GetComputerName =getcomputernamea +function winbase.h GetCurrentHwProfile =getcurrenthwprofilea +function winbase.h GetFirmwareEnvironmentVariable =getfirmwareenvironmentvariablea +function winbase.h GetFirmwareEnvironmentVariableEx =getfirmwareenvironmentvariableexa +function winbase.h GetFirmwareType +function winbase.h GetSystemRegistryQuota +function winbase.h GetUserName =getusernamea +function winbase.h IsNativeVhdBoot +function winbase.h SetFirmwareEnvironmentVariable =setfirmwareenvironmentvariablea +function winbase.h SetFirmwareEnvironmentVariableEx =setfirmwareenvironmentvariableexa +function winbase.h VerifyVersionInfo =verifyversioninfoa +function windowsceip.h CeipIsOptedIn +function winnt.h VerSetConditionMask +function winternl.h NtQuerySystemInformation +function wow64apiset.h GetSystemWow64Directory =getsystemwow64directorya +function wow64apiset.h GetSystemWow64Directory2 =getsystemwow64directory2a +function wow64apiset.h IsWow64GuestMachineSupported + +# Desktop App Technologies > System Services > Windows System Information > System Information > System Information Reference > System Information Structures +struct sysinfoapi.h SYSTEM_INFO +struct winbase.h HW_PROFILE_INFO =hw_profile_infoa +struct winnt.h OSVERSIONINFO =osversioninfoa +struct winnt.h OSVERSIONINFOEX =osversioninfoexa + +# Desktop App Technologies > System Services > Windows System Information > System Information > System Information Reference > System Information Macros +function winnt.h VER_SET_CONDITION + +# Desktop App Technologies > System Services > Windows System Information > System Information > System Information Reference > System Information Enumeration Types +enum secext.h EXTENDED_NAME_FORMAT +enum sysinfoapi.h COMPUTER_NAME_FORMAT +enum winnt.h FIRMWARE_TYPE + +# Desktop App Technologies > System Services > Windows System Information > Time > Time Reference > Time Functions +function fileapi.h CompareFileTime +function fileapi.h FileTimeToLocalFileTime +function fileapi.h GetFileTime +function fileapi.h LocalFileTimeToFileTime +function fileapi.h SetFileTime +function processthreadsapi.h GetSystemTimes +function realtimeapiset.h ConvertAuxiliaryCounterToPerformanceCounter +function realtimeapiset.h ConvertPerformanceCounterToAuxiliaryCounter +function realtimeapiset.h QueryAuxiliaryCounterFrequency +function realtimeapiset.h QueryInterruptTime +function realtimeapiset.h QueryInterruptTimePrecise +function realtimeapiset.h QueryUnbiasedInterruptTime +function realtimeapiset.h QueryUnbiasedInterruptTimePrecise +function sysinfoapi.h GetLocalTime +function sysinfoapi.h GetSystemTime +function sysinfoapi.h GetSystemTimeAdjustment +function sysinfoapi.h GetSystemTimeAdjustmentPrecise +function sysinfoapi.h GetSystemTimeAsFileTime +function sysinfoapi.h GetSystemTimePreciseAsFileTime +function sysinfoapi.h GetTickCount +function sysinfoapi.h GetTickCount64 +function sysinfoapi.h SetLocalTime +function sysinfoapi.h SetSystemTime +function sysinfoapi.h SetSystemTimeAdjustment +function sysinfoapi.h SetSystemTimeAdjustmentPrecise +function timezoneapi.h EnumDynamicTimeZoneInformation +function timezoneapi.h FileTimeToSystemTime +function timezoneapi.h GetDynamicTimeZoneInformation +function timezoneapi.h GetDynamicTimeZoneInformationEffectiveYears +function timezoneapi.h GetTimeZoneInformation +function timezoneapi.h GetTimeZoneInformationForYear +function timezoneapi.h SetDynamicTimeZoneInformation +function timezoneapi.h SetTimeZoneInformation +function timezoneapi.h SystemTimeToFileTime +function timezoneapi.h SystemTimeToTzSpecificLocalTime +function timezoneapi.h SystemTimeToTzSpecificLocalTimeEx +function timezoneapi.h TzSpecificLocalTimeToSystemTime +function timezoneapi.h TzSpecificLocalTimeToSystemTimeEx +function winbase.h DosDateTimeToFileTime +function winbase.h FileTimeToDosDateTime +function winternl.h NtQuerySystemTime +function winternl.h RtlLocalTimeToSystemTime +function winternl.h RtlTimeToSecondsSince1970 + +# Desktop App Technologies > System Services > Windows System Information > Time > Time Reference > Time Structures +struct minwinbase.h FILETIME +struct minwinbase.h SYSTEMTIME +struct timezoneapi.h DYNAMIC_TIME_ZONE_INFORMATION +struct timezoneapi.h TIME_ZONE_INFORMATION + +# Desktop App Technologies > System Services > Windows System Information > Time Provider > Time Provider Reference > Time Provider Functions +callback timeprov.h AlertSamplesAvailFunc +callback timeprov.h GetTimeSysInfoFunc +callback timeprov.h LogTimeProvEventFunc +callback timeprov.h SetProviderStatusFunc +callback timeprov.h SetProviderStatusInfoFreeFunc +function timeprov.h TimeProvClose +function timeprov.h TimeProvCommand +function timeprov.h TimeProvOpen + +# Desktop App Technologies > System Services > Windows System Information > Time Provider > Time Provider Reference > Time Provider Structures +struct timeprov.h SetProviderStatusInfo +struct timeprov.h TimeProvSysCallbacks +struct timeprov.h TimeSample +struct timeprov.h TpcGetSamplesArgs + +# Desktop App Technologies > System Services > Windows System Information > WaaS Assessment Platform > WaaS Assessment Reference > WaaS Assessment Structures +struct waasapitypes.h OSUpdateAssessment +struct waasapitypes.h UpdateAssessment + +# Desktop App Technologies > System Services > Windows System Information > WaaS Assessment Platform > WaaS Assessment Reference > WaaS Assessment Interfaces +interface waasapi.h IWaaSAssessor + +# Desktop App Technologies > Developer Notes > Application Verifier > Resource Enumeration > Resource Enumeration Reference > Resource Enumeration Enumerations +enum avrfsdk.h eAvrfResourceTypes +enum avrfsdk.h eHANDLE_TRACE_OPERATIONS +enum avrfsdk.h eHeapAllocationState +enum avrfsdk.h eHeapEnumerationLevel +enum avrfsdk.h eUserAllocationState + +# Desktop App Technologies > Developer Notes > Application Verifier > Resource Enumeration > Resource Enumeration Reference > Resource Enumeration Functions +callback avrfsdk.h AVRF_HANDLEOPERATION_ENUMERATE_CALLBACK +callback avrfsdk.h AVRF_HEAP_ALLOCATION_ENUMERATE_CALLBACK +callback avrfsdk.h AVRF_RESOURCE_ENUMERATE_CALLBACK +function avrfsdk.h VerifierEnumerateResource + +# Desktop App Technologies > Developer Notes > Application Verifier > Resource Enumeration > Resource Enumeration Reference > Resource Enumeration Structures +struct avrfsdk.h AVRF_BACKTRACE_INFORMATION +struct avrfsdk.h AVRF_HANDLE_OPERATION +struct avrfsdk.h AVRF_HEAP_ALLOCATION + +# Desktop App Technologies > Developer Notes > Application Verifier +function avrfsdk.h VerifierIsPerUserSettingsEnabled + +# Desktop App Technologies > Developer Notes > Audio and Video +interface camerauicontrol.h ICameraUIControl +interface camerauicontrol.h ICameraUIControlEventCallback + +# Desktop App Technologies > Developer Notes > Cabinet API > Cabinet API Reference > Cabinet API Functions +function fci.h FCIAddFile +function fci.h FCICreate +function fci.h FCIDestroy +function fci.h FCIFlushCabinet +function fci.h FCIFlushFolder +function fdi.h FDICopy +function fdi.h FDICreate +function fdi.h FDIDestroy +function fdi.h FDIIsCabinet +function fdi.h FDITruncateCabinet + +# Desktop App Technologies > Developer Notes > Cabinet API > Cabinet API Reference > Cabinet API Macros +function fci.h FNFCIALLOC +function fci.h FNFCICLOSE +function fci.h FNFCIDELETE +function fci.h FNFCIFILEPLACED +function fci.h FNFCIFREE +function fci.h FNFCIGETNEXTCABINET +function fci.h FNFCIGETOPENINFO +function fci.h FNFCIGETTEMPFILE +function fci.h FNFCIOPEN +function fci.h FNFCIREAD +function fci.h FNFCISEEK +function fci.h FNFCISTATUS +function fci.h FNFCIWRITE +function fdi_fci_types.h TCOMPfromLZXWindow +function fdi.h FNALLOC +function fdi.h FNCLOSE +function fdi.h FNFDINOTIFY +function fdi.h FNFREE +function fdi.h FNOPEN +function fdi.h FNREAD +function fdi.h FNSEEK +function fdi.h FNWRITE + +# Desktop App Technologies > Developer Notes > Cabinet API > Cabinet API Reference > Cabinet API Structures +struct fci.h CCAB +struct fdi_fci_types.h ERF +struct fdi.h FDICABINETINFO +struct fdi.h FDINOTIFICATION + +# Desktop App Technologies > Developer Notes > Debugging > Virtual DOS Machine Debug +function vdmdbg.h VDMEnumProcessWOW +function vdmdbg.h VDMEnumTaskWOWEx + +# Desktop App Technologies > Developer Notes > Debugging +callback dbghelp.h SymbolServerCallback =psymbolservercallbackproc + +# Desktop App Technologies > Developer Notes > Edition Upgrade API +interface editionupgradehelper.h IEditionUpgradeHelper + +# Desktop App Technologies > Developer Notes > Feature Staging +enum featurestagingapi.h FEATURE_CHANGE_TIME +enum featurestagingapi.h FEATURE_ENABLED_STATE +function featurestagingapi.h GetFeatureEnabledState +function featurestagingapi.h GetFeatureVariant +function featurestagingapi.h RecordFeatureError +function featurestagingapi.h RecordFeatureUsage +function featurestagingapi.h SubscribeFeatureStateChangeNotification +function featurestagingapi.h UnsubscribeFeatureStateChangeNotification +struct featurestagingapi.h FEATURE_ERROR + +# Desktop App Technologies > Developer Notes > File History API +enum fhcfg.h FH_BACKUP_STATUS +enum fhcfg.h FH_DEVICE_VALIDATION_RESULT +enum fhcfg.h FH_LOCAL_POLICY_TYPE +enum fhcfg.h FH_PROTECTED_ITEM_CATEGORY +enum fhcfg.h FH_RETENTION_TYPES +enum fhcfg.h FH_TARGET_DRIVE_TYPES +enum fhcfg.h FH_TARGET_PROPERTY_TYPE +function fhsvcctl.h FhServiceBlockBackup +function fhsvcctl.h FhServiceClosePipe +function fhsvcctl.h FhServiceOpenPipe +function fhsvcctl.h FhServiceReloadConfiguration +function fhsvcctl.h FhServiceStartBackup +function fhsvcctl.h FhServiceStopBackup +function fhsvcctl.h FhServiceUnblockBackup +interface fhcfg.h IFhConfigMgr +interface fhcfg.h IFhReassociation +interface fhcfg.h IFhScopeIterator +interface fhcfg.h IFhTarget + +# Desktop App Technologies > Developer Notes > Files +function winbase.h _lcreat +function winbase.h _llseek +function winbase.h _lwrite +function winternl.h NtCreateFile +function winternl.h NtOpenFile +function winternl.h RtlIsNameLegalDOS8Dot3 + +# Desktop App Technologies > Developer Notes > Graphics Low-Level Client Support +function dciman.h DCIBeginAccess +function dciman.h DCICloseProvider +function dciman.h DCICreatePrimary +function dciman.h DCIDestroy +function dciman.h DCIEndAccess +function dciman.h DCIOpenProvider +function ddrawgdi.h DdAttachSurface +function ddrawgdi.h DdCreateDIBSection +function ddrawgdi.h DdCreateDirectDrawObject +function ddrawgdi.h DdCreateSurfaceObject +function ddrawgdi.h DdDeleteDirectDrawObject +function ddrawgdi.h DdDeleteSurfaceObject +function ddrawgdi.h DdGetDC +function ddrawgdi.h DdGetDxHandle +function ddrawgdi.h DdQueryDirectDrawObject +function ddrawgdi.h DdQueryDisplaySettingsUniqueness +function ddrawgdi.h DdReenableDirectDrawObject +function ddrawgdi.h DdReleaseDC +function ddrawgdi.h DdResetVisrgn +function ddrawgdi.h DdSetGammaRamp +function ddrawgdi.h DdSwapTextureHandles +function ddrawgdi.h DdUnattachSurface + +# Desktop App Technologies > Developer Notes > Miscellaneous Low-Level Client Support +function comppkgsup.h AreDvdCodecsEnabled +function comppkgsup.h GetMediaComponentPackageInfo +function comppkgsup.h GetMediaExtensionCommunicationFactory +function comppkgsup.h GetServerForPMP +function comppkgsup.h InstantiateComponentFromPackage +function comppkgsup.h IsMediaBehaviorEnabled +function comppkgsup.h RegisterServerForPMP +function comppkgsup.h RequireNetworkDuringMediaTaskCompletion +function comppkgsup.h UnregisterServerForPMP +function ime.h SendIMEMessageEx =sendimemessageexa +function winbase.h _lclose +function winbase.h _lopen +function winbase.h _lread +function winnls32.h WINNLSEnableIME +function winternl.h NtClose +function winternl.h NtDeviceIoControlFile +function winternl.h NtWaitForSingleObject +function winternl.h RtlAnsiStringToUnicodeString +function winternl.h RtlCharToInteger +function winternl.h RtlFreeAnsiString +function winternl.h RtlFreeOemString +function winternl.h RtlFreeUnicodeString +function winternl.h RtlInitString +function winternl.h RtlInitUnicodeString +function winternl.h RtlUnicodeStringToAnsiString +function winternl.h RtlUnicodeStringToOemString +function winternl.h RtlUnicodeToMultiByteSize +function winuser.h DisableProcessWindowsGhosting +struct ime.h IMESTRUCT +struct winternl.h STRING + +# Desktop App Technologies > Developer Notes > MSHTML +interface exdisp.h IWebBrowser2 + +# Desktop App Technologies > Developer Notes > Installation +function loadperf.h InstallPerfDll =installperfdlla +function objbase.h CoInstall + +# Desktop App Technologies > Developer Notes > Java VM +struct capi.h JAVA_TRUST + +# Desktop App Technologies > Developer Notes > MSXML 1.0 (Obsolete) +interface msxml.h IXMLElementCollection + +# Desktop App Technologies > Developer Notes > Networking > RPC NDR Engine +function rpcndr.h NdrComformantArrayBufferSize =ndrconformantarraybuffersize +function rpcndr.h NdrComformantArrayMarshall =ndrconformantarraymarshall +function rpcndr.h NdrComplexArrayBufferSize +function rpcndr.h NdrComplexArrayMarshall +function rpcndr.h NdrComplexArrayUnmarshall +function rpcndr.h NdrComplexStructBufferSize +function rpcndr.h NdrComplexStructMarshall +function rpcndr.h NdrComplexStructUnmarshall +function rpcndr.h NdrSimpleStructBufferSize +function rpcndr.h NdrSimpleStructMarshall +function rpcndr.h NdrSimpleStructUnmarshall +function rpcndr.h NdrUserMarshalUnmarshall + +# Desktop App Technologies > Developer Notes > Networking +function icwcfg.h CheckConnectionWizard +function lmaccess.h I_NetLogonControl2 + +# Desktop App Technologies > Developer Notes > Security > Windows Security app > Windows Security app enumerations +enum iwscapi.h WSC_SECURITY_PRODUCT_STATE +enum iwscapi.h WSC_SECURITY_SIGNATURE_STATUS +enum wscapi.h WSC_SECURITY_PROVIDER +enum wscapi.h WSC_SECURITY_PROVIDER_HEALTH + +# Desktop App Technologies > Developer Notes > Security > Windows Security app > Windows Security app interfaces +interface iwscapi.h IWscProduct +interface iwscapi.h IWSCProductList + +# Desktop App Technologies > Developer Notes > Security > Windows Security app > Windows Security Center Functions +function wscapi.h WscGetSecurityProviderHealth +function wscapi.h WscRegisterForChanges +function wscapi.h WscUnRegisterChanges + +# Desktop App Technologies > Developer Notes > Security +interface isolatedapplauncher.h IIsolatedAppLauncher + +# Desktop App Technologies > Developer Notes > SMTP > FCachDLL Library > FCachDLL Callbacks +callback filehc.h CACHE_DESTROY_CALLBACK + +# Desktop App Technologies > Developer Notes > SMTP > FCachDLL Library > FCachDLL Functions +function filehc.h AssociateContextWithName +function filehc.h AssociateFileEx +function filehc.h CacheCreateFile +function filehc.h CacheRichCreateFile +function filehc.h FindOrCreateNameCache +function filehc.h FindSyncContextFromName +function filehc.h GetDotStuffState +function filehc.h GetFileSizeFromContext +function filehc.h InitializeCache +function filehc.h InsertFile +function filehc.h InvalidateName +function filehc.h ProduceDotStuffedContext +function filehc.h ReleaseNameCache +function filehc.h SetDotStuffingOnWrites +function filehc.h SetDotStuffState +function filehc.h TerminateCache + +# Desktop App Technologies > Developer Notes > SMTP > FCachDLL Library > FCachDLL Structure +struct filehc.h NAME_CACHE_CONTEXT + +# Desktop App Technologies > Developer Notes > System > Aux Library +function aux_ulib.h AuxUlibInitialize +function aux_ulib.h AuxUlibIsDLLSynchronizationHeld +function aux_ulib.h AuxUlibSetSystemFileCacheSize + +# Desktop App Technologies > Developer Notes > System > Run-time Library +function rtlsupportapi.h RtlRaiseException + +# Desktop App Technologies > Developer Notes > System > Winternl +control-code tcpioctl.h IOCTL_TCP_QUERY_INFORMATION_EX +function winternl.h NtNotifyChangeMultipleKeys +function winternl.h NtQueryMultipleValueKey +function winternl.h NtQueryObject +function winternl.h NtRenameKey +function winternl.h NtSetInformationKey +function winternl.h RtlUniform + +# Desktop App Technologies > Developer Notes > System +function stralign.h uaw_wcslen + +# Desktop App Technologies > Developer Notes > User Interface +function appcompatapi.h ApphelpCheckShellObject +function icwcfg.h SetShellNext + +# Desktop App Technologies > Developer Notes > Windows Secure Mode Policy +enum wldp.h WLDP_EXECUTION_EVALUATION_OPTIONS +enum wldp.h WLDP_EXECUTION_POLICY +enum wldp.h WLDP_HOST_ID +enum wldp.h WLDP_WINDOWS_LOCKDOWN_MODE +function wldp.h WldpCanExecuteBuffer +function wldp.h WldpCanExecuteFile +function wldp.h WldpCanExecuteStream +function wldp.h WldpIsDynamicCodePolicyEnabled +function wldp.h WldpQueryDynamicCodeTrust +function wldp.h WldpQueryWindowsLockdownMode +function wldp.h WldpSetDynamicCodeTrust +function wldp.h WlpdGetLockdownPolicy =wldpgetlockdownpolicy +function wldp.h WlpdIsClassInApprovedList =wldpisclassinapprovedlist +struct wldp.h WLDP_HOST_INFORMATION diff --git a/WIN32-LEARN-MAP.md b/WIN32-LEARN-MAP.md new file mode 100644 index 000000000..183c9e84b --- /dev/null +++ b/WIN32-LEARN-MAP.md @@ -0,0 +1,42 @@ +# Where Microsoft Learn files each Win32 symbol + +[WIN32-LEARN-MAP-DATA.txt](WIN32-LEARN-MAP-DATA.txt) is a dated record of the sections Microsoft Learn +documents Win32 symbols under, so that "which section does this belong to" is a lookup in a file. It +exists to apply the placement rule recorded in +[DESIGN-NOTES.md](DESIGN-NOTES.md#safe-wrappers-are-placed-by-the-learn-section-they-are-documented-under); +the rule is stated there and not here. + +The file is generated by [tools/snapshot-learn-toc.ps1](tools/snapshot-learn-toc.ps1) from Learn's own +table of contents, and says in its first lines the day it was read, what it covers and what it does not. +It covers the areas under Develop > Desktop App Technologies, and the areas with no table of contents of +their own are named in its header. + +## Reading it + +A line starting `# ` is the path of section titles that reaches a list of symbols. Under it, each line is +`kind`, `header`, `name` and, only where Learn's name differs from its page address (the `A` and `W` forms), +`=slug`, separated by tabs. The kinds are `function`, `struct`, `enum`, `callback`, `interface`, `class`, +`method`, `delegate` and `control-code` (an `IOCTL_` or `FSCTL_` value). + +## Classifying a symbol + +1. Find the symbol: `Select-String -Path WIN32-LEARN-MAP-DATA.txt -Pattern '\tGetSecurityInfo$'` gives + the line, and the `# ` line above it, the section. +2. A title can be shared by different symbols: Learn lists a callback titled `DuplicateHandle` under + `ntsecpkg.h` and the function of that name under `handleapi.h`. Learn's pages convey meaning to people + and a reader looks them up by title, so this is not an error and the record keeps it. The kind and + header with the name identify the symbol; check them against the `windows-sys` binding the wrapper calls. +3. Take the second-level topic under the area (for `GetSecurityInfo`, *Authorization*; for + `GetHandleInformation`, *Handles and Objects*), and look for a crate that already covers it. +4. **The record is a relation and not a function.** The same symbol can be listed under several sections, + because the documentation puts it where a reader would look for it, and that is Learn doing its job + and not a fault in it. The record keeps every listing and this recipe does not pick one for you: when a + symbol is listed more than once, the placement is a judgement by meaning, and the crate's notes say + which section was chosen and which others list it. + +## Keeping it + +Regenerate it with the tool when a symbol is looked up and is not there, or when Learn is known to have +reorganised; a regeneration is a commit on its own with the new date. The header and the section names it +records are Microsoft's and are not used in any crate name or public path, so a rename is a new snapshot and +never a release. diff --git a/crates/durable-ioring-events/Cargo.toml b/crates/durable-ioring-events/Cargo.toml new file mode 100644 index 000000000..8f0f5d3ef --- /dev/null +++ b/crates/durable-ioring-events/Cargo.toml @@ -0,0 +1,43 @@ +# Copyright (c) 2026 Mike Grier + +[package] +name = "durable-ioring-events" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +readme = "README.md" +description = "The ETW provider for durable-ioring: its events and manifest, and EtwDiagnostics, a diagnostic sink that writes each event to the provider. Costs a test of whether a session is listening when nobody is." +keywords = ["windows", "etw", "tracing", "durability", "diagnostics"] +categories = ["os::windows-apis", "development-tools::debugging"] + +# Named by the engineer, 2026-10-20: "win-dioring, I'd put it in its own crate", and then "Change the name of the event source +# to durable-ioring-events" (DE-D-1). It is the provider's name too. + +[dependencies] +# The sink trait, the events it carries and the kinds it is asked about (DI-D-51). +durable-ioring = { version = "0.2.0", path = "../durable-ioring" } +# The provider's registration and the write of an event (DI-12.1.4). This crate has no `unsafe` of its own. +win-etw-sys = { version = "0.0.1", path = "../win-etw-sys" } +# The failure a registration reports. +win-error-codes = { version = "0.0.2", path = "../win-error-codes" } + +# Pairs the begins and ends of a trace's CSV dump (DE-D-6). Its tests run with the crate's. +[[example]] +name = "pair_trace" +path = "examples/pair_trace/main.rs" +test = true + +[dev-dependencies] +# The trace session the tests use to see an event reach ETW (win-etw-sys' WE-D-3): it needs privilege, so the tests that use it +# are ignored by default. A dev-dependency's features reach test builds only. +win-etw-sys = { path = "../win-etw-sys", features = ["test-util"] } + +[package.metadata.docs.rs] +# The crate is Windows-only, so docs.rs must build on a Windows target or it +# would render an almost-empty crate. +default-target = "x86_64-pc-windows-msvc" +targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"] diff --git a/crates/durable-ioring-events/DESIGN-NOTES.md b/crates/durable-ioring-events/DESIGN-NOTES.md new file mode 100644 index 000000000..83b5e6d8a --- /dev/null +++ b/crates/durable-ioring-events/DESIGN-NOTES.md @@ -0,0 +1,208 @@ +# Design notes: durable-ioring-events + +## DE-D-1: the provider is `durable-ioring-events`, in a crate of its own, and its GUID is fixed + +**The name is the engineer's, 2026-10-20.** Asked where the delay events' provider should live and what it should be called, +the engineer answered: "win-dioring, I'd put it in its own crate" ([DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76)). +When the scope of that name was asked about, it read as broader than the dioring family, and the engineer said: "Change the +name of the event source to durable-ioring-events, replacing dashes with underscores as appropriate." So the provider is +named `durable-ioring-events`, and so is the crate; its library is `durable_ioring_events`, and the provider's name keeps its +dashes because it is a label in the manifest. + +**The GUID is generated once, 2026-10-20**, with `[guid]::NewGuid()`, and is `{47067f6f-8f2b-4278-9b92-092dfb3385e6}`, +`PROVIDER_ID`. A session enables the provider by it, so changing it breaks every session that did, and a test pins it. It is +recorded here and in the code and nowhere is it derived. + +**The scope is the dioring family.** The provider carries the begin and end events of [DI-D-80](../durable-ioring/DESIGN-NOTES.md#di-d-80) +that `durable-ioring` and `win-durable-ioring` report, which replaced the delay events of +[DI-D-26](../durable-ioring/DESIGN-NOTES.md#di-d-26) ([DE-D-5](#de-d-5)), and the inconsistency report, and nothing from +another crate. The +provider is registered once for a process and shared by every sink that is alive, which is the assistant's working position +from the question of its scope and is built as that: it is not a ruling. + +**Scheduled work.** None. The Windows driver's default sink is [DI-12.1.6](../durable-ioring/CHECKLIST.md). + +## DE-D-2: the events are one table, an event of durable-ioring is one event of the provider, and a flush answered is two + +**Partly superseded by [DE-D-5](#de-d-5):** the delay events, their ids 2 to 6, the duration and boolean fields, and opcode and task 0 gave way to the pairs; the table, the split of a flush by its part, and the rest stand. + +**The table is the one description of an event.** `EVENTS` holds, for each event, its id, name, the kind of event of +durable-ioring that becomes it, its level, its keyword and its fields with their types. The manifest is rendered from it +([DE-D-3](#de-d-3)), the sink encodes each event to its columns, and a test reads an event back from the bytes by it, so the +three cannot disagree about a field, which is the restatement drift the repository's rules name. + +**Each event of durable-ioring is one event here, except a flush answered, which is two.** An inconsistency, a lock wait, a +flush issued and a seal waited are one each. A flush answered carries a file or a domain, and the two have different fields (a +file's key, a domain's name as bytes with a length), so each is an event of its own: `FlushAnsweredFile` and +`FlushAnsweredDomain`. They share a level and a keyword, which a test holds, so that a kind can be asked about by its first +event. This is the assistant's choice and is open to a ruling; the alternative is one event with a part kind and fields that +are empty for the other part. + +**Fields.** Every event begins with the instance, the number it was minted with, so a reader can tell instances apart; a +lineage is a number; an epoch is text, built from the `Debug` form durable-ioring renders it in ([DI-D-77](../durable-ioring/DESIGN-NOTES.md#di-d-77)), +and a duration is nanoseconds as a 64-bit number, the largest if it does not fit. A flush's outcome is a four-byte boolean, which is +what a manifest's `Boolean` is. A domain's name is bytes the consumer chose, so it is a `Binary` with its length written +first, and a name too long for the length is cut to what the length can say so that the two agree. + +**Level and keyword.** An inconsistency is an error and each delay is informational. The keywords are the categories a reader +filters by: locks, flushes, seals and inconsistencies, one bit each in the provider's forty-eight. Every event has a nonzero +level and keyword, as the page for the structure asks of every event, and is version 0, channel 0, opcode 0 and task 0. + +**Text.** Text is written as UTF-16 with a terminator, as a manifest's `UnicodeString` is read. **Amended by +[DE-D-7](#de-d-7):** a zero in the text ends it where it is, and what follows is not written. It was written whole, on the +ground that the one arbitrary text is the last field of its event, which holds for an inconsistency's detail and not for a +lock's name, which is followed by the pair. An event larger than a session's buffer is dropped by the call and the sink +ignores that. + +**The ids are pinned.** A reader decodes by an event's id, so they are 1 to 6 in this order and a test holds them. + +**Scheduled work.** None. + +## DE-D-3: the manifest is rendered from the table and kept with the crate, and it was checked with the real tools + +**Rendered, and kept.** `manifest()` renders the manifest from `EVENTS`, and `durable-ioring-events.man`, beside the crate's +`Cargo.toml`, is that text, held equal to it by a test; the ignored test `rewrite_the_manifest_file` brings the file up to +date. The crate has no build step that needs the Windows SDK, so nothing a consumer builds depends on it. + +**What it says.** The provider's name and GUID, its keywords, each event with its id, version, level, keyword and template, and +each template with the types of its fields in order. The provider's resource and message file names are +`durable-ioring-events.dll`, which an installer overrides with the file it is given; the events have no localized messages, so a +tool shows a field's name and value and no sentence. + +**Installing it is the reader's.** The provider records without it ([DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76), and +the page for `EventRegister`: "It is not necessary to install a manifest to write events or capture traces"). A reader who +wants names compiles it with the Windows SDK's `mc` and `rc`, links the resource-only library, and installs it with +`wevtutil im`, which the README gives step by step. + +**Checked, 2026-10-20.** The steps in the README were run: `mc -um` accepted the manifest, `rc` and `link` made the library, +and `wevtutil im` installed it. A session for the provider recorded the events the example writes, and `tracerpt` named the +provider and showed each of the six events with its level and keyword by name, and every field by name with its value: the +instance, the lock's name and its wait, the epoch as text, a file's key, a flush's outcome as true or false, and a domain's +bytes with their length (`0x7373642D34` for `ssd-4`). `tracerpt` warned that "Some events do not match the schema", which was +about the two header events every trace carries and not the provider's. The manifest was uninstalled afterwards. This was done +by hand and is not a test: it needs the SDK and an elevated process, and what the tests hold is that the manifest is the table +and that the table is what is written ([DE-D-2](#de-d-2)). + +**Scheduled work.** None. + +## DE-D-4: the sink shares one registration, wants a kind only when a session listens to it, and ignores a failed write + +**One registration, shared.** `EtwDiagnostics` holds an `Arc` to a registration that every sink of the process shares while +any is alive: the first makes it, a later one finds it, and the last to be dropped unregisters it. The page asks a component +to register "one or two" providers, a process has a limit, and a library that is unloaded must give its registration back +("A DLL that unloads without freeing all of the provider handles that it registered may cause the process to crash"). A static +that is never dropped would do the first and not the last. The assistant chose this; it is open to a ruling. + +**`wants` is a test, and `report` tests again.** `wants` asks `EventProviderEnabled` for the kind's first event's level and +keyword, so a session that is not listening costs the instance one branch and no text is built, as +[DI-D-51](../durable-ioring/DESIGN-NOTES.md#di-d-51) and [DI-D-26](../durable-ioring/DESIGN-NOTES.md#di-d-26) require. `report` +asks `EventEnabled` of the event itself before it builds anything, because `wants` was asked of a kind and an event's +descriptor is finer. A unit test counts how many times a field's text is formatted: none with no session, and once with one. + +**A sink that cannot register wants nothing.** `new` carries on with no registration if `EventRegister` fails, as the page says +production code should; `is_registered` says whether it did, and `try_new` returns the failure for a caller that wants it. + +**A failed write is ignored.** The page says the code "is primarily intended for use in debugging and diagnostic scenarios", and +a sink has no way to report a failure of its own. + +**The tests.** Without a session, in memory: a sink registers, wants nothing, builds nothing, shares one registration and +gives it back. With one, `tests/session.rs`, which starts a real session, enables the provider in it, reports through the sink +and reads the events back, decoding them by the table: every event reaches a session as its values, a keyword filter and a +level filter are what the sink is asked about, a sink made before a session wants events once one listens, two sinks and four +threads record each event once, a zero in text ends it there ([DE-D-7](#de-d-7)), and an event too large for a session is dropped with no +failure. Starting a session needs privilege, so those tests are ignored by default and CI runs them with `--include-ignored` +([WE-D-3](../win-etw-sys/DESIGN-NOTES.md#we-d-3)). They were run elevated on 2026-10-20 and all nine pass. + +**Scheduled work.** None beyond [DI-12.1.6](../durable-ioring/CHECKLIST.md), which makes this the Windows driver's default. + +## DE-D-5: the pairs are events with Start and Stop opcodes, a task for each kind, and new ids + +Made by the assistant under [DI-D-87](../durable-ioring/DESIGN-NOTES.md#di-d-87), whose ruling brought the opcodes into +[DI-19.2.4](../durable-ioring/CHECKLIST.md) with the pairs; open to a ruling. + +**The events.** durable-ioring's begin and end events ([DI-D-80](../durable-ioring/DESIGN-NOTES.md#di-d-80)) replace its +delay events, and each end is an event of its own: `SealBegan` and `SealEnded`, `FlushBeganFile` and `FlushEndedFile`, +`FlushBeganDomain` and `FlushEndedDomain`, and `LockWaitBegan` and `LockWaitEnded`, beside `Inconsistency`. A flush is still +split by its part, for [DE-D-2](#de-d-2)'s reason. An end repeats its begin's identity fields, so one record can be read alone. + +**The ids are 7 to 14, and 2 to 6 are retired.** A reader decodes by id, and a trace written before this change carries the +delay events under 2 to 6, so those ids are not given again and no record of an old trace is decoded as one of these. `Inconsistency` keeps 1. A test pins every id. + +**Opcodes come from `Diagnostic::pairing`.** Each event's row says which end of a pair it is, a begin is written as +`win:Start`, an end as `win:Stop`, and an event that is not a pair as `win:Info`. The row restates what `Diagnostic::pairing` +defines ([DI-D-88](../durable-ioring/DESIGN-NOTES.md#di-d-88)), so a test encodes an event of each variant and holds its row's +end equal to what `pairing` says of it; a variant that changes ends cannot leave its row behind. + +**A task for each kind of pair**: `Seal` 1, `Flush` 2 and `LockWait` 3, so a reader groups a Start with its Stop by task, +and the activity id that joins them is [DE-D-6](#de-d-6)'s. `Inconsistency` has no task. The manifest +renders the tasks before the keywords and names each event's task and opcode. + +**Fields.** A pair's id is `Pair`, a 64-bit number. An end's outcome is `Outcome`, text, the word durable-ioring's `Display` +gives it -- durable, failed, blocked or ended for a seal, succeeded, failed or abandoned for a flush -- so a reader sees the +word and an outcome added later needs no new field. No event carries a duration: it is the gap between a Start's and its +Stop's timestamps, which ETW writes on every record. + +**Checked, 2026-10-21**, as [DE-D-3](#de-d-3) was. `mc -um` accepted the rendered manifest with its `` element and +the events' `task` and `opcode` attributes, `rc` and `link` made the resource library, and `wevtutil im` installed it. A +session recorded the events the example writes, and `tracerpt` showed each of the nine by name with its task by name and its +opcode as `win:Info`, `win:Start` or `win:Stop`, and every field by name: the pair ids shared by each begin and its end, an +end's outcome as its word, and a domain's bytes with their length. The same "Some events do not match the schema" warning +as before was about the trace's header events. The manifest was uninstalled afterwards. The nine session tests and the +Windows driver's ETW test were run elevated the same day and pass. + +**Scheduled work.** None; the activity id is [DE-D-6](#de-d-6). + +## DE-D-6: a pair is one ETW activity, whose id the platform creates at the begin and the end repeats + +The engineer's choice, 2026-10-21, of the assistant's option "c" ([DI-D-93](../durable-ioring/DESIGN-NOTES.md#di-d-93)); the +details below are the assistant's and open to a ruling. + +**Why not an id derived from the pair.** The working position was the process id in the upper 64 bits and the pair id in the +lower. A process id can be reused within one trace and the pair counter starts again in each process, so a later process's +pair would carry an earlier one's activity: a reader that groups by activity would merge them, and an unfinished begin of a +process that died could be paired with another process's end. `EventActivityIdControl` creates ids its page guarantees unique +"across all processes on the local system until the system reboots" ([WE-D-5](../win-etw-sys/DESIGN-NOTES.md#we-d-5)), so no +formula of ours has to be. + +**How.** When a pair's begin is written, an id is created and held against the pair id; its end takes the id back and writes +it, and both are written by `write_transfer` with that activity, whatever thread writes each. The map is the process's, behind +a lock that is a leaf as `DiagnosticSink` requires: a pair written by two sinks -- a clone, or two in a fan-out -- is one +activity, held until its last end. An end lets its activity go whether or not a session is listening for it, so nothing is +held for a session that stopped between the two. An end whose begin was not written, because no session listened then, is +written in an activity of its own, so it joins no other, because a sink assumes no begin for an end ([DI-D-96](../durable-ioring/DESIGN-NOTES.md#di-d-96)); a begin whose id cannot be created is written in the thread's +activity, with nothing held. Any event that is not a pair is written in the thread's activity, as before. The pair id stays in +the `Pair` field, so a text sink's line and a trace's record still name the same pair. + +**Cost.** With no session listening, nothing: the sink is not asked to report. With one, a begin costs the call and a map +insert under the lock, and an end a map removal. + +**Read back by a program, 2026-10-21.** A trace of `emit_every_event`, recorded elevated with the manifest installed and dumped +by `tracerpt` to CSV, is read by the example `pair_trace`, which pairs each `win:Start` with the `win:Stop` in its activity, +checks that the two agree on task and on the `Pair` field, and reports the gap between their clock times. It paired all four +pairs, each in an activity of its own, each no shorter than the wait the example makes, and the two flushes ended on another +thread than they began on. The dump is kept as the example's test fixture, `examples/pair_trace/trace.csv`. The session tests +were run elevated the same day and pass, and the sabotages only a session catches are `sabotage-session.json`, swept then, each +caught and its control surviving. + +**Scheduled work.** None. + +## DE-D-7: a zero in text ends it where it is, and an end is let go whenever the sink is given it + +The review of 2026-10-22 and the engineer's rulings on it ([DI-D-96](../durable-ioring/DESIGN-NOTES.md#di-d-96)); the details +are the assistant's and open to a ruling. + +**A zero ends the text.** A `UnicodeString` is read to its first zero unit, so a zero in the text of a field that is not the last +makes a reader take the units after it as the fields after it. The review measured it with `TdhGetProperty` over the encoder's output for a +lock wait named `ab` with pair 7: the call succeeded and the pair it read was 30064771170. So text is cut at its +first zero where it is made, by one function, `before_zero`, which the encoder and the write of a value both use, and what a +reader is shown of a field is its text up to that zero. The alternatives were to escape the zero or to write the length first; +neither is a `UnicodeString`, so a manifest would have to change for each, and the text is a diagnostic. A text with no zero is +unchanged. + +**An end is written whenever a session wants it, with or without its begin.** The sink is given every pair's end by a fan-out +whatever it wants, lets its pair go at that end whether or not it writes it, and writes it if the event is enabled: in the +begin's activity if the begin was written, and in one of its own if it was not. A sink assumes no begin for an end; a begin +whose end never comes is the producer's to answer for. + +**Scheduled work.** None; [DI-22.2](../durable-ioring/COMPLETED-CHECKLIST.md#di-222) is this. + diff --git a/crates/durable-ioring-events/PLANS.md b/crates/durable-ioring-events/PLANS.md new file mode 100644 index 000000000..e3e613c13 --- /dev/null +++ b/crates/durable-ioring-events/PLANS.md @@ -0,0 +1,8 @@ +# Plans: durable-ioring-events + +The crate has no checklist of its own. It is `DI-12.1.5` in the durable-ioring +[CHECKLIST.md](../durable-ioring/CHECKLIST.md), and its first caller is `DI-12.1.6` in that component. Add a row here when +new work is planned against a `CHECKLIST.md` in this directory. + +| Path to CHECKLIST.md | Status | Brief description | Design Notes | +|---|---|---|---| diff --git a/crates/durable-ioring-events/README.md b/crates/durable-ioring-events/README.md new file mode 100644 index 000000000..449e33d7c --- /dev/null +++ b/crates/durable-ioring-events/README.md @@ -0,0 +1,60 @@ +# durable-ioring-events + +The ETW provider for [durable-ioring](../durable-ioring), as a crate of its own: the events it writes and the manifest that +describes them, and `EtwDiagnostics`, a diagnostic sink that writes each event to the provider. + +- The provider is named `durable-ioring-events`, and `PROVIDER_ID` is its GUID, generated once. A session enables it by the + GUID. +- `EtwDiagnostics` is a `DiagnosticSink`. It wants a kind of event only when a session is listening for it, so with none its + cost is a test of that: no text is formatted and nothing is written. It registers the provider once for the process, and + gives the registration back when the last sink is dropped. +- It records whether or not the provider's manifest is installed. **A reader needs the manifest**, to see an event's name and + its fields and not only its bytes; the application that writes the events does not. +- `EVENTS` is each event as data: its id, name, level, keyword, task, opcode and fields with their types, and `manifest()` + renders the manifest from it, so the two cannot disagree. +- durable-ioring's begin and end events are written as pairs: a begin with the `win:Start` opcode and its end with + `win:Stop`, under a task for each kind -- a seal, a flush, a lock wait -- and both carrying the pair's id. A wait's length + is the gap between the two records' timestamps. A pair is one ETW activity, whose id ETW creates at the begin and the end + repeats, so a begin and an end written on different threads are grouped together ([DE-D-6](DESIGN-NOTES.md#de-d-6)). The manifest is kept with the crate as `durable-ioring-events.man`, and a test + holds the file equal to what is rendered. + +```rust +use durable_ioring_events::{EtwDiagnostics, PROVIDER_NAME}; + +let sink = EtwDiagnostics::new(); +assert!(sink.is_registered()); +assert_eq!(PROVIDER_NAME, "durable-ioring-events"); +``` + +## Reading the events + +Without the manifest a trace shows the provider's GUID, each event's id, level and keyword, and its data as bytes. With it a +reader sees the names. Installing it is a step for whoever reads the trace, from an elevated prompt, with the Windows SDK's +tools on the path: + +1. Write the manifest, or take `durable-ioring-events.man` from the crate: + `durable_ioring_events::manifest()` is its text. +2. Compile it: `mc -um durable-ioring-events.man`. That makes a resource script and the binary form of the manifest. +3. Compile the resources: `rc durable-ioring-events.rc`. +4. Link them into a resource-only library: `link -dll -noentry -machine:x64 durable-ioring-events.res -out:durable-ioring-events.dll` + (`-machine:arm64` on an ARM machine). +5. Install it, naming the library by its full path: + `wevtutil im durable-ioring-events.man /rf:C:\path\durable-ioring-events.dll /mf:C:\path\durable-ioring-events.dll`. + +Then a tool that decodes by manifest, such as `tracerpt`, shows each event by name. `wevtutil um durable-ioring-events.man` +uninstalls it. An event's fields are written in the order the manifest gives, with no padding, and a text field ends at its +first zero unit, so a zero in a message cuts it short for a reader. + +## Pairing a trace + +The example `pair_trace` reads `tracerpt`'s CSV dump of a trace, decoded with the manifest installed, and prints each pair +with how long it took, or what broke the pairing: + +```text +tracerpt trace.etl -o trace.csv -of CSV +cargo run -p durable-ioring-events --example pair_trace -- trace.csv +``` + +See [DE-D-1](DESIGN-NOTES.md#de-d-1) for the name and the GUID, [DE-D-2](DESIGN-NOTES.md#de-d-2) for the events, +[DE-D-3](DESIGN-NOTES.md#de-d-3) for the manifest, [DE-D-4](DESIGN-NOTES.md#de-d-4) for the sink and +[DE-D-5](DESIGN-NOTES.md#de-d-5) for the pairs and [DE-D-6](DESIGN-NOTES.md#de-d-6) for their activities. diff --git a/crates/durable-ioring-events/durable-ioring-events.man b/crates/durable-ioring-events/durable-ioring-events.man new file mode 100644 index 000000000..47c0c52e2 --- /dev/null +++ b/crates/durable-ioring-events/durable-ioring-events.man @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/durable-ioring-events/examples/emit_every_event.rs b/crates/durable-ioring-events/examples/emit_every_event.rs new file mode 100644 index 000000000..c9121fca8 --- /dev/null +++ b/crates/durable-ioring-events/examples/emit_every_event.rs @@ -0,0 +1,114 @@ +// Copyright (c) 2026 Mike Grier +//! Writes one of each event through `EtwDiagnostics`, for a person to see them decoded: start a session for the provider, +//! install the manifest as the README says, run this, stop the session, and read the trace with a tool that decodes by manifest. +//! Each pair's begin and end are written, the begin as `win:Start` and the end as `win:Stop`, in one activity (DE-D-6). +//! +//! Each pair's end follows its begin by at least a known wait, and the flushes end on another thread than they began on, so a +//! trace of this shows an activity carried across threads and a duration no shorter than the wait: [`WAITS`] says which. + +#![cfg(windows)] + +use durable_ioring::{ + CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink, FlushDomain, FlushOutcome, + FlushPart, IoTargetKey, SealOutcome, +}; +use durable_ioring_events::EtwDiagnostics; +use std::thread; +use std::time::Duration; + +/// The least time between each pair's begin and its end, by the event names of its begin: the lock wait, the flush of a file, +/// the flush of a domain, and the seal. +pub const WAITS: [(&str, Duration); 4] = [ + ("LockWaitBegan", Duration::from_millis(20)), + ("FlushBeganFile", Duration::from_millis(30)), + ("FlushBeganDomain", Duration::from_millis(40)), + ("SealBegan", Duration::from_millis(50)), +]; + +/// A sink that wants every kind, to mint the pairs' ids from, so the example writes whether or not a session listens. +struct Wanting; + +impl DiagnosticSink for Wanting { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn main() { + let sink = EtwDiagnostics::new(); + let id = |kind| CorrelationId::for_pair(&Wanting, kind).expect("wanted"); + let domain = FlushDomain::new(b"ssd-4".to_vec()); + sink.report(&Diagnostic::Inconsistency { + instance: 7, + what: "a write completed twice", + detail: format_args!("lineage {}", 3), + }); + let lock = id(DiagnosticKind::LockWait); + sink.report(&Diagnostic::LockWaitBegan { + instance: 4, + lock: "engine", + id: lock, + }); + thread::sleep(WAITS[0].1); + sink.report(&Diagnostic::LockWaitEnded { + instance: 4, + lock: "engine", + id: lock, + }); + let seal = id(DiagnosticKind::Seal); + sink.report(&Diagnostic::SealBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + id: seal, + }); + let file = id(DiagnosticKind::Flush); + sink.report(&Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: file, + }); + let part = id(DiagnosticKind::Flush); + sink.report(&Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + }); + // The flushes are answered on another thread, as a provider answers. + thread::scope(|scope| { + scope.spawn(|| { + thread::sleep(WAITS[1].1); + sink.report(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: file, + outcome: FlushOutcome::Failed, + }); + thread::sleep(WAITS[2].1 - WAITS[1].1); + sink.report(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + outcome: FlushOutcome::Succeeded, + }); + }); + }); + thread::sleep(WAITS[3].1 - WAITS[2].1); + sink.report(&Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + id: seal, + outcome: SealOutcome::Failed, + }); +} diff --git a/crates/durable-ioring-events/examples/pair_trace/main.rs b/crates/durable-ioring-events/examples/pair_trace/main.rs new file mode 100644 index 000000000..3f8948cad --- /dev/null +++ b/crates/durable-ioring-events/examples/pair_trace/main.rs @@ -0,0 +1,324 @@ +// Copyright (c) 2026 Mike Grier +//! Pairs the begin and end events of a trace of the provider and says how long each pair took (DE-D-6): the way a reader of +//! the trace computes a duration, since nothing in durable-ioring measures one. +//! +//! The input is `tracerpt`'s CSV dump of a trace, `tracerpt trace.etl -o trace.csv -of CSV`. A `Start` opens a pair in its +//! activity and a `Stop` in the same activity ends it, and each must agree with the other on its task and on the pair id it +//! carries in its `Pair` field. Its duration is the gap between their `Clock-Time` columns, which `tracerpt` gives as system +//! time in 100-nanosecond units unless asked for raw timestamps. Events of other providers are skipped. +//! +//! `cargo run -p durable-ioring-events --example pair_trace -- trace.csv` prints each pair and exits with a failure if any +//! event breaks the pairing. + +#![cfg(windows)] + +use std::collections::BTreeMap; +use std::fmt; +use std::time::Duration; + +use durable_ioring_events::{PROVIDER_NAME, TASKS, schema_of_id}; + +#[cfg(test)] +mod tests; + +/// The opcodes a pair is written with: `win:Start` and `win:Stop`. +mod opcode { + pub const START: u8 = 1; + pub const STOP: u8 = 2; +} + +/// The activity id the platform says is no activity. +const NO_ACTIVITY: &str = "{00000000-0000-0000-0000-000000000000}"; + +/// One event of the provider, as the dump gives it. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Row { + id: u16, + opcode: u8, + task: u16, + thread: String, + activity: String, + clock: u64, + data: Vec, +} + +impl Row { + /// The pair id the event carries in its `Pair` field, found by the provider's own table. + fn pair(&self) -> Option<&str> { + let schema = schema_of_id(self.id)?; + let at = schema.columns.iter().position(|c| c.name == "Pair")?; + self.data.get(at).map(String::as_str) + } +} + +/// A begin and its end. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Pair { + task: u16, + pair: String, + activity: String, + took: Duration, + began_on: String, + ended_on: String, +} + +impl fmt::Display for Pair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let task = TASKS + .iter() + .find(|t| t.value == self.task) + .map_or("?", |t| t.name); + write!( + f, + "{task} pair {} in activity {}: {:.3} ms, begun on thread {} and ended on {}", + self.pair, + self.activity, + self.took.as_secs_f64() * 1000.0, + self.began_on, + self.ended_on + ) + } +} + +/// An event that breaks the pairing. +#[derive(Clone, Debug, PartialEq, Eq)] +enum Broken { + /// A start or a stop in no activity. + NoActivity { id: u16 }, + /// A start in an activity a start already opened. + StartedTwice { activity: String }, + /// A stop in an activity no start opened. + StopWithoutStart { activity: String }, + /// A stop of another task than its start's. + TaskDiffers { + activity: String, + began: u16, + ended: u16, + }, + /// A stop that names another pair than its start. + PairDiffers { + activity: String, + began: Option, + ended: Option, + }, + /// A stop that came before its start. + EndedBeforeBegun { activity: String }, + /// A start never stopped. + LeftOpen { activity: String }, +} + +impl fmt::Display for Broken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NoActivity { id } => write!(f, "event {id} is a pair's but has no activity"), + Self::StartedTwice { activity } => write!(f, "activity {activity} was started twice"), + Self::StopWithoutStart { activity } => { + write!(f, "activity {activity} was stopped and never started") + } + Self::TaskDiffers { + activity, + began, + ended, + } => { + write!( + f, + "activity {activity} began as task {began} and ended as task {ended}" + ) + } + Self::PairDiffers { + activity, + began, + ended, + } => { + write!( + f, + "activity {activity} began as pair {began:?} and ended as pair {ended:?}" + ) + } + Self::EndedBeforeBegun { activity } => { + write!(f, "activity {activity} ended before it began") + } + Self::LeftOpen { activity } => { + write!(f, "activity {activity} was started and never stopped") + } + } + } +} + +/// The provider's events in a dump, in the order it gives them. +/// +/// # Errors +/// +/// A dump without the columns this reads, or a row of the provider whose fields cannot be read. +fn read(dump: &str) -> Result, String> { + let mut lines = dump.lines(); + let header: Vec<&str> = lines + .next() + .ok_or("an empty dump")? + .split(',') + .map(str::trim) + .collect(); + let at = |name: &str| { + header + .iter() + .position(|h| *h == name) + .ok_or(format!("no `{name}` column")) + }; + let (provider, id, opcode, task, thread, activity, clock) = ( + at("Event Name")?, + at("Event ID")?, + at("Opcode")?, + at("Task")?, + at("TID")?, + at("Activity ID")?, + at("Clock-Time")?, + ); + let data = at("User Data")?; + if data + 1 != header.len() { + return Err("`User Data` is not the last column".into()); + } + let mut rows = Vec::new(); + for (n, line) in lines.enumerate() { + // Only the last column can hold a comma, inside a quoted string, so the fields before it split plainly. + let fields: Vec<&str> = line.splitn(header.len(), ',').map(str::trim).collect(); + if fields.get(provider) != Some(&PROVIDER_NAME) { + continue; + } + let field = |i: usize| { + fields + .get(i) + .copied() + .ok_or(format!("row {} is short", n + 2)) + }; + let number = |i: usize| -> Result { + field(i)? + .parse() + .map_err(|_| format!("row {}: `{}` is not a number", n + 2, fields[i])) + }; + rows.push(Row { + id: u16::try_from(number(id)?).map_err(|e| e.to_string())?, + opcode: u8::try_from(number(opcode)?).map_err(|e| e.to_string())?, + task: u16::try_from(number(task)?).map_err(|e| e.to_string())?, + thread: field(thread)?.to_owned(), + activity: field(activity)?.to_lowercase(), + clock: number(clock)?, + data: values(fields.get(data).copied().unwrap_or(""))?, + }); + } + Ok(rows) +} + +/// The values of a `User Data` column: separated by commas, a string quoted, with a doubled quote for a quote in it. +fn values(data: &str) -> Result, String> { + let mut out = Vec::new(); + let mut chars = data.trim().chars().peekable(); + while chars.peek().is_some() { + while chars.next_if(|c| *c == ' ').is_some() {} + let mut value = String::new(); + if chars.next_if_eq(&'"').is_some() { + loop { + match chars.next() { + Some('"') if chars.next_if_eq(&'"').is_some() => value.push('"'), + Some('"') => break, + Some(c) => value.push(c), + None => return Err(format!("an unterminated string in `{data}`")), + } + } + while chars.next_if(|c| *c == ' ').is_some() {} + } else { + while let Some(c) = chars.next_if(|c| *c != ',') { + value.push(c); + } + value = value.trim_end().to_owned(); + } + out.push(value); + match chars.next() { + None | Some(',') => {} + Some(c) => return Err(format!("`{c}` after a value in `{data}`")), + } + } + Ok(out) +} + +/// Each begin with its end, in the order the ends came, and what broke the pairing. +fn pair(rows: &[Row]) -> (Vec, Vec) { + let mut open: BTreeMap<&str, &Row> = BTreeMap::new(); + let (mut pairs, mut broken) = (Vec::new(), Vec::new()); + for row in rows { + if row.opcode != opcode::START && row.opcode != opcode::STOP { + continue; + } + if row.activity == NO_ACTIVITY { + broken.push(Broken::NoActivity { id: row.id }); + continue; + } + let activity = row.activity.as_str(); + if row.opcode == opcode::START { + if open.insert(activity, row).is_some() { + broken.push(Broken::StartedTwice { + activity: activity.to_owned(), + }); + } + continue; + } + let Some(began) = open.remove(activity) else { + broken.push(Broken::StopWithoutStart { + activity: activity.to_owned(), + }); + continue; + }; + if began.task != row.task { + broken.push(Broken::TaskDiffers { + activity: activity.to_owned(), + began: began.task, + ended: row.task, + }); + continue; + } + if began.pair() != row.pair() || row.pair().is_none() { + broken.push(Broken::PairDiffers { + activity: activity.to_owned(), + began: began.pair().map(str::to_owned), + ended: row.pair().map(str::to_owned), + }); + continue; + } + let Some(ticks) = row.clock.checked_sub(began.clock) else { + broken.push(Broken::EndedBeforeBegun { + activity: activity.to_owned(), + }); + continue; + }; + pairs.push(Pair { + task: row.task, + pair: row.pair().unwrap_or_default().to_owned(), + activity: activity.to_owned(), + took: Duration::from_nanos(ticks.saturating_mul(100)), + began_on: began.thread.clone(), + ended_on: row.thread.clone(), + }); + } + broken.extend(open.into_keys().map(|activity| Broken::LeftOpen { + activity: activity.to_owned(), + })); + (pairs, broken) +} + +fn main() -> Result<(), String> { + let path = std::env::args() + .nth(1) + .ok_or("usage: pair_trace ")?; + let dump = std::fs::read_to_string(&path).map_err(|e| format!("{path}: {e}"))?; + let (pairs, broken) = pair(&read(&dump)?); + for pair in &pairs { + println!("{pair}"); + } + for fault in &broken { + println!("broken: {fault}"); + } + if broken.is_empty() { + Ok(()) + } else { + Err(format!("{} events broke the pairing", broken.len())) + } +} diff --git a/crates/durable-ioring-events/examples/pair_trace/tests.rs b/crates/durable-ioring-events/examples/pair_trace/tests.rs new file mode 100644 index 000000000..1678eca90 --- /dev/null +++ b/crates/durable-ioring-events/examples/pair_trace/tests.rs @@ -0,0 +1,272 @@ +// Copyright (c) 2026 Mike Grier +//! The pairing, over the dump of a real trace and over dumps made for each way a pairing breaks. +//! +//! `trace.csv` is `tracerpt`'s CSV dump of a trace of `emit_every_event`, recorded elevated on 2026-10-21 with the manifest +//! installed (DE-D-6): one inconsistency and four pairs, whose ends follow their begins by at least 20, 30, 40 and 50 ms, the +//! two flushes ending on another thread than they began on. + +use std::time::Duration; + +use super::{Broken, NO_ACTIVITY, Pair, pair, read, values}; + +const TRACE: &str = include_str!("trace.csv"); + +/// The dump's header, as `tracerpt` writes it, padding and all. +const HEADER: &str = " Event Name, Type, Event ID, Version, Channel, Level, Opcode, Task, Keyword, PID, TID, Processor Number, Instance ID, Parent Instance ID, Activity ID, Related Activity ID, Clock-Time, Kernel(ms), User(ms), User Data"; + +const A: &str = "{0795ed5d-9da0-0002-189f-908e285ddd01}"; +const B: &str = "{0795ed5d-9da0-0002-199f-908e285ddd01}"; + +/// A row of the provider: a lock wait's begin (13) or end (14) unless `id` says otherwise. +fn row(id: u16, opcode: u8, task: u16, activity: &str, clock: u64, data: &str) -> String { + format!( + "durable-ioring-events, Start, {id}, 0, 0, 4, {opcode}, {task}, 0x1, 0x10, 0x20, 2, , , {activity}, , {clock}, 0, 0, {data}" + ) +} + +fn dump(rows: &[String]) -> String { + let mut text = String::from(HEADER); + for row in rows { + text.push('\n'); + text.push_str(row); + } + text +} + +fn paired(rows: &[String]) -> (Vec, Vec) { + pair(&read(&dump(rows)).expect("read")) +} + +#[test] +fn the_traces_four_pairs_are_paired_and_nothing_breaks() { + let (pairs, broken) = pair(&read(TRACE).expect("read")); + assert_eq!(broken, []); + assert_eq!(pairs.len(), 4, "{pairs:?}"); +} + +#[test] +fn each_pair_of_the_trace_took_at_least_the_wait_the_example_made() { + let (pairs, _) = pair(&read(TRACE).expect("read")); + // By task and pair id: the lock wait (3), the file and domain flushes (2), and the seal (1). + let waits = [(3, "1", 20), (2, "3", 30), (2, "4", 40), (1, "2", 50)]; + for (task, id, ms) in waits { + let found = pairs + .iter() + .find(|p| p.task == task && p.pair == id) + .unwrap_or_else(|| panic!("task {task} pair {id} in {pairs:?}")); + assert!(found.took >= Duration::from_millis(ms), "{found}"); + } +} + +#[test] +fn the_traces_durations_are_the_gaps_between_the_clock_times() { + let (pairs, _) = pair(&read(TRACE).expect("read")); + let lock = pairs.iter().find(|p| p.task == 3).expect("the lock wait"); + // 134371033678956115 - 134371033678751420 ticks of 100 ns. + assert_eq!(lock.took, Duration::from_nanos(204_695 * 100)); +} + +#[test] +fn the_traces_flushes_end_on_another_thread_in_the_activity_they_began_in() { + let (pairs, _) = pair(&read(TRACE).expect("read")); + let flushes: Vec<_> = pairs.iter().filter(|p| p.task == 2).collect(); + assert_eq!(flushes.len(), 2); + for flush in flushes { + assert_ne!(flush.began_on, flush.ended_on, "{flush}"); + } +} + +#[test] +fn the_traces_pairs_are_four_activities() { + let (pairs, _) = pair(&read(TRACE).expect("read")); + let mut activities: Vec<_> = pairs.iter().map(|p| p.activity.clone()).collect(); + activities.sort(); + activities.dedup(); + assert_eq!(activities.len(), 4); + assert!(!activities.contains(&NO_ACTIVITY.to_owned())); +} + +#[test] +fn the_trace_is_read_as_the_providers_nine_events_and_no_others() { + let rows = read(TRACE).expect("read"); + assert_eq!(rows.len(), 9); + assert_eq!(rows[0].id, 1, "the inconsistency first"); + assert_eq!(rows[0].data, ["7", "a write completed twice", "lineage 3"]); +} + +#[test] +fn a_pair_is_displayed_with_its_task_and_its_duration_in_milliseconds() { + let (pairs, _) = pair(&read(TRACE).expect("read")); + let lock = pairs.iter().find(|p| p.task == 3).expect("the lock wait"); + assert_eq!( + lock.to_string(), + format!( + "LockWait pair 1 in activity {A}: 20.470 ms, begun on thread 0x00008DE4 and ended on 0x00008DE4" + ) + ); +} + +#[test] +fn a_start_and_its_stop_are_one_pair_whatever_comes_between() { + let (pairs, broken) = paired(&[ + row(13, 1, 3, A, 100, "4, \"engine\", 1"), + row(13, 1, 3, B, 150, "4, \"engine\", 2"), + row(14, 2, 3, A, 400, "4, \"engine\", 1"), + row(14, 2, 3, B, 450, "4, \"engine\", 2"), + ]); + assert_eq!(broken, []); + assert_eq!( + pairs.iter().map(|p| p.took).collect::>(), + [Duration::from_nanos(30_000); 2] + ); +} + +#[test] +fn a_stop_with_no_start_breaks_the_pairing() { + let (pairs, broken) = paired(&[row(14, 2, 3, A, 400, "4, \"engine\", 1")]); + assert_eq!(pairs, []); + assert_eq!( + broken, + [Broken::StopWithoutStart { + activity: A.to_owned() + }] + ); +} + +#[test] +fn a_start_never_stopped_is_left_open() { + let (_, broken) = paired(&[row(13, 1, 3, A, 100, "4, \"engine\", 1")]); + assert_eq!( + broken, + [Broken::LeftOpen { + activity: A.to_owned() + }] + ); +} + +#[test] +fn an_activity_started_twice_breaks_the_pairing() { + let (_, broken) = paired(&[ + row(13, 1, 3, A, 100, "4, \"engine\", 1"), + row(13, 1, 3, A, 200, "4, \"engine\", 1"), + row(14, 2, 3, A, 400, "4, \"engine\", 1"), + ]); + assert_eq!( + broken, + [Broken::StartedTwice { + activity: A.to_owned() + }] + ); +} + +#[test] +fn a_stop_of_another_task_breaks_the_pairing() { + let (_, broken) = paired(&[ + row(13, 1, 3, A, 100, "4, \"engine\", 1"), + row(8, 2, 1, A, 400, "2, 1, \"9\", 1, \"durable\""), + ]); + assert_eq!( + broken, + [Broken::TaskDiffers { + activity: A.to_owned(), + began: 3, + ended: 1 + }] + ); +} + +#[test] +fn a_stop_naming_another_pair_breaks_the_pairing() { + let (_, broken) = paired(&[ + row(13, 1, 3, A, 100, "4, \"engine\", 1"), + row(14, 2, 3, A, 400, "4, \"engine\", 9"), + ]); + assert_eq!( + broken, + [Broken::PairDiffers { + activity: A.to_owned(), + began: Some("1".into()), + ended: Some("9".into()) + }] + ); +} + +#[test] +fn a_stop_before_its_start_breaks_the_pairing() { + let (_, broken) = paired(&[ + row(13, 1, 3, A, 400, "4, \"engine\", 1"), + row(14, 2, 3, A, 100, "4, \"engine\", 1"), + ]); + assert_eq!( + broken, + [Broken::EndedBeforeBegun { + activity: A.to_owned() + }] + ); +} + +#[test] +fn a_pairs_event_in_no_activity_breaks_the_pairing() { + let (_, broken) = paired(&[row(13, 1, 3, NO_ACTIVITY, 100, "4, \"engine\", 1")]); + assert_eq!(broken, [Broken::NoActivity { id: 13 }]); +} + +#[test] +fn an_event_that_is_not_a_pair_is_not_paired() { + let (pairs, broken) = paired(&[row(1, 0, 0, NO_ACTIVITY, 100, "7, \"what\", \"detail\"")]); + assert_eq!((pairs, broken), (vec![], vec![])); +} + +#[test] +fn another_providers_rows_are_skipped() { + let other = + row(13, 1, 3, A, 100, "4, \"engine\", 1").replace("durable-ioring-events", "EventTrace"); + assert_eq!(read(&dump(&[other])).expect("read"), []); +} + +#[test] +fn a_dump_without_the_columns_is_refused() { + assert!(read("Event Name, Opcode").is_err()); + assert!(read("").is_err()); +} + +#[test] +fn a_dump_with_crlf_lines_reads_as_one_with_lf() { + let rows = [ + row(13, 1, 3, A, 100, "4, \"engine\", 1"), + row(14, 2, 3, A, 400, "4, \"engine\", 1"), + ]; + let lf = dump(&rows); + assert_eq!( + read(&lf.replace('\n', "\r\n")).expect("read"), + read(&lf).expect("read") + ); +} + +#[test] +fn user_data_splits_at_commas_outside_strings() { + assert_eq!(values("4, \"a, b\", 1").expect("read"), ["4", "a, b", "1"]); +} + +#[test] +fn user_data_reads_a_doubled_quote_as_one() { + assert_eq!(values("\"say \"\"hi\"\"\"").expect("read"), ["say \"hi\""]); +} + +#[test] +fn user_data_keeps_a_binary_field_as_written() { + assert_eq!( + values("5, 0x7373642D34, 4").expect("read"), + ["5", "0x7373642D34", "4"] + ); +} + +#[test] +fn user_data_that_is_empty_is_no_values() { + assert_eq!(values("").expect("read"), Vec::::new()); +} + +#[test] +fn an_unterminated_string_is_refused() { + assert!(values("\"open").is_err()); +} diff --git a/crates/durable-ioring-events/examples/pair_trace/trace.csv b/crates/durable-ioring-events/examples/pair_trace/trace.csv new file mode 100644 index 000000000..6b13ea41f --- /dev/null +++ b/crates/durable-ioring-events/examples/pair_trace/trace.csv @@ -0,0 +1,12 @@ + Event Name, Type, Event ID, Version, Channel, Level, Opcode, Task, Keyword, PID, TID, Processor Number, Instance ID, Parent Instance ID, Activity ID, Related Activity ID, Clock-Time, Kernel(ms), User(ms), User Data + EventTrace, Header, 0, 2, 0, 0, 0, 0, 0x0000000000000000, 0x0000842C, 0x00002AA4, 0, , , {00000000-0000-0000-0000-000000000000}, , 134371033678401958, 30, 0, 8192, 83951626, 26300, 16, 134371033685143575, 156250, 0, 0x0, 4, 1, 8, 0, 2445, 0xA, 0x7, 134365992645000000, 10000000, 134371033678401958, 0x1, 0, "dioring194", "q:\github\windows-threadpool-sys\.scratch\trace-19-4\trace.etl" + EventTrace, PartitionInfoExtensionV2, 0, 2, 0, 0, 80, 0, 0x0000000000000000, 0x0000842C, 0x00002AA4, 0, , , {00000000-0000-0000-0000-000000000000}, , 134371033678401958, 30, 0, 0, 0, 0, 0, "", "", 0x00000000000000000000000000000000000000000000000000000000 +durable-ioring-events, Info , 1, 0, 0, 2, 0, 0, 0x0000000000000008, 0x00005540, 0x00008DE4, 2, , , {00000000-0000-0000-0000-000000000000}, , 134371033678751300, 0, 0, 7, "a write completed twice", "lineage 3" +durable-ioring-events, Start , 13, 0, 0, 4, 1, 3, 0x0000000000000001, 0x00005540, 0x00008DE4, 2, , , {0795ed5d-9da0-0002-189f-908e285ddd01}, , 134371033678751420, 0, 0, 4, "engine", 1 +durable-ioring-events, Stop , 14, 0, 0, 4, 2, 3, 0x0000000000000001, 0x00005540, 0x00008DE4, 2, , , {0795ed5d-9da0-0002-189f-908e285ddd01}, , 134371033678956115, 0, 0, 4, "engine", 1 +durable-ioring-events, Start , 7, 0, 0, 4, 1, 1, 0x0000000000000004, 0x00005540, 0x00008DE4, 2, , , {0795ed5d-9da0-0002-199f-908e285ddd01}, , 134371033678956309, 0, 0, 2, 1, "9", 2 +durable-ioring-events, Start , 9, 0, 0, 4, 1, 2, 0x0000000000000002, 0x00005540, 0x00008DE4, 2, , , {0795ed5d-9da0-0002-1a9f-908e285ddd01}, , 134371033678956403, 0, 0, 2, 1, "9", 77, 3 +durable-ioring-events, Start , 11, 0, 0, 4, 1, 2, 0x0000000000000002, 0x00005540, 0x00008DE4, 2, , , {0795ed5d-9da0-0002-1b9f-908e285ddd01}, , 134371033678956483, 0, 0, 2, 1, "9", 5, 0x7373642D34, 4 +durable-ioring-events, Stop , 10, 0, 0, 4, 2, 2, 0x0000000000000002, 0x00005540, 0x00004F74, 10, , , {0795ed5d-9da0-0002-1a9f-908e285ddd01}, , 134371033679263685, 0, 0, 2, 1, "9", 77, 3, "failed" +durable-ioring-events, Stop , 12, 0, 0, 4, 2, 2, 0x0000000000000002, 0x00005540, 0x00004F74, 10, , , {0795ed5d-9da0-0002-1b9f-908e285ddd01}, , 134371033679365330, 0, 0, 2, 1, "9", 5, 0x7373642D34, 4, "succeeded" +durable-ioring-events, Stop , 8, 0, 0, 4, 2, 1, 0x0000000000000004, 0x00005540, 0x00008DE4, 4, , , {0795ed5d-9da0-0002-199f-908e285ddd01}, , 134371033679470543, 0, 0, 2, 1, "9", 2, "failed" diff --git a/crates/durable-ioring-events/sabotage-session.json b/crates/durable-ioring-events/sabotage-session.json new file mode 100644 index 000000000..b5b163f80 --- /dev/null +++ b/crates/durable-ioring-events/sabotage-session.json @@ -0,0 +1,83 @@ +{ + "package": "durable-ioring-events", + "description": "Sabotages of durable-ioring-events that only a trace session can catch: tests/session.rs, run with --include-ignored. Run with tools/run-sabotage.ps1 from an elevated process or one in the Performance Log Users group, as CI's runners are (DE-D-4); unelevated, every session test fails to start and the baseline is red, so the sweep stops before any entry.", + "testArgs": [ + "-p", + "durable-ioring-events", + "--test", + "session", + "--", + "--include-ignored" + ], + "notCoveredHere": "What a unit test can see is sabotage.json.", + "sabotages": [ + { + "name": "DE-D-6: a pair's begin is written in an activity no end will share", + "file": "src/sink.rs", + "expect": "caught", + "why": "The begin's activity is the one its end looks up; one created and not held leaves the end to create another, and the pair is two activities.", + "find": [ + " Edge::Begin => activity::begun(pairing.id, || create_activity_id().ok())," + ], + "replace": [ + " Edge::Begin => create_activity_id().ok()," + ] + }, + { + "name": "DE-D-6: a pair's end is written in an activity of its own", + "file": "src/sink.rs", + "expect": "caught", + "why": "The end must repeat its begin's activity, or a reader cannot pair them by it.", + "find": [ + " Edge::End => ended.or_else(|| create_activity_id().ok())," + ], + "replace": [ + " Edge::End => create_activity_id().ok()," + ] + }, + { + "name": "DE-D-6: a pair's events are written in the thread's activity", + "file": "src/sink.rs", + "expect": "caught", + "why": "Written by the plain call, a begin and an end on two threads carry the two threads' ids, and a pair whose end comes from another thread is never one activity.", + "find": [ + " registration.write_transfer(&descriptor, Some(&activity), None, &fields)" + ], + "replace": [ + " registration.write(&descriptor, &{ let _ = activity; fields })" + ] + }, + { + "name": "DE-D-6: an end whose begin was not written is written in the thread's activity", + "file": "src/sink.rs", + "expect": "caught", + "why": "An end with no begin written is an activity of its own, so that it joins no other.", + "find": [ + " Edge::End => ended.or_else(|| create_activity_id().ok())," + ], + "replace": [ + " Edge::End => ended," + ] + }, + { + "name": "DE-D-6 control: no activity is spelled another way for an event that is not a pair", + "file": "src/sink.rs", + "expect": "survives", + "why": "A control: the same value by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " None => None," + ], + "replace": [ + " None => Option::::None," + ] + }, + { + "name": "DE-D-7: an end lets its pair go only when it is written", + "file": "src/sink.rs", + "expect": "caught", + "why": "An end reported after its session stopped is not written and must still release what was held for its pair, or the process map grows by a pair for each stop. A later session then writes an end of that pair in the begin's activity, which `a_pair_ended_through_a_fan_out_after_its_session_stopped_is_let_go` reads.", + "find": [" Some(pairing) if pairing.edge == Edge::End => activity::ended(pairing.id),"], + "replace": [" Some(pairing) if pairing.edge == Edge::End && registration.is_enabled(&schema.descriptor()) => {\n activity::ended(pairing.id)\n }"] + } + ] +} diff --git a/crates/durable-ioring-events/sabotage.json b/crates/durable-ioring-events/sabotage.json new file mode 100644 index 000000000..7a07b61ac --- /dev/null +++ b/crates/durable-ioring-events/sabotage.json @@ -0,0 +1,276 @@ +{ + "package": "durable-ioring-events", + "description": "Sabotages for durable-ioring-events. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", + "notCoveredHere": "What only a trace session can show is not swept here: that the sink wants a kind only when a session listens to it, that the level and keyword filters hold, and that each event reaches a session as its values are tests/session.rs, which are ignored without elevation (DE-D-4), and are swept by sabotage-session.json on a machine that can, with the activity each pair is written in (DE-D-6), so a sweep on a machine that cannot start a session would report a sabotage of those as surviving for a reason that is not a hole. That the manifest decodes is checked by hand with mc, rc, wevtutil and tracerpt (DE-D-3).", + "sabotages": [ + { + "name": "DE-D-1: the provider's GUID is changed", + "file": "src/schema.rs", + "expect": "caught", + "why": "A GUID is what a session enables the provider by; changing it breaks every session that enabled it, so it is pinned.", + "find": ["pub const PROVIDER_ID: Guid = Guid::from_u128(0x4706_7f6f_8f2b_4278_9b92_092d_fb33_85e6);\n"], + "replace": ["pub const PROVIDER_ID: Guid = Guid::from_u128(0x4706_7f6f_8f2b_4278_9b92_092d_fb33_85e7);\n"] + }, + { + "name": "DE-D-5: the lock wait's begin takes a retired id", + "file": "src/schema.rs", + "expect": "caught", + "why": "A reader decodes by an event's id, so each is pinned; the ids the delay events had are retired and are not given again, or an old trace decodes as the wrong event.", + "find": [" pub const LOCK_WAIT_BEGAN: u16 = 13;\n"], + "replace": [" pub const LOCK_WAIT_BEGAN: u16 = 3;\n"] + }, + { + "name": "DE-D-2: two keywords share a bit", + "file": "src/schema.rs", + "expect": "caught", + "why": "A session filters by keyword bits, so a bit shared by two categories enables both together.", + "find": [" mask: 0x1,\n"], + "replace": [" mask: 0x2,\n"] + }, + { + "name": "DE-D-5: a lock wait's pair is written as zero", + "file": "src/encode.rs", + "expect": "caught", + "why": "The pair id is what a reader joins a begin to its end by, so a wait written without it cannot be paired.", + "find": [" Value::UInt64(id.get()),\n"], + "replace": [" Value::UInt64(0),\n"] + }, + { + "name": "DE-D-2: a domain's length is written as zero", + "file": "src/encode.rs", + "expect": "caught", + "why": "The length is what a reader takes the domain's bytes by, so a length that does not match them misreads every field after.", + "find": [" Value::UInt32(kept.len() as u32),\n"], + "replace": [" Value::UInt32(0),\n"] + }, + { + "name": "DE-D-5: every flush's end is written as succeeded", + "file": "src/encode.rs", + "expect": "caught", + "why": "A failed or abandoned flush reported as a success hides what the end exists to show.", + "find": [" values.push(units(&outcome.to_string()));\n"], + "replace": [" values.push(units(\"succeeded\"));\n"] + }, + { + "name": "DE-D-2: text is cut to its first unit", + "file": "src/encode.rs", + "expect": "caught", + "why": "A name or a detail written as one unit is no name.", + "find": [" let all: Vec = text.encode_utf16().collect();\n"], + "replace": [" let all: Vec = text.encode_utf16().take(1).collect();\n"] + }, + { + "name": "DE-D-3: the manifest names an error as a warning", + "file": "src/manifest.rs", + "expect": "caught", + "why": "The manifest is rendered from the table, and a level it names wrong disagrees with the descriptor the sink writes.", + "find": [" level::ERROR => \"win:Error\",\n"], + "replace": [" level::ERROR => \"win:Warning\",\n"] + }, + { + "name": "DE-D-4: an event is built though no session is listening to it", + "file": "src/sink.rs", + "expect": "caught", + "why": "A sink that does not check the event itself before building it formats text for an event no one wants: the cost the sink exists to avoid.", + "find": [" if !registration.is_enabled(&descriptor) {\n"], + "replace": [" if false {\n"] + }, + { + "name": "DE-D-4: each sink registers the provider of its own", + "file": "src/sink.rs", + "expect": "caught", + "why": "The page asks that a component register one or two providers; a registration for each sink would use a process's up.", + "find": [" if let Some(registration) = slot.upgrade() {\n"], + "replace": [" if let Some(registration) = None::> {\n"] + }, + { + "name": "DE-D-2 control: a domain's kept length is bounded by another spelling of the same bound", + "file": "src/encode.rs", + "expect": "survives", + "why": "A control: the same bound by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let kept = &bytes[..bytes.len().min(u32::MAX as usize)];\n"], + "replace": [" let kept = &bytes[..bytes.len().min(usize::try_from(u32::MAX).unwrap_or(usize::MAX))];\n"] + }, + { + "name": "DE-D-5: a pair's end is written as a start", + "file": "src/schema.rs", + "expect": "caught", + "why": "A reader pairs a begin with its end by the Start and Stop opcodes; an end written as a start opens a second activity that never closes.", + "find": [" Some(Edge::End) => opcode::STOP,\n"], + "replace": [" Some(Edge::End) => opcode::START,\n"] + }, + { + "name": "DE-D-5: the flush task takes the seal task's value", + "file": "src/schema.rs", + "expect": "caught", + "why": "A reader groups a pair's events by task, so two kinds of pair under one task value are read as one.", + "find": [" value: 2,\n"], + "replace": [" value: 1,\n"] + }, + { + "name": "DE-D-5: the manifest names a stop as a start", + "file": "src/manifest.rs", + "expect": "caught", + "why": "The manifest is rendered from the table, and an opcode it names wrong disagrees with the descriptor the sink writes.", + "find": [" opcode::STOP => \"win:Stop\",\n"], + "replace": [" opcode::STOP => \"win:Start\",\n"] + }, + { + "name": "DI-19.2.5: the one definition calls a seal's end a begin, as the provider's tests see it", + "file": "../durable-ioring/src/diagnostics.rs", + "expect": "caught", + "why": "DI-19.2.5: Diagnostic::pairing is the one definition of which events pair (DI-D-88). Changing it must fail this suite through the code that binds to it; a suite that held a copy of the rule would pass. The provider's table restates each event's edge for its opcode, and a test holds every row equal to what pairing says of an event of it.", + "find": [ + " | Self::LockWaitBegan { id, .. } => (*id, Edge::Begin),", + " Self::SealEnded { id, .. }", + " | Self::FlushEnded { id, .. }" + ], + "replace": [ + " | Self::LockWaitBegan { id, .. }", + " | Self::SealEnded { id, .. } => (*id, Edge::Begin),", + " Self::FlushEnded { id, .. }" + ] + }, + { + "name": "DE-D-6: a pair's second begin makes an activity of its own", + "file": "src/activity.rs", + "expect": "caught", + "why": "Two sinks writing one pair must write it in one activity, or a reader sees the pair's begin twice in two activities, one with no end.", + "find": [ + " *holders += 1;", + " return Some(*id);" + ], + "replace": [ + " let _ = (holders, id);" + ] + }, + { + "name": "DE-D-6: a pair's activity is let go at its first end", + "file": "src/activity.rs", + "expect": "caught", + "why": "A pair written by two sinks has two ends, and the second must carry the activity the begins did.", + "find": [ + " if *holders == 0 {" + ], + "replace": [ + " if *holders == 0 || true {" + ] + }, + { + "name": "DE-D-6: an ended pair's activity is held for ever", + "file": "src/activity.rs", + "expect": "caught", + "why": "Each pair that ends must leave nothing held, or the map grows with every pair the process writes.", + "find": [ + " self.held.remove(&pair);" + ], + "replace": [ + " let _ = &pair;" + ] + }, + { + "name": "DE-D-6 control: the activity is created by another spelling of the same call", + "file": "src/activity.rs", + "expect": "survives", + "why": "A control: the same value by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " let id = create()?;" + ], + "replace": [ + " let id = (create)()?;" + ] + }, + { + "name": "DE-D-6: the pairing takes a stop of another task as its start's end", + "file": "examples/pair_trace/main.rs", + "expect": "caught", + "why": "A reader pairs by activity and task; a stop of another task in the same activity is a broken pairing and not a duration.", + "find": [ + " if began.task != row.task {" + ], + "replace": [ + " if false {" + ] + }, + { + "name": "DE-D-6: the pairing takes a stop naming another pair as its start's end", + "file": "examples/pair_trace/main.rs", + "expect": "caught", + "why": "The pair id a stop carries must be its start's; the activity alone could hide a provider writing the wrong one.", + "find": [ + " if began.pair() != row.pair() || row.pair().is_none() {" + ], + "replace": [ + " if row.pair().is_none() {" + ] + }, + { + "name": "DE-D-6: the pairing measures a duration backwards", + "file": "examples/pair_trace/main.rs", + "expect": "caught", + "why": "A duration is the stop's time less the start's.", + "find": [ + " let Some(ticks) = row.clock.checked_sub(began.clock) else {" + ], + "replace": [ + " let Some(ticks) = began.clock.checked_sub(row.clock) else {" + ] + }, + { + "name": "DE-D-6: the pairing lets a second start replace the first unseen", + "file": "examples/pair_trace/main.rs", + "expect": "caught", + "why": "An activity started twice is a broken pairing, and a reader that overwrote the first start would measure from the second.", + "find": [ + " if open.insert(activity, row).is_some() {" + ], + "replace": [ + " if { open.insert(activity, row); false } {" + ] + }, + { + "name": "DE-D-6 control: the pairing scales a duration to milliseconds by another spelling of the same factor", + "file": "examples/pair_trace/main.rs", + "expect": "survives", + "why": "A control: the same factor by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " self.took.as_secs_f64() * 1000.0," + ], + "replace": [ + " self.took.as_secs_f64() * 1e3," + ] + }, + { + "name": "DE-D-7: the encoder keeps what follows a zero in text", + "file": "src/encode.rs", + "expect": "caught", + "why": "The values an event is written with are what a reader reads; text that keeps its tail after a zero is a field whose later fields a reader takes from the middle of it.", + "find": [" Value::Text(before_zero(&all).to_vec())"], + "replace": [" Value::Text(all)"] + }, + { + "name": "DE-D-7: the write of a text value keeps what follows a zero", + "file": "src/encode.rs", + "expect": "caught", + "why": "A text value built by anyone is still written up to its first zero; the encoder cuts the text it makes and the write cuts whatever it is given.", + "find": [" Self::Text(units) => Field::Utf16Terminated(before_zero(units)),"], + "replace": [" Self::Text(units) => Field::Utf16Terminated(units),"] + }, + { + "name": "DE-D-7: the cut keeps the zero", + "file": "src/encode.rs", + "expect": "caught", + "why": "A text cut after its zero instead of before it carries a zero unit into the value, which is then a unit longer than the text a reader reads.", + "find": [" &units[..end]"], + "replace": [" &units[..(end + 1).min(units.len())]"] + }, + { + "name": "DE-D-7 control: the first zero is found by another spelling", + "file": "src/encode.rs", + "expect": "survives", + "why": "A control: `unwrap_or_else` is `unwrap_or` over a value that costs nothing to make, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" .unwrap_or(units.len());"], + "replace": [" .unwrap_or_else(|| units.len());"] + } + ] +} diff --git a/crates/durable-ioring-events/src/activity.rs b/crates/durable-ioring-events/src/activity.rs new file mode 100644 index 000000000..d561370f7 --- /dev/null +++ b/crates/durable-ioring-events/src/activity.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2026 Mike Grier +//! Each pair's ETW activity id (DE-D-6): one created by `EventActivityIdControl` when the pair's begin is written, kept until +//! its end is, and written on both, so a reader groups them by an id the platform guarantees unique across every process of +//! the system until it restarts. +//! +//! The pair id is unique only within its process, and a process id can be reused within a trace, so neither alone nor +//! together do they make an id a later process cannot repeat. + +use std::collections::BTreeMap; +use std::sync::{Mutex, PoisonError}; + +use durable_ioring::CorrelationId; +use win_etw_sys::Guid; + +#[cfg(test)] +mod tests; + +/// The activity ids of the pairs whose begin was written and whose end has not been, for the whole process. +/// +/// Shared by every sink because a pair is one pair however many sinks write it: a sink cloned, or two given to a fan-out, +/// write each begin and end once each, and both copies carry one activity. Its lock is taken only here, around map work and +/// the creation of an id, and nothing is called with it held that reaches dioring, which is the rule `DiagnosticSink` gives a +/// sink's own synchronization. +static HELD: Mutex = Mutex::new(Activities::new()); + +/// The activity of a pair whose begin is being written: the one already held for it, or one `create` makes. `None` if +/// `create` fails, and then the pair has none. +pub(crate) fn begun(pair: CorrelationId, create: impl FnOnce() -> Option) -> Option { + HELD.lock() + .unwrap_or_else(PoisonError::into_inner) + .begun(pair, create) +} + +/// The activity of a pair whose end is being reported, which is let go when its last holder ends; `None` if its begin was +/// not written. +pub(crate) fn ended(pair: CorrelationId) -> Option { + HELD.lock() + .unwrap_or_else(PoisonError::into_inner) + .ended(pair) +} + +/// The pairs' activities, each with how many begins written for it have not yet been ended. +#[derive(Debug, Default)] +pub(crate) struct Activities { + held: BTreeMap, +} + +impl Activities { + pub(crate) const fn new() -> Self { + Self { + held: BTreeMap::new(), + } + } + + /// As [`begun`], over this map. + pub(crate) fn begun( + &mut self, + pair: CorrelationId, + create: impl FnOnce() -> Option, + ) -> Option { + if let Some((id, holders)) = self.held.get_mut(&pair) { + *holders += 1; + return Some(*id); + } + let id = create()?; + self.held.insert(pair, (id, 1)); + Some(id) + } + + /// As [`ended`], over this map. + pub(crate) fn ended(&mut self, pair: CorrelationId) -> Option { + let (id, holders) = self.held.get_mut(&pair)?; + let id = *id; + *holders -= 1; + if *holders == 0 { + self.held.remove(&pair); + } + Some(id) + } + + /// How many pairs hold an activity. + #[cfg(test)] + pub(crate) fn len(&self) -> usize { + self.held.len() + } +} diff --git a/crates/durable-ioring-events/src/activity/tests.rs b/crates/durable-ioring-events/src/activity/tests.rs new file mode 100644 index 000000000..213423d03 --- /dev/null +++ b/crates/durable-ioring-events/src/activity/tests.rs @@ -0,0 +1,136 @@ +// Copyright (c) 2026 Mike Grier +//! The activity map in memory, with ids made by a counter in place of the call. + +use std::cell::Cell; + +use durable_ioring::{CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink}; +use win_etw_sys::Guid; + +use super::Activities; + +struct Wanting; + +impl DiagnosticSink for Wanting { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn pair() -> CorrelationId { + CorrelationId::for_pair(&Wanting, DiagnosticKind::Seal).expect("wanted") +} + +/// Ids from a counter, and how many were made. +struct Maker(Cell); + +impl Maker { + fn new() -> Self { + Self(Cell::new(0)) + } + + fn make(&self) -> Option { + self.0.set(self.0.get() + 1); + Some(Guid::from_u128(self.0.get())) + } + + fn made(&self) -> u128 { + self.0.get() + } +} + +#[test] +fn a_begin_makes_an_activity_and_its_end_gives_the_same_one_back() { + let (mut map, maker, p) = (Activities::new(), Maker::new(), pair()); + let began = map.begun(p, || maker.make()); + assert_eq!(began, Some(Guid::from_u128(1))); + assert_eq!(map.ended(p), began); +} + +#[test] +fn an_ended_pair_holds_nothing() { + let (mut map, maker, p) = (Activities::new(), Maker::new(), pair()); + map.begun(p, || maker.make()); + map.ended(p); + assert_eq!(map.len(), 0); +} + +#[test] +fn an_end_whose_begin_was_not_written_has_no_activity() { + let mut map = Activities::new(); + assert_eq!(map.ended(pair()), None); + assert_eq!(map.len(), 0); +} + +#[test] +fn two_pairs_have_two_activities() { + let (mut map, maker) = (Activities::new(), Maker::new()); + let (a, b) = (pair(), pair()); + let first = map.begun(a, || maker.make()); + let second = map.begun(b, || maker.make()); + assert_ne!(first, second); + assert_eq!(map.ended(b), second); + assert_eq!(map.ended(a), first); +} + +#[test] +fn pairs_end_in_any_order() { + let (mut map, maker) = (Activities::new(), Maker::new()); + let pairs: Vec<_> = (0..5).map(|_| pair()).collect(); + let ids: Vec<_> = pairs + .iter() + .map(|&p| map.begun(p, || maker.make())) + .collect(); + for i in [3, 0, 4, 1, 2] { + assert_eq!(map.ended(pairs[i]), ids[i]); + } + assert_eq!(map.len(), 0); +} + +#[test] +fn a_begin_written_twice_shares_one_activity_and_holds_it_until_both_ends() { + let (mut map, maker, p) = (Activities::new(), Maker::new(), pair()); + let first = map.begun(p, || maker.make()); + let second = map.begun(p, || maker.make()); + assert_eq!(first, second); + assert_eq!(maker.made(), 1, "the second begin makes none"); + assert_eq!(map.ended(p), first); + assert_eq!(map.len(), 1, "held for the second end"); + assert_eq!(map.ended(p), first); + assert_eq!(map.len(), 0); +} + +#[test] +fn an_end_beyond_its_begins_has_no_activity() { + let (mut map, maker, p) = (Activities::new(), Maker::new(), pair()); + let began = map.begun(p, || maker.make()); + assert_eq!(map.ended(p), began); + assert_eq!(map.ended(p), None); +} + +#[test] +fn a_failed_creation_leaves_the_pair_with_none_and_holds_nothing() { + let (mut map, p) = (Activities::new(), pair()); + assert_eq!(map.begun(p, || None), None); + assert_eq!(map.len(), 0); + assert_eq!(map.ended(p), None); +} + +#[test] +fn a_pair_begun_again_after_it_ended_has_a_new_activity() { + let (mut map, maker, p) = (Activities::new(), Maker::new(), pair()); + let first = map.begun(p, || maker.make()); + map.ended(p); + let second = map.begun(p, || maker.make()); + assert_ne!(first, second); +} + +#[test] +fn the_process_map_is_left_holding_nothing_by_a_pair_that_ended() { + let p = pair(); + let id = super::begun(p, || Some(Guid::from_u128(0xfeed))); + assert_eq!(id, Some(Guid::from_u128(0xfeed))); + assert_eq!(super::ended(p), id); + assert_eq!(super::ended(p), None, "let go"); +} diff --git a/crates/durable-ioring-events/src/encode.rs b/crates/durable-ioring-events/src/encode.rs new file mode 100644 index 000000000..c490e6690 --- /dev/null +++ b/crates/durable-ioring-events/src/encode.rs @@ -0,0 +1,197 @@ +// Copyright (c) 2026 Mike Grier +//! What is written to an event, from an event of durable-ioring, and the write of it (DE-D-2). + +use durable_ioring::{Diagnostic, FlushPart}; +use win_etw_sys::Field; + +use crate::schema::{ColumnType, EVENTS, EventSchema, id}; + +#[cfg(test)] +mod tests; + +/// A value of one field, owned: the text is encoded and the domain's bytes are copied, once the event is known to be wanted. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Value { + /// A `UInt32`. + UInt32(u32), + /// A `UInt64`. + UInt64(u64), + /// A `Boolean`. + Boolean(bool), + /// A `UnicodeString`, as UTF-16 units without the terminator, and **without a zero unit**: a zero in the text ends it where + /// it is, as a reader of a `UnicodeString` ends it, so the units after it are not part of the value (DE-D-7). + Text(Vec), + /// A `Binary`. + Bytes(Vec), +} + +impl Value { + /// Whether the value is of the column type: a `Binary` takes bytes whatever its length column says, which is held by a + /// test and by [`encode`]. + #[must_use] + pub fn is_of(&self, ty: ColumnType) -> bool { + matches!( + (self, ty), + (Self::UInt32(_), ColumnType::UInt32) + | (Self::UInt64(_), ColumnType::UInt64) + | (Self::Boolean(_), ColumnType::Boolean) + | (Self::Text(_), ColumnType::UnicodeString) + | (Self::Bytes(_), ColumnType::Binary { .. }) + ) + } + + /// The field that writes this value. + pub(crate) fn field(&self) -> Field<'_> { + match self { + Self::UInt32(v) => Field::U32(*v), + Self::UInt64(v) => Field::U64(*v), + Self::Boolean(v) => Field::boolean(*v), + Self::Text(units) => Field::Utf16Terminated(before_zero(units)), + Self::Bytes(bytes) => Field::Bytes(bytes), + } + } +} + +/// `units` up to its first zero unit, which is where a reader of a `UnicodeString` field ends the string. A zero left in would end +/// it there for the reader while the units after it were taken as the next fields, so each later field of the event would be +/// misread (DE-D-7). The one place the rule is written: [`encode`] makes its text through it, and so does the write of a value +/// whoever made it. +fn before_zero(units: &[u16]) -> &[u16] { + let end = units + .iter() + .position(|&unit| unit == 0) + .unwrap_or(units.len()); + &units[..end] +} + +/// The event an event of durable-ioring becomes, or `None` for one this crate does not know, which a newer durable-ioring may +/// add. +#[must_use] +pub fn schema_of(event: &Diagnostic<'_>) -> Option<&'static EventSchema> { + let wanted = match event { + Diagnostic::Inconsistency { .. } => id::INCONSISTENCY, + Diagnostic::SealBegan { .. } => id::SEAL_BEGAN, + Diagnostic::SealEnded { .. } => id::SEAL_ENDED, + Diagnostic::FlushBegan { part, .. } => match part { + FlushPart::File(_) => id::FLUSH_BEGAN_FILE, + FlushPart::Domain(_) => id::FLUSH_BEGAN_DOMAIN, + _ => return None, + }, + Diagnostic::FlushEnded { part, .. } => match part { + FlushPart::File(_) => id::FLUSH_ENDED_FILE, + FlushPart::Domain(_) => id::FLUSH_ENDED_DOMAIN, + _ => return None, + }, + Diagnostic::LockWaitBegan { .. } => id::LOCK_WAIT_BEGAN, + Diagnostic::LockWaitEnded { .. } => id::LOCK_WAIT_ENDED, + _ => return None, + }; + EVENTS.iter().find(|schema| schema.id == wanted) +} + +/// The values of a flush part's columns: a file's key, or a domain's length and bytes. +fn part_values(part: &FlushPart<'_>) -> Option> { + Some(match part { + FlushPart::File(key) => vec![Value::UInt64(key.0)], + FlushPart::Domain(domain) => { + let bytes = domain.bytes(); + // A length that does not fit the column cuts the bytes to what it can say, so the two agree. + let kept = &bytes[..bytes.len().min(u32::MAX as usize)]; + vec![ + Value::UInt32(kept.len() as u32), + Value::Bytes(kept.to_vec()), + ] + } + _ => return None, + }) +} + +/// The values of an event's fields, in the order of the schema's columns, or `None` for an event this crate does not know. +/// +/// Text that is formatted, an epoch and an inconsistency's detail, is formatted here, which is why this is called only for an +/// event a session wants. +#[must_use] +pub fn encode(event: &Diagnostic<'_>) -> Option> { + let units = |text: &str| { + let all: Vec = text.encode_utf16().collect(); + Value::Text(before_zero(&all).to_vec()) + }; + Some(match event { + Diagnostic::Inconsistency { + instance, + what, + detail, + } => vec![ + Value::UInt64(*instance), + units(what), + units(&detail.to_string()), + ], + Diagnostic::SealBegan { + instance, + lineage, + through, + id, + } => vec![ + Value::UInt64(*instance), + Value::UInt64(*lineage), + units(&through.to_string()), + Value::UInt64(id.get()), + ], + Diagnostic::SealEnded { + instance, + lineage, + through, + id, + outcome, + } => vec![ + Value::UInt64(*instance), + Value::UInt64(*lineage), + units(&through.to_string()), + Value::UInt64(id.get()), + units(&outcome.to_string()), + ], + Diagnostic::FlushBegan { + instance, + lineage, + through, + part, + id, + } => { + let mut values = vec![ + Value::UInt64(*instance), + Value::UInt64(*lineage), + units(&through.to_string()), + ]; + values.extend(part_values(part)?); + values.push(Value::UInt64(id.get())); + values + } + Diagnostic::FlushEnded { + instance, + lineage, + through, + part, + id, + outcome, + } => { + let mut values = vec![ + Value::UInt64(*instance), + Value::UInt64(*lineage), + units(&through.to_string()), + ]; + values.extend(part_values(part)?); + values.push(Value::UInt64(id.get())); + values.push(units(&outcome.to_string())); + values + } + Diagnostic::LockWaitBegan { instance, lock, id } + | Diagnostic::LockWaitEnded { instance, lock, id } => { + vec![ + Value::UInt64(*instance), + units(lock), + Value::UInt64(id.get()), + ] + } + _ => return None, + }) +} diff --git a/crates/durable-ioring-events/src/encode/tests.rs b/crates/durable-ioring-events/src/encode/tests.rs new file mode 100644 index 000000000..418971a76 --- /dev/null +++ b/crates/durable-ioring-events/src/encode/tests.rs @@ -0,0 +1,506 @@ +// Copyright (c) 2026 Mike Grier +//! What an event of durable-ioring is written as (DE-D-2): the event it becomes, the values of its fields in the order the table +//! gives, and that those agree with the table in type and in the length a `Binary` field is told. + +use durable_ioring::{ + CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink, FlushDomain, FlushOutcome, + FlushPart, IoTargetKey, SealOutcome, +}; + +use super::{Value, encode, schema_of}; +use crate::schema::{ColumnType, id}; + +/// That `values` are the schema's fields: as many, each of the column's type, and a `Binary` as long as its length column says. +fn agrees(event: &Diagnostic<'_>) { + let schema = schema_of(event).expect("an event this crate knows"); + let values = encode(event).expect("values"); + assert_eq!(values.len(), schema.columns.len(), "{}", schema.name); + for (value, column) in values.iter().zip(schema.columns) { + assert!( + value.is_of(column.ty), + "{} {}: {value:?}", + schema.name, + column.name + ); + if let (Value::Bytes(bytes), ColumnType::Binary { length }) = (value, column.ty) { + let at = schema + .columns + .iter() + .position(|c| c.name == length) + .expect("a length"); + assert_eq!( + values[at], + Value::UInt32(u32::try_from(bytes.len()).expect("small")) + ); + } + } +} + +fn text(s: &str) -> Value { + Value::Text(s.encode_utf16().collect()) +} + +#[test] +fn an_inconsistency_is_its_instance_what_and_detail() { + let event = Diagnostic::Inconsistency { + instance: 7, + what: "a write completed twice", + detail: format_args!("lineage {}", 3), + }; + agrees(&event); + assert_eq!(schema_of(&event).map(|s| s.id), Some(id::INCONSISTENCY)); + assert_eq!( + encode(&event), + Some(vec![ + Value::UInt64(7), + text("a write completed twice"), + text("lineage 3") + ]) + ); +} + +/// A sink that wants every kind, to mint ids from. +struct Wanting; + +impl DiagnosticSink for Wanting { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn minted() -> CorrelationId { + CorrelationId::for_pair(&Wanting, DiagnosticKind::Seal).expect("wanted") +} + +/// The schema says which end of a pair an event is exactly as durable-ioring does, since the opcode follows from it. +fn edge_agrees(event: &Diagnostic<'_>) { + let schema = schema_of(event).expect("an event this crate knows"); + assert_eq!( + schema.edge, + event.pairing().map(|pairing| pairing.edge), + "{}", + schema.name + ); + assert_eq!(Some(schema.kind), Some(event.kind()), "{}", schema.name); +} + +#[test] +fn a_seals_begin_is_its_identity_the_epoch_as_text_and_its_pair() { + let pair = minted(); + let event = Diagnostic::SealBegan { + instance: 2, + lineage: 1, + through: format_args!("{:?}", 5_u64), + id: pair, + }; + agrees(&event); + edge_agrees(&event); + assert_eq!(schema_of(&event).map(|s| s.id), Some(id::SEAL_BEGAN)); + assert_eq!( + encode(&event), + Some(vec![ + Value::UInt64(2), + Value::UInt64(1), + text("5"), + Value::UInt64(pair.get()) + ]) + ); +} + +#[test] +fn a_seals_end_is_its_identity_its_pair_and_how_it_ended() { + let pair = minted(); + for (outcome, said) in [ + (SealOutcome::Durable, "durable"), + (SealOutcome::Failed, "failed"), + (SealOutcome::Blocked, "blocked"), + (SealOutcome::Ended, "ended unanswered"), + ] { + let event = Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("5"), + id: pair, + outcome, + }; + agrees(&event); + edge_agrees(&event); + assert_eq!(schema_of(&event).map(|s| s.id), Some(id::SEAL_ENDED)); + assert_eq!( + encode(&event), + Some(vec![ + Value::UInt64(2), + Value::UInt64(1), + text("5"), + Value::UInt64(pair.get()), + text(said) + ]) + ); + } +} + +#[test] +fn a_files_flush_is_a_pair_naming_the_file_key() { + let pair = minted(); + let began = Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: pair, + }; + agrees(&began); + edge_agrees(&began); + assert_eq!(schema_of(&began).map(|s| s.id), Some(id::FLUSH_BEGAN_FILE)); + assert_eq!( + encode(&began), + Some(vec![ + Value::UInt64(2), + Value::UInt64(1), + text("9"), + Value::UInt64(77), + Value::UInt64(pair.get()) + ]) + ); + let ended = Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: pair, + outcome: FlushOutcome::Failed, + }; + agrees(&ended); + edge_agrees(&ended); + assert_eq!(schema_of(&ended).map(|s| s.id), Some(id::FLUSH_ENDED_FILE)); + assert_eq!( + encode(&ended), + Some(vec![ + Value::UInt64(2), + Value::UInt64(1), + text("9"), + Value::UInt64(77), + Value::UInt64(pair.get()), + text("failed") + ]) + ); +} + +#[test] +fn a_domains_flush_is_a_pair_naming_the_domains_bytes_with_their_length_first() { + let pair = minted(); + let domain = FlushDomain::new(b"ssd-4".to_vec()); + let began = Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: pair, + }; + agrees(&began); + edge_agrees(&began); + assert_eq!( + schema_of(&began).map(|s| s.id), + Some(id::FLUSH_BEGAN_DOMAIN) + ); + assert_eq!( + encode(&began), + Some(vec![ + Value::UInt64(2), + Value::UInt64(1), + text("9"), + Value::UInt32(5), + Value::Bytes(b"ssd-4".to_vec()), + Value::UInt64(pair.get()) + ]) + ); + for (outcome, said) in [ + (FlushOutcome::Succeeded, "succeeded"), + (FlushOutcome::Abandoned, "abandoned"), + ] { + let ended = Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: pair, + outcome, + }; + agrees(&ended); + edge_agrees(&ended); + assert_eq!( + schema_of(&ended).map(|s| s.id), + Some(id::FLUSH_ENDED_DOMAIN) + ); + assert_eq!(encode(&ended).expect("values")[6], text(said)); + } +} + +#[test] +fn a_domain_whose_name_is_not_text_is_written_as_the_bytes_it_is() { + let domain = FlushDomain::new(vec![0xff, 0, 0x61]); + let event = Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id: minted(), + }; + agrees(&event); + let values = encode(&event).expect("values"); + assert_eq!(values[3], Value::UInt32(3)); + assert_eq!(values[4], Value::Bytes(vec![0xff, 0, 0x61])); +} + +#[test] +fn an_empty_domain_name_is_a_length_of_zero_and_no_bytes() { + let domain = FlushDomain::new(Vec::new()); + let event = Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id: minted(), + outcome: FlushOutcome::Succeeded, + }; + agrees(&event); + let values = encode(&event).expect("values"); + assert_eq!(values[3], Value::UInt32(0)); + assert_eq!(values[4], Value::Bytes(Vec::new())); +} + +#[test] +fn a_lock_waits_pair_is_its_instance_its_lock_and_its_pair() { + let pair = minted(); + let began = Diagnostic::LockWaitBegan { + instance: 4, + lock: "engine", + id: pair, + }; + let ended = Diagnostic::LockWaitEnded { + instance: 4, + lock: "engine", + id: pair, + }; + for (event, expected) in [(&began, id::LOCK_WAIT_BEGAN), (&ended, id::LOCK_WAIT_ENDED)] { + agrees(event); + edge_agrees(event); + assert_eq!(schema_of(event).map(|s| s.id), Some(expected)); + assert_eq!( + encode(event), + Some(vec![ + Value::UInt64(4), + text("engine"), + Value::UInt64(pair.get()) + ]) + ); + } +} + +#[test] +fn an_inconsistency_is_no_end_of_a_pair() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("y"), + }; + edge_agrees(&event); +} + +#[test] +fn text_outside_the_basic_plane_is_its_surrogate_pairs() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "\u{1f600}", + detail: format_args!("\u{3042}"), + }; + agrees(&event); + let values = encode(&event).expect("values"); + assert_eq!(values[1], Value::Text(vec![0xd83d, 0xde00])); + assert_eq!(values[2], Value::Text(vec![0x3042])); +} + +#[test] +fn a_largest_identity_and_pair_are_kept() { + let event = Diagnostic::SealBegan { + instance: u64::MAX, + lineage: u64::MAX, + through: format_args!(""), + id: minted(), + }; + agrees(&event); + let values = encode(&event).expect("values"); + assert_eq!( + (values[0].clone(), values[1].clone()), + (Value::UInt64(u64::MAX), Value::UInt64(u64::MAX)) + ); +} + +#[test] +fn a_value_is_of_its_column_type_and_no_other() { + let cases = [ + (Value::UInt32(0), ColumnType::UInt32), + (Value::UInt64(0), ColumnType::UInt64), + (Value::Boolean(true), ColumnType::Boolean), + (Value::Text(vec![]), ColumnType::UnicodeString), + (Value::Bytes(vec![]), ColumnType::Binary { length: "L" }), + ]; + for (i, (value, _)) in cases.iter().enumerate() { + for (j, (_, ty)) in cases.iter().enumerate() { + assert_eq!(value.is_of(*ty), i == j, "{value:?} against {ty:?}"); + } + } +} + +#[test] +fn a_boolean_is_written_as_four_bytes_and_text_with_a_terminator() { + use win_etw_sys::Field; + assert!(matches!(Value::Boolean(true).field(), Field::U32(1))); + assert!(matches!(Value::Boolean(false).field(), Field::U32(0))); + assert!(matches!(Value::UInt32(5).field(), Field::U32(5))); + assert!(matches!(Value::UInt64(5).field(), Field::U64(5))); + assert!(matches!( + Value::Text(vec![1]).field(), + Field::Utf16Terminated(_) + )); + assert!(matches!(Value::Bytes(vec![1]).field(), Field::Bytes(_))); +} + +// --- a zero in text (DE-D-7) --- + +/// Every text value an event's encoding holds. +fn texts(event: &Diagnostic<'_>) -> Vec> { + encode(event) + .expect("values") + .into_iter() + .filter_map(|value| match value { + Value::Text(units) => Some(units), + _ => None, + }) + .collect() +} + +#[test] +fn a_zero_in_text_ends_it_where_it_is() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "a\0b", + detail: format_args!("c\0d\0e"), + }; + agrees(&event); + assert_eq!( + encode(&event), + Some(vec![Value::UInt64(1), text("a"), text("c")]), + "what follows a zero is not part of the field" + ); +} + +#[test] +fn text_that_starts_with_a_zero_is_empty() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "\0abc", + detail: format_args!("\0"), + }; + assert_eq!(texts(&event), [Vec::::new(), Vec::new()]); +} + +#[test] +fn text_with_no_zero_is_kept_whole() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "abc", + detail: format_args!("{}", "d".repeat(1000)), + }; + assert_eq!(texts(&event)[0], "abc".encode_utf16().collect::>()); + assert_eq!(texts(&event)[1].len(), 1000); +} + +#[test] +fn a_zero_in_any_text_of_any_event_leaves_every_other_field_where_it_was() { + // The corruption a zero caused was in the fields after it, so the check is that the values after a cut text are the ones + // that would be there without the zero, for each event with a text that is not the last field. + let id = minted(); + let events = [ + Diagnostic::Inconsistency { + instance: 5, + what: "w\0x", + detail: format_args!("d"), + }, + Diagnostic::SealBegan { + instance: 5, + lineage: 6, + through: format_args!("e\0x"), + id, + }, + Diagnostic::SealEnded { + instance: 5, + lineage: 6, + through: format_args!("e\0x"), + id, + outcome: SealOutcome::Durable, + }, + Diagnostic::FlushBegan { + instance: 5, + lineage: 6, + through: format_args!("e\0x"), + part: FlushPart::File(IoTargetKey(9)), + id, + }, + Diagnostic::FlushEnded { + instance: 5, + lineage: 6, + through: format_args!("e\0x"), + part: FlushPart::File(IoTargetKey(9)), + id, + outcome: FlushOutcome::Succeeded, + }, + Diagnostic::LockWaitBegan { + instance: 5, + lock: "l\0x", + id, + }, + Diagnostic::LockWaitEnded { + instance: 5, + lock: "l\0x", + id, + }, + ]; + for event in &events { + agrees(event); + let values = encode(event).expect("values"); + assert!( + texts(event).iter().all(|units| !units.contains(&0)), + "{event}: {values:?}" + ); + assert_eq!(values[0], Value::UInt64(5), "{event}"); + let pair = id.get(); + if event.pairing().is_some() { + assert!( + values.contains(&Value::UInt64(pair)), + "{event}: the pair is where it was" + ); + } + } +} + +#[test] +fn the_write_of_a_text_value_stops_at_a_zero_whoever_made_it() { + use win_etw_sys::Field; + let written = |units: Vec| -> Vec { + let value = Value::Text(units); + let Field::Utf16Terminated(written) = value.field() else { + panic!("text is written terminated"); + }; + written.to_vec() + }; + assert_eq!(written(vec![0x61, 0, 0x62]), [0x61]); + assert!(written(vec![0, 0x62]).is_empty()); + assert_eq!( + written(vec![0x61, 0x62]), + [0x61, 0x62], + "no zero, nothing cut" + ); +} diff --git a/crates/durable-ioring-events/src/lib.rs b/crates/durable-ioring-events/src/lib.rs new file mode 100644 index 000000000..91a427864 --- /dev/null +++ b/crates/durable-ioring-events/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright (c) 2026 Mike Grier +#![doc = include_str!("../README.md")] +#![cfg(windows)] +#![deny(missing_docs)] +// This crate has no unsafe code: the calls are `win-etw-sys`'. +#![forbid(unsafe_code)] + +mod activity; +mod encode; +mod manifest; +mod schema; +mod sink; + +pub use encode::{Value, encode, schema_of}; +pub use manifest::{MANIFEST_FILE, manifest}; +pub use schema::{ + Column, ColumnType, EVENTS, EventSchema, FLUSH_TASK, FLUSHES, INCONSISTENCIES, KEYWORDS, + Keyword, LOCK_WAIT_TASK, LOCKS, PROVIDER_ID, PROVIDER_NAME, SEAL_TASK, SEALS, TASKS, Task, + VERSION, id, opcode, schema_of_id, schema_of_kind, +}; +pub use sink::EtwDiagnostics; +pub use win_etw_sys::{EventDescriptor, Guid}; diff --git a/crates/durable-ioring-events/src/manifest.rs b/crates/durable-ioring-events/src/manifest.rs new file mode 100644 index 000000000..fa79c9f5c --- /dev/null +++ b/crates/durable-ioring-events/src/manifest.rs @@ -0,0 +1,155 @@ +// Copyright (c) 2026 Mike Grier +//! The provider's manifest (DE-D-3), rendered from the table of events, so that it cannot disagree with it. +//! +//! The manifest is what turns an event's bytes back into names and fields for a reader. The provider records its events +//! without it ([DE-D-1](../DESIGN-NOTES.md#de-d-1)), and a reader installs it to see them decoded. + +use std::fmt::Write; + +use crate::schema::{ + ColumnType, EVENTS, EventSchema, KEYWORDS, PROVIDER_ID, PROVIDER_NAME, TASKS, opcode, +}; +use win_etw_sys::level; + +#[cfg(test)] +mod tests; + +/// The file the manifest is kept in, beside the crate's `Cargo.toml`, which a test holds equal to [`manifest`]. +pub const MANIFEST_FILE: &str = "durable-ioring-events.man"; + +/// The name a manifest's resource and message files are given, which the installer overrides with the files it is told of. +const RESOURCE_FILE: &str = "durable-ioring-events.dll"; + +/// The manifest, as the text of an instrumentation manifest file. +/// +/// It names the provider and its GUID, its tasks, its keywords, each event with its id, level, keyword, task, opcode and +/// template, and each template with the types of its fields. An event's fields are what the sink writes, in that order, with no padding. +#[must_use] +pub fn manifest() -> String { + let mut xml = String::new(); + let guid = PROVIDER_ID.to_string(); + xml.push_str("\n"); + xml.push_str("\n"); + xml.push_str(" \n \n"); + let _ = writeln!( + xml, + " " + ); + + xml.push_str(" \n"); + for task in TASKS { + let _ = writeln!( + xml, + " ", + task.name, task.value + ); + } + xml.push_str(" \n"); + + xml.push_str(" \n"); + for keyword in KEYWORDS { + let _ = writeln!( + xml, + " ", + keyword.name, keyword.mask + ); + } + xml.push_str(" \n"); + + xml.push_str(" \n"); + for event in &EVENTS { + let task = event + .task + .map(|task| format!(" task=\"{}\"", task.name)) + .unwrap_or_default(); + let _ = writeln!( + xml, + " ", + event.id, + event.name, + event.descriptor().version, + level_name(event.level), + event.keyword.name, + opcode_name(event.descriptor().opcode), + template_name(event) + ); + } + xml.push_str(" \n"); + + xml.push_str(" \n"); + for event in &EVENTS { + let _ = writeln!(xml, " \n"); + } + xml.push_str(" \n"); + + xml.push_str( + " \n \n \n\n", + ); + xml +} + +/// The name of an event's template. +fn template_name(event: &EventSchema) -> String { + format!("{}Template", event.name) +} + +/// The manifest's name for a level. +fn level_name(value: u8) -> &'static str { + match value { + level::CRITICAL => "win:Critical", + level::ERROR => "win:Error", + level::WARNING => "win:Warning", + level::INFO => "win:Informational", + level::VERBOSE => "win:Verbose", + other => panic!( + "level {other} has no name in a manifest: the table uses only the levels the page defines" + ), + } +} + +/// The manifest's name for an opcode. +fn opcode_name(value: u8) -> &'static str { + match value { + opcode::INFO => "win:Info", + opcode::START => "win:Start", + opcode::STOP => "win:Stop", + other => { + panic!("opcode {other} has no name here: the table uses only Info, Start and Stop") + } + } +} + +/// The manifest's name for a type. +fn in_type(ty: ColumnType) -> &'static str { + match ty { + ColumnType::UInt32 => "win:UInt32", + ColumnType::UInt64 => "win:UInt64", + ColumnType::Boolean => "win:Boolean", + ColumnType::UnicodeString => "win:UnicodeString", + ColumnType::Binary { .. } => "win:Binary", + } +} diff --git a/crates/durable-ioring-events/src/manifest/tests.rs b/crates/durable-ioring-events/src/manifest/tests.rs new file mode 100644 index 000000000..f994bb50a --- /dev/null +++ b/crates/durable-ioring-events/src/manifest/tests.rs @@ -0,0 +1,207 @@ +// Copyright (c) 2026 Mike Grier +//! The manifest (DE-D-3): that the file kept with the crate is what the table renders, and that the rendering says what the table +//! does. That the manifest compiles and decodes is `tests/session.rs` and the steps in the README. + +use super::{MANIFEST_FILE, level_name, manifest, opcode_name}; +use crate::schema::{ColumnType, EVENTS, KEYWORDS, PROVIDER_ID, PROVIDER_NAME, TASKS, opcode}; + +/// The file kept beside the crate's `Cargo.toml`. +const FILE: &str = include_str!("../../durable-ioring-events.man"); + +#[test] +fn the_file_kept_with_the_crate_is_the_manifest_the_table_renders() { + assert_eq!( + FILE, + manifest(), + "durable-ioring-events.man is out of date: run `cargo test -p durable-ioring-events --lib -- --ignored rewrite_the_manifest_file`" + ); +} + +/// Rewrites the file from the table. Ignored: it changes the tree, and is how the file is brought up to date. +#[test] +#[ignore = "rewrites durable-ioring-events.man from the table"] +fn rewrite_the_manifest_file() { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(MANIFEST_FILE); + std::fs::write(path, manifest()).expect("the manifest file was written"); +} + +#[test] +fn the_manifest_names_the_provider_and_its_guid() { + let text = manifest(); + assert!(text.contains(&format!("name=\"{PROVIDER_NAME}\""))); + assert!(text.contains(&format!("guid=\"{PROVIDER_ID}\""))); +} + +#[test] +fn the_manifest_has_each_event_with_its_id_level_keyword_and_template() { + let text = manifest(); + for event in &EVENTS { + let line = text + .lines() + .find(|line| line.contains(&format!(" assert!(line.contains(&format!("task=\"{}\"", task.name)), "{line}"), + None => assert!(!line.contains("task="), "{line}"), + } + } +} + +#[test] +fn the_manifest_has_each_task_with_its_value() { + let text = manifest(); + for task in TASKS { + assert!( + text.contains(&format!( + "", + task.name, task.value + )), + "{}", + task.name + ); + } + assert_eq!(text.matches("") < text.find(""), + "tasks come before keywords, as the schema orders them" + ); +} + +#[test] +fn the_opcodes_written_have_their_winmeta_names() { + assert_eq!(opcode_name(opcode::INFO), "win:Info"); + assert_eq!(opcode_name(opcode::START), "win:Start"); + assert_eq!(opcode_name(opcode::STOP), "win:Stop"); +} + +#[test] +fn the_manifest_has_one_event_element_per_event_and_one_template_per_event() { + let text = manifest(); + assert_eq!(text.matches("").count(), EVENTS.len()); +} + +#[test] +fn the_manifest_has_each_keyword_with_its_mask() { + let text = manifest(); + for keyword in KEYWORDS { + assert!( + text.contains(&format!( + "", + keyword.name, keyword.mask + )), + "{}", + keyword.name + ); + } +} + +#[test] +fn each_template_has_the_events_fields_in_order_with_their_types() { + let text = manifest(); + for event in &EVENTS { + let start = text + .find(&format!("").expect("closed"); + let body = &text[start..end]; + let names: Vec<&str> = body + .lines() + .filter(|line| line.contains(" = event.columns.iter().map(|column| column.name).collect(); + assert_eq!(names, expected, "{}", event.name); + for column in event.columns { + let wanted = match column.ty { + ColumnType::UInt32 => "win:UInt32", + ColumnType::UInt64 => "win:UInt64", + ColumnType::Boolean => "win:Boolean", + ColumnType::UnicodeString => "win:UnicodeString", + ColumnType::Binary { .. } => "win:Binary", + }; + assert!( + body.contains(&format!( + "")); + let binaries = EVENTS + .iter() + .flat_map(|event| event.columns) + .filter(|column| matches!(column.ty, ColumnType::Binary { .. })) + .count(); + assert_eq!(text.matches("length=\"").count(), binaries); +} + +#[test] +fn the_manifest_is_lf_only_ends_with_a_newline_and_is_ascii() { + let text = manifest(); + assert!(!text.contains('\r')); + assert!(text.ends_with("\n")); + assert!(text.is_ascii()); + assert!(text.starts_with("\n")); +} + +#[test] +fn the_manifest_is_the_same_each_time() { + assert_eq!(manifest(), manifest()); +} + +#[test] +fn the_levels_the_page_defines_have_names() { + assert_eq!(level_name(win_etw_sys::level::CRITICAL), "win:Critical"); + assert_eq!(level_name(win_etw_sys::level::ERROR), "win:Error"); + assert_eq!(level_name(win_etw_sys::level::WARNING), "win:Warning"); + assert_eq!(level_name(win_etw_sys::level::INFO), "win:Informational"); + assert_eq!(level_name(win_etw_sys::level::VERBOSE), "win:Verbose"); +} + +#[test] +#[should_panic(expected = "has no name in a manifest")] +fn a_level_the_page_does_not_define_has_no_name() { + let _ = level_name(16); +} diff --git a/crates/durable-ioring-events/src/schema.rs b/crates/durable-ioring-events/src/schema.rs new file mode 100644 index 000000000..d440a33e4 --- /dev/null +++ b/crates/durable-ioring-events/src/schema.rs @@ -0,0 +1,354 @@ +// Copyright (c) 2026 Mike Grier +//! The events of the provider, as data (DE-D-2): what each is called, how serious it is, which keyword it belongs to, and the +//! fields it carries, in order, with their types. +//! +//! The table is the one place an event is described. The manifest is rendered from it, the sink encodes each event to it, and +//! a reader's decoder can be written from it, so the three cannot disagree about a field. + +use durable_ioring::{DiagnosticKind, Edge}; +use win_etw_sys::{EventDescriptor, Guid, level}; + +#[cfg(test)] +mod tests; + +/// The provider's name, which is the crate's. +pub const PROVIDER_NAME: &str = "durable-ioring-events"; + +/// The provider's GUID, generated once on 2026-10-20. +/// +/// Changing it breaks every session that enabled the provider by it, which is what a GUID identifies; so it is not changed. +pub const PROVIDER_ID: Guid = Guid::from_u128(0x4706_7f6f_8f2b_4278_9b92_092d_fb33_85e6); + +/// What a field is, as a manifest names it and as it is laid out in the event. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ColumnType { + /// `win:UInt32`: four bytes. + UInt32, + /// `win:UInt64`: eight bytes. + UInt64, + /// `win:Boolean`: four bytes, one for true and zero for false. + Boolean, + /// `win:UnicodeString`: UTF-16 units and then a zero unit. A zero unit in the text ends it for a reader. + UnicodeString, + /// `win:Binary`: as many bytes as the `UInt32` column of this name, earlier in the same event, says. + Binary { + /// The name of that column. + length: &'static str, + }, +} + +/// One field of an event. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Column { + /// The field's name in the manifest. + pub name: &'static str, + /// What it is. + pub ty: ColumnType, +} + +/// A keyword: the category an event belongs to, which a session filters by. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Keyword { + /// The keyword's name in the manifest. + pub name: &'static str, + /// Its bit. The top sixteen bits of a keyword are Microsoft's, so these are in the low forty-eight. + pub mask: u64, +} + +/// A task: what a pair of events is about, which a reader groups a begin and its end by. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Task { + /// The task's name in the manifest. + pub name: &'static str, + /// Its value, which is an event's `EVENT_DESCRIPTOR`'s `Task`. Zero is the default and names none. + pub value: u16, +} + +/// One event of the provider. +#[derive(Clone, Copy, Debug)] +pub struct EventSchema { + /// The event's id, which is its `EVENT_DESCRIPTOR`'s `Id` and the manifest's `value`. + pub id: u16, + /// The event's name in the manifest. + pub name: &'static str, + /// The kind of event of durable-ioring that becomes this one. A kind with several events -- a pair's begin and end, and a + /// flush's for a file or a domain -- names them all, and they share a level, a keyword and a task. + pub kind: DiagnosticKind, + /// Which end of a pair the event is, as [`Diagnostic::pairing`](durable_ioring::Diagnostic::pairing) says; `None` for an + /// event that is no end of one. It decides the event's opcode. + pub edge: Option, + /// The task a pair's events share; `None` for an event that is no end of one. + pub task: Option<&'static Task>, + /// The level: see [`win_etw_sys::level`]. + pub level: u8, + /// The keyword. + pub keyword: &'static Keyword, + /// The fields, in the order they are written. + pub columns: &'static [Column], +} + +impl EventSchema { + /// The event's descriptor, as it is written and as a session filters by it. + #[must_use] + pub const fn descriptor(&self) -> EventDescriptor { + EventDescriptor { + id: self.id, + version: VERSION, + channel: CHANNEL, + level: self.level, + opcode: opcode(self.edge), + task: match self.task { + Some(task) => task.value, + None => NO_TASK, + }, + keyword: self.keyword.mask, + } + } +} + +/// The opcode an event is written with: a pair's begin is `win:Start` and its end `win:Stop`, which a reader pairs by task and +/// activity, and any other event `win:Info`. +#[must_use] +pub const fn opcode(edge: Option) -> u8 { + match edge { + None => opcode::INFO, + Some(Edge::Begin) => opcode::START, + Some(Edge::End) => opcode::STOP, + } +} + +/// The opcodes the provider writes, as `winmeta.xml` defines them. +pub mod opcode { + /// `win:Info`: "Most events use INFO (0)." + pub const INFO: u8 = 0; + /// `win:Start`: the beginning of an activity. + pub const START: u8 = 1; + /// `win:Stop`: the end of an activity. + pub const STOP: u8 = 2; +} + +/// Every event is version 0: this is the first definition, and a change to an event's fields is a new version (DE-D-2). +pub const VERSION: u8 = 0; +/// "Manifest-based events normally use channel 0." +const CHANNEL: u8 = 0; +/// "The task code 0 is the default." +const NO_TASK: u16 = 0; + +/// A seal, from its being asked for to its answer. +pub static SEAL_TASK: Task = Task { + name: "Seal", + value: 1, +}; +/// One part of a seal's flush, from its issue to its answer. +pub static FLUSH_TASK: Task = Task { + name: "Flush", + value: 2, +}; +/// A wait for one of an instance's locks. +pub static LOCK_WAIT_TASK: Task = Task { + name: "LockWait", + value: 3, +}; + +/// Waits for the instance's locks. +pub static LOCKS: Keyword = Keyword { + name: "Locks", + mask: 0x1, +}; +/// Each part of a seal's flush. +pub static FLUSHES: Keyword = Keyword { + name: "Flushes", + mask: 0x2, +}; +/// Each seal. +pub static SEALS: Keyword = Keyword { + name: "Seals", + mask: 0x4, +}; +/// An instance finding its own state inconsistent. +pub static INCONSISTENCIES: Keyword = Keyword { + name: "Inconsistencies", + mask: 0x8, +}; + +const fn column(name: &'static str, ty: ColumnType) -> Column { + Column { name, ty } +} + +const INSTANCE: Column = column("Instance", ColumnType::UInt64); +const LINEAGE: Column = column("Lineage", ColumnType::UInt64); +const THROUGH: Column = column("Through", ColumnType::UnicodeString); +const FILE: Column = column("File", ColumnType::UInt64); +const DOMAIN_LENGTH: Column = column("DomainLength", ColumnType::UInt32); +const DOMAIN: Column = column( + "Domain", + ColumnType::Binary { + length: "DomainLength", + }, +); +const LOCK: Column = column("Lock", ColumnType::UnicodeString); +const PAIR: Column = column("Pair", ColumnType::UInt64); +const OUTCOME: Column = column("Outcome", ColumnType::UnicodeString); + +/// The id of each event. Changing one is a breaking change to every reader that decodes by it. Ids 2 to 6 were the delay events +/// that preceded the pairs, and are not used again, so a trace recorded with them is never read as one of these (DE-D-5). +pub mod id { + /// An instance found its own state inconsistent. + pub const INCONSISTENCY: u16 = 1; + /// A seal was asked for. + pub const SEAL_BEGAN: u16 = 7; + /// A seal was answered, or never will be. + pub const SEAL_ENDED: u16 = 8; + /// A file's flush was issued. + pub const FLUSH_BEGAN_FILE: u16 = 9; + /// A file's flush answered, or never will. + pub const FLUSH_ENDED_FILE: u16 = 10; + /// A domain's part of a flush was issued. + pub const FLUSH_BEGAN_DOMAIN: u16 = 11; + /// A domain's part of a flush answered, or never will. + pub const FLUSH_ENDED_DOMAIN: u16 = 12; + /// A wait for one of an instance's locks began. + pub const LOCK_WAIT_BEGAN: u16 = 13; + /// The wait took the lock. + pub const LOCK_WAIT_ENDED: u16 = 14; +} + +/// One end of a pair: its id, name, kind, end and columns, at the information level, with its kind's keyword and task. +const fn pair_end( + id: u16, + name: &'static str, + kind: DiagnosticKind, + edge: Edge, + keyword: &'static Keyword, + task: &'static Task, + columns: &'static [Column], +) -> EventSchema { + EventSchema { + id, + name, + kind, + edge: Some(edge), + task: Some(task), + level: level::INFO, + keyword, + columns, + } +} + +/// The events of the provider, in the order of their ids. +pub static EVENTS: [EventSchema; 9] = [ + EventSchema { + id: id::INCONSISTENCY, + name: "Inconsistency", + kind: DiagnosticKind::Inconsistency, + edge: None, + task: None, + level: level::ERROR, + keyword: &INCONSISTENCIES, + columns: &[ + INSTANCE, + column("What", ColumnType::UnicodeString), + column("Detail", ColumnType::UnicodeString), + ], + }, + pair_end( + id::SEAL_BEGAN, + "SealBegan", + DiagnosticKind::Seal, + Edge::Begin, + &SEALS, + &SEAL_TASK, + &[INSTANCE, LINEAGE, THROUGH, PAIR], + ), + pair_end( + id::SEAL_ENDED, + "SealEnded", + DiagnosticKind::Seal, + Edge::End, + &SEALS, + &SEAL_TASK, + &[INSTANCE, LINEAGE, THROUGH, PAIR, OUTCOME], + ), + pair_end( + id::FLUSH_BEGAN_FILE, + "FlushBeganFile", + DiagnosticKind::Flush, + Edge::Begin, + &FLUSHES, + &FLUSH_TASK, + &[INSTANCE, LINEAGE, THROUGH, FILE, PAIR], + ), + pair_end( + id::FLUSH_ENDED_FILE, + "FlushEndedFile", + DiagnosticKind::Flush, + Edge::End, + &FLUSHES, + &FLUSH_TASK, + &[INSTANCE, LINEAGE, THROUGH, FILE, PAIR, OUTCOME], + ), + pair_end( + id::FLUSH_BEGAN_DOMAIN, + "FlushBeganDomain", + DiagnosticKind::Flush, + Edge::Begin, + &FLUSHES, + &FLUSH_TASK, + &[INSTANCE, LINEAGE, THROUGH, DOMAIN_LENGTH, DOMAIN, PAIR], + ), + pair_end( + id::FLUSH_ENDED_DOMAIN, + "FlushEndedDomain", + DiagnosticKind::Flush, + Edge::End, + &FLUSHES, + &FLUSH_TASK, + &[ + INSTANCE, + LINEAGE, + THROUGH, + DOMAIN_LENGTH, + DOMAIN, + PAIR, + OUTCOME, + ], + ), + pair_end( + id::LOCK_WAIT_BEGAN, + "LockWaitBegan", + DiagnosticKind::LockWait, + Edge::Begin, + &LOCKS, + &LOCK_WAIT_TASK, + &[INSTANCE, LOCK, PAIR], + ), + pair_end( + id::LOCK_WAIT_ENDED, + "LockWaitEnded", + DiagnosticKind::LockWait, + Edge::End, + &LOCKS, + &LOCK_WAIT_TASK, + &[INSTANCE, LOCK, PAIR], + ), +]; + +/// The tasks of the provider. +pub static TASKS: [&Task; 3] = [&SEAL_TASK, &FLUSH_TASK, &LOCK_WAIT_TASK]; + +/// The keywords of the provider. +pub static KEYWORDS: [&Keyword; 4] = [&LOCKS, &FLUSHES, &SEALS, &INCONSISTENCIES]; + +/// The event a kind of event of durable-ioring is asked about by: the first of its events, and its level and keyword are the +/// others' too. `None` for a kind this crate does not know, which a newer durable-ioring may add. +#[must_use] +pub fn schema_of_kind(kind: DiagnosticKind) -> Option<&'static EventSchema> { + EVENTS.iter().find(|event| event.kind == kind) +} + +/// The event with this id. +#[must_use] +pub fn schema_of_id(id: u16) -> Option<&'static EventSchema> { + EVENTS.iter().find(|event| event.id == id) +} diff --git a/crates/durable-ioring-events/src/schema/tests.rs b/crates/durable-ioring-events/src/schema/tests.rs new file mode 100644 index 000000000..a40558539 --- /dev/null +++ b/crates/durable-ioring-events/src/schema/tests.rs @@ -0,0 +1,332 @@ +// Copyright (c) 2026 Mike Grier +//! The table of events (DE-D-2): that it is consistent with itself, and the facts that identify the provider and its events and +//! that a reader decodes by. + +use durable_ioring::{DiagnosticKind, Edge}; + +use super::{ + ColumnType, EVENTS, KEYWORDS, PROVIDER_ID, PROVIDER_NAME, TASKS, VERSION, id, opcode, + schema_of_id, schema_of_kind, +}; + +const KINDS: [DiagnosticKind; 4] = [ + DiagnosticKind::Inconsistency, + DiagnosticKind::Seal, + DiagnosticKind::Flush, + DiagnosticKind::LockWait, +]; + +#[test] +fn the_provider_is_identified_by_the_name_and_guid_it_was_given() { + // Pinned: changing either breaks every session that enabled the provider by it. + assert_eq!(PROVIDER_NAME, "durable-ioring-events"); + assert_eq!( + PROVIDER_ID.to_string(), + "{47067f6f-8f2b-4278-9b92-092dfb3385e6}" + ); +} + +#[test] +fn the_ids_are_the_published_numbers_in_ascending_order() { + // Pinned: a reader decodes by them. + let ids: Vec = EVENTS.iter().map(|event| event.id).collect(); + assert_eq!( + ids, + [ + id::INCONSISTENCY, + id::SEAL_BEGAN, + id::SEAL_ENDED, + id::FLUSH_BEGAN_FILE, + id::FLUSH_ENDED_FILE, + id::FLUSH_BEGAN_DOMAIN, + id::FLUSH_ENDED_DOMAIN, + id::LOCK_WAIT_BEGAN, + id::LOCK_WAIT_ENDED + ] + ); + assert_eq!(ids, [1, 7, 8, 9, 10, 11, 12, 13, 14]); +} + +#[test] +fn the_ids_of_the_retired_delay_events_are_not_used_again() { + for retired in 2..=6 { + assert!(schema_of_id(retired).is_none(), "id {retired} is in use"); + } +} + +#[test] +fn no_two_events_share_an_id_or_a_name() { + for (i, a) in EVENTS.iter().enumerate() { + for b in &EVENTS[i + 1..] { + assert_ne!(a.id, b.id); + assert_ne!(a.name, b.name); + } + } +} + +#[test] +fn every_event_is_in_the_order_of_its_id() { + assert!(EVENTS.windows(2).all(|pair| pair[0].id < pair[1].id)); +} + +#[test] +fn every_event_has_a_nonzero_level_and_keyword_as_the_page_asks() { + for event in &EVENTS { + assert_ne!(event.level, 0, "{}", event.name); + assert_ne!(event.keyword.mask, 0, "{}", event.name); + } +} + +#[test] +fn every_keyword_is_one_bit_in_the_providers_forty_eight() { + for keyword in KEYWORDS { + assert_eq!(keyword.mask.count_ones(), 1, "{}", keyword.name); + assert_eq!(keyword.mask & 0xffff_0000_0000_0000, 0, "{}", keyword.name); + } +} + +#[test] +fn no_two_keywords_share_a_bit_or_a_name() { + for (i, a) in KEYWORDS.iter().enumerate() { + for b in &KEYWORDS[i + 1..] { + assert_eq!(a.mask & b.mask, 0); + assert_ne!(a.name, b.name); + } + } +} + +#[test] +fn every_events_keyword_is_one_of_the_listed_keywords() { + for event in &EVENTS { + assert!( + KEYWORDS + .iter() + .any(|keyword| std::ptr::eq(*keyword, event.keyword)), + "{}", + event.name + ); + } +} + +#[test] +fn every_keyword_is_used_by_an_event() { + for keyword in KEYWORDS { + assert!( + EVENTS + .iter() + .any(|event| std::ptr::eq(event.keyword, keyword)), + "{}", + keyword.name + ); + } +} + +#[test] +fn the_events_of_one_kind_share_a_level_a_keyword_and_a_task() { + // The sink asks about a kind by its first event, so the others must agree with it. + for kind in KINDS { + let of_kind: Vec<_> = EVENTS.iter().filter(|event| event.kind == kind).collect(); + assert!(!of_kind.is_empty(), "{kind:?} has no event"); + for event in &of_kind { + assert_eq!(event.level, of_kind[0].level, "{}", event.name); + assert_eq!( + event.keyword.mask, of_kind[0].keyword.mask, + "{}", + event.name + ); + assert_eq!(event.task, of_kind[0].task, "{}", event.name); + } + } +} + +#[test] +fn every_event_belongs_to_a_kind_the_sink_is_asked_about() { + for event in &EVENTS { + assert!(KINDS.contains(&event.kind), "{}", event.name); + } +} + +#[test] +fn a_kind_is_asked_about_by_its_first_event() { + for kind in KINDS { + let first = EVENTS + .iter() + .find(|event| event.kind == kind) + .expect("an event"); + assert_eq!(schema_of_kind(kind).map(|event| event.id), Some(first.id)); + } +} + +#[test] +fn an_event_is_found_by_its_id_and_an_unknown_id_is_none() { + for event in &EVENTS { + assert_eq!( + schema_of_id(event.id).map(|found| found.name), + Some(event.name) + ); + } + assert!(schema_of_id(0).is_none()); + assert!(schema_of_id(15).is_none()); + assert!(schema_of_id(u16::MAX).is_none()); +} + +#[test] +fn no_event_has_two_fields_of_one_name() { + for event in &EVENTS { + for (i, a) in event.columns.iter().enumerate() { + for b in &event.columns[i + 1..] { + assert_ne!(a.name, b.name, "{}", event.name); + } + } + } +} + +#[test] +fn every_binary_field_names_an_earlier_four_byte_length() { + for event in &EVENTS { + for (i, column) in event.columns.iter().enumerate() { + if let ColumnType::Binary { length } = column.ty { + let found = event.columns[..i] + .iter() + .find(|earlier| earlier.name == length) + .unwrap_or_else(|| { + panic!("{} names a length that is not earlier", column.name) + }); + assert_eq!(found.ty, ColumnType::UInt32, "{}", column.name); + } + } + } +} + +#[test] +fn every_event_starts_with_the_instance_so_a_reader_can_tell_instances_apart() { + for event in &EVENTS { + assert_eq!(event.columns[0].name, "Instance", "{}", event.name); + assert_eq!(event.columns[0].ty, ColumnType::UInt64); + } +} + +#[test] +fn the_descriptor_is_the_events_id_level_keyword_task_and_opcode() { + for event in &EVENTS { + let descriptor = event.descriptor(); + assert_eq!(descriptor.id, event.id); + assert_eq!(descriptor.level, event.level); + assert_eq!(descriptor.keyword, event.keyword.mask); + assert_eq!(descriptor.version, VERSION); + assert_eq!(descriptor.channel, 0); + assert_eq!(descriptor.task, event.task.map_or(0, |task| task.value)); + assert_eq!(descriptor.opcode, super::opcode(event.edge)); + } +} + +#[test] +fn a_begin_is_written_as_start_an_end_as_stop_and_anything_else_as_info() { + assert_eq!(super::opcode(None), opcode::INFO); + assert_eq!(super::opcode(Some(Edge::Begin)), opcode::START); + assert_eq!(super::opcode(Some(Edge::End)), opcode::STOP); + assert_eq!( + (opcode::INFO, opcode::START, opcode::STOP), + (0, 1, 2), + "as winmeta.xml defines them" + ); +} + +#[test] +fn every_pair_kind_has_a_begin_and_an_end_with_a_task_and_nothing_else_has_either() { + for kind in KINDS { + let of_kind: Vec<_> = EVENTS.iter().filter(|event| event.kind == kind).collect(); + if kind == DiagnosticKind::Inconsistency { + assert!( + of_kind + .iter() + .all(|event| event.edge.is_none() && event.task.is_none()) + ); + continue; + } + assert!( + of_kind.iter().any(|event| event.edge == Some(Edge::Begin)), + "{kind:?}" + ); + assert!( + of_kind.iter().any(|event| event.edge == Some(Edge::End)), + "{kind:?}" + ); + assert!(of_kind.iter().all(|event| event.task.is_some()), "{kind:?}"); + } +} + +#[test] +fn a_kind_is_asked_about_by_its_begin() { + for kind in [ + DiagnosticKind::Seal, + DiagnosticKind::Flush, + DiagnosticKind::LockWait, + ] { + assert_eq!( + schema_of_kind(kind).and_then(|event| event.edge), + Some(Edge::Begin) + ); + } +} + +#[test] +fn every_tasks_value_is_its_own_and_not_the_default() { + for (i, a) in TASKS.iter().enumerate() { + assert_ne!(a.value, 0, "{}", a.name); + for b in &TASKS[i + 1..] { + assert_ne!(a.value, b.value); + assert_ne!(a.name, b.name); + } + } +} + +#[test] +fn every_events_task_is_one_of_the_listed_tasks_and_every_task_is_used() { + for event in &EVENTS { + if let Some(task) = event.task { + assert!( + TASKS.iter().any(|listed| std::ptr::eq(*listed, task)), + "{}", + event.name + ); + } + } + for task in TASKS { + assert!( + EVENTS + .iter() + .any(|event| event.task.is_some_and(|t| std::ptr::eq(t, task))), + "{}", + task.name + ); + } +} + +#[test] +fn every_end_of_a_pair_carries_the_pair_and_every_end_says_how_it_ended_but_a_lock_waits() { + for event in &EVENTS { + let names: Vec<&str> = event.columns.iter().map(|c| c.name).collect(); + match event.edge { + None => assert!(!names.contains(&"Pair"), "{}", event.name), + Some(edge) => { + assert!(names.contains(&"Pair"), "{}", event.name); + let says = names.contains(&"Outcome"); + let should = edge == Edge::End && event.kind != DiagnosticKind::LockWait; + assert_eq!(says, should, "{}", event.name); + } + } + } +} + +#[test] +fn an_inconsistency_is_an_error_and_every_delay_is_informational() { + for event in &EVENTS { + let expected = if event.kind == DiagnosticKind::Inconsistency { + win_etw_sys::level::ERROR + } else { + win_etw_sys::level::INFO + }; + assert_eq!(event.level, expected, "{}", event.name); + } +} diff --git a/crates/durable-ioring-events/src/sink.rs b/crates/durable-ioring-events/src/sink.rs new file mode 100644 index 000000000..b6c4bb1ed --- /dev/null +++ b/crates/durable-ioring-events/src/sink.rs @@ -0,0 +1,149 @@ +// Copyright (c) 2026 Mike Grier +//! The diagnostic sink that writes each event to the provider (DE-D-4). + +use std::sync::{Arc, Mutex, PoisonError, Weak}; + +use durable_ioring::{Diagnostic, DiagnosticKind, DiagnosticSink, Edge}; +use win_error_codes::Win32Error; +use win_etw_sys::{Registration, create_activity_id}; + +use crate::activity; + +use crate::encode::{encode, schema_of}; +use crate::schema::{PROVIDER_ID, schema_of_kind}; + +#[cfg(test)] +mod tests; + +/// The registration every sink of the process shares while any of them is alive: the provider is registered once, as the page +/// asks ("you should limit the number of providers that your component registers to one or two"), and unregistered when the +/// last sink is dropped, so a library that is unloaded gives it back. +static SHARED: Mutex> = Mutex::new(Weak::new()); + +fn shared() -> Result, Win32Error> { + let mut slot = SHARED.lock().unwrap_or_else(PoisonError::into_inner); + if let Some(registration) = slot.upgrade() { + return Ok(registration); + } + let registration = Arc::new(Registration::register(PROVIDER_ID)?); + *slot = Arc::downgrade(®istration); + Ok(registration) +} + +/// A sink that writes each event to the provider `durable-ioring-events`, for a session that is listening. +/// +/// It records whether or not the provider's manifest is installed: the manifest is what decodes an event for a reader, and +/// installing it is a step for whoever reads the trace and not for the application that writes it. See [`manifest`]. +/// +/// It wants a kind only when a session is listening for it, so with none its cost is a test of that, and no event is built: +/// no text is formatted and nothing is written. Cloning it shares the registration, which every sink of the process does. +/// +/// A pair's begin and end are written in one activity: an id `EventActivityIdControl` creates when the begin is written and +/// the end repeats, whatever thread writes either, and which the platform guarantees no process of the system repeats until it +/// restarts. An end whose begin was not written, because no session was listening then, is written in an activity of its own. +/// Any other event is written in the writing thread's activity. See DE-D-6. +/// +/// [`manifest`]: fn@crate::manifest +#[derive(Clone, Debug)] +pub struct EtwDiagnostics { + registration: Option>, +} + +impl EtwDiagnostics { + /// A sink, registering the provider if no sink of the process has. + /// + /// # Errors + /// + /// The registration's failure, as the code the call returned. Most callers have no use for it: the page says "Most + /// production code should continue to run even if an ETW provider failed to register", and [`new`](Self::new) is the sink + /// that does. + pub fn try_new() -> Result { + Ok(Self { + registration: Some(shared()?), + }) + } + + /// A sink, registering the provider if no sink of the process has. If the provider cannot be registered the sink writes + /// nothing and wants nothing, as the page says production code should carry on: [`is_registered`](Self::is_registered) + /// says whether it did. + #[must_use] + pub fn new() -> Self { + Self { + registration: shared().ok(), + } + } + + /// Whether the provider is registered, so that a session can listen for it. + #[must_use] + pub fn is_registered(&self) -> bool { + self.registration.is_some() + } + + /// Whether any sink of this process holds the provider's registration now. It is made by the first sink and given back + /// when the last is dropped, so this is false before the first and after the last. + #[must_use] + pub fn provider_is_registered_in_process() -> bool { + SHARED + .lock() + .unwrap_or_else(PoisonError::into_inner) + .strong_count() + > 0 + } +} + +impl Default for EtwDiagnostics { + fn default() -> Self { + Self::new() + } +} + +impl DiagnosticSink for EtwDiagnostics { + fn wants(&self, kind: DiagnosticKind) -> bool { + let Some(registration) = &self.registration else { + return false; + }; + // The level and keyword are the same for each of a kind's events, so the kind is asked about by the first. + schema_of_kind(kind) + .is_some_and(|schema| registration.is_level_enabled(schema.level, schema.keyword.mask)) + } + + fn report(&self, event: &Diagnostic<'_>) { + let Some(registration) = &self.registration else { + return; + }; + let Some(schema) = schema_of(event) else { + return; + }; + let pairing = event.pairing(); + // An end lets its pair's activity go whether or not it is written, so a session that stopped between a begin and its + // end leaves nothing held. + let ended = match pairing { + Some(pairing) if pairing.edge == Edge::End => activity::ended(pairing.id), + _ => None, + }; + let descriptor = schema.descriptor(); + // Asked again, of the event itself, before anything is built: `wants` was asked of its kind. + if !registration.is_enabled(&descriptor) { + return; + } + let Some(values) = encode(event) else { + return; + }; + let fields: Vec<_> = values.iter().map(crate::encode::Value::field).collect(); + let activity = match pairing { + None => None, + Some(pairing) => match pairing.edge { + Edge::Begin => activity::begun(pairing.id, || create_activity_id().ok()), + Edge::End => ended.or_else(|| create_activity_id().ok()), + }, + }; + // The page says the code "is primarily intended for use in debugging and diagnostic scenarios" and that most callers + // ignore it, and a sink has no way to report a failure of its own. + let _ = match activity { + Some(activity) => { + registration.write_transfer(&descriptor, Some(&activity), None, &fields) + } + None => registration.write(&descriptor, &fields), + }; + } +} diff --git a/crates/durable-ioring-events/src/sink/tests.rs b/crates/durable-ioring-events/src/sink/tests.rs new file mode 100644 index 000000000..c53bf3c37 --- /dev/null +++ b/crates/durable-ioring-events/src/sink/tests.rs @@ -0,0 +1,188 @@ +// Copyright (c) 2026 Mike Grier +//! The sink with no session listening (DE-D-4): it wants nothing, builds nothing, and shares one registration. What it does for +//! a session that is listening is `tests/session.rs`. + +use durable_ioring::{ + CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink, FlushDomain, FlushOutcome, + FlushPart, IoTargetKey, SealOutcome, +}; +use std::fmt; +use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; + +use super::EtwDiagnostics; + +const KINDS: [DiagnosticKind; 4] = [ + DiagnosticKind::Inconsistency, + DiagnosticKind::LockWait, + DiagnosticKind::Seal, + DiagnosticKind::Flush, +]; + +/// A sink that wants every kind, to mint pair ids from: the ETW sink with no session wants none. +struct Wanting; + +impl DiagnosticSink for Wanting { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn minted() -> CorrelationId { + CorrelationId::for_pair(&Wanting, DiagnosticKind::Seal).expect("wanted") +} + +/// Counts how many times it is formatted: text that is built for an event that no one wants would show here. +struct Counted(Arc); + +impl fmt::Display for Counted { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fetch_add(1, Ordering::Relaxed); + f.write_str("counted") + } +} + +#[test] +fn a_sink_registers_the_provider() { + let sink = EtwDiagnostics::new(); + assert!(sink.is_registered()); + assert!(EtwDiagnostics::try_new().is_ok()); +} + +#[test] +fn with_no_session_a_sink_wants_no_kind() { + let sink = EtwDiagnostics::new(); + for kind in KINDS { + assert!(!sink.wants(kind), "{kind:?}"); + } +} + +#[test] +fn with_no_session_reporting_builds_no_text_and_writes_nothing() { + let sink = EtwDiagnostics::new(); + let formatted = Arc::new(AtomicU32::new(0)); + let detail = Counted(formatted.clone()); + let through = Counted(formatted.clone()); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("{detail}"), + }); + sink.report(&Diagnostic::SealBegan { + instance: 1, + lineage: 0, + through: format_args!("{through}"), + id: minted(), + }); + sink.report(&Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("{through}"), + part: FlushPart::File(IoTargetKey(1)), + id: minted(), + }); + assert_eq!( + formatted.load(Ordering::Relaxed), + 0, + "text was built for events no session was listening to" + ); +} + +#[test] +fn with_no_session_every_kind_of_event_is_reported_with_no_failure() { + let sink = EtwDiagnostics::new(); + let domain = FlushDomain::new(b"d".to_vec()); + let id = minted(); + sink.report(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id, + }); + sink.report(&Diagnostic::LockWaitEnded { + instance: 1, + lock: "engine", + id, + }); + sink.report(&Diagnostic::SealEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + id, + outcome: SealOutcome::Durable, + }); + sink.report(&Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::File(IoTargetKey(1)), + id, + outcome: FlushOutcome::Succeeded, + }); + sink.report(&Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id, + }); + sink.report(&Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id, + outcome: FlushOutcome::Failed, + }); +} + +#[test] +fn sinks_in_one_process_share_one_registration() { + let a = EtwDiagnostics::new(); + let b = EtwDiagnostics::new(); + let c = a.clone(); + let (ra, rb, rc) = ( + a.registration.as_ref().expect("registered"), + b.registration.as_ref().expect("registered"), + c.registration.as_ref().expect("registered"), + ); + assert!(Arc::ptr_eq(ra, rb)); + assert!(Arc::ptr_eq(ra, rc)); +} + +#[test] +fn a_sink_that_could_not_register_wants_nothing_and_writes_nothing() { + let sink = EtwDiagnostics { registration: None }; + assert!(!sink.is_registered()); + for kind in KINDS { + assert!(!sink.wants(kind)); + } + sink.report(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id: minted(), + }); +} + +#[test] +fn a_sink_is_default_constructed_and_debuggable() { + let sink = EtwDiagnostics::default(); + assert!(sink.is_registered()); + assert!(format!("{sink:?}").starts_with("EtwDiagnostics")); +} + +#[test] +fn sinks_are_made_and_dropped_on_many_threads_at_once() { + std::thread::scope(|scope| { + for _ in 0..8 { + scope.spawn(|| { + for _ in 0..50 { + let sink = EtwDiagnostics::new(); + assert!(sink.is_registered()); + let _ = sink.wants(DiagnosticKind::LockWait); + } + }); + } + }); +} diff --git a/crates/durable-ioring-events/tests/registration.rs b/crates/durable-ioring-events/tests/registration.rs new file mode 100644 index 000000000..3f46a7302 --- /dev/null +++ b/crates/durable-ioring-events/tests/registration.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2026 Mike Grier +//! The registration's lifetime (DE-D-4): the provider is registered while any sink is alive, and given back when the last is +//! dropped, so that a library that is unloaded does not leave a registration behind. +//! +//! # Why this is an integration test +//! +//! It asks whether the registration is gone, which any other sink alive in the same process would keep, so it needs a process +//! of its own, which an integration test file is. + +#![cfg(windows)] + +use durable_ioring_events::EtwDiagnostics; + +#[test] +fn the_registration_is_given_back_when_the_last_sink_is_dropped_and_made_again_for_the_next() { + assert!( + !EtwDiagnostics::provider_is_registered_in_process(), + "before any sink" + ); + let first = EtwDiagnostics::new(); + let second = first.clone(); + assert!(first.is_registered()); + assert!(EtwDiagnostics::provider_is_registered_in_process()); + drop(first); + assert!( + EtwDiagnostics::provider_is_registered_in_process(), + "given back while a sink was alive" + ); + drop(second); + assert!( + !EtwDiagnostics::provider_is_registered_in_process(), + "kept after every sink was dropped" + ); + + let next = EtwDiagnostics::new(); + assert!(next.is_registered()); + assert!(EtwDiagnostics::provider_is_registered_in_process()); +} diff --git a/crates/durable-ioring-events/tests/session.rs b/crates/durable-ioring-events/tests/session.rs new file mode 100644 index 000000000..31e551915 --- /dev/null +++ b/crates/durable-ioring-events/tests/session.rs @@ -0,0 +1,684 @@ +// Copyright (c) 2026 Mike Grier +//! What the sink does for a session that is listening (DE-D-4): this test starts a real trace session logging to a file, enables +//! the provider in it, reports events through `EtwDiagnostics`, stops the session and reads the file back, with `win-etw-sys`' +//! `test-util` session. The events are decoded from the table (DE-D-2), as a reader that had the manifest would. +//! +//! # Why this is an integration test +//! +//! It starts a system-wide trace session and reads a file the operating system wrote, which a unit test in memory cannot. +//! +//! # Privilege, and why these are ignored +//! +//! Starting a session needs an elevated process or a user in the Performance Log Users group, which an ordinary developer +//! account is not. So these tests are ignored by default, and CI runs them with `--include-ignored` on a runner that is an +//! administrator. Run them by hand from an elevated shell: +//! `cargo test -p durable-ioring-events --test session -- --include-ignored`. +//! +//! # Serial +//! +//! A session records every event of a provider enabled in it, from any thread of the process, and these tests all use the one +//! provider, so they run one at a time. + +#![cfg(windows)] + +use std::fmt; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use std::time::{Duration, Instant}; + +use durable_ioring::{ + CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink, FanOutDiagnostics, FlushDomain, + FlushOutcome, FlushPart, IoTargetKey, SealOutcome, +}; +use durable_ioring_events::{ + ColumnType, EVENTS, EtwDiagnostics, EventSchema, INCONSISTENCIES, LOCKS, PROVIDER_ID, Value, + encode, schema_of, schema_of_id, +}; +use win_etw_sys::{Guid, Recorded, Session, level}; + +/// A sink that wants every kind, to mint pair ids from. +struct Wanting; + +impl DiagnosticSink for Wanting { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn minted() -> CorrelationId { + CorrelationId::for_pair(&Wanting, DiagnosticKind::Seal).expect("wanted") +} + +/// The id of a lock wait's begin, which several tests write as a plain event to look for. +const LOCK_WAIT_BEGAN: u16 = 13; + +/// Held by each test: they share one provider, and a session records every event of it. +fn serial() -> MutexGuard<'static, ()> { + static LOCK: Mutex<()> = Mutex::new(()); + LOCK.lock().unwrap_or_else(PoisonError::into_inner) +} + +fn session() -> Session { + Session::start().expect("a trace session (see the privilege note in this file's documentation)") +} + +/// A session with the provider enabled for everything. +fn listening() -> Session { + let mut session = session(); + session + .enable(PROVIDER_ID, level::VERBOSE, u64::MAX) + .expect("enabled"); + session +} + +/// Whether `condition` becomes true within a bound: a provider is told of a session after the call that started it returns. +fn becomes(condition: impl Fn() -> bool) -> bool { + let started = Instant::now(); + while started.elapsed() < Duration::from_secs(10) { + if condition() { + return true; + } + std::thread::sleep(Duration::from_millis(5)); + } + false +} + +/// Reads an event's data back into values, by the table, as a decoder with the manifest does. +fn decode(schema: &EventSchema, data: &[u8]) -> Vec { + let mut at = 0; + let mut values: Vec = Vec::new(); + for column in schema.columns { + let take = |at: &mut usize, n: usize| { + let bytes = &data[*at..*at + n]; + *at += n; + bytes + }; + let value = match column.ty { + ColumnType::UInt32 => Value::UInt32(u32::from_ne_bytes( + take(&mut at, 4).try_into().expect("four"), + )), + ColumnType::UInt64 => Value::UInt64(u64::from_ne_bytes( + take(&mut at, 8).try_into().expect("eight"), + )), + ColumnType::Boolean => { + Value::Boolean(u32::from_ne_bytes(take(&mut at, 4).try_into().expect("four")) != 0) + } + ColumnType::UnicodeString => { + let mut units = Vec::new(); + loop { + let unit = u16::from_ne_bytes(take(&mut at, 2).try_into().expect("two")); + if unit == 0 { + break; + } + units.push(unit); + } + Value::Text(units) + } + ColumnType::Binary { length } => { + let index = schema + .columns + .iter() + .position(|c| c.name == length) + .expect("a length column"); + let Value::UInt32(n) = values[index] else { + panic!("the length column is a UInt32") + }; + Value::Bytes(take(&mut at, n as usize).to_vec()) + } + _ => panic!("a column type the test does not know"), + }; + values.push(value); + } + assert_eq!( + at, + data.len(), + "{}: the fields did not use all of the data", + schema.name + ); + values +} + +fn only(events: Vec) -> Recorded { + assert_eq!(events.len(), 1, "{events:?}"); + events.into_iter().next().expect("one") +} + +/// That a recorded event is the event reported: the schema by its id, the descriptor the table gives, and the values. +fn is_recorded_as(recorded: &Recorded, event: &Diagnostic<'_>) { + let schema = schema_of(event).expect("an event the crate knows"); + assert_eq!(recorded.provider, PROVIDER_ID); + assert_eq!(recorded.descriptor, schema.descriptor()); + assert_eq!(recorded.process_id, std::process::id()); + assert_eq!( + decode(schema, &recorded.data), + encode(event).expect("values") + ); +} + +/// Counts how many times it is formatted. +struct Counted(Arc); + +impl fmt::Display for Counted { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fetch_add(1, Ordering::Relaxed); + f.write_str("counted") + } +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn every_event_reaches_a_session_as_the_values_it_was_reported_with() { + let _serial = serial(); + let domain = FlushDomain::new(b"ssd-4".to_vec()); + let odd = FlushDomain::new(vec![0xff, 0, 0x61, 0x80]); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + let (seal, file, part, other, lock) = (minted(), minted(), minted(), minted(), minted()); + let events: [Diagnostic<'_>; 10] = [ + Diagnostic::Inconsistency { + instance: 7, + what: "a write completed twice", + detail: format_args!("lineage {}", 3), + }, + Diagnostic::LockWaitBegan { + instance: 4, + lock: "engine", + id: lock, + }, + Diagnostic::LockWaitEnded { + instance: 4, + lock: "engine", + id: lock, + }, + Diagnostic::SealBegan { + instance: 2, + lineage: 1, + through: format_args!("{:?}", 9_u64), + id: seal, + }, + Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: file, + }, + Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + }, + Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(77)), + id: file, + outcome: FlushOutcome::Failed, + }, + Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + outcome: FlushOutcome::Succeeded, + }, + Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&odd), + id: other, + outcome: FlushOutcome::Abandoned, + }, + Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + id: seal, + outcome: SealOutcome::Failed, + }, + ]; + for event in &events { + sink.report(event); + } + let recorded = session.stop().expect("stopped and read"); + assert_eq!(recorded.len(), events.len(), "{recorded:?}"); + for (recorded, event) in recorded.iter().zip(&events) { + is_recorded_as(recorded, event); + } + // Each of the table's events was among them. + for schema in &EVENTS { + assert!( + recorded.iter().any(|r| r.descriptor.id == schema.id), + "{}", + schema.name + ); + } +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_session_with_one_keyword_is_asked_for_that_kind_and_no_other() { + let _serial = serial(); + let mut session = session(); + session + .enable(PROVIDER_ID, level::VERBOSE, LOCKS.mask) + .expect("enabled"); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + for kind in [ + DiagnosticKind::Seal, + DiagnosticKind::Flush, + DiagnosticKind::Inconsistency, + ] { + assert!(!sink.wants(kind), "{kind:?}"); + } + let formatted = Arc::new(AtomicU32::new(0)); + let through = Counted(formatted.clone()); + sink.report(&Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("{through}"), + part: FlushPart::File(IoTargetKey(1)), + id: minted(), + }); + assert_eq!( + formatted.load(Ordering::Relaxed), + 0, + "built for a keyword no session listens to" + ); + sink.report(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id: minted(), + }); + let event = only(session.stop().expect("stopped")); + assert_eq!(event.descriptor.id, LOCK_WAIT_BEGAN); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_session_at_the_error_level_is_asked_for_an_inconsistency_and_for_no_delay() { + let _serial = serial(); + let mut session = session(); + session + .enable(PROVIDER_ID, level::ERROR, u64::MAX) + .expect("enabled"); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::Inconsistency))); + for kind in [ + DiagnosticKind::LockWait, + DiagnosticKind::Seal, + DiagnosticKind::Flush, + ] { + assert!(!sink.wants(kind), "{kind:?}"); + } + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("y"), + }); + sink.report(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id: minted(), + }); + let event = only(session.stop().expect("stopped")); + assert_eq!(event.descriptor.keyword, INCONSISTENCIES.mask); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn text_is_built_once_for_an_event_a_session_is_listening_to() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::Inconsistency))); + let formatted = Arc::new(AtomicU32::new(0)); + let detail = Counted(formatted.clone()); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("{detail}"), + }); + assert_eq!(formatted.load(Ordering::Relaxed), 1); + let _ = session.stop().expect("stopped"); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_sink_made_before_a_session_wants_events_once_one_listens_and_not_after_it_stops() { + let _serial = serial(); + let sink = EtwDiagnostics::new(); + assert!(!sink.wants(DiagnosticKind::LockWait)); + let session = listening(); + assert!( + becomes(|| sink.wants(DiagnosticKind::LockWait)), + "never wanted while" + ); + drop(session.stop().expect("stopped")); + assert!( + becomes(|| !sink.wants(DiagnosticKind::LockWait)), + "still wanted after" + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn two_sinks_each_write_through_the_one_registration_and_each_event_is_recorded_once() { + let _serial = serial(); + let session = listening(); + let (a, b) = (EtwDiagnostics::new(), EtwDiagnostics::new()); + assert!(becomes(|| a.wants(DiagnosticKind::LockWait))); + for (sink, instance) in [(&a, 1_u64), (&b, 2)] { + sink.report(&Diagnostic::LockWaitBegan { + instance, + lock: "engine", + id: minted(), + }); + } + let recorded = session.stop().expect("stopped"); + let schema = schema_of_id(LOCK_WAIT_BEGAN).expect("a lock wait's begin"); + let instances: Vec = recorded + .iter() + .map(|r| decode(schema, &r.data)[0].clone()) + .collect(); + assert_eq!(instances, [Value::UInt64(1), Value::UInt64(2)]); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn threads_report_through_one_sink_and_every_event_is_recorded() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + std::thread::scope(|scope| { + for t in 0..4_u64 { + let sink = &sink; + scope.spawn(move || { + for _ in 0..25_u64 { + sink.report(&Diagnostic::LockWaitBegan { + instance: t, + lock: "engine", + id: minted(), + }); + } + }); + } + }); + let schema = schema_of_id(LOCK_WAIT_BEGAN).expect("a lock wait's begin"); + let seen: Vec<(u64, u64)> = session + .stop() + .expect("stopped") + .iter() + .map(|r| { + let values = decode(schema, &r.data); + match (&values[0], &values[2]) { + (Value::UInt64(t), Value::UInt64(pair)) => (*t, *pair), + other => panic!("{other:?}"), + } + }) + .collect(); + assert_eq!(seen.len(), 100); + for t in 0..4 { + assert_eq!( + seen.iter().filter(|(of, _)| *of == t).count(), + 25, + "thread {t}" + ); + } + let pairs: std::collections::HashSet = seen.iter().map(|(_, pair)| *pair).collect(); + assert_eq!(pairs.len(), 100, "every pair id distinct"); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_zero_in_text_ends_it_there_and_leaves_the_fields_after_it_where_they_were() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + // A zero in a field that is not the last: what follows it in the data would be read as the next fields if it were written. + let id = minted(); + sink.report(&Diagnostic::LockWaitBegan { + instance: 7, + lock: "en\0gine", + id, + }); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x\0y", + detail: format_args!("ab\0cd"), + }); + let recorded = session.stop().expect("stopped"); + assert_eq!(recorded.len(), 2, "{recorded:?}"); + let lock = schema_of_id(recorded[0].descriptor.id).expect("a known event"); + assert_eq!( + decode(lock, &recorded[0].data), + [ + Value::UInt64(7), + Value::Text("en".encode_utf16().collect()), + Value::UInt64(id.get()) + ], + "the pair id after the cut text is the pair id" + ); + let inconsistency = schema_of_id(recorded[1].descriptor.id).expect("a known event"); + assert_eq!( + decode(inconsistency, &recorded[1].data), + [ + Value::UInt64(1), + Value::Text("x".encode_utf16().collect()), + Value::Text("ab".encode_utf16().collect()) + ] + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_event_too_large_for_the_session_is_dropped_without_a_failure_and_the_next_is_recorded() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::Inconsistency))); + let huge = "x".repeat(70_000); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("{huge}"), + }); + sink.report(&Diagnostic::LockWaitBegan { + instance: 9, + lock: "engine", + id: minted(), + }); + let event = only(session.stop().expect("stopped")); + assert_eq!(event.descriptor.id, LOCK_WAIT_BEGAN); +} + +/// The activity id the platform says is no activity. +const NO_ACTIVITY: Guid = Guid::from_u128(0); + +fn lock_wait(edge_began: bool, instance: u64, id: CorrelationId) -> Diagnostic<'static> { + if edge_began { + Diagnostic::LockWaitBegan { + instance, + lock: "engine", + id, + } + } else { + Diagnostic::LockWaitEnded { + instance, + lock: "engine", + id, + } + } +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_pairs_begin_and_end_written_on_two_threads_are_one_activity_created_for_it() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + let id = minted(); + std::thread::scope(|scope| { + scope + .spawn(|| sink.report(&lock_wait(true, 1, id))) + .join() + .expect("the begin's thread"); + scope + .spawn(|| sink.report(&lock_wait(false, 1, id))) + .join() + .expect("the end's thread"); + }); + let recorded = session.stop().expect("stopped"); + assert_eq!(recorded.len(), 2, "{recorded:?}"); + assert_eq!( + recorded + .iter() + .map(|r| r.descriptor.opcode) + .collect::>(), + [1, 2], + "a Start and a Stop" + ); + assert_ne!(recorded[0].activity, NO_ACTIVITY); + assert_eq!(recorded[0].activity, recorded[1].activity); + assert!( + recorded.iter().all(|r| r.related_activity.is_none()), + "{recorded:?}" + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn two_pairs_are_two_activities_however_they_interleave() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + let (a, b) = (minted(), minted()); + for event in [ + lock_wait(true, 1, a), + lock_wait(true, 2, b), + lock_wait(false, 1, a), + lock_wait(false, 2, b), + ] { + sink.report(&event); + } + let recorded = session.stop().expect("stopped"); + let activities: Vec = recorded.iter().map(|r| r.activity).collect(); + assert_eq!(activities.len(), 4, "{recorded:?}"); + assert_eq!(activities[0], activities[2]); + assert_eq!(activities[1], activities[3]); + assert_ne!(activities[0], activities[1]); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn two_sinks_writing_one_pair_write_it_in_one_activity() { + let _serial = serial(); + let session = listening(); + let (a, b) = (EtwDiagnostics::new(), EtwDiagnostics::new()); + assert!(becomes(|| a.wants(DiagnosticKind::LockWait))); + let id = minted(); + for began in [true, false] { + a.report(&lock_wait(began, 1, id)); + b.report(&lock_wait(began, 1, id)); + } + let recorded = session.stop().expect("stopped"); + assert_eq!(recorded.len(), 4, "{recorded:?}"); + let first = recorded[0].activity; + assert_ne!(first, NO_ACTIVITY); + assert!(recorded.iter().all(|r| r.activity == first), "{recorded:?}"); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_end_whose_begin_was_not_written_is_an_activity_of_its_own() { + let _serial = serial(); + let sink = EtwDiagnostics::new(); + let id = minted(); + // No session yet, so the begin is not written. + sink.report(&lock_wait(true, 1, id)); + let session = listening(); + assert!(becomes(|| sink.wants(DiagnosticKind::LockWait))); + let other = minted(); + sink.report(&lock_wait(false, 1, id)); + sink.report(&lock_wait(true, 2, other)); + sink.report(&lock_wait(false, 2, other)); + let recorded = session.stop().expect("stopped"); + // A sink does not expect its begins and ends to match (DI-D-96), so the end is written, and in an activity no other event shares. + assert_eq!(recorded.len(), 3, "{recorded:?}"); + assert_ne!(recorded[0].activity, NO_ACTIVITY); + assert_ne!(recorded[0].activity, recorded[1].activity); + assert_eq!(recorded[1].activity, recorded[2].activity); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_pair_ended_through_a_fan_out_after_its_session_stopped_is_let_go() { + let _serial = serial(); + let first = listening(); + let fan = FanOutDiagnostics::new(vec![Arc::new(EtwDiagnostics::new())]); + assert!(becomes(|| fan.wants(DiagnosticKind::LockWait))); + let id = minted(); + fan.report(&lock_wait(true, 1, id)); + let begun = only(first.stop().expect("stopped")); + assert_ne!(begun.activity, NO_ACTIVITY); + assert!( + becomes(|| !fan.wants(DiagnosticKind::LockWait)), + "no session listens now" + ); + // The fan-out is given the end and passes it on whatever the sink wants, so the sink lets the pair go. + fan.report(&lock_wait(false, 1, id)); + + // An end of that pair that a later session sees is then an end with no begin: in an activity of its own. Had the pair been + // held, it would be written in the begin's. + let second = listening(); + assert!(becomes(|| fan.wants(DiagnosticKind::LockWait))); + fan.report(&lock_wait(false, 1, id)); + let ended = only(second.stop().expect("stopped")); + assert_ne!(ended.activity, NO_ACTIVITY); + assert_ne!( + ended.activity, begun.activity, + "the pair was let go at the end the session did not record" + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_event_that_is_not_a_pair_is_written_in_the_threads_activity() { + let _serial = serial(); + let session = listening(); + let sink = EtwDiagnostics::new(); + assert!(becomes(|| sink.wants(DiagnosticKind::Inconsistency))); + let id = minted(); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "a test", + detail: format_args!("none"), + }); + sink.report(&lock_wait(true, 1, id)); + sink.report(&lock_wait(false, 1, id)); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "a test", + detail: format_args!("none"), + }); + let recorded = session.stop().expect("stopped"); + assert_eq!(recorded.len(), 4, "{recorded:?}"); + assert_eq!( + recorded[0].activity, recorded[3].activity, + "the thread's, unchanged" + ); + assert_ne!(recorded[0].activity, recorded[1].activity); +} diff --git a/crates/durable-ioring/API.md b/crates/durable-ioring/API.md index 506e6100b..db62aca41 100644 --- a/crates/durable-ioring/API.md +++ b/crates/durable-ioring/API.md @@ -292,8 +292,20 @@ rules. providers**. The built-in default flushes each file through the instance's own ring and is the only code that touches it. `Setup::provider` may add **one** consumer `DurabilityProvider`, which states the flush domains it serves when the instance is built (a domain named twice refuses construction); -everything else, files declared with no domains included, is the default's. Several providers are -composed by a router dioring ships, itself a provider (DI-3.8). +everything else, files declared with no domains included, is the default's. + +Several providers are composed by `RoutingProvider`, which dioring ships and which is itself a +provider ([DI-D-81](DESIGN-NOTES.md#di-d-81), [DI-D-82](DESIGN-NOTES.md#di-d-82), +[DI-D-83](DESIGN-NOTES.md#di-d-83)). `RoutingProvider::builder()` takes providers by `register`, and +`build` reads each one's `domains()` once: each serves exactly the domains it declares, the router +serves their union, and a domain claimed twice refuses the build with `RoutedTwice`, naming both +registrations. Each seal's request is split by domain, and each provider with a part is called once, +in registration order, with its completions as they came, so each answer reaches the instance +directly. A file whose domains span providers is in each one's call and is durable when every one +has succeeded; coupled domains split across providers are correct but each costs its provider a +flush, since the router merges nothing across them -- documented, not enforced. A panic in one +provider propagates and abandons the domains of those not yet called; a domain no provider serves +is a caller breaking the protocol, and is panicked on. Once every write a seal covers has completed, dioring hands the provider one `FlushRequest`: per domain it serves, the files (`FileKey`, `SharedHandle`) and the `OpId`s named for each -- identities, @@ -1182,6 +1194,38 @@ impl DomainCompletion { } } +/// Several providers as one (DI-D-81 to DI-D-83): each serves exactly the domains it declares, +/// read once at `build`; the router serves their union and dispatches each seal's request by domain. +pub struct RoutingProvider { /* the registrations and their domains */ } + +impl RoutingProvider { + pub fn builder() -> RouterBuilder { + todo!() + } +} + +impl DurabilityProvider for RoutingProvider { /* as section 15 says */ } + +pub struct RouterBuilder { /* the providers registered so far */ } + +impl RouterBuilder { + pub fn register(self, provider: impl DurabilityProvider + 'static) -> Self { + todo!() + } + + /// Refuses a domain claimed twice, by two registrations or twice by one. + pub fn build(self) -> Result, RoutedTwice> { + todo!() + } +} + +/// The domain, and the two registrations that claimed it, numbered from 0 in registration order. +pub struct RoutedTwice { + pub domain: FlushDomain, + pub first: usize, + pub second: usize, +} + // --------------------------------------------------------------------------------------------- // dioring: the implementation over `windows-ioring-sys`. // --------------------------------------------------------------------------------------------- diff --git a/crates/durable-ioring/CHECKLIST.md b/crates/durable-ioring/CHECKLIST.md index bc2067d72..589a4fd9e 100644 --- a/crates/durable-ioring/CHECKLIST.md +++ b/crates/durable-ioring/CHECKLIST.md @@ -9,453 +9,38 @@ DI-M3, which closed on 2026-10-11 with its open work restructured into DI-M7 to because its items had nested six levels deep; the table from its old IDs to the new ones is in [the archive](COMPLETED-CHECKLIST.md#di-m3-restructured), and older records still cite the old IDs. The new milestones are numbered after the parked DI-M5+ and DI-M6+, which are cited too widely to -renumber. The flush-failure spike (DI-M4) and the reviews (DI-M9) run alongside and gate nothing. Headings marked `+` are parked behind the milestone they name, per the workspace's `M{n}+` -convention. - -## Where the open-surface work goes next (read this first when picking the work back up) - -The open surface of a file ([DI-M13](#di-m13----opening-a-file-the-surface-and-its-home) to -[DI-M15](#di-m15----opening-by-identity-and-reopening)) cannot be done in this crate alone: it -changes four others. This is the order across all of them. **No status is kept here**, because each -item's own checkbox is the status and a copy would go stale; this list is only the order and the -links. Each item below also ends with a `Next:` line pointing at the step after it. - -**Step 0, added 2026-10-15, before step 1** (the engineer ruled that volume operations belong in the -volume crate and that the error codes become one shared type; see -[WV-D-5](../win-volume-sys/DESIGN-NOTES.md#wv-d-5) and -[WE-D-1](../win-error-codes/DESIGN-NOTES.md#we-d-1)): - -- 0.1. [WE-1.1 to WE-1.3](../win-error-codes/CHECKLIST.md) -- the new `win-error-codes` crate. -- 0.2. In any order, each needing only `WE-1.1`: - [NR-4.1](../windows-namespace-request-sys/CHECKLIST.md) (request crate adopts it), - [ERR-1](../windows-file-enumeration-sys/CHECKLIST.md) (enumeration crate adopts it), and - [WV-2.1 to WV-2.5](../win-volume-sys/CHECKLIST.md) (the volume crate takes in the storage-locality - entries, in that order). -- 0.3. [NR-2.8](../windows-namespace-request-sys/CHECKLIST.md) -- the request crate deletes the - originals. **It must land before step 4's release**, or the entries are published from the crate - they do not belong in. `NR-4.1` should land before it too, so that release carries the - `io::Error` fix. - -Steps 1 to 3 do not depend on step 0 and may be done first if the order suits; step 4 does. - -1. [NR-3.3](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: the captured root and the - graft, as one internal type. Additive; nothing breaks. -2. [NR-3.4](../windows-namespace-request-sys/CHECKLIST.md), - [NR-3.5](../windows-namespace-request-sys/CHECKLIST.md) and - [NR-3.6](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: **one coupled breaking - commit.** `OpenFile` and `WatchDirectory` take a `Wtf16String`, an interior NUL is refused, - `prepare` is retired. `win-durable-ioring` and `windows-platform-probes` call `prepare` through - path dependencies, so **the same commit adapts both just enough to build**, and the request - crate's own tests and docs; a commit that leaves the workspace unbuilt is the worse outcome. -3. [NR-3.1](../windows-namespace-request-sys/CHECKLIST.md) and - [NR-3.2](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: the SDDL constructor and - the `ReOpenFile` entry. Additive, independent of steps 1 and 2; may be done first. -4. **Release the request crate** (the release-please pull request, then publish). A breaking change, - so a new minor. This is a release action and not a checklist item. -5. After step 2, in any order: [DI-13.1's parked questions](#di-131-open-questions) are yours to - rule on (no code waits on you to ask them, but [DI-13.7](#di-137-rework) and `DI-13.3` onward - wait on the answers). -6. After step 4: [PATH-1](../windows-file-enumeration-sys/CHECKLIST.md), the enumeration crate - consumes the new contract and deletes its older copy (the workspace's `M26+.3`, and - [REVIEW-1](../windows-file-enumeration-sys/CHECKLIST.md) is re-read); `DI-13.5` and `DI-13.6`, this - crate takes the release that carries the SDDL constructor and the `ReOpenFile` entry. -7. Then, here: `DI-13.7`, `DI-13.1`, `DI-13.3`, `DI-13.4`, `DI-M14`, `DI-M15`, and `DI-10.3` last. - -If you remember only one thing: **the request crate comes first, and its breaking commit moves three -crates at once.** - -## DI-M9 -- Reviews: the engineer confirms or amends the assistant's choices (ungated; gates nothing) - -Each item is one choice the assistant made while building and the engineer has not yet confirmed. -The code binds to each already, because the steps could not be built without choosing; what is open -is the review. These reviews gate no other work (the engineer, 2026-10-10: "I cannot be the blocking -factor"). Where the engineer had already decided part of a row, that part is not here. A rejected -point is reworked as an item of the milestone current when it is rejected, with its decision row -amended. Every item carries one of three tags, which say what is asked: - -- **DECIDE** -- the choice changes what a consumer can do or must avoid, or trades against a real - alternative. The action is to accept it or amend it; "accept 9.1.1" is a complete answer. Each says - what the code does now. -- **RATIFY** -- a name, a placement, an internal shape or a test method. It stands unless the - engineer objects; "ratify 9.3.3" closes it. -- **MINE** -- not a choice: a gap or check the assistant owes. Nothing is asked of the engineer. - -A group may be answered whole ("accept all DECIDE in 9.1"). The wording of each choice as first -written, which these items condense, is in git at the revision before the one that restructured this -milestone; the decision rows named below carry the reasons. - -### DI-9.1 -- Lineage handles ([DI-D-42](DESIGN-NOTES.md#di-d-42), from `DI-3.2.5.1`) - -- [x] **DI-9.1.1** -- A lineage handle's release runs on whichever thread drops the last copy. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-911) - -- [x] **DI-9.1.2** -- An ended lineage is not live from the moment it ends. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-912) - -- [x] **DI-9.1.3** -- Heals take effect as soon as nothing can delay them. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-913) - -- [x] **DI-9.1.4** -- The oracle drops DI-D-39's tightening, and gains four rules for an ended lineage. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-914) - -- [x] **DI-9.1.5** -- The lineage handles' shapes. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-915) - -### DI-9.2 -- Gates and seals ([DI-D-43](DESIGN-NOTES.md#di-d-43), from `DI-3.2.5.2`; the refusal of a seal that closes a cycle is the engineer's own) - -- [x] **DI-9.2.1** -- A held write whose own epoch is abandoned ends as `NeverIssued` naming that epoch. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-921) - -- [x] **DI-9.2.2** -- A held write is outstanding for its lineage's seals, and no failure suspects it until it is released. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-922) - -- [x] **DI-9.2.3** -- `make_durable_through` returns `SealError::{UnknownLineage, GateCycle}` in place of `io::Error`. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-923) - -- [x] **DI-9.2.4** -- The gate and seal shapes, and the oracle's gate rules. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-924) - -### DI-9.3 -- The provider ([DI-D-44](DESIGN-NOTES.md#di-d-44), from `DI-3.2.6`; read it with its amendments by DI-D-46 and DI-D-50) - -- [x] **DI-9.3.1** -- The provider is called only from the consumer's next call, with dioring's lock released. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-931) - -- [x] **DI-9.3.2** -- Every provider request is built before any is handed over. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-932) - -- [x] **DI-9.3.3** -- The provider request's shape, and when a write is covered. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-933) - -### DI-9.4 -- Faults, diagnostics, the clock ([DI-3.10.3.1 to DI-3.10.3.4](COMPLETED-CHECKLIST.md)) - -- [x] **DI-9.4.1** -- On Windows the default diagnostic sink is `NoDiagnostics`, until `DI-12.1`. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-941) - -- [x] **DI-9.4.2** -- A diagnostic sink is called with dioring's lock held. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-942) - -- [x] **DI-9.4.3** -- Two `IoFault`s are equal when they name the same source and code and share the one report. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-943) - -- [x] **DI-9.4.4** -- The fault, diagnostic and clock renames and shapes. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-944) - -### DI-9.5 -- The backend trait and the core on it ([DI-3.10.3.5.1 to DI-3.10.3.5.5](COMPLETED-CHECKLIST.md)) - -- [x] **DI-9.5.1** -- Registered buffers are given at construction only. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-951) - -- [x] **DI-9.5.2** -- What a Windows commit and a target claim by default. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-952) - -- [x] **DI-9.5.3** -- The backend trait's surface. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-953) - -- [x] **DI-9.5.4** -- The in-memory backend. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-954) - -- [x] **DI-9.5.5** -- The core on the backend trait. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-955) - -- [x] **DI-9.5.6** -- The Windows backend's mapping. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-956) - -### DI-9.6 -- The driver on the core ([DI-3.10.3.5.6.1 to DI-3.10.3.5.6.2.6](COMPLETED-CHECKLIST.md)) - -- [x] **DI-9.6.1** -- What the backend refuses for good, the core ends ([DI-D-54](DESIGN-NOTES.md#di-d-54)). -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-961) - -- [x] **DI-9.6.2** -- `Dioring` requires `R: Send + 'static`. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-962) - -- [x] **DI-9.6.3** -- A lineage's last-handle release reports a core refusal as not live, and ignores the default lineage's. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-963) - -- [x] **DI-9.6.4** -- A declared geometry is now enforced on the driver's path. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-964) - -- [x] **DI-9.6.5** -- The driver's structure. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-965) - -- [x] **DI-9.6.6** -- The trait and type shapes. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-966) - -- [x] **DI-9.6.7** -- The sabotage migration. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-967) - -### DI-9.7 -- From DI-M7 on (each later step adds its own here as `DI-9.7.n`, with a tag) - -- [x] **DI-9.7.1** -- `pump` on a pool-built instance is a runtime `Unsupported`, not a type error. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-971) - -- [x] **DI-9.7.2** -- `completion_signal` hands out a full-access duplicate of dioring's own auto-reset event. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-972) - -- [x] **DI-9.7.3** -- An allocation that fails is an error the caller receives, not a panic or an abort. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-973) - -- [x] **DI-9.7.4** -- `NumaBuffer`'s buffer impls move from the ring crate to `win-numa-sys`. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-974) - -- [x] **DI-9.7.5** -- The portable crate's public surface for other drivers. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-975) - -- [x] **DI-9.7.6** -- The renames the split forced, and the clock's bounds. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-976) - -- [x] **DI-9.7.7** -- Delivery modes. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-977) - -- [x] **DI-9.7.8** -- Construction, the fault responder, and their tests. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-978) - -- [x] **DI-9.7.9** -- The example and the sibling crates. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-979) - -- [x] **DI-9.7.10** -- The buffer crate and the core's tests. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-9710) - -- [x] **DI-9.7.11** -- DECIDED (c): `ErrorCode`'s `from_win32` and `win32` move out of the portable core into `win-durable-ioring`, built on `HResult`. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#di-9711) - -### DI-9.8 -- Owed by the assistant - -- [x] **DI-9.8.1** -- Each declared blind spot is covered, or records what would cover it. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-981) - -- [x] **DI-9.8.2** -- The portable CI job's first run is read: the crate's tests run on Linux and macOS. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#di-982) - -## DI-M7+ -- Delivery follow-ups - -- [x] **DI-7+.1** -- A wait-only handle on the ring's own completion event, offered by a pumped instance as `Dioring::completion_event`. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-7+1) - -- [x] **DI-7+.2** -- The engine rings the doorbell, a backend's push only queues, and a failed doorbell or an unreadable ring reaches the consumer once, with no timer. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#di-7+2) - -- [x] **DI-7+.3** -- The Windows backend's ring is an owned one: its pop has no case of a completion the ring held nothing for, and the panic and error rule is recorded. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#di-7+3) - -- [x] **DI-7+.4** -- Model A's handler is told when the instance's pop fails, and the in-memory backend refuses what it cannot hold. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#di-7+4) - -## DI-M10 -- Target backings - -- [x] **DI-10.1** -- A Win32 file target is made from a file this crate opened, so its class, alignment and write-through follow from how it was opened and not from a declaration. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#di-101) - -- [x] **DI-10.2** -- A raw volume is a file target made from a volume this crate opened, with its disks found and never `Complete`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#di-102) - -- [x] **DI-10.4** -- A whole physical disk is a file target made from a disk this crate opened by number, its own flush domain and never `Complete`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#di-104) - -`DI-10.3`, whether a handle opened elsewhere can be given to a file target, is parked behind -`DI-M15` as `DI-M15+` below; it kept its ID because [DI-D-60](DESIGN-NOTES.md#di-d-60), -[DI-D-61](DESIGN-NOTES.md#di-d-61) and [DI-D-62](DESIGN-NOTES.md#di-d-62) cite it. - -## DI-M13 -- Opening a file: the surface and its home - -Why this milestone exists. [DI-D-60](DESIGN-NOTES.md#di-d-60) made a file target from a file this -crate opened and from nothing else, and [`FileOptions`](../win-durable-ioring/src/windows_backend/opened_file.rs) -carries only `read`, `write`, `create`, `create_new` and `truncate`. A consumer whose open needs more -(a share mode, a security descriptor, delete-on-close, an open by file ID) cannot say so, and would -want a handle from elsewhere ([DI-10.3](#di-m15----handles-opened-elsewhere-parked-behind-di-m15)). -Widening what this crate can open is the structural answer: every option a consumer can pass is an -option recorded by construction. The rule throughout is documented APIs only -([copilot-instructions.md](../../.github/copilot-instructions.md)): no `Nt*`, no `Rtl*`. - -- [ ] **DI-13.1** -- **The surface, written down before any of it is built.** One decision in - [DESIGN-NOTES.md](DESIGN-NOTES.md), with a table over every parameter of the documented open - calls -- `CreateFileW`, which the open goes through ([DI-D-63](DESIGN-NOTES.md#di-d-63)), and - `CreateFile2`, which has the same parameters, `OpenFileById`, `ReOpenFile` -- saying for each - whether it is exposed (and as which type) or excluded (and the analytic reason, per the option - integrity rule: it computes the wrong answer, not that no consumer is in sight). Each row also says - what the option does to a target's claims: class, alignment, write-through, offsets. Rulings by the - engineer on 2026-10-15, to be recorded in the table with their reasons. **The principle: this - crate reflects Win32, not the Rust standard library.** A disposition, a share mode and an access - right are the call's own, mapped into structured Rust types and not a word of flags to OR, and no - default or combination check is added that Win32 does not make. **Paths are opaque:** a - `Wtf16String` passed through untouched for Win32 to interpret (`\\?\`, long paths where enabled, - `/` against `\`); the one possible exception is a path whose open can block without being - cancellable, which is to be identified before anything is refused. **Excluded:** append-only access - (it does not fit this crate's use pattern, and as the assistant reads the documentation - `FILE_APPEND_DATA` without `FILE_WRITE_DATA` lands a write at end of file whatever offset it names, - which the core addresses by, so it would compute the wrong answer); `CreateFileTransacted` (a - transacted write is durable at the transaction's commit, so the epoch's warranty would mean - nothing); `hTemplateFile`; `FILE_ATTRIBUTE_ENCRYPTED` (it brings a set of certificate problems this - crate does not take on); the security quality-of-service flags, `SECURITY_SQOS_PRESENT` and what - it enables. **Not in v1, each a v1 ruling and not a rejection:** handle-level - `FILE_FLAG_WRITE_THROUGH` (what it can warrant is unclear); inheritable handles (no call this - crate makes chooses inheritance, and that is not a structural restriction on the request crate); - `FILE_FLAG_BACKUP_SEMANTICS` (there is no reason against it, it serves backup and restore - applications); `FILE_FLAG_SESSION_AWARE` (the session-specific part of the device namespace is a - small interest and hard to test); `FILE_FLAG_OVERLAPPED`, unless the ring turns out to require it. - **Exposed:** `FILE_FLAG_OPEN_REPARSE_POINT` (if the caller wants to follow a reparse point, so be - it); access as the table lists it, passed through; every share mode; every creation disposition - `CreateFileW` has, `CREATE_ALWAYS` included. None of the "not in v1" rulings schedules work; - a consumer or a measurement reopens each, not this checklist. - - **Open, parked 2026-10-15 while the path contract is settled in - [NR-3.3](../windows-namespace-request-sys/CHECKLIST.md)** (the engineer: "I want to work through - this before we continue on to subsequent items"). These are questions, not decisions, and the - table waits on them. (a) The public signature for a path: `&Wtf16Str` only, or anything that - converts to one, and whether an empty path is passed to Win32 or refused. (b) The remote-device - check: with paths opaque the pre-open check, which uses `fs::canonicalize` and so itself opens the - path, cannot stay; the check moves onto the opened handle (`DI-14.5`), and an unbuffered create on - a remote path then creates the file before it is refused, unless the engineer names a path-based - check as the one exception. (c) Which paths can block an open without being cancellable, from - documentation and not recollection: dead remote shares and oplock breaks are the candidates; the - answer decides between documenting that `open` blocks (and that it can be run on a worker) and - refusing specific forms. (d) What an `OpenedFile` claims when its path names a device, which gets - a file's default geometry and no flush domains; whether a documented handle query can tell a - raw disk from a file is unmeasured. (e) The shape of the structured types: the workspace's - `CaptureSet` pattern (a typed set with named constants, `union` and `contains`) for rights, share - mode and attributes, an enum for the disposition, builder methods for flags, and whether - `create`, `create_new` and `truncate` go entirely (a breaking change to a 0.1.0 crate). (f) Cache - hints on an unbuffered file: pass and document, in place of refusing. (g) Whether to run an - evidence-only test of the ring on an overlapped handle, without exposing the flag. Standing unless - the engineer objects: our calls pass a non-inheritable handle explicitly; - `FILE_ATTRIBUTE_TEMPORARY`, the object-ID kind of `OpenFileById` and the four lesser access rights - (`ACCESS_SYSTEM_SECURITY`, `FILE_EXECUTE`, `SYNCHRONIZE`, extended attributes) are exposed; - `FILE_FLAG_FIRST_PIPE_INSTANCE` is excluded, for it is for pipes. **Superseded by these - rulings and still to be reworked:** commit `9b45563f` and [DI-D-63](DESIGN-NOTES.md#di-d-63) - made the open match std (create plus truncate as `OPEN_ALWAYS` and an emptying, an automatic - `FILE_FLAG_OPEN_REPARSE_POINT` on a new file, long paths made verbatim, std's combination checks); - none of that is Win32's, so the rework is `DI-13.7`, which waits on the questions above. - -- [x] **DI-13.2** -- A file is opened through `windows-namespace-request-sys`, so this crate has no `unsafe` of its own for it and `std::fs::OpenOptions` is no longer the opener. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#di-132) - -- [ ] **DI-13.3** -- **Access as named rights, not a read/write pair.** A typed set over the - documented file access rights the table in `DI-13.1` exposes (data read and write; attributes; - `DELETE`; the security rights; append-only if ruled in), with no raw `u32`. A target needs a data - right, which is checked when the file is opened and not when the first operation fails. Tests: each - right opens and is exercised; a right the file's security refuses reaches the caller as the - kernel's error and not as a panic. - -- [ ] **DI-13.4** -- **Share mode as a typed set**, with today's all-three default kept and an - exclusive open (no sharing) expressible, since a database that owns its file wants it. A sharing - violation is a typed error that names which side conflicted where the kernel says. Tests with two - opens: each combination the table lists is accepted or refused as `CreateFileW` documents. - -- [ ] **DI-13.5** -- **`SecurityDescriptor` from SDDL, taken from `windows-namespace-request-sys`.** - That crate has no constructor from text; [NR-3.1](../windows-namespace-request-sys/CHECKLIST.md) - adds one. This item is done when this crate depends on the release that carries it. `DI-14.3` - needs it. **Next:** `DI-13.6`. - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/windows-namespace-request-sys` - > -> `NR-3` -> `NR-3.1` (`SecurityDescriptor` from SDDL). See - > [CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md). - -- [ ] **DI-13.6** -- **A `ReOpenFile` entry, taken from `windows-namespace-request-sys`.** That crate - has no entry for it; [NR-3.2](../windows-namespace-request-sys/CHECKLIST.md) adds one. This item is - done when this crate depends on the release that carries it. `DI-15.4` needs it. - **Next:** `DI-13.7`, then `DI-13.1`. - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/windows-namespace-request-sys` - > -> `NR-3` -> `NR-3.2` (`ReOpenFile` entry). See - > [CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md). - -- [ ] **DI-13.7** -- **Rework `OpenedFile::open` to reflect Win32, and amend `DI-D-63`.** - Commit `9b45563f` and [DI-D-63](DESIGN-NOTES.md#di-d-63) made the open match `std::fs::OpenOptions` - (create plus truncate as `OPEN_ALWAYS` and an emptying, an automatic - `FILE_FLAG_OPEN_REPARSE_POINT` on a new file, a path longer than `MAX_PATH` made verbatim, std's - combination checks, and the std-shaped `create`, `create_new` and `truncate`). The engineer's ruling - is that this crate reflects Win32 and not std, so all of that goes: the path is handed to `OpenFile` - as the caller gave it, a `Wtf16String`, with no conversion here (the request crate grafts or passes - it, [NR-3.3](../windows-namespace-request-sys/CHECKLIST.md)); the disposition is Win32's own, as a - Rust enum, in place of the booleans; combinations the kernel accepts or refuses are the kernel's - to answer; `FILE_FLAG_OPEN_REPARSE_POINT` is an explicit option. Four of the five sabotage entries - added with `DI-13.2` pin the std behaviours and are replaced; `DI-D-63`'s "a caller sees no - difference" principle is amended and its list of differences rewritten. Waits on the request - crate's coupled commit (step 2 of the order at the top of this file), which adapts this crate only - enough to build, and on the answers to the parked questions in `DI-13.1` (a), (e) and (f). - **Next:** `DI-13.1` (the table is written from the answers), then `DI-13.3`. - -## DI-M14 -- Opening a file: flags, attributes, security - -- [ ] **DI-14.1** -- **Cache hints.** `FILE_FLAG_RANDOM_ACCESS` and `FILE_FLAG_SEQUENTIAL_SCAN`, - each with what `DI-13.1` ruled it warrants. A hint that only means something to a cached file is - not offered to an unbuffered one, by type where the buffering mode is in the type and by a typed - error where it is not. Handle-level `FILE_FLAG_WRITE_THROUGH` is not offered in v1 by the - engineer's ruling (`DI-13.1`); the per-write flag is unaffected, and is known to fail with error - 509 on a cached handle even one opened with that flag (a kept test). - -- [ ] **DI-14.2** -- **Namespace and lifetime flags.** `FILE_FLAG_DELETE_ON_CLOSE`, - `FILE_FLAG_OPEN_REPARSE_POINT`, `FILE_FLAG_OPEN_NO_RECALL` and `FILE_FLAG_POSIX_SEMANTICS`, plus - `FILE_FLAG_OVERLAPPED` if the ring turns out to need or refuse it (settled by a test on the ring, - not by recollection). The remote-device check in `DI-14.5` must see a reparse point's target, not - the reparse point. Tests: delete-on-close removes the file when the last handle closes; a reparse - point opened with the flag is the point and without it is the target. - -- [ ] **DI-14.3** -- **Creation attributes, security descriptor, inheritance.** The file attributes a - new file can be created with (hidden, read-only, temporary, encrypted and the others the table - lists), as a typed set. A security descriptor built from SDDL through the constructor `DI-13.5` - brings, owned and freed by it, so a caller never holds a raw pointer. Handles are never - inheritable in v1 (the engineer's ruling, `DI-13.1`): an inheritable handle reaches child processes - that can write through it, so there is no option to set. Security quality-of-service flags where - the table keeps them. Tests: a created file carries the attributes and the descriptor asked for, - read back with a documented query; the handle is not inheritable, read back with - `GetHandleInformation`. - -- [ ] **DI-14.4** -- **Combinations are refused where the kernel refuses them.** Every pair the table - marks as invalid or silently ignored is tested on this machine and is either refused with a typed - error before the file is created, or documented as ignored with the test that shows it. A - combination refused only after a file was created is the worse outcome, so the refusal is ahead of - the open wherever the table lets it be. - -- [ ] **DI-14.5** -- **The remote-device check is made on the opened handle, not on the path.** - [DI-D-60](DESIGN-NOTES.md#di-d-60) records a window between canonicalising the path and opening it, - in which a link can be swapped. A documented query of the handle closes it, and it is also what an - open by ID needs, since it has no path. The assistant must say which documented handle queries it - checked and which it chose. A file created on a remote device before the check can refuse it - leaves a file behind, which is why the path check stays for a create and the handle check is the - authority for every open. - -- [ ] **DI-14.6** -- **What an open can reach that is not a disk file is measured, and what this - crate does about it is decided from the measurements.** The request crate passes every path to - `CreateFileW` and is policy-free ([D-19](../windows-namespace-request-sys/DESIGN-NOTES.md#d-19) - records what was measured there). A device reached by name is not stopped by the graft rules: a - `\\.\` path is passed through untouched, and `COM10` and above need that prefix. Measured on one - machine, a read on an opened serial port (`COM3`) was still blocked after 8 seconds, while `NUL` - returned end of file and accepted writes that go nowhere. The work: query the opened handle's type - (`GetFileType` is the candidate; the assistant has not tried it) and measure what it says for a - disk file, a directory, `NUL`, a console, a serial port, a named pipe and a raw volume; whether - `CancelIoEx` or `CancelSynchronousIo` can interrupt the serial read; and what `FlushFileBuffers` does - on `NUL`. Then decide, with the engineer, what an open of a handle that is not a disk file does: it - is refused, or classified and returned as such. That is a working position, not a decision, until - the measurements exist. - -## DI-M15 -- Opening by identity, and reopening - -- [ ] **DI-15.1** -- **A file's identity, read from an opened file.** A `FileId` type over the - documented identity query (the 128-bit form that also covers 64-bit file systems), and the volume it - belongs to. Which of the identifier kinds `FILE_ID_DESCRIPTOR` accepts are offered (the file ID, - the extended 128-bit ID, the legacy object ID) is a row in `DI-13.1`'s table. Tests: the ID of an - opened file is stable across two opens and differs between two files. - -- [ ] **DI-15.2** -- **`OpenedFile::open_by_id`.** An open by `FileId` through `OpenFileById`, with a - volume hint that must be a file this crate opened or a volume it opened, so the hint is - provenance-checked like everything else. It takes the full typed set from `DI-M13` and `DI-M14`. The - remote check runs on the hint's device (`DI-14.5`). Tests: round trip of a file opened by path and - again by ID; an ID from a directory listing (`FileIdExtdDirectoryInfo`) opens by ID; a stale ID is - the kernel's error. - -- [ ] **DI-15.3** -- **Opening relative to a directory handle: confirm there is no documented route, - then record what stands in for it.** The assistant knows of no documented Win32 call that opens a - name relative to a directory handle; the only one it knows is `NtCreateFile` with a root directory, - which is out under the repository's rule. That is a reading to verify against the documentation and - not a finding. If it holds, the decision records it and names the substitute: list the directory, - take the child's ID, open it by ID (`DI-15.2`). If a documented route exists, it is an item here. - -- [ ] **DI-15.4** -- **`OpenedFile::reopen`, through `ReOpenFile`** (the entry is `DI-13.6`). A new `OpenedFile` from an - owned one, changing access, sharing and flags: cached to unbuffered and back, a read-only handle for - a scrubber beside the writer. The target's alignment is recomputed for the new mode, and going - unbuffered runs the remote check. `ReOpenFile` documents that the new access and sharing cannot - conflict with a previous open that is still open; each conflict the table lists is a test, and a - sharing violation reaches the caller as the typed error from `DI-13.4`, not a panic. Documented as - well: the object must have been created by `CreateFile`, which an `OpenedFile` is and an - `OpenedVolume` or `OpenedDisk` may not be, so whether those reopen is a test, not an assumption. - -## DI-M15+ -- Handles opened elsewhere (parked behind DI-M15) - -- [ ] **DI-10.3** -- **Whether a handle opened elsewhere can be given to a file target, and under what attestation, asked of what `DI-M13` to `DI-M15` leave.** Disallowed in this version by the engineer's ruling ([DI-D-60](DESIGN-NOTES.md#di-d-60)): the open mode of a handle cannot be read with a documented call, so the only routes are a consumer's declaration, which can be wrong, or an `unsafe` attestation in the style of `WaitableHandle`'s seam for handles from elsewhere. The blocker is that ruling and not the lack of a consumer. It is parked here because most reasons to want a foreign handle are opens this crate could not express, which the milestones above remove, and what remains is the handle a broker or another library hands over. Decide which route, if either, and what class such a handle may claim: at most `BestEffort` unless the consumer attests more. The same mechanism is how a raw volume or a whole disk could claim `Complete` ([DI-D-61](DESIGN-NOTES.md#di-d-61), [DI-D-62](DESIGN-NOTES.md#di-d-62)): a handle cannot show that nothing else writes the disks, so only an attestation by a consumer that knows could. A wrong attestation is silent, which for a durability layer is the worst failure; that is the argument for `unsafe` over a builder method. - -## DI-M11 -- The core driven by script - -- [ ] **DI-11.1** -- **An in-memory backend whose targets and backend fault on a script** - ([DI-D-50](DESIGN-NOTES.md#di-d-50) P13), public and deterministic, in the portable crate: an - operation failed, held, or completed late or out of order; a submission refused; a full queue; a - short count, to test a backend's short-write policy. It answers the problem that flush failure - cannot be produced on a healthy machine, for consumers' failure paths and not only this crate's, - in the spirit of [RESPONSE-SPACE.md](../windows-ioring-sys/RESPONSE-SPACE.md), and is the basis - for unit testing and driving the core (the engineer: "In-memory is a good basis for unit testing - and driving"). It does not replace DI-4.1. - -- [ ] **DI-11.2** -- **The fault seam's merge-or-delete decision**, once `DI-11.1` is proven: the - `fault-injection` feature's `fail_next_flush`, and the Windows backend's `fail_next_commit` added - beside it in `DI-3.10.3.5.5`, duplicate what the in-memory backend scripts - ([DI-D-50](DESIGN-NOTES.md#di-d-50)), and stay until then under the duplicate-then-decide rule. - -## DI-M12 -- Beside the core: diagnostics, routing, and release - -- [ ] **DI-12.1** -- **Emit the delay events** DI-2.11 designs, as typed events the core reports to - its diagnostic sink, and **the Windows sinks** in `win-durable-ioring`: `EtwDiagnostics`, from a - manifest-based ETW provider that dioring owns and the Windows driver's default, and - `DebugOutputDiagnostics`, through `OutputDebugStringW` ([DI-D-51](DESIGN-NOTES.md#di-d-51)). - Gated on `DI-8.2`, which creates `win-durable-ioring`. Carries what DI-2.11 deliberately left to - implementation: - - **Identity per event** -- what each event names (instance, lineage, epoch id, domain, lock), - "going to depend on the event" (the engineer), and how the generic epoch id is rendered. - - **The time base** is settled: interrupt time, dioring's one time base - ([DI-D-37](DESIGN-NOTES.md#di-d-37)), until a requirement for finer resolution appears. - - **The provider's name and GUID**, and whether it stands alone or joins a workspace scheme. - - **Installing the manifest**: how an application shipping dioring registers it, or documents - that it must. - -- [ ] **DI-12.2** -- **The routing provider** (DI-2.12 point 1): a provider dioring ships that - dispatches each seal's work by flush domain to providers registered with it when it is built, - refuses a domain routed twice at build time, and passes each domain's outcome back as it arrives. - It reports to the instance's diagnostic sink ([DI-D-51](DESIGN-NOTES.md#di-d-51)), so its - diagnostics go wherever the consumer sent the instance's. Tested alone against fake providers. - Gated on the provider trait from DI-2.12. - -- [ ] **DI-12.3** -- **The `epoch_log` merge-or-delete decision**, once DI-3.2 is proven - ([DI-D-7](DESIGN-NOTES.md#di-d-7)). - -- [x] **DI-12.4** -- The five path-only dependencies of `win-durable-ioring` are pinned by version, keeping the path. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#di-124) +renumber. The flush-failure spike (DI-M4) runs alongside and gates nothing. This file holds only work in +scope now; parked work, gated (`M{n}+`), on the horizon or deferred, is in [PARKED.md](PARKED.md). + +## DI-M21 -- A sample log on durable-ioring + +Queued by the engineer, 2026-10-21 ([DI-D-85](DESIGN-NOTES.md#di-d-85)): survey common practice in database and file-system +logs and provide a log built on the durable ring as a sample; once it proves out it is a candidate to promote, which is parked +as [DI-21+.1](PARKED.md#di-m21----promoting-the-sample-log-parked-behind-di-m21). It does not start from the `epoch_log` +sample, which it may cite only incidentally. The engineer: "I don't see it as being important to me personally in the +immediate future" -- a statement of priority, not a deferral. Beyond the first two items this milestone is deliberately +coarse: what the sample is follows from the survey. + +- [ ] **DI-21.1** -- **Survey common practice in database and file-system logs.** Write-ahead logging and its recovery + (redo and undo, log sequence numbers and a page's own LSN, fuzzy checkpoints, compensation records -- ARIES and what + followed it); file-system journaling (physical block journals and their ordering modes, metadata-only journals, a log + with restart areas); and what every log faces: group commit, detecting a torn or partial record, a record's identity + across a crash, reclaiming log space, and how a log and the data it protects are ordered. Each point is cited to its + source, published papers and the systems' own documentation, and recorded as a design session in + [design-sessions/](../../design-sessions/). The survey says what is common to both kinds of log, what differs and why, + and, for each, which `durable-ioring` facility a log built on it would use -- epochs and seals, lineages, gates across + lineages, flush domains, failures and their suspects -- or that none does. + +- [ ] **DI-21.2** -- **Bring the survey to the engineer, to settle the sample's scope.** Which practices the sample + shows, which it names and leaves out, and where it lives -- an example of `win-durable-ioring`, or a portable example of + `durable-ioring` over its in-memory backend that a Windows run also exercises -- are decided there. A gap the survey + finds in `durable-ioring` is queued as work on the crate, not worked around in the sample. + +- [ ] **DI-21.3** -- **Build the sample log** to the scope `DI-21.2` sets, with its own contract written first, tests, + and a run that shows recovery after a crash it stages. + + > **-> UNBLOCKS PARKED WORK:** completing this unblocks `DI-M21+` in + > [PARKED.md](PARKED.md#di-m21----promoting-the-sample-log-parked-behind-di-m21). ## DI-M4 -- Evidence: Windows' flush-failure behaviour (ungated; gates nothing) @@ -472,118 +57,3 @@ option recorded by construction. The rule throughout is documented APIs only Microsoft assigning the handling to the application, and nothing on retention after a failed flush -- was gathered on 2026-10-09 and is in [the design session](../../design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md#what-the-published-record-says-research-2026-10-09). - -## DI-M5+ -- The retention layer (parked behind DI-M12) - -- [ ] **DI-5+.1** -- **A separate crate that retains written data until it is durable and can rewrite - it after a failure**, built on DI-1.2's resolution protocol and the inventory, and resolving - overlapping suspect writes newest-wins, and resolving at the granularity of individual buffers it - can reissue (DI-1.2 Q6). The layer [DI-D-5](DESIGN-NOTES.md#di-d-5) moves out of - this crate; its first natural consumers are log-less designs such as FAT. Needs its own name and - component when it begins. - -## DI-M6+ -- Deliberately coarse (parked behind DI-M12) - -Left coarse on purpose, per the workspace's resolution-gradient rule: each needs a working -implementation before it is answerable. - -- [ ] **DI-6+.1** -- **Measure alternating rings**, which stretch "one durability sequence per - instance" to two `IoRing`s, and the conditions under which dioring's own use of a ring barrier - beats completion-gated flushes (DI-6+.5). The consumer-facing strategy comparison is gone: - [DI-D-22](DESIGN-NOTES.md#di-d-22) offers the consumer no choice. - -- [ ] **DI-6+.5** -- **Choose the flush mechanism dynamically.** The engineer, 2026-10-06: "as - perf optimizations we may want to note if specialized flush domains are in use at all, or which - are in a given ring so that we may apply a different synchronization strategy dynamically." - [DI-D-22](DESIGN-NOTES.md#di-d-22) keeps the consumer from choosing; this is dioring choosing - for itself, per seal, where a ring barrier adds no coupling of its own. Working position, coarse: - - *State to keep:* whether any file declares domains at all (if none does, every file - intersects every other, so no lineages are separable and the barrier cannot break the - expectation), and per interned domain a count of operations outstanding on the ring, so a seal - can tell whether everything it would wait on intersects its own domains. - - *What the barrier buys:* the flush can go into the same submission as the writes it covers, - with no round trip -- the case of a log append followed by a commit on one file. - - *What it still costs when allowed:* it waits on unrelated earlier work, including reads and - the lineage's own later epochs, and N flushes run one after another, so it suits few files - on a quiet ring. Which policy wins is workload-dependent, so this item restores a - measurement for DI-6+.1 to carry, and DI-2.11's events record which mechanism each seal used. - - *The common case:* the engineer, 2026-10-06: "at least "naive" meaning no files with any flush - domains set will be common." That is the case where the barrier is always admissible, decided - by one flag rather than per-domain counts, so it is the first case to build and measure -- - the choice there is purely one of performance. - Applies to the default provider (DI-2.12), the only one that uses dioring's wioring. - Gated on DI-3.2. - -- [ ] **DI-6+.6** -- **Make the default provider cheaper than per-file sync.** From DI-2.12 point 1; - the engineer, 2026-10-06, asked these be recorded as "optimizations we should strongly consider", - then: "this is a performance feature that we will tackle during development and if we can't make - it safe, we will omit it. It is not required for shipping." Gated on DI-3.2. Candidates: - - *One device sync per flush domain, for declared cache-free files.* Write-through and no - buffering "are good for the filesystem cache but don't issue the flush at the device level, and - fua is largely ignored nowadays" (the engineer), so such files still need a device sync. The - consumer declares in `FileOptions` that a handle was opened `FILE_FLAG_NO_BUFFERING` with - `FILE_FLAG_WRITE_THROUGH`, and dioring issues one syncing flush per flush domain, on one of its - declared files. **The open safety question, settled during development or the feature is - omitted:** Windows does not document that a flush on one file commits the device cache behind - the others; if adopted, that becomes part of the consumer's warranty for the domain, not an - assumption of dioring's. Also open: the representative flush should use the default mode, since - a cheaper mode on a clean file may not send the device sync. A failed flush puts the whole - domain at risk, as DI-D-21 already scopes. - - *One flush per volume*, opt-in: `FlushFileBuffers` on a volume handle flushes "all open files on - a volume", but "The caller must have administrative privileges" and it flushes other processes' - files too. Whether IoRing's flush accepts a volume handle is unverified. - - *A cheaper flush mode per file* (`Data`, `MinMetadata`), chosen in `FileOptions`. - -- [ ] **DI-6+.8** -- **A completion fence.** From DI-2.6 point 3; parked, neither added nor dropped, - because neither the engineer nor the assistant sees a use yet ("I assume the use of it would be - the next layer up"). What it would be: a consumer-posted entry delivered by `pop` only once every - operation pushed before it has completed -- a completion sequencing point, saying nothing about - durability. Submission order needs no marker, since `OpId` is ordered by push order - ([DI-D-15](DESIGN-NOTES.md#di-d-15)). dioring would deliver the fence from its own bookkeeping - rather than a drain-flagged `Nop`, which would wait on everything on the ring -- the coupling - [DI-D-22](DESIGN-NOTES.md#di-d-22) avoided -- and could therefore scope it to a lineage or a set - of files. A likely user is the retention layer (DI-5+.1), releasing buffers at a fence. - -- [ ] **DI-6+.7** -- **Credit one provider call to other seals.** From DI-2.4 point 3, which settled - the contract: a call must make the sealing lineage's named writes durable, and making more durable - is "not incorrect, that's a performance issue" (the engineer, 2026-10-06). The optimization: a - call names every completed, not-yet-durable write on the files it covers, whichever lineage made - it, so its success can be credited and a later seal -- or one arriving while the call is in - flight, for writes that completed before it was issued -- needs no call of its own. Crediting is - safe only for writes the call named, since the provider's warranty covers what it was asked - about; coverage nobody asked for is never credited. It adds no failure exposure (those writes - share the files, so DI-D-21 already puts them at risk) and no coupling between separable - lineages, which share no files. Applies to every provider, so it lives in dioring's core. - Gated on DI-3.2. - -- [x] **DI-6+.3** -- **Withdrawn: rebinding a file to a new handle is not this layer's** - ([DI-D-11](DESIGN-NOTES.md#di-d-11)). Nothing remains to do here; the obligation it left is in - DI-2.2. - -- [ ] **DI-6+.4** -- **A dioring-mediated cancel.** Not in v1 (the engineer's lean): cancel - guarantees no response time, so it does not deliver what shutdown or deadlines want, and draining - serves shutdown already. When it is taken up, the accounting is that a cancelled write's outcome is - indeterminate, so it joins the suspect set and its epoch cannot be reported durable until resolved. - The v1 design must not foreclose adding it. - -- [ ] **DI-6+.9** -- **Let the oracle check when a heal takes effect, by a per-lineage entry in the - stream.** From `DI-9.1.4`, which accepted the oracle's weaker rule - ([DI-D-42](DESIGN-NOTES.md#di-d-42)). The stream records when a seal is accepted, not when it - starts, so the oracle cannot tell a `Durable` from a seal that started after a heal from one that - started before it and finished after; it accepts both, and the core's own tests are what pin the - timing. Working position, not a decision: an entry such as `HealEffective { failure, lineage }`, - reported when a heal takes effect in a lineage, would let the oracle require it before a `Durable` - passes that failure's epochs there. It adds a public entry kind that every consumer's `match` sees, - and it changes [CONTRACT.md](CONTRACT.md) and the shared oracle together. Gated on a driver or a - consumer whose own tests need the oracle to check this, since that is what would show what the - entry should carry. - -- [ ] **DI-6+.10** -- **Fault seams in `win-sync-sys` and `windows-threadpool-sys`, and a safe `Mutex`, so - the Windows driver's remaining declared gaps can be tested.** From `DI-9.8.1`. Creating the readiness - event, the pool and signalled deliveries' thread-pool waits and events, and duplicating the notification - fail only when the process runs out of handles or memory, which a test cannot cause without breaking its - own harness; the ring crate has a seam for such failures (its `kernel-seam`) and neither of these crates - does. The notification's wait ending abandoned needs a `Mutex` to abandon, which is - [WS-1+.3](../win-sync-sys/CHECKLIST.md). Working position, not a decision: each is a change to a crate that - is not this one, and what the seam should look like is shaped by the first failure it has to produce. - Gated on a consumer, or this crate's own review, wanting those paths tested. diff --git a/crates/durable-ioring/COMPLETED-CHECKLIST.md b/crates/durable-ioring/COMPLETED-CHECKLIST.md index d0469f85e..75711ffc9 100644 --- a/crates/durable-ioring/COMPLETED-CHECKLIST.md +++ b/crates/durable-ioring/COMPLETED-CHECKLIST.md @@ -376,7 +376,7 @@ The decision is [DI-D-17](DESIGN-NOTES.md#di-d-17), and the authoritative text i The decision is [DI-D-18](DESIGN-NOTES.md#di-d-18). The item as it stood at completion, with each point's outcome, follows verbatim. Point 11's prerequisite moved to `DI-3+.2` on the engineer's agreement: the design binds to the contract `M31.1` states, and the first code that relies on it is `DI-3+.2`. -- [ ] **DI-2.3** -- **Completion routing under Model A and Model B.** The two delivery +- [x] **DI-2.3** -- **Completion routing under Model A and Model B.** The two delivery architectures `windows-ioring-sys` supports ([D-3](../windows-ioring-sys/DESIGN-NOTES.md#d-3)): **Model A, threadless dispatch**, is the thread pool's -- the ring's completion event is wired to a pool wait (`EventDelivery`), and a pool thread runs a callback when completions arrive, so no @@ -442,7 +442,7 @@ The decision is [DI-D-18](DESIGN-NOTES.md#di-d-18). The item as it stood at comp The decision is [DI-D-19](DESIGN-NOTES.md#di-d-19), whose closing summary states the settled specifics, and the authoritative text is [API.md](API.md) section 12. Point 8's outcome is [DI-D-21](DESIGN-NOTES.md#di-d-21), whose specifics continue as `DI-2.10`. The item as it stood at completion follows verbatim, including point 1's superseded answer and point 5's correction. -- [ ] **DI-2.9** -- **Durability lineages: the specifics** ([DI-D-19](DESIGN-NOTES.md#di-d-19)). +- [x] **DI-2.9** -- **Durability lineages: the specifics** ([DI-D-19](DESIGN-NOTES.md#di-d-19)). Lineages are adopted as the normal way to get independent durability within one instance; this item settles how they work. Working position from the session (not decided): @@ -541,7 +541,7 @@ The decision is [DI-D-19](DESIGN-NOTES.md#di-d-19), whose closing summary states The decision is [DI-D-21](DESIGN-NOTES.md#di-d-21), whose closing summary states the settled specifics, and the authoritative text is [API.md](API.md) section 13 and [CONTRACT.md](CONTRACT.md)'s failures section and requirements. The item as it stood at completion follows verbatim. -- [ ] **DI-2.10** -- **Caller-declared flush domains: the specifics** +- [x] **DI-2.10** -- **Caller-declared flush domains: the specifics** ([DI-D-21](DESIGN-NOTES.md#di-d-21)). Graduated from the parked `DI-6+.2`, whose concept and handover are recorded at `M33+.5` in [CHECKLIST-io-domains.md](../../CHECKLIST-io-domains.md). Reworked 2026-10-06 from "one declared regime per file" to **a set of flush domains per file**. @@ -604,7 +604,7 @@ The decision is [DI-D-21](DESIGN-NOTES.md#di-d-21), whose closing summary states The decisions are [DI-D-22](DESIGN-NOTES.md#di-d-22) (completion-gated flushes only) and [DI-D-23](DESIGN-NOTES.md#di-d-23) (gate cycles detected); the contract change is [CONTRACT.md](CONTRACT.md)'s non-guarantee `Nothing about writes outside the seal`. Point 2 moved to DI-2.12, point 3's optimization is DI-6+.7, and the redefinition of `covered` that point 5 surfaced is DI-2.12 point 9. The item as it stood at completion follows verbatim. -- [ ] **DI-2.4** -- **Multi-file epochs.** The set of files each epoch wrote, and how +- [x] **DI-2.4** -- **Multi-file epochs.** The set of files each epoch wrote, and how `make_durable_through(n)` over epochs that touched N files is done: a chain of N covering flushes (serialised device flushes, and a barrier that also waits on writes of still-open epochs above `n`) or host sequencing (observe only the writes tagged at or below `n`, then N unordered flushes @@ -691,7 +691,7 @@ The decisions are [DI-D-22](DESIGN-NOTES.md#di-d-22) (completion-gated flushes o The decision is [DI-D-24](DESIGN-NOTES.md#di-d-24). The readiness signal's form and wake-up rules continue as DI-2.14, restating [API.md](API.md) as the trait is DI-2.15, and the conformance oracle is DI-3+.7. The item as it stood at completion follows verbatim. -- [ ] **DI-2.5** -- **A trait or a concrete type first.** The contract is something layers above may +- [x] **DI-2.5** -- **A trait or a concrete type first.** The contract is something layers above may implement as well as consume; decide whether it is a trait from the start, or a concrete type until the fault-injecting implementation (DI-3+.3) gives a second implementation to extract one from. @@ -730,7 +730,7 @@ The decision is [DI-D-24](DESIGN-NOTES.md#di-d-24). The readiness signal's form The authoritative text is [API.md](API.md) section 11 (`WriteOptions::caching`, no consumer flush). The completion fence is parked as DI-6+.8, and per-operation cancel stays parked as DI-6+.4. The item as it stood at completion follows verbatim. -- [ ] **DI-2.6** -- **The operation set.** Reads go through the ring and are not epoch members; +- [x] **DI-2.6** -- **The operation set.** Reads go through the ring and are not epoch members; tagged writes; `make_durable_through`. No consumer operation may interfere with dioring's control of the ring ([DI-D-10](DESIGN-NOTES.md#di-d-10)), so what remains open is which *mediated* operations dioring offers. **Cancel: the engineer leans towards not offering it in v1**, because @@ -764,7 +764,7 @@ The authoritative text is [API.md](API.md) section 11 (`WriteOptions::caching`, The decision is [DI-D-25](DESIGN-NOTES.md#di-d-25); the authoritative text is [CONTRACT.md](CONTRACT.md)'s `Ending an instance` and [API.md](API.md) section 14. Ending a lineage, which need not wait, continues under DI-2.13. The item as it stood at completion follows verbatim. -- [ ] **DI-2.7** -- **Review what dropping a dioring instance means.** Raised by the engineer while +- [x] **DI-2.7** -- **Review what dropping a dioring instance means.** Raised by the engineer while approving DI-1.2 Q3 ("I suspect none but something that should be reviewed"). Points to settle, each with the assistant's first view: @@ -858,7 +858,7 @@ The decision is [DI-D-25](DESIGN-NOTES.md#di-d-25); the authoritative text is [C The decision is [DI-D-26](DESIGN-NOTES.md#di-d-26). What each event names, the time base, the provider's name and GUID, and installing the manifest continue as DI-3+.6. The ring crate's `M31+.1` was queued and withdrawn during this item. The item as it stood at completion follows verbatim. -- [ ] **DI-2.11** -- **ETW events for delays.** The engineer, 2026-10-06: "delays are inevitable +- [x] **DI-2.11** -- **ETW events for delays.** The engineer, 2026-10-06: "delays are inevitable with shared resources. we should plan for etw logging for delays." Lineages and instances share submission-queue capacity, the deliverer, dioring's lock and (under Model B) a thread, so a seal can be delayed by work it has no durability relation to; the remedy is to make every such delay @@ -928,7 +928,7 @@ The decision is [DI-D-26](DESIGN-NOTES.md#di-d-26). What each event names, the t The decision is [DI-D-27](DESIGN-NOTES.md#di-d-27). Points 2-9 were the assistant's stances, approved together by the engineer ("seems right, let's move on"). Writing them into [CONTRACT.md](CONTRACT.md) and [API.md](API.md) is DI-2.16; the router is DI-3+.8; the default provider's optimizations are DI-6+.6. The item as it stood at completion follows verbatim. -- [ ] **DI-2.12** -- **The durability provider.** From DI-2.4 point 2. The engineer first proposed +- [x] **DI-2.12** -- **The durability provider.** From DI-2.4 point 2. The engineer first proposed registered domains and a three-phase flush session, so that coupled domains with different names could minimise the physical flush count. Reshaped 2026-10-06 after the assistant's feedback that per-domain participants and a session protocol add back the coupling they were meant to remove @@ -1029,7 +1029,7 @@ The decision is [DI-D-27](DESIGN-NOTES.md#di-d-27). Points 2-9 were the assistan The decision is [DI-D-28](DESIGN-NOTES.md#di-d-28). Stating the wake-up rules in [CONTRACT.md](CONTRACT.md) and the signal in [API.md](API.md) is DI-2.16; the harness check is DI-3+.7. Point 5 went against the assistant's stance, and point 4's first stance is superseded in place. The item as it stood at completion follows verbatim. -- [ ] **DI-2.14** -- **The readiness signal: form and wake-up rules.** From DI-2.5 point 4: the trait +- [x] **DI-2.14** -- **The readiness signal: form and wake-up rules.** From DI-2.5 point 4: the trait carries a signal set whenever an entry becomes available, from any source, so Model A and Model B are written once over the trait. To settle, and to state in CONTRACT.md rather than leave to each implementation: @@ -1071,7 +1071,7 @@ The decision is [DI-D-28](DESIGN-NOTES.md#di-d-28). Stating the wake-up rules in The authoritative text is [CONTRACT.md](CONTRACT.md) (Terms `Durability provider` and `Covered`; guarantee 4; the non-guarantees on writes outside the seal, on what a commit waits for, and on timeouts; the provider warranty; `How a seal is made durable`; per-domain failure reach; `The readiness signal`) and [API.md](API.md) sections 15 and 16 with the sketch. Two restatements outside the item's list were corrected in the same sweep: guarantee 4's `never a silent stall` (with an amendment marker on [DI-D-13](DESIGN-NOTES.md#di-d-13)), and `No bound on what a commit waits for`, which still described the ring barrier DI-D-22 had removed. The item as it stood at completion follows verbatim. -- [ ] **DI-2.16** -- **Write the durability provider into [CONTRACT.md](CONTRACT.md) and +- [x] **DI-2.16** -- **Write the durability provider into [CONTRACT.md](CONTRACT.md) and [API.md](API.md)** ([DI-D-27](DESIGN-NOTES.md#di-d-27)). CONTRACT.md: redefine "Covered" in Terms; restate DI-D-9 rule (c)'s incidental coverage; add the per-domain failure scope beside DI-D-21's rule; add the provider's success warranty to the consumer's requirements; state that a @@ -1089,7 +1089,7 @@ The authoritative text is [CONTRACT.md](CONTRACT.md) (Terms `Durability provider The authoritative text is [API.md](API.md) section 17 and its sketch. One consequence was forced rather than chosen and is recorded as a refinement on [DI-D-24](DESIGN-NOTES.md#di-d-24): `Epoch` and every shared type that carries an identity is generic over one `Identities` bundle, because an epoch holds a lineage. The readiness signal is `readiness()` rather than a placeholder, DI-2.14 having settled its form. Renaming the concrete type to `Dioring` and the signal's move to dioring's own event left markers on DI-D-15, DI-D-17 and DI-D-18. One open question surfaced and is DI-2.17. The item as it stood at completion follows verbatim. -- [ ] **DI-2.15** -- **Restate [API.md](API.md) as the trait** ([DI-D-24](DESIGN-NOTES.md#di-d-24)). +- [x] **DI-2.15** -- **Restate [API.md](API.md) as the trait** ([DI-D-24](DESIGN-NOTES.md#di-d-24)). The sketch becomes a core trait with associated identity types and a registered-buffer extension trait, `DurableRing` becomes dioring's implementation of both, and the prose sections that name concrete types are updated to match. The readiness signal is a placeholder until DI-2.14 settles @@ -1101,7 +1101,7 @@ The authoritative text is [API.md](API.md) section 17 and its sketch. One conseq The decision, with the list of features such a type has, is [DI-D-29](DESIGN-NOTES.md#di-d-29); the type is in [API.md](API.md)'s sketch and section 17. The engineer asked whether a canonical type existed and, assuming not, for one of the crate's own: none fits -- `std::fs::File` is not cheap to share, the ring's `SharedFile` exposes no handle (so a provider could not flush it out of band), and the namespace crate's `CapturedHandle` owns a duplicate. Handing the ring the same handle needs `windows-ioring-sys` `M31.3`, added to DI-3+.2's prerequisite. The item as it stood at completion follows verbatim. -- [ ] **DI-2.17** -- **Does the contract own its file type?** Found while restating the API as the +- [x] **DI-2.17** -- **Does the contract own its file type?** Found while restating the API as the trait (DI-2.15): the core trait names `windows-ioring-sys`' `SharedFile` in `add_file`, `remove_file` and `FileWrites`, so every implementation depends on the ring crate for that one type -- unlike `WriteCaching`, which the contract owns for exactly that reason (DI-2.6). Either @@ -1114,7 +1114,7 @@ The decision, with the list of features such a type has, is [DI-D-29](DESIGN-NOT The decision is [DI-D-30](DESIGN-NOTES.md#di-d-30); the authoritative text is [CONTRACT.md](CONTRACT.md) `Ending a lineage` and [API.md](API.md) section 18 (`end_lineage`, `EndLineageError`, `Entry::LineageEnded`), compile-checked. The engineer approved the assistant's six stances together ("close it"). The item as it stood at completion follows verbatim. -- [ ] **DI-2.13** -- **A lineage its holder will not finish.** Raised with DI-2.4 point 4; the +- [x] **DI-2.13** -- **A lineage its holder will not finish.** Raised with DI-2.4 point 4; the engineer, 2026-10-06: "We may need to implementation cancellation just in general but only because somewhere along the line the holder of a lineage may decide not to finish it for whatever reason." Working position, hedged as the engineer hedged it -- whether v1 needs it is itself open: @@ -1127,7 +1127,7 @@ The decision is [DI-D-30](DESIGN-NOTES.md#di-d-30); the authoritative text is [C 3. [x] **Other lineages' gates on it.** The hard part: a gate in another lineage naming an epoch that will now never be durable can never release. Those writes must end -- refused, or reported never issued -- rather than wait forever, and this is the case cycle detection cannot see. - 4. [x] **Relation to cancel.** Not operation cancel, which [DI-6+.4](CHECKLIST.md) parks; this is + 4. [x] **Relation to cancel.** Not operation cancel, which [DI-6+.4](PARKED.md) parks; this is cancelling a lineage's future. Whether the two share a mechanism is open. 5. [x] **Ending a lineage does not wait.** The engineer, 2026-10-06: shutting down a lineage "does not have to wait because there is not a lifetime issue and there is not a dependency around @@ -1226,6 +1226,7 @@ Two findings. The kernel accepts queue sizes of zero, so ring creation is refuse and a zero-length buffer both registered -- so those error paths are unreached by tests; [dioring.rs](src/dioring.rs) says what would reach them, and [sabotage.json](sabotage.json) records it. Six sabotages, each caught, and a control that survives. + ## Moved 2026-10-07 19:18:33 -04:00 -- DI-3.2.2.1: plain I/O and the shared delivery ### DI-3.2.2.1 -- Plain I/O and the delivery every front end shares: pushes, `pop`, the readiness `Event`, and the conformance oracle with its readiness check. *(completed 2026-10-07 19:18:33 -04:00)* @@ -2250,7 +2251,7 @@ what is left for it. The assistant's choices are queued in `DI-9.7`. The item as it stood at completion: -- [ ] **DI-8.1** -- **The core's tests made portable, while still in one crate.** They read +- [x] **DI-8.1** -- **The core's tests made portable, while still in one crate.** They read interrupt time and Windows faults today, so they move to a portable mock clock and fault, which the split needs: once `durable-ioring` has no Windows dependency, they could not build. @@ -2280,7 +2281,7 @@ its own crate, and the core's runs both, because the driver's tests also pin the The item as it stood at completion: -- [ ] **DI-8.2** -- **The split**: `win-durable-ioring` created and registered for release, holding +- [x] **DI-8.2** -- **The split**: `win-durable-ioring` created and registered for release, holding the driver, the Windows backend and file target, `ErrorCode`'s reading from an `io::Error`, the readiness `Event`, the interrupt clock, the oracle's readiness check, and `DioringIds`' default time type; `durable-ioring` left with no Windows dependency. CONTRACT.md's worked examples drive @@ -2304,7 +2305,7 @@ they are, and a failure there is the item's finding, not a regression in the spl The item as it stood at completion: -- [ ] **DI-8.3** -- **`durable-ioring` added to CI's `portable` job**, so its non-Windows build, +- [x] **DI-8.3** -- **`durable-ioring` added to CI's `portable` job**, so its non-Windows build, tests and lints are checked on Linux and macOS, as [DEVELOPMENT.md](../../DEVELOPMENT.md) describes for a crate with a portable core. @@ -2772,7 +2773,7 @@ Done by the assistant, as the item was its own. Of the gaps the Windows driver's - **Not covered, with what would cover them:** the wait ending *abandoned* needs a mutex to abandon, which is [WS-1+.3](../win-sync-sys/CHECKLIST.md); and the creation failures (events, thread-pool waits, duplicating the notification) need a fault seam in `win-sync-sys` and `windows-threadpool-sys`. Both are - parked as [DI-6+.10](CHECKLIST.md), and the manifest's `notCoveredHere` says each. + parked as [DI-6+.10](PARKED.md), and the manifest's `notCoveredHere` says each. Changed in `win-durable-ioring`: a test-only [held_pool.rs](../win-durable-ioring/src/held_pool.rs), four tests, and the manifest. @@ -2838,6 +2839,7 @@ of [DI-D-46](DESIGN-NOTES.md#di-d-46): a panic is for a programming error, never panics; no test can reach it, since making one needs `unsafe`, so the manifest's `notCoveredHere` says so. - The manifest's `notCoveredHere` no longer says a failing pop and a full submission queue are untestable: a failing pop is reached through the ring crate's call responder, and a push no longer submits. + ### DI-7+.4 -- Model A's handler is told when the instance's pop fails, and the in-memory backend refuses what it cannot hold. *(completed 2026-10-14 18:04:18 -04:00)* From the second round of the review of [PR 126](https://github.com/MikeGrier/windows-threadpool-sys/pull/126). @@ -2854,6 +2856,7 @@ From the second round of the review of [PR 126](https://github.com/MikeGrier/win panics if an operation is queued or a completion is waiting, since replacing the buffers would invalidate a region it names, a caller's programming error ([DI-D-59](DESIGN-NOTES.md#di-d-59)). Three sabotage entries. + ## Moved 2026-10-14 19:26:34 -04:00 -- DI-9.8.2: the portable job's first CI run ### DI-9.8.2 -- The portable CI job's first run is read: the crate's tests run on Linux and macOS. *(completed 2026-10-14 19:26:34 -04:00)* @@ -2911,3 +2914,1311 @@ The sabotage entry that masked a Win32 code to sixteen bits left the core's mani covered; the rule is covered by `win-error-codes`' own entries, and `win-durable-ioring`'s manifest gained entries for the three ways its helpers can go wrong, with a control. The tests that pinned the three facts moved with the helpers and gained one that the wrapping agrees with `HResult`. + +## Moved 2026-10-16 18:40:00 -04:00 -- DI-13.1, DI-13.5 and DI-13.6: the open surface written down, and the request crate's release taken + +### DI-13.1 -- The open surface, written down as a table over every parameter of the open calls, with the engineer's rulings (a) to (g). *(completed 2026-10-16 18:40:00 -04:00)* + +The table, its sources, what was not verified and the calls awaiting the engineer's confirmation are +[DI-D-64](DESIGN-NOTES.md#di-d-64). The rulings of 2026-10-15 are in its Ruling column. The +rulings of 2026-10-16, recorded here because the checklist item that carried them is archived: + +- (a) `open` takes a `Wtf16String` only; the caller converts. Passing an empty path to Win32 was + proposed with it and not separately ruled. +- (b) The only check before the open is syntactic, on the path handed to Win32, and only for the forms + a string alone shows remote (UNC and verbatim UNC): no drive-type query and nothing that opens the + path. Every other validation is made on the opened handle, so an unbuffered create refused there has + already created the file, which is documented and not prevented. +- (c) Nothing refuses a path for being able to block. A blocking `CreateFileW` is answered by the + request crate: a request is built on one thread and performed on another, and `open` becomes that + two-phase shape (`DI-13.7`). Whether `CancelSynchronousIo` interrupts a create changes nothing in the + design; the engineer expects it cannot. +- (d) What an `OpenedFile` claims when its path names a device is measured before anything is decided, + as part of `DI-14.6`. +- (e) The assistant's recommendation, adopted: `CaptureSet`-style typed sets for rights, share mode + and attributes, an enum for the disposition, builder methods for flags, and `create`, `create_new` and + `truncate` removed outright, a breaking change to `win-durable-ioring` that ships as a new minor. +- (f) A cache hint on an unbuffered file is passed through and documented, not refused (`DI-14.1`). +- (g) The evidence-only test of the ring on an overlapped handle is `DI-14.7`. + +### DI-13.5 -- `SecurityDescriptor` from SDDL, taken from `windows-namespace-request-sys`. *(completed 2026-10-16 18:40:00 -04:00)* + +Done when this crate depends on the release that carries the constructor. `win-durable-ioring` +depends on `windows-namespace-request-sys` 0.4.0, and that release's tag carries +`SecurityDescriptor::from_sddl` and `to_sddl` (`NR-3.1`), checked against the tag's source on +2026-10-16. `DI-14.3` uses it. + +### DI-13.6 -- A `ReOpenFile` entry, taken from `windows-namespace-request-sys`. *(completed 2026-10-16 18:40:00 -04:00)* + +As `DI-13.5`: the same release's tag carries the `ReOpenFile` entry (`NR-3.2`), checked against the +tag's source on 2026-10-16. `DI-15.4` uses it. + +## Moved 2026-10-16 19:57:37 -04:00 -- DI-14.1: cache hints + +### DI-14.1 -- Cache hints: `FILE_FLAG_RANDOM_ACCESS` and `FILE_FLAG_SEQUENTIAL_SCAN` are builder methods on `FileOptions`, passed to a cached and an unbuffered file alike. *(completed 2026-10-16 19:57:37 -04:00)* + +`FileOptions::random_access` and `FileOptions::sequential_scan` add their flag to what the buffering +mode and the options already imply, and never refuse. The ruling that a hint is passed to an unbuffered +file and documented there ([DI-13.1](COMPLETED-CHECKLIST.md#di-131) (f)) is what the methods' own +documentation says, with the measurement: every combination of the two hints, cached and unbuffered, +opens and carries a sector-aligned write and read, an unbuffered one also through the ring, on Windows +11 build 26300. Whether a hint changes the system's caching was not measured. A hint changes neither +the class, the alignment nor what a target offers, which a test reads off the target made from each +file. Five sabotage entries in `win-durable-ioring`, including a control, cover the flag, the pair, the +flags the mode opens with, and a refusal on an unbuffered file. Handle-level `FILE_FLAG_WRITE_THROUGH` +stays out of v1, and the per-write flag's error 509 on a cached handle is the test that was already +kept. + +### DI-13.3 -- Access is a typed set of named rights, `FileRights`, with no raw `u32`; a file target needs a data right, checked when it is opened. *(completed 2026-10-16 20:22:03 -04:00)* + +`FileOptions::access` takes a `FileRights`, joined with `|`, in place of the `read` and `write` +booleans, which are removed. The set holds the documented file rights and the generic ones, per +[DI-D-64](DESIGN-NOTES.md#di-d-64); there is no constructor from a raw mask and no member for +`FILE_APPEND_DATA` alone, so append-only access cannot be asked for. An open with no data right is +refused as invalid input before any call is made; what the file's security allows is the kernel's to +say, and a right it refuses reaches the caller as its error. Tests open each right and have the kernel +judge the operation it does and does not cover. Measured: an unbuffered open of `FILE_ALL_ACCESS` is +refused with `ERROR_INVALID_PARAMETER`, which is recorded in the `DI-D-64` row for the generic rights +and on the constant. Six sabotage entries, including a control, are in `win-durable-ioring`. + +### DI-13.8 -- Data access is the type parameter `A` of `OpenedFile`, `OpenedDisk` and `OpenedVolume`, and `FileRights` keeps only the rest. *(completed 2026-10-16 21:15:29 -04:00)* + +The engineer's ruling of 2026-10-16, [DI-D-65](DESIGN-NOTES.md#di-d-65), which holds the types, where the +line between the type and the rights set falls, and what building it settled. `FileOptions` moves `A` +with `read()` and `write()`; the three opened types carry it, and a disk and a volume can be opened for +reading only; `Win32FileTarget::new`, `from_disk` and `from_volume` take `A: HasData`. `FileRights` lost +the data rights, the generic constants and `ALL_ACCESS`. Ten sabotage entries, two of them controls; a +`compile_fail` doctest for each refusal; and a read-only test for a raw disk and a raw volume that runs +in CI, since it needs an administrator. A transition that leaves a marker where it was changes a type, so +its detection is a compile error and not a sabotage entry. + +### DI-13.9 -- `IoTarget` states its data access, and the core refuses what the access does not cover. *(completed 2026-10-16 21:46:00 -04:00)* + +The engineer's ruling of 2026-10-16, layer 1 of [DI-D-66](DESIGN-NOTES.md#di-d-66). The markers moved to +`durable-ioring`; `IoTarget::access` is required with no default, `IoTargetDescription` carries it, and +`RefusalCause::AccessDenied` is a permission-denied fault. Both backends refuse a write to a target that +cannot write and a read from one that cannot read at the push, after the unknown-target check and before +geometry; a commit has no rule. `Win32FileTarget` states the access of its marker. What a marker asks of +Win32 is a function over the core's `READ` and `WRITE`, not a trait, so the Windows crate adds nothing the +core must know. Nine sabotage entries in `durable-ioring` and seven in `win-durable-ioring`, each set +with a control. The read-only raw disk and volume tests run only in CI, and the stated access of a disk +and a volume is pinned locally by assembled targets. + +### DI-13.10 -- A typed `Key` carries the access to the ring's submit methods. *(completed 2026-10-16 22:08:00 -04:00)* + +Layer 2 of [DI-D-66](DESIGN-NOTES.md#di-d-66), which holds the types and what building them settled. +`Key` is an `IoTargetKey` with a declaration; a sealed `TargetKey` is what registration takes, +and `WriteKey` and `ReadKey` over it are what the four submit methods and the four registered-buffer +methods take, so a write through a `Key` does not compile and an untyped key is judged at the +push. `add_target` and construction refuse a key that declares more than its target states, with the +target or every file handed back; `AddTargetError` gained a `cause` and `TargetSetup` gained `declared`. +Five sabotage entries in `durable-ioring` and five in `win-durable-ioring`, each set with a control, and +`compile_fail` doctests on `WriteKey` and `ReadKey`. + +### DI-13.4 -- Share mode as a typed set, with a sharing violation as a typed error. *(completed 2026-10-16 22:40:00 -04:00)* + +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s `dwShareMode` row, and its "What building DI-13.4 settled", which holds the +decisions. `ShareMode` is a set of `READ`, `WRITE` and `DELETE`; `FileOptions::share` takes it and the +default stays all three. A conflict is a `SharingViolation`, kind `ResourceBusy`, naming what this open +asked for and keeping the kernel's error as its source; the kernel does not say which side conflicted. All +576 pairs of opens over the three accesses and eight modes are tested against the documented rule. +Twelve sabotage entries, one a control. `DI-13.11` carries the volume and the disk. + +### DI-13.11 -- A share mode for `OpenedVolume` and `OpenedDisk`, through one device open. *(completed 2026-10-16 23:05:00 -04:00)* + +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-13.11 settled". `VolumeOptions::share` and a new +`DiskOptions` take the `ShareMode` of `DI-13.4`, and a conflict is its `SharingViolation`. Both opens are +`raw_open::open`, tried on a file against the documented rule. The two exclusive tests run only in CI and +are unrun; `DI-13.12` carries them and the lock. Nine sabotage entries caught, three expected to survive. + +### DI-13.12 -- The exclusive-volume and exclusive-disk tests run elevated, and the lock measured against the share mode. *(completed 2026-10-16 23:20:00 -04:00)* + +Run through `sudo` on 2026-10-16; the findings are in [DI-D-64](DESIGN-NOTES.md#di-d-64). Both exclusive +tests pass and each wiring mutated to ignore its options fails its test; a locked volume refuses a data +open as access denied whatever the share mode. The two `survives` entries stay, as the sweep runs no ignored test. + +### DI-13.7.1 -- The path is handed over as the caller gave it, a `Wtf16String`. *(completed 2026-10-16 23:30:00 -04:00)* + +Commit `5ac36393`. `OpenedFile::open` takes a `Wtf16String` and nothing is lengthened, shortened or refused: +a path past `MAX_PATH` without a verbatim prefix and an empty one are `CreateFileW`'s to answer. Converting a +`Path` is `Wtf16String::from_os_str`, re-exported at the crate root. The `DI-13.2` long-path sabotage entry is +replaced by one that shows the path reaches the request unaltered, with a `survives` control. + +### DI-13.7.3 -- The disposition is Win32's own, an enum, and `FILE_FLAG_OPEN_REPARSE_POINT` is an explicit option. *(completed 2026-10-17 00:27:16 -04:00)* + +Commit `dfdecb8a`; the ruling is [DI-D-64](DESIGN-NOTES.md#di-d-64)'s `dwCreationDisposition` row. +`Disposition` is `CreateNew`, `OpenExisting` (the default) or `OpenAlways`, `#[non_exhaustive]`, and +`FileOptions::disposition` replaces `create`, `create_new` and `truncate`. `FileOptions::open_reparse_point` +sets the flag, which a new-file open no longer sets itself. Deleted: `truncate_after`, the `OPEN_ALWAYS` plus +`set_len(0)` stand-in and the refusal of a create without the right to write, so `parameters()` is infallible. +Tests open each disposition against a present and an absent file for each typed access, and make a link to +show a new file is created through it unless the option is set; the link test is skipped where a symbolic link +cannot be made. Seven sabotage entries and a `survives` control replace the five that pinned std's behaviours. + +### DI-13.7.4 -- `open` is a build, `OpenedFile::request`, and a perform, `OpenRequest::perform`. *(completed 2026-10-17 13:03:45 -04:00)* + +Commit `013990cd`; ruling (c) of [DI-13.1](#di-131). `OpenedFile::request(path, options)` builds an +`OpenRequest` on the calling thread, fixing a relative path to the current directory as it is then, and +`perform(&self)` makes the call wherever the caller chooses; `open` is the two inline and is documented as +blocking. The request is `Send` and can be performed repeatedly, each time opening the file again. The +unbuffered remote check stays on the path inside `perform` until `DI-14.5`, recorded in +[DI-D-64](DESIGN-NOTES.md#di-d-64) as an interim. The tests are in `opened_file/request_tests.rs`; the +remote refusal itself is the existing test that needs an administrative share, which passed here. Three +sabotage entries and a `survives` control. + +### DI-13.7 -- `OpenedFile::open` reflects Win32 and not `std`, and `DI-D-63` is amended. *(completed 2026-10-17 14:14:17 -04:00)* + +Five items, each its own commit, under the engineer's ruling that this crate reflects Win32 +([DI-D-64](DESIGN-NOTES.md#di-d-64)): [DI-13.7.1](#di-1371) (the path as given), `DI-13.7.2` (ruled: v1 offers +neither `TRUNCATE_EXISTING` nor `CREATE_ALWAYS`), [DI-13.7.3](#di-1373) (`Disposition` and +`open_reparse_point`), [DI-13.7.4](#di-1374) (`request` and `perform`), and `DI-13.7.5` below. The two that broke +`win-durable-ioring` ship as one new minor (the ruling of [DI-13.1](#di-131), item (e)). + +### DI-13.7.5 -- `DI-D-63` amended, and the README and module documentation say what the open now is. *(completed 2026-10-17 14:14:17 -04:00)* + +[DI-D-63](DESIGN-NOTES.md#di-d-63)'s superseded list of what was kept so that a caller sees no difference from +`std` is replaced by what the open is now, and its two checks and its unverified items are kept. The README's +open section gains the path, the disposition, the reparse option and `request`/`perform`, with a doctest of the +second; the module documentation of `opened_file.rs` says the same. [DI-14.2](#di-142) already +lacks the reparse flag. + +### DI-14.2 -- `FILE_FLAG_DELETE_ON_CLOSE`, `FILE_FLAG_OPEN_NO_RECALL` and `FILE_FLAG_POSIX_SEMANTICS` are builder methods on `FileOptions`. *(completed 2026-10-17 16:05:14 -04:00)* + +`FileOptions::delete_on_close`, `open_no_recall` and `posix_semantics`, each setting its own bit and passed to +a cached and an unbuffered file alike; measurements and the three non-findings are in +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-14.2 settled". Delete-on-close removes the file when the +last handle closes and is not refused without the right to delete. `FILE_FLAG_OVERLAPPED`, which this item's +text named conditionally, is not offered here: it waits on [DI-14.7](CHECKLIST.md)'s run, which now carries the +question of whether `FileOptions` offers it. The reparse-target requirement moved to +[DI-14.5](CHECKLIST.md). Eight sabotage entries. + +### DI-14.3 -- Creation attributes (`FileAttributes`), a security descriptor on the request, and no inheritance. *(completed 2026-10-17 16:22:48 -04:00)* + +`FileAttributes` is a typed set of the six attributes the table keeps, passed with `FileOptions::attributes`; +`OpenRequest::with_security_descriptor` gives a created file a `SecurityDescriptor` built from SDDL, owned and +freed by the request; no handle is asked to be inheritable. What was measured and the choices made are in +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-14.3 settled". **Not done as written:** the item asked +for the descriptor and the inheritance read back with documented queries; both need `unsafe`, which this crate +forbids and no workspace crate offers safely, so the descriptor is read back by behaviour (a deny-read DACL +refuses a later open) and the inheritance choice is pinned on the request, and the read-backs are +[DI-14.8](CHECKLIST.md), raised with the engineer. The item named *encrypted* among the attributes and the +table excludes it. Five sabotage entries. + +### DI-14.7 -- The ring on a handle opened with `FILE_FLAG_OVERLAPPED`, run against the same handle without it, on a cached and an unbuffered file. *(completed 2026-10-17 20:36:00 -04:00)* + +`win-durable-ioring`'s `tests/overlapped_evidence.rs` opens four handles through the request crate's `OpenFile` +(cached and unbuffered, with the flag and without) and drives one sequence through `windows-ioring-sys` on +each. What was measured is in [DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-14.7 settled". **Not done +as written:** the item asked for the status and information values, which the safe API does not expose +separately from the completion's result and byte count, so those are what was compared. The ruling the +item's text left to the engineer is [DI-14.9](CHECKLIST.md). The test adds no public API, so there is no +sabotage entry. + +### DI-14.5 -- The remote-device check is made on the opened handle, and nowhere else. *(completed 2026-10-18 00:10:00 -04:00)* + +`OpenRequest::perform` opens the file and then asks the handle where it is, with `GetFinalPathNameByHandleW` +through the request crate's `QueryFinalPath`, and refuses an unbuffered file whose final path is a UNC share. +The path canonicalised ahead of the open is gone, and with it the window in which a link could be swapped. +Which documented queries were checked, why the final path was chosen, how a link is judged, and what was +measured are in [DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-14.5 settled". **Not decided:** whether a +handle opened on a link to a share (`open_reparse_point`) should be refused, since the handle is the link on a +local volume; the item's text asked that the check see the target, and this item judges what the handle is. +The ruling is [DI-14.10](CHECKLIST.md). +Two sabotage entries replace the one that named the removed path function; the refusal itself is reached only by +the ignored administrative-share tests, which were run with the ignored tests included. + +### DI-14.9 -- `FILE_FLAG_OVERLAPPED` is a builder method on `FileOptions`, ruled by the engineer, 2026-10-18. *(completed 2026-10-18 00:36:00 -04:00)* + +The engineer ruled that it is offered. `FileOptions::overlapped` is a bool that sets its own bit, is passed to a +cached and an unbuffered file alike, and is kept when the data access changes. `tests/overlapped_evidence.rs` +now drives the [DI-14.7](#di-147) sequence on handles opened through it as well as on the raw ones, with the +same result for every step in each buffering mode, and with the option left off the completions are inline, so +the flag is not set unasked. The table row in [DI-D-64](DESIGN-NOTES.md#di-d-64) is amended. Three sabotage +entries. `opened_file.rs` is past 32 KiB with this; the one fracture candidate, the two refusal types with +their helpers, would need the helpers' visibility changed, which a pure relocation does not do, so it is left whole. + +### DI-14.8.1 -- `win-authorization-sys` is created and the request crate's `security` module is moved into it and re-exported. *(completed 2026-10-18 00:51:00 -04:00)* + +The engineer ruled on 2026-10-18 that the crate is named `win-authorization-sys` and that the types move with +it. `SecurityDescriptor`, `SecurityAttributes`, `SecurityInformation`, `AclState` and the capture errors, with +their tests and eighteen sabotage entries (all behaving as declared), are in the new crate, and the request +crate re-exports it as `security` and at its root. It is two commits so the request crate's release is not +attributed to the move: the crate added with the code copied (`Split-Source` and `Split-Into` trailers, and +`blame -w -C1 -C1` traces all but four lines of `sddl.rs` back through it), then the request crate switched over +and its copy deleted. What changed in the code: `SecurityCaptureError::into_source` is `pub`, and the tests that +open a file through a request stayed in the request crate. The request crate's own tests that built an absolute +descriptor now build one from SDDL, since the absolute form is the new crate's to test. See +[win-authorization-sys](../win-authorization-sys/DESIGN-NOTES.md) `WA-D-1`. + +### DI-14.8.2 -- `GetSecurityInfo` as a safe query in `win-authorization-sys`. *(completed 2026-10-18 01:22:00 -04:00)* + +`get_security_info(handle, ObjectType, SecurityInformation)` returns an owned `SecurityDescriptor`; Windows' +refusal comes back with its own code. Tests cover files created with ten different DACLs, a file made without a +descriptor (owner, group and DACL), the selection of single parts, a directory, a handle without the right to +read, a SACL without the right, a named event as a kernel object, and a descriptor outliving its handle; five +sabotage entries, all caught, and 100 repeat runs without a failure. **Not done as written:** the item named "a +handle that is not a securable object". Every handle measured is securable, and what stands in for it is an +object that has no descriptor, which reads as one with no parts, and a kind Windows refuses. What was measured +is in [win-authorization-sys](../win-authorization-sys/DESIGN-NOTES.md) `WA-D-2`. The request crate's test that +created files with a descriptor now reads them back through this query and no longer needs the authorization +feature of `windows-sys`. + +### DI-14.8.3 -- `win-handles-sys` is created and `duplicate_handle_with_access` and its `Access` mask move into it from `win-sync-sys`. *(completed 2026-10-18 01:42:00 -04:00)* + +Two commits, as for [DI-14.8.1](#di-1481): the crate added with the code copied (`Split-Source` and `Split-Into` +trailers), then `win-sync-sys` switched over and its copy, its tests and its seven sabotage entries moved or +deleted. `win-sync-sys` re-exports both names at the paths they had, and `win-durable-ioring`, which takes them +from `win-sync-sys`, is unchanged. The tests observe a duplicate through raw calls, so they now make their events +with `CreateEventW` where they used the sync crate's `Event`; one of them reads the inheritance flag back through +raw `GetHandleInformation`, which [DI-14.8.4](CHECKLIST.md) replaces with the safe query. `Access` keeps both of +its constants, `EVENT_MODIFY_STATE` included though it is an event's right, because an associated constant can +be declared only where the type is and moving it would have broken `Access::EVENT_MODIFY_STATE`; that is +recorded in [win-handles-sys](../win-handles-sys/DESIGN-NOTES.md) `WH-D-1`. + +### DI-14.8.4 -- `GetHandleInformation` as a safe query in `win-handles-sys`. *(completed 2026-10-18 01:52:00 -04:00)* + +`get_handle_information(handle)` returns `HandleFlags`, a typed set of `INHERIT` and `PROTECT_FROM_CLOSE` that keeps +a bit it does not name. Tests set the flags through raw `SetHandleInformation` and read them back through the +query (neither flag, either, both, set and cleared, a handle `std` opens, a duplicate of an inheritable source, +and the handle borrowed, owned and by reference), with five sabotage entries caught and one declared blind spot, +and 100 repeat runs without a failure. The duplicate's inheritance test, which read the flag through the raw +call, reads it through this query. The error path has no test, since `AsHandle` makes a handle that is not open +unrepresentable, and says so where it is defined. What was measured is in [win-handles-sys](../win-handles-sys/DESIGN-NOTES.md) `WH-D-2`. + +### DI-14.8.5 -- `win-durable-ioring`'s `DI-14.3` tests read back through the two queries. *(completed 2026-10-18 02:05:00 -04:00)* + +A created file's DACL reads back, through `get_security_info`, as the one it was given, for five descriptors on a +cached file and one on an unbuffered one; a created file's handle, with a descriptor and without, reads back +through `get_handle_information` as carrying neither flag. Each was seen to fail alone, in a run with the +mutation it guards applied by hand (the descriptor withheld from the call fails the read-back tests; the +inheritance choice flipped fails the handle test), and the two sabotage entries now say so; the five `DI-14.3` +entries still behave as declared. The crate also takes `SecurityDescriptor` from `win-authorization-sys` and +`duplicate_handle_with_access` from `win-handles-sys`, the crates the Learn-section rule gives them, instead of +through the request and sync crates, which still re-export them; the types are the same, so no public path +changed. + +### DI-14.8 -- Safe read-backs of what an open was given, in the two crates the Learn-section rule names: `win-authorization-sys` and `win-handles-sys`. *(completed 2026-10-18 02:06:00 -04:00)* + +`DI-14.3` could not read back a handle's inheritability or a file's descriptor, because this crate forbids `unsafe` +and no crate offered `GetHandleInformation` or `GetSecurityInfo` safely. The engineer ruled on 2026-10-18 that the +crates are `win-authorization-sys` (Security and Identity > Authorization) and `win-handles-sys` (System Services > +Windows System Information > Handles and Objects), and that the function and the types that belong move with +them. It was five items: [DI-14.8.1](#di-1481) (the request crate's `security` module moved to the first), +[DI-14.8.2](#di-1482) (`GetSecurityInfo`), [DI-14.8.3](#di-1483) (`duplicate_handle_with_access` and `Access` +moved to the second), [DI-14.8.4](#di-1484) (`GetHandleInformation`) and [DI-14.8.5](#di-1485) (the read-backs). +The remaining functions of the two Learn sections are queued in the two crates' own checklists +([win-authorization-sys](../win-authorization-sys/CHECKLIST.md), [win-handles-sys](../win-handles-sys/CHECKLIST.md)), +and not here. + +### DI-14.10 -- An unbuffered open of a link to a share, opened as the link, is accepted, ruled by the engineer, 2026-10-18. *(completed 2026-10-18 13:25:00 -04:00)* + +The engineer ruled that it is accepted. [DI-14.5](#di-145) judges an opened handle by where it is, so a local link +that leads to a share, opened with `open_reparse_point`, is a local handle and passes; followed, the handle is the +share's file and is refused. Refusing the first case would have needed a second open that follows the link, which +brings back the window `DI-14.5` closed. No code changed: the ignored administrative-share test +`a_refusal_made_on_the_handle_comes_after_a_new_file_was_created_and_a_link_is_judged_by_the_handle` already +asserts both cases, and the reading is in [DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What building DI-14.5 settled". + +### DI-14.4.1 -- The reading pass: every condition the pages state on the open's arguments, sorted and cited. *(completed 2026-10-18 15:14:02 -04:00)* + +Read on 2026-10-18 from the sources the Learn pages are built from: `CreateFileW`, "Creating and Opening Files" +(added because the `CreateFileW` page defers to it for combinations and special cases), "File Buffering", "File +Access Rights Constants" and "File Attribute Constants". The sorted list, with citations and page dates, is in +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s "What the reading pass of DI-14.4.1 found". Findings: every condition on the +arguments alone that a page calls invalid is already unrepresentable in v1 (`FILE_ATTRIBUTE_NORMAL` alone, the +reparse flag with `CREATE_ALWAYS`, `TRUNCATE_EXISTING` with `GENERIC_WRITE`, the dispositions not combinable), and +the two that return with [DI-6+.11](PARKED.md) are recorded there. **Two citations this crate carried were +wrong:** that delete-on-close needs the `DELETE` right, and that the append right is incompatible with +no-buffering, are on `ZwCreateFile`'s page and not on any page `CreateFileW` is governed by; both statements are +corrected, and whether the lower layer's conditions count is asked in [DI-14.4.2](CHECKLIST.md). The volume +section's requirement of `FILE_SHARE_WRITE` is a condition on `OpenedVolume`'s arguments that its share option +does not honour, raised as [DI-14.11](CHECKLIST.md). + +### DI-14.4.2 -- No check ahead of `OpenedFile`'s open: a lower layer's page is not the call's contract. *(completed 2026-10-18 15:26:12 -04:00)* + +The engineer ruled on 2026-10-18 that `ZwCreateFile`'s page "is not direct evidence" of `CreateFileW`'s contract, +though it is "good evidence to use for an experiment". With the reading pass having found every other documented +condition unrepresentable, there is nothing to check, and nothing was built. Delete-on-close without `DELETE` and +the append right with no-buffering stay what they were measured to be, evidence and not checks. The rule is +amended to say so ([DESIGN-NOTES.md](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract)). + +### DI-14.4.3 -- `FILE_ATTRIBUTE_TEMPORARY` on an unbuffered file is supported. *(completed 2026-10-18 15:26:12 -04:00)* + +The engineer, 2026-10-18: "I think we should support it." The pages are silent on the pair, so it was never a +contract refusal; the assistant had raised it as a possible narrowing because the warranty was not demonstrated +for it, and there is no analytic ground for that: the attribute concerns when cached data is written back, and an +epoch's durability comes from the flush this crate issues. No code changed; the attribute was already offered. + +### DI-14.4 -- The open's arguments, read against the documented contract: no condition `OpenedFile` can express is invalid, so no check is built. *(completed 2026-10-18 15:26:12 -04:00)* + +Re-planned under the documented-contract rule and split in three: [DI-14.4.1](#di-1441) (the reading pass), +[DI-14.4.2](#di-1442) (no check, ruled) and [DI-14.4.3](#di-1443) (`TEMPORARY` supported, ruled). As first written +the item would have refused what the kernel refused on this machine. What it produced is a sorted, cited list of +every condition the pages state on the open's arguments, in [DI-D-64](DESIGN-NOTES.md#di-d-64); two corrected +citations; the conditions that return with `CREATE_ALWAYS` and `TRUNCATE_EXISTING`, recorded on +[DI-6+.11](PARKED.md); and [DI-14.11](#di-1411). + +### DI-14.11 -- A volume opened without sharing writes stays allowed: the measurement contradicts the page's requirement, so it is documented and not refused. *(completed 2026-10-18 15:26:12 -04:00)* + +The `CreateFileW` page says a volume open "must have the `FILE_SHARE_WRITE` flag"; `DI-13.12`'s elevated test +opened a volume exclusively and was refused a second open until it closed. The engineer's amendment of +2026-10-18 settles it: "If we have evidence that the documentation is wrong, I believe the remedy is to not embody +the documentation structurally." `VolumeOptions::share` keeps every mode, and its documentation quotes the page and +names the test; the test's comment says it is that evidence. + +### DI-14.6.1 -- What an open handle reports about its own kind is measured over a disk file, a directory, `NUL`, a console, a pipe, a volume and a physical disk. *(completed 2026-10-18 15:41:29 -04:00)* + +`DI-14.6` split in three when the measuring started: this item (the measurements), [DI-14.6.2](CHECKLIST.md) +(the decision, the engineer's) and [DI-14.6.3](CHECKLIST.md) (the serial port, blocked on a port that opens). +The table and what it shows are in [DI-D-64](DESIGN-NOTES.md#di-d-64), under "Measured for DI-14.6". The pinned +part is [device_handle_evidence.rs](../windows-namespace-request-sys/tests/device_handle_evidence.rs), six tests +over `OpenFile`'s handles, with both directions of the device-number control checked by sabotage. The candidate +named in the item, `GetFileType`, answers `DISK` for a volume and a physical disk as it does for a file, so it +does not tell the two apart; the device-number control does. `FlushFileBuffers` on `NUL` fails with +`ERROR_INVALID_FUNCTION`. The `ReadFile`/`CancelIoEx` part could not be measured: no serial port opened +repeatably, one open blocked for over 45 seconds, and the process holding it was hard to kill. + +### DI-14.6.2 -- A cached open of a handle that is not a disk file is accepted; an unbuffered one is refused as `UnknownLocation`, this crate's own narrowing, and no longer as the final-path call's error. *(completed 2026-10-18 16:44:12 -04:00)* + +The item was a decision with the engineer and was implemented on his instruction after the value of refusing had +been discussed and not ruled, so the positions are the assistant's and stand until amended. Recorded in +[DI-D-64](DESIGN-NOTES.md#di-d-64) under "What DI-14.6.2 settled". The discussion leaned toward removing the +unbuffered refusal of `NUL`; building it showed that removal would let an unbuffered `NUL` claim +`WarrantyClass::SharedDevice`, so it stays and is named. An open that blocks is not this item's +([DI-D-67](DESIGN-NOTES.md#di-d-67)). Three sabotages in `win-durable-ioring`'s manifest, one of them refused by a +`const` assertion at build. + +### DI-10.3 -- Whether a handle opened elsewhere can be given to a file target is decided: not in this version, and a device's exclusivity can be attested instead. *(completed 2026-10-18 19:23:26 -04:00)* + +Decided by the engineer, 2026-10-18, on the assistant's recommendation: [DI-D-68](DESIGN-NOTES.md#di-d-68). The item +bundled two questions. Foreign handles stay out, with a `ReOpenFile` route that needs no attestation +([DI-15.4](CHECKLIST.md), amended to take any handle) and a measured residue parked as `DI-10.6` with its blocker +named. The attestation of exclusivity needs no foreign handle, since the handle is one this crate opened, and is +`DI-10.5`. The item's own argument for `unsafe` is not followed: the class is information only, so a wrong claim +mislabels a report and changes nothing the layer does. + +### DI-10.5 -- A consumer who knows a device is theirs alone can attest it through `new_attested_exclusive`, `from_volume_attested_exclusive` and `from_disk_attested_exclusive`, and the target is then `Complete`. *(completed 2026-10-18 19:29:21 -04:00)* + +Three constructors on `Win32FileTarget`, each taking only a handle this crate opened `Unbuffered`, so a cached one is a +compile error (a `compile_fail` doctest, and a sabotage that widens the type, which only that doctest catches). One +private function makes the claim, and the domains, geometry, capabilities and access are those of the same handle +through the plain constructor, as tests of each of the three kinds compare. A declaration of domains still adds and +never removes. The constructors are not `unsafe`, for the reason in [DI-D-68](DESIGN-NOTES.md#di-d-68), which is the +assistant's position until amended. Six sabotages, one of them a control that survives. + +### DI-14.6.4 -- The open's documentation states that where a name leads is the caller's responsibility. *(completed 2026-10-18 19:35:11 -04:00)* + +The statement is on `OpenedFile::open`, which `OpenRequest::perform` points to: a name can be redirected to a device +by a reparse point in an intermediate directory or an object-manager link, no reparse flag applies to the second, an +open of a serial port's name was observed not to return, and this crate does not inspect, refuse, bound or cancel it. +It cites what `CancelSynchronousIo` is documented as, and names [DI-D-67](DESIGN-NOTES.md#di-d-67) for the sources and +the measurement; it gives no advice, and carries no figure from the measurement. Documentation only: no behaviour +changed, so no sabotage applies. + +### DI-14.6.3 -- The serial-port measurement is withdrawn: nothing decided rests on it, and what was observed is recorded. *(withdrawn 2026-10-18 19:52:00 -04:00)* + +Withdrawn, not completed. The item queued the measurements `DI-14.6.1` could not make on a serial port. The engineer +asked why they are needed at all, and nothing decided depends on them: [DI-14.6.2](#di-1462) takes cached opens of any +non-disk handle and refuses unbuffered ones with no final path, and [DI-D-67](DESIGN-NOTES.md#di-d-67) leaves a +blocking open to the caller. It was filed open in a milestone it could not be worked in, which the checklist's rule +for deferred work does not allow. The investigation found the ports are Remote Desktop redirections of the client's, +and that `CancelSynchronousIo` returned success on a blocked open there without shortening it; both are in +[DI-D-64](DESIGN-NOTES.md#di-d-64) under "Measured for DI-14.6". The unmeasured remainder is listed there with the +reason it is not queued. + +### DI-14.12 -- A caller asks what kind of handle an opened file is, of an observer, `HandleKindQuery`, and the opener does not return it. *(completed 2026-10-18 20:13:46 -04:00)* + +The engineer, 2026-10-18: "There is no need to return the type to the caller", and the `win-*-sys` crates first, then +the observer. `HandleKindQuery::of(handle)` and `perform` compose `QueryFileType` +([NR-3.9](../windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-39-filetype)), `storage_device_number` +([WV-3.1](../win-volume-sys/COMPLETED-CHECKLIST.md#wv-31)) and `volume_disk_extents` into a `HandleKind`. The shape, what +each variant claims and the measurement are in [DI-D-64](DESIGN-NOTES.md#di-d-64) under "What DI-14.12 built". Eighteen +tests, the classification over every answer and then real handles (a file, a directory, `NUL`, a named pipe, the system +volume and its disk), and seven sabotages, one of them a declared survivor. + +### DI-15.1 -- A file's identity, `FileId`, is read from an opened file or any shared handle, and the `lpFileId` row cites the `FILE_ID_DESCRIPTOR` page. *(completed 2026-10-18 23:38:29 -04:00)* + +`FileId` in `win-durable-ioring`'s `windows_backend` carries the volume serial and the 128-bit identifier that +`QueryFileIdInfo` ([NR-3.10](../windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-310-fileid)) reports, read +through the handle shared; `FileId::of(&SharedHandle)` and `OpenedFile::file_id` are the two ways in. The decision is +[DI-D-69](DESIGN-NOTES.md#di-d-69). The page was read and lists the request crate's three kinds, so +[DI-D-64](DESIGN-NOTES.md#di-d-64)'s `lpFileId` row now cites it and the ruling stands; [DI-15.2](CHECKLIST.md) was +amended to open by all three. The tests include the item's two (stable across two opens, different between two files), +and the sabotages include a control. + +### DI-15.2 -- An open by identifier, `OpenedFile::open_by_id`, takes any of the three identifier kinds, its own `ByIdOptions` and any handle as the hint, and checks a `FileId` against the file opened. *(completed 2026-10-19 00:22:26 -04:00)* + +`FileIdentifier` names the three kinds of the `FILE_ID_DESCRIPTOR` page (`Extended(FileId)`, `FileId64(u64)`, +`ObjectId([u8; 16])`); `ByIdOptions` carries what `OpenFileById` takes and [DI-D-64](DESIGN-NOTES.md#di-d-64) +exposes; `request_by_id` and `open_by_id` build and perform the open, with the path open's checks made by the same +function. An open by `FileId` reads the opened file's identity and refuses another volume's file with +`IdentityMismatch`, and `SharingViolation` now names the file by `OpenedBy`. The decision and what was observed are +[DI-D-70](DESIGN-NOTES.md#di-d-70). The observation that a directory opens by identifier without the backup flag is +queued as [DI-15.5](CHECKLIST.md) for the engineer to rule on. + +### DI-15.4 -- A reopen, `OpenedFile::reopen`, makes a new file of the type asked for from any handle through `ReOpenFile`, with its own `ReopenOptions` and the path open's checks. *(completed 2026-10-19 09:44:07 -04:00)* + +`OpenedFile::reopen` and `request_reopen` take any handle as the original and `ReopenOptions`, which carries what +the `ReOpenFile` page lists and [DI-D-64](DESIGN-NOTES.md#di-d-64) exposes. The result's alignment is its new mode's, +the checks on the reopened handle are the open's by the same function, and a sharing violation names the file by +`OpenedBy::Reopen`. The decision and what was observed are [DI-D-71](DESIGN-NOTES.md#di-d-71): every pair of an +original and a reopen follows the page's sharing rule, judged by the share-mode tests' statement of it; `NUL` reaches +`UnknownLocation`; a handle from `OpenFileById` reopens; a volume and a disk do not, unelevated. + +## Moved 2026-10-19 10:51:05 -04:00 -- DI-M15: opening by identity, and reopening + +The milestone's items are complete: `DI-15.1`, `DI-15.2` and `DI-15.4` are archived above, and `DI-15.5` below. +`DI-15.3`, opening relative to a directory handle, was deferred by the engineer and is parked in the checklist's +horizon section, not completed. + +### DI-15.5 -- A directory reached by an open by identifier is allowed, and what it does is documented. *(completed 2026-10-19 10:51:05 -04:00)* + +The engineer ruled it "harmless" after the measurements, directly and through the ring, and the assistant agreed on +the contract's first guarantee: a faulted write is covered by no durability report. A directory target faults every +read and write with the kernel's error and completes every commit. The decision and the measurements are +[DI-D-72](DESIGN-NOTES.md#di-d-72); the ring's behaviour is a test in `win-durable-ioring`, and `open_by_id`'s +documentation says it. + +## Moved 2026-10-19 11:54:55 -04:00 -- DI-M15+: handles opened elsewhere, decided + +`DI-10.3` is archived above. With `DI-10.6` decided, the parked milestone is empty and is removed. + +### DI-10.6 -- A handle opened elsewhere that `ReOpenFile` cannot convert is not accepted at this time, in any form. *(completed 2026-10-19 11:54:55 -04:00)* + +The engineer, 2026-10-19: "I say it's not accepted at this time." The residue is restrictive sharing, an access the +caller was never granted (the broker case), objects not created by `CreateFile`, and, observed in `DI-15.4`, volume +and disk handles. A consumer's declaration of such a handle's mode is not accepted. The ruling and the option that +was laid out are in [DI-D-68](DESIGN-NOTES.md#di-d-68); nothing is scheduled. + +## Moved 2026-10-19 12:04:14 -04:00 -- DI-M7+, DI-M9, DI-M10, DI-M13 and DI-M14 complete, with the open-surface order + +Every item of these milestones was already archived above when it completed; the stubs that stood in the checklist +are dropped, and what follows is the prose each section carried, moved verbatim, with headings one level deeper. +`DI-M7+` and `DI-M14` carried none. `DI-M15` was moved on 2026-10-19 10:51:05 -04:00, above. + +### Where the open-surface work goes next (read this first when picking the work back up) + +The open surface of a file ([DI-M13](#di-m13----opening-a-file-the-surface-and-its-home) to +[DI-M15](COMPLETED-CHECKLIST.md#di-151)) cannot be done in this crate alone: it +changes four others. This is the order across all of them. **No status is kept here**, because each +item's own checkbox is the status and a copy would go stale; this list is only the order and the +links. Each item below also ends with a `Next:` line pointing at the step after it. + +**Step 0, added 2026-10-15, before step 1** (the engineer ruled that volume operations belong in the +volume crate and that the error codes become one shared type; see +[WV-D-5](../win-volume-sys/DESIGN-NOTES.md#wv-d-5) and +[WE-D-1](../win-error-codes/DESIGN-NOTES.md#we-d-1)): + +- 0.1. [WE-1.1 to WE-1.3](../win-error-codes/CHECKLIST.md) -- the new `win-error-codes` crate. +- 0.2. In any order, each needing only `WE-1.1`: + [NR-4.1](../windows-namespace-request-sys/CHECKLIST.md) (request crate adopts it), + [ERR-1](../windows-file-enumeration-sys/CHECKLIST.md) (enumeration crate adopts it), and + [WV-2.1 to WV-2.5](../win-volume-sys/CHECKLIST.md) (the volume crate takes in the storage-locality + entries, in that order). +- 0.3. [NR-2.8](../windows-namespace-request-sys/CHECKLIST.md) -- the request crate deletes the + originals. **It must land before step 4's release**, or the entries are published from the crate + they do not belong in. `NR-4.1` should land before it too, so that release carries the + `io::Error` fix. + +Steps 1 to 3 do not depend on step 0 and may be done first if the order suits; step 4 does. + +1. [NR-3.3](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: the captured root and the + graft, as one internal type. Additive; nothing breaks. +2. [NR-3.4](../windows-namespace-request-sys/CHECKLIST.md), + [NR-3.5](../windows-namespace-request-sys/CHECKLIST.md) and + [NR-3.6](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: **one coupled breaking + commit.** `OpenFile` and `WatchDirectory` take a `Wtf16String`, an interior NUL is refused, + `prepare` is retired. `win-durable-ioring` and `windows-platform-probes` call `prepare` through + path dependencies, so **the same commit adapts both just enough to build**, and the request + crate's own tests and docs; a commit that leaves the workspace unbuilt is the worse outcome. +3. [NR-3.1](../windows-namespace-request-sys/CHECKLIST.md) and + [NR-3.2](../windows-namespace-request-sys/CHECKLIST.md) -- request crate: the SDDL constructor and + the `ReOpenFile` entry. Additive, independent of steps 1 and 2; may be done first. +4. **Release the request crate** (the release-please pull request, then publish). A breaking change, + so a new minor. This is a release action and not a checklist item. +5. `DI-13.1` is done: its table is [DI-D-64](DESIGN-NOTES.md#di-d-64), and `DI-13.3` onward build on + it. +6. After step 4: [PATH-1](../windows-file-enumeration-sys/CHECKLIST.md), the enumeration crate + consumes the new contract and deletes its older copy (the workspace's `M26+.3`, and + [REVIEW-1](../windows-file-enumeration-sys/CHECKLIST.md) is re-read); `DI-13.5` and `DI-13.6`, this + crate takes the release that carries the SDDL constructor and the `ReOpenFile` entry. +7. Then, here: `DI-13.7`, which is five items `DI-13.7.1` to `DI-13.7.5` (none needs another crate), `DI-M14`, + `DI-M15`. `DI-10.5` needs none of them and may be done at any point. + +If you remember only one thing: **the request crate comes first, and its breaking commit moves three +crates at once.** + +### DI-M9 -- Reviews: the engineer confirms or amends the assistant's choices (ungated; gates nothing) + +Each item is one choice the assistant made while building and the engineer has not yet confirmed. +The code binds to each already, because the steps could not be built without choosing; what is open +is the review. These reviews gate no other work (the engineer, 2026-10-10: "I cannot be the blocking +factor"). Where the engineer had already decided part of a row, that part is not here. A rejected +point is reworked as an item of the milestone current when it is rejected, with its decision row +amended. Every item carries one of three tags, which say what is asked: + +- **DECIDE** -- the choice changes what a consumer can do or must avoid, or trades against a real + alternative. The action is to accept it or amend it; "accept 9.1.1" is a complete answer. Each says + what the code does now. +- **RATIFY** -- a name, a placement, an internal shape or a test method. It stands unless the + engineer objects; "ratify 9.3.3" closes it. +- **MINE** -- not a choice: a gap or check the assistant owes. Nothing is asked of the engineer. + +A group may be answered whole ("accept all DECIDE in 9.1"). The wording of each choice as first +written, which these items condense, is in git at the revision before the one that restructured this +milestone; the decision rows named below carry the reasons. + +#### DI-9.1 -- Lineage handles ([DI-D-42](DESIGN-NOTES.md#di-d-42), from `DI-3.2.5.1`) + +#### DI-9.2 -- Gates and seals ([DI-D-43](DESIGN-NOTES.md#di-d-43), from `DI-3.2.5.2`; the refusal of a seal that closes a cycle is the engineer's own) + +#### DI-9.3 -- The provider ([DI-D-44](DESIGN-NOTES.md#di-d-44), from `DI-3.2.6`; read it with its amendments by DI-D-46 and DI-D-50) + +#### DI-9.4 -- Faults, diagnostics, the clock ([DI-3.10.3.1 to DI-3.10.3.4](COMPLETED-CHECKLIST.md)) + +#### DI-9.5 -- The backend trait and the core on it ([DI-3.10.3.5.1 to DI-3.10.3.5.5](COMPLETED-CHECKLIST.md)) + +#### DI-9.6 -- The driver on the core ([DI-3.10.3.5.6.1 to DI-3.10.3.5.6.2.6](COMPLETED-CHECKLIST.md)) + +#### DI-9.7 -- From DI-M7 on (each later step adds its own here as `DI-9.7.n`, with a tag) + +#### DI-9.8 -- Owed by the assistant + +### DI-M10 -- Target backings + +`DI-10.3` and `DI-10.6`, whether a handle opened elsewhere can be given to a file target, are decided by +[DI-D-68](DESIGN-NOTES.md#di-d-68): not in this version, and not on a consumer's declaration of its mode. + +### DI-M13 -- Opening a file: the surface and its home + +Why this milestone exists. [DI-D-60](DESIGN-NOTES.md#di-d-60) made a file target from a file this +crate opened and from nothing else, and [`FileOptions`](../win-durable-ioring/src/windows_backend/opened_file.rs) +carries only `read`, `write`, `create`, `create_new` and `truncate`. A consumer whose open needs more +(a share mode, a security descriptor, delete-on-close, an open by file ID) cannot say so, and would +want a handle from elsewhere ([DI-D-68](DESIGN-NOTES.md#di-d-68), [DI-10.6](COMPLETED-CHECKLIST.md#di-106)). +Widening what this crate can open is the structural answer: every option a consumer can pass is an +option recorded by construction. The rule throughout is documented APIs only +([copilot-instructions.md](../../.github/copilot-instructions.md)): no `Nt*`, no `Rtl*`. + +## Moved 2026-10-19 12:13:59 -04:00 -- DI-6+.3, withdrawn, on the parked work's move to PARKED.md + +The parked milestones moved from [CHECKLIST.md](CHECKLIST.md) to [PARKED.md](PARKED.md), which holds no checked +items; this withdrawn item, checked in place in `DI-M6+`, comes here instead, verbatim. + +- [x] **DI-6+.3** -- **Withdrawn: rebinding a file to a new handle is not this layer's** + ([DI-D-11](DESIGN-NOTES.md#di-d-11)). Nothing remains to do here; the obligation it left is in + DI-2.2. + +## Moved 2026-10-19 20:44:41 -04:00 -- DI-M16, the request crate handed the handles already held + +### DI-16.1 -- `win-durable-ioring` stops duplicating handles it holds. *(completed 2026-10-19 20:44:41 -04:00)* + +`HandleKindQuery::of` takes `&SharedHandle` and is infallible; `final_location` asks the final path of the +`SharedHandle` an `OpenedFile` holds. `OpenedVolume` and `OpenedDisk` had nothing to change. What was decided and +what is tested: [DESIGN-NOTES.md](DESIGN-NOTES.md#di-16-1). The item as it stood at completion follows verbatim. + +- [x] **DI-16.1** -- **`win-durable-ioring` stops duplicating handles it holds.** The request crate's `NR-6` + ([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32)) has its entries hold a `SharedHandle`. An + `OpenedFile` already holds one, yet `final_location` in + [opened_file.rs](../win-durable-ioring/src/windows_backend/opened_file.rs) and `HandleKindQuery::of` in + `handle_kind.rs` (since removed, [DI-D-73](DESIGN-NOTES.md#di-d-73)) duplicate it to ask its final path and + kind. Both take the `SharedHandle` instead and give the entry a clone; `HandleKindQuery::of` changes signature, + so this is breaking for `win-durable-ioring`, in a commit of its own. `OpenedVolume` and `OpenedDisk` are + checked for the same pattern. Tests: the existing ones, and that asking an opened file's kind makes no handle. + Lands after `NR-6.1` and before `NR-6.2`. + + > **CROSS-COMPONENT PREREQUISITE:** [NR-6.1](../windows-namespace-request-sys/CHECKLIST.md). + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/windows-namespace-request-sys` -> `NR-M6` -> + > `NR-6.2` (delete capture). See [CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md). + +## Moved 2026-10-20 17:59:38 -04:00 -- DI-17.1: HandleKind rests on what was observed + +### DI-17.1 -- `HandleKind` is restated as what was observed, and its variants as patterns of answers, not a kind any page establishes. *(completed 2026-10-20 17:59:38 -04:00)* + +The item as written: + +**Correct what `HandleKind` says it rests on.** The module doc of + `win-durable-ioring/src/windows_backend/handle_kind.rs` says "`IOCTL_STORAGE_GET_DEVICE_NUMBER`, which + the control's page says a volume or a disk answers"; neither the Win32 nor the WDK page says which + devices answer. Restate it, and the DI-14.12 paragraph in [DESIGN-NOTES.md](DESIGN-NOTES.md), as what + was observed (a volume and a physical disk answered, an ordinary file was refused, on one machine), + and say that `Volume`, `PhysicalDisk` and `DiskFile` name a pattern of answers and not a kind any + page establishes. Documentation only; behaviour does not change. + +What was done. The module doc of `handle_kind.rs` (since removed, [DI-D-73](DESIGN-NOTES.md#di-d-73)) no longer +says the control's page names the devices that answer it. It says no page does, and gives what was observed on one +machine: a volume and a physical disk answered the device-number control, an ordinary file, a directory, `NUL`, a +console and a pipe refused it, and a volume answered the extents control where a physical disk refused it. The docs of +`HandleKind` and of `DiskFile`, `Volume` and `PhysicalDisk` say they name those patterns and not a kind any page +establishes. The DI-14.12 paragraph of [DESIGN-NOTES.md](DESIGN-NOTES.md) says the same and points at +[WV-D-12](../win-volume-sys/DESIGN-NOTES.md#wv-d-12) and at `DI-17.2`, which decides what the names rest on. The +DI-14.6 measurement note's bullet "The device-number control does [tell them apart]" stated the same claim as a rule +and is restated as an observation too. No behaviour, name or signature changed. + +## Moved 2026-10-20 18:25:16 -04:00 -- DI-M17: HandleKindQuery is removed, and what a handle's kind rests on is decided + +### DI-17.2 -- `HandleKindQuery` and `HandleKind` are removed: the question they answered has no answer in the Windows API set. *(completed 2026-10-20 18:25:16 -04:00)* + +The item as written: + +**Decide what `HandleKind`'s kind rests on** (the engineer's ruling). The + documented routes WV-D-12 names, each ending in some part of the storage stack's own word: the + device's `device_type` from the device-number control; the disk and volume device interfaces + (`SetupDiGetClassDevs` with `GUID_DEVINTERFACE_DISK` / `GUID_DEVINTERFACE_VOLUME`) matched by device + number; or the kind recorded where the handle is opened, which `OpenedVolume` and `OpenedDisk` + already do for handles this crate opens. Keeping the answer pattern, with names that say so, is also + an answer, and so is leaving it as it is for now. The implementation the ruling implies is + scheduled by it as a following item. + +The engineer, 2026-10-20: "this query is a bug farm, we will never get it right because the question as stated is not +answerable within the Windows API set as far as I know. Remove the API and if that is the only way to obtain that +type, the type." So the answer to the item is none of its options: the query is removed, which makes +[DI-17.1](#di-171)'s restatement of it moot, and the work the ruling implies is done with the ruling and not scheduled. +The module and its tests, the exports of `HandleKind` and `HandleKindQuery`, the `StorageDeviceNumber` and `FileType` +re-exports that only it needed, the `win-pipes-sys` dev-dependency, and nine sabotage entries are deleted from +`win-durable-ioring`. The reasoning and what stays are [DI-D-73](DESIGN-NOTES.md#di-d-73). The review's finding is +discharged by removal: `win-volume-sys` makes no claim about what a handle is +([WV-D-12](../win-volume-sys/DESIGN-NOTES.md#wv-d-12)), and nothing here makes one now. + +## Moved 2026-10-20 19:26:09 -04:00 -- DI-11.1: the in-memory backend faults on a script + +### DI-11.1 -- The in-memory backend's targets and the backend itself fault on a script: failed, held, late, out of order, short, refused. *(completed 2026-10-20 19:26:09 -04:00)* + +The item as written: + +**An in-memory backend whose targets and backend fault on a script** + ([DI-D-50](DESIGN-NOTES.md#di-d-50) P13), public and deterministic, in the portable crate: an + operation failed, held, or completed late or out of order; a submission refused; a full queue; a + short count, to test a backend's short-write policy. It answers the problem that flush failure + cannot be produced on a healthy machine, for consumers' failure paths and not only this crate's, + in the spirit of [RESPONSE-SPACE.md](../windows-ioring-sys/RESPONSE-SPACE.md), and is the basis + for unit testing and driving the core (the engineer: "In-memory is a good basis for unit testing + and driving"). It does not replace DI-4.1. + +What was done. [memory/script.rs](src/memory/script.rs) holds `FaultScript`, `When`, `Fault`, `ScriptOp` and `Fired`, and +[memory.rs](src/memory.rs) runs them: `MemoryBackend::with_script` takes a script, and `held`, `release`, `release_all` +and `fired` let a test hold completions, release them in an order it chooses, and see what fired. The faults are a refused +push, a failed or failed-after-applying write, read or commit, a short count, a held or late completion, a refused +doorbell and a failed pop. A held completion counts against the completion queue. Recorded as +[DI-D-74](DESIGN-NOTES.md#di-d-74), which also names what is not scripted and why. Tests: those of +`memory/script/tests.rs`, those driving the core in [scripted.rs](src/io_core/tests/scripted.rs), and sabotage +entries, with a control, in [sabotage.json](sabotage.json). It does not replace `DI-4.1`. + +## Moved 2026-10-20 19:53:07 -04:00 -- DI-M11: the core driven by script + +### DI-11.2 -- Both paths for fault injection stay: the in-memory script and the Windows backend's seam are kept. *(completed 2026-10-20 19:53:07 -04:00)* + +The item as written: + +**The fault seam's merge-or-delete decision**, once `DI-11.1` is proven: the + `fault-injection` feature's `fail_next_flush`, and the Windows backend's `fail_next_commit` added + beside it in `DI-3.10.3.5.5`, duplicate what the in-memory backend scripts + ([DI-D-50](DESIGN-NOTES.md#di-d-50)), and stay until then under the duplicate-then-decide rule. + +The engineer, 2026-10-20: "I think it's a good idea to keep both paths for fault injection." So the seam is neither +merged into the script nor deleted, and the duplicate-then-decide rule's decision is taken: the two are different layers, +not a duplicate to be collapsed. The account of why, and the one question left unruled (whether tests that only check +the core's answer to a failure move to the portable crate), are [DI-D-75](DESIGN-NOTES.md#di-d-75). Nothing in the code +changed beyond the pointer in `Dioring::fail_next_flush`'s documentation. + +## Moved 2026-10-20 20:46:51 -04:00 -- DI-12.1.1: the delay events in the portable core + +### DI-12.1.1 -- The delay events are in the portable core: lock waits, flush issue and answer, and a seal's wait, built only for a sink that wants them. *(completed 2026-10-20 20:46:51 -04:00)* + +The item as written: + +**DI-12.1.1** -- **The delay events in the portable core.** New `DiagnosticKind` and `Diagnostic` variants + (both are `#[non_exhaustive]`), reported to the instance's sink and built only when the sink `wants` the kind, with + no clock read before that answer. *Lock waits* follow the engineer's pattern ([DI-D-26](DESIGN-NOTES.md#di-d-26)): + try-lock; only if that fails, read the clock, block, and on acquiring record the wait; emit after releasing, so the + event does not lengthen the hold it measures; one wrapper type, a name per lock, poisoning handled separately. + *Flushes* are timed at the durability-provider boundary: the call issued, and each domain's outcome with how long + it took, so an event names the slow domain. *A seal's other phases*, waiting for covered writes and for gated writes + to be released, are the secondary events the engineer allowed. How the generic epoch id is rendered is settled in + this step and recorded. `StderrDiagnostics` prints each. Tests: each event with a recording sink, the lock-wait + pattern by an uncontended lock (no event, no clock read) and a contended one, and a sink that wants nothing costing + no clock read. + +What was built, and the choices it left to its implementation, are [DI-D-77](DESIGN-NOTES.md#di-d-77). The lock waits are +timed by a `LockWatch` beside the engine's slot, and `StampClock` gained a required `elapsed` method, a breaking change. + +## Moved 2026-10-20 22:17:44 -04:00 -- DI-12.1.2: win-debugging-sys is created + +### DI-12.1.2 -- `win-debugging-sys` is created, with `output_debug_string` and `output_debug_str` over `OutputDebugStringW`, and a NUL refused as the crate's own error. *(completed 2026-10-20 22:17:44 -04:00)* + +The item as written: + +**DI-12.1.2** -- **Create `win-debugging-sys`, with `OutputDebugStringW`.** The name is the engineer's, + 2026-10-20 ("how about win-debugging-sys?"). The crate's notes record, at creation, the naming decision with that + quotation, the Learn section the snapshot files `OutputDebugString` under (*Debugging Functions*, in + [DI-D-76](DESIGN-NOTES.md#di-d-76)), and that a `-sys` crate here is memory-safe over the call and adds no + policy. The first wrapper takes a string and writes it. A NUL cannot be passed in the call's terminated string, so a + string holding one is refused as the crate's own narrowing, with an error kind of its own and not framed as the call's + refusal. Registered with the workspace, release-please and the publish workflow, as `win-text-sys` was. Tests: + ordinary and empty strings, a NUL, a long string, and the call made. + +What was built is in the crate's [DESIGN-NOTES.md](../win-debugging-sys/DESIGN-NOTES.md): the name and where the function is +filed are [WD-D-1](../win-debugging-sys/DESIGN-NOTES.md#wd-d-1), and the shape, the one refusal and how the call is +observed are [WD-D-2](../win-debugging-sys/DESIGN-NOTES.md#wd-d-2). The call is observed by a debugger the test starts, +over the documented debugger functions, and not by the shared-section protocol that no page describes. The crate is a new +workspace member, registered for release-please and the publish workflow as `win-text-sys` was. + +## Moved 2026-10-20 22:32:55 -04:00 -- DI-12.1.3: the debugger sink + +### DI-12.1.3 -- `DebugOutputDiagnostics` writes each event as a `dioring:` line to the debugger, through `win-debugging-sys`, and wants every kind. *(completed 2026-10-20 22:32:55 -04:00)* + +The item as written: + +**DI-12.1.3** -- **`DebugOutputDiagnostics` in `win-durable-ioring`**, through `win-debugging-sys`: each event as + a `dioring:` line, wanting every kind. Tests: the formatting apart from the emission, and the emission observed by a + documented route, which this step records. + +What was built and chosen is [DI-D-78](DESIGN-NOTES.md#di-d-78). The emission is observed by a debugger the test starts, through +the `test-util` feature of `win-debugging-sys` ([WD-D-3](../win-debugging-sys/DESIGN-NOTES.md#wd-d-3)), which is the one copy +of that harness. `FlushPart` was exported from the portable crate on the way, and the line prefix became one constant. + +## Moved 2026-10-20 22:54:21 -04:00 -- DI-12.1.4 and DI-12.1.4.1: win-etw-sys is created and its session tests run + +### DI-12.1.4 -- `win-etw-sys` is created: a `Registration` over `EventRegister`, `EventUnregister`, `EventEnabled`, `EventProviderEnabled` and `EventWrite`, with an event's data built from typed fields. *(completed 2026-10-20 22:54:21 -04:00)* + +The item as written: + +**DI-12.1.4** -- **A safe wrapper for the event-tracing provider functions** (register, unregister, whether a + session is listening, write an event), in `win-etw-sys` (the engineer, 2026-10-20: "win-etw-sys is good"). The step reads each function's page and records where it is documented before it names a section + ([DI-D-76](DESIGN-NOTES.md#di-d-76)). The registration is owned and unregistered when dropped, and an event's data is + built from typed fields without `unsafe` for the caller. Tests: against a real session started by a documented + route, or by what the pages let a test observe, which the step records. + +The crate's name and where Learn files the functions, which the snapshot omits, are +[WE-D-1](../win-etw-sys/DESIGN-NOTES.md#we-d-1); the shape and the two kinds of failure are +[WE-D-2](../win-etw-sys/DESIGN-NOTES.md#we-d-2); the session facility the tests use, and why those tests are ignored without +privilege, are [WE-D-3](../win-etw-sys/DESIGN-NOTES.md#we-d-3). + +### DI-12.1.4.1 -- `win-etw-sys`' session tests were run elevated and pass, and what they observed is recorded in the crate's notes. *(completed 2026-10-20 22:54:21 -04:00)* + +The item as written: + +**DI-12.1.4.1** -- **Run `win-etw-sys`' session tests on a machine that can start a trace session, and record what + they observed.** `tests/session.rs` starts a real session, which needs an elevated process or the Performance Log Users + group; the development machine has neither, so the harness and the tests were written and not run + ([WE-D-3](../win-etw-sys/DESIGN-NOTES.md#we-d-3)). Run + `cargo test -p win-etw-sys --test session -- --include-ignored` where a session can be started, fix what it shows, and + record in that note what a session recorded, what the call made of 129 blocks, and what it returned for an event over + 64 KB. `DI-12.1.4` is done when this is. + +The tests were run from a process elevated by a prompt the engineer approved, twelve times in a row after the first run's +three failures were fixed. The first run found a real defect: an event with no fields was passed with the pointer an empty +vector holds, and the call refused it. What the sessions recorded, and what the call made of 129 blocks and of an event +over 64 KB, are in [WE-D-3](../win-etw-sys/DESIGN-NOTES.md#we-d-3). + +## Moved 2026-10-20 23:12:00 -04:00 -- DI-12.1.5: durable-ioring-events + +### DI-12.1.5 -- `durable-ioring-events` is created: the provider, its manifest rendered from one table of events, and `EtwDiagnostics`, which wants a kind only when a session listens. *(completed 2026-10-20 23:12:00 -04:00)* + +The item as written: + +**DI-12.1.5** -- **Create `durable-ioring-events`: the provider, its manifest and `EtwDiagnostics`.** The provider is + named `durable-ioring-events` (the engineer, 2026-10-20: "Change the name of the event source to + durable-ioring-events", having first said "win-dioring, I'd put it in its own crate"), with a GUID generated + once and recorded in the crate's notes. Its manifest ships with the crate and maps each event of `DI-12.1.1` + one-to-one. It records whether or not the manifest is installed ("you don't actually have to install the manifest to + record events"); the crate's documentation says that a reader needs it to see names and fields, and how to install it. + An event is built only when a session is listening, so it costs nothing otherwise. Registered with the workspace, + release-please and the publish workflow. Tests: each event reaches a session, and none is built when nobody listens. + + > **CROSS-COMPONENT PREREQUISITE:** `DI-12.1.1` and `DI-12.1.4`, in this checklist. + +The name and the GUID are [DE-D-1](../durable-ioring-events/DESIGN-NOTES.md#de-d-1), the events [DE-D-2](../durable-ioring-events/DESIGN-NOTES.md#de-d-2), +the manifest and how it was checked with the real tools [DE-D-3](../durable-ioring-events/DESIGN-NOTES.md#de-d-3), and the sink +[DE-D-4](../durable-ioring-events/DESIGN-NOTES.md#de-d-4). The session tests were run elevated and pass, and the manifest was +installed and the events decoded by `tracerpt`. + +## Moved 2026-10-20 23:37:27 -04:00 -- DI-12.1.6 and DI-12.1: the driver's default sink, and the delay events are done + +### DI-12.1.6 -- `win-durable-ioring`'s default sink is `EtwDiagnostics`, so an instance built with none writes its delay events to ETW, and a sink given replaces it. *(completed 2026-10-20 23:37:27 -04:00)* + +The item as written: + +**DI-12.1.6** -- **`win-durable-ioring`'s default sink is `EtwDiagnostics`**, so a consumer who chooses nothing + still gets the events ([DI-D-51](DESIGN-NOTES.md#di-d-51) T4), and any sink, `NoDiagnostics` included, can be passed + when the driver is built. Tests: the default, and a chosen sink replacing it. + +The choices are [DI-D-79](DESIGN-NOTES.md#di-d-79). The default is observed without privilege (a process registers the provider +for an instance with no sink and not for one with a sink of its own) and with it (the instance's flush events reach a session). + +### DI-12.1 -- The delay events are emitted as typed events the core reports to its sink, and the Windows sinks exist: the debugger sink, the ETW provider in its own crate, and the ETW sink as the driver's default. *(completed 2026-10-20 23:37:27 -04:00)* + +The item as written: + +**DI-12.1** -- **Emit the delay events** DI-2.11 designs, as typed events the core reports to its diagnostic + sink, and **the Windows sinks** ([DI-D-51](DESIGN-NOTES.md#di-d-51)). Restructured on 2026-10-20 into `DI-12.1.1` to + `DI-12.1.6` after the engineer's answers ([DI-D-76](DESIGN-NOTES.md#di-d-76)); it is done when they all are. What + DI-2.11 left to implementation is settled, and each step carries its part: + - **Identity per event** is the instance plus what the event is about: a lock wait names the lock, a flush the + lineage, the epoch and the domain. + - **The time base** is interrupt time, dioring's one ([DI-D-37](DESIGN-NOTES.md#di-d-37)). + - **The provider** is `durable-ioring-events`, in a crate of its own, and records whether or not its manifest is installed. + +It was restructured into six steps on 2026-10-20 after the engineer's answers +([DI-D-76](DESIGN-NOTES.md#di-d-76)), and each is archived: the core's events +([DI-12.1.1](COMPLETED-CHECKLIST.md#di-1211), [DI-D-77](DESIGN-NOTES.md#di-d-77)), the debugger crate and sink +([DI-12.1.2](COMPLETED-CHECKLIST.md#di-1212), [DI-12.1.3](COMPLETED-CHECKLIST.md#di-1213), [DI-D-78](DESIGN-NOTES.md#di-d-78)), +the ETW wrapper ([DI-12.1.4](COMPLETED-CHECKLIST.md#di-1214)), the provider crate +([DI-12.1.5](COMPLETED-CHECKLIST.md#di-1215)) and the default ([DI-12.1.6](COMPLETED-CHECKLIST.md#di-1216)). + +## Moved 2026-10-21 13:57:42 -04:00 -- DI-12.2.1: the router's build + +### DI-12.2.1 -- `RoutingProvider` is built by `RouterBuilder` from registered providers, serving the union of their own domains, and a domain claimed twice refuses the build naming both registrations. *(completed 2026-10-21 13:57:42 -04:00)* + +The item as written: + +**DI-12.2.1** -- **The router and its build.** A `RoutingProvider` in `durable-ioring`, itself a `DurabilityProvider`, + built from providers registered with it; its `domains()` is the union of theirs, read once as the trait says. A domain + routed to two providers is refused at build, naming the domain and both registrations: earlier and clearer than the core's + `DuplicateDomain` for a single provider. A registration takes a provider and no domains: what it serves is its own + `domains()` ([DI-D-81](DESIGN-NOTES.md#di-d-81)). Tests: each of one, + two and many providers, a provider that serves no domain, a router with no provider, a domain routed twice by adjacent and by + distant registrations, and the union's order. + +Built in `src/router.rs`; the choices are [DI-D-82](DESIGN-NOTES.md#di-d-82). The router does not yet implement +`DurabilityProvider`, whose `make_durable` is [DI-12.2.2](CHECKLIST.md)'s dispatch, so the trait impl moved to that step. + +## Moved 2026-10-21 14:09:11 -04:00 -- DI-12.2.2: the router's dispatch + +### DI-12.2.2 -- `RoutingProvider` is a `DurabilityProvider`: each provider with a part of a seal is called once, in registration order, with its completions untouched, a panic abandons the parts not yet handed on, and a domain no provider serves is failed with `Unrouted`. *(completed 2026-10-21 14:09:11 -04:00)* + +The item as written: + +**DI-12.2.2** -- **Dispatch, and the router as a provider.** `RoutingProvider` implements `DurabilityProvider`, its + `domains()` the union `DI-12.2.1` built; it was not implemented there, because `make_durable` is this step's dispatch. `make_durable` splits a request by the provider that owns each domain and makes one call + per provider that has parts, in registration order and with each provider's parts in the request's order. Each + `DomainCompletion` is handed to its owner unchanged, so a domain's outcome returns as it arrives and nothing is buffered or + re-answered. A panic in one provider propagates, as the trait says, and the providers not yet called have their completions + dropped, which is `ProviderAbandoned` for their domains. A domain no provider owns cannot come from the core; it fails that + domain and is not a panic. Tested alone against fake providers that answer inline, answer late from another thread, never + answer, drop their completions, and panic, with fake completion homes, and with several domains per provider. + +Built in `src/router.rs`, tested against fake providers and a fake completion home in `src/router/tests/dispatch.rs`; +the choices are [DI-D-83](DESIGN-NOTES.md#di-d-83). + +## Moved 2026-10-21 14:44:32 -04:00 -- DI-12.2.3: what the router reports + +### DI-12.2.3 -- The router reports to no diagnostic sink: a domain no provider serves is a caller breaking the protocol, panicked on where it is found, and the panic is its only report. *(completed 2026-10-21 14:44:32 -04:00)* + +The item as written: + +**DI-12.2.3** -- **What the router reports.** BLOCKED on the first open question of `DI-12.2`: whether it reports an + inconsistency for a domain no provider owns, and how it knows the instance's number. When answered this becomes that one + report or the decision that it reports nothing; until then nothing is built, and this is recorded as a blocker and not as + "no consumer". It reports no timing ([DI-D-80](DESIGN-NOTES.md#di-d-80)). + +Answered by the engineer, 2026-10-21, after the assistant traced how a domain could reach the router unrouted -- only through +a driver building requests wrongly, a caller outside an instance, or a provider forwarding to a router domains it does not +serve -- and pointed out that [DI-12.2.2](COMPLETED-CHECKLIST.md#di-1222)'s failing such a domain with `Unrouted` went against +[DI-D-46](DESIGN-NOTES.md#di-d-46) and [DI-D-59](DESIGN-NOTES.md#di-d-59): "I agree, these exogenous messages should panic and +it's the only report." `make_durable` now panics, naming the domain, before any provider is called, and `Unrouted` is gone. +The decision is [DI-D-83](DESIGN-NOTES.md#di-d-83), corrected. + +## Moved 2026-10-21 14:56:18 -04:00 -- DI-12.2.4 and DI-12.2: the routing provider is done + +### DI-12.2.4 -- `RoutingProvider` is proven through a real instance over two providers, and the driver's README and API.md say how to build one. *(completed 2026-10-21 14:56:18 -04:00)* + +The item as written: + +**DI-12.2.4** -- **The router through the driver, and its record.** A router given as `Setup::provider` to a real + instance over two fake providers: a seal that touches both is one call to each; a file whose domains span providers is durable + only when every one succeeds (DI-2.12 point 5); one provider failing fails only its own domains; coupled domains split across + providers are correct and cost physical flushes, documented and not enforced. The README and + [API.md](API.md) say how to build one; the decision is recorded in [DESIGN-NOTES.md](DESIGN-NOTES.md); the sabotage manifest + gets entries and a control. + +The instance-level tests are in `win-durable-ioring`'s `src/dioring/tests/providers.rs`, under the routing provider's heading, +and the sabotage entries that patch the router and run only that crate's suite are in its manifest. The README's example is +compiled and run as a doctest. The record is [DI-D-84](DESIGN-NOTES.md#di-d-84). + +### DI-12.2 -- The routing provider: `RoutingProvider`, which composes several providers as the instance's one, each serving the domains it declares. *(completed 2026-10-21 14:56:18 -04:00)* + +The item as written: + +**DI-12.2** -- **The routing provider** (DI-2.12 point 1, which called it `DI-3+.8`): a provider dioring ships that + dispatches each seal's work by flush domain to providers registered with it when it is built, refuses a domain routed + twice at build time, and passes each domain's outcome back as it arrives. The core holds the built-in default and at most + one consumer-supplied provider ([DI-2.12](COMPLETED-CHECKLIST.md#di-212), the engineer, 2026-10-06: "do your clean form of + b"), so the router is that one provider, composed of others; it is portable and lives in `durable-ioring`. Restructured on + 2026-10-20 into `DI-12.2.1` to `DI-12.2.4`, because it is four pieces that can each be built and checked alone and one of + them is blocked; it is done when they all are. The gate this item carried, the provider trait, is met: `DurabilityProvider` + and `DomainCompletion` exist. The router measures nothing: timing comes from paired begin and end events with correlation + ids, which the sinks and their analysis tools use ([DI-D-80](DESIGN-NOTES.md#di-d-80), [DI-M19](#di-m19----paired-begin-and-end-events-with-correlation-ids)). + A registered provider serves exactly the domains its own `domains()` returns ([DI-D-81](DESIGN-NOTES.md#di-d-81)). A + domain no provider serves is panicked on, and the panic is the router's only report ([DI-D-83](DESIGN-NOTES.md#di-d-83)). + +Its four steps are archived: the build ([DI-12.2.1](COMPLETED-CHECKLIST.md#di-1221), [DI-D-82](DESIGN-NOTES.md#di-d-82)), +dispatch ([DI-12.2.2](COMPLETED-CHECKLIST.md#di-1222)), what it reports ([DI-12.2.3](COMPLETED-CHECKLIST.md#di-1223), +[DI-D-83](DESIGN-NOTES.md#di-d-83)), and the router through the driver ([DI-12.2.4](COMPLETED-CHECKLIST.md#di-1224), +[DI-D-84](DESIGN-NOTES.md#di-d-84)). + +## Moved 2026-10-21 15:28:42 -04:00 -- DI-12.3 withdrawn, and DI-M12 is complete + +### DI-12.3 -- Withdrawn: there is no merge-or-delete decision between the `epoch_log` sample and this crate, and a sample log built on this crate is queued as DI-M21. *(completed 2026-10-21 15:28:42 -04:00)* + +The item as written: + +**DI-12.3** -- **The `epoch_log` merge-or-delete decision**, once DI-3.2 is proven + ([DI-D-7](DESIGN-NOTES.md#di-d-7)). + + > **-> UNBLOCKS PARKED WORK:** completing `DI-M12` (this item, `DI-12.1` and `DI-12.2`) unblocks + > [DI-M5+](PARKED.md#di-m5----the-retention-layer-parked-behind-di-m12) and + > [DI-M6+](PARKED.md#di-m6----deliberately-coarse-parked-behind-di-m12) in [PARKED.md](PARKED.md). + +Withdrawn by the engineer, 2026-10-21, after explaining how the sample came to be: "I meant to be developing the durable +ioring as an example. there was a miscommunication [...] and instead the epoch_log example was developed which was +interesting. [...] I preserved it because while a common log driven by epoch based durability notations is useful it's not +what I wanted at the time and I assumed we could revisit it and retarget the effort onto the durabliity ioring". Asked +whether to map the sample onto this crate: "I don't think you need to really refer back to epoch_log at all other than +incidentally for any common log facility based on the durable ioring. Just survey common practices for database/filesystem +[...] logs and provide it as a sample." The decision is [DI-D-85](DESIGN-NOTES.md#di-d-85); the work is +[DI-M21](CHECKLIST.md#di-m21----a-sample-log-on-durable-ioring). + +### DI-M12 is complete + +Every item of `DI-M12` is archived: the delay events and their sinks ([DI-12.1](COMPLETED-CHECKLIST.md#di-121)), the routing +provider ([DI-12.2](COMPLETED-CHECKLIST.md#di-122)), this withdrawal, and the pinned dependencies +([DI-12.4](COMPLETED-CHECKLIST.md#di-124)). Its completion met the gate on `DI-M5+` and `DI-M6+` in +[PARKED.md](PARKED.md); no item there was graduated, since which to take up is the engineer's to choose, and each section now +says so. + +## Moved 2026-10-21 15:36:54 -04:00 -- DI-18.1: the fault seam's census + +### DI-18.1 -- Every test and example in `win-durable-ioring` that arms the Windows fault seam is censused by a recorded search and classified *core*, *windows* or *both*, with the reason for each. *(completed 2026-10-21 15:36:54 -04:00)* + +The item as written: + +**DI-18.1** -- **Census every test and example that arms the Windows fault seam, and classify each.** The census is + produced by a search recorded with it, for `fail_next_flush` and `fail_next_commit` across `win-durable-ioring`'s + source, tests and README, and not from recollection. Each is one of: *core* -- its assertions are about how the core + answers a failed flush (an epoch blocks, which writes are suspect, a heal, a lineage, a marking, a stamp, how a + lineage ends) and name nothing the Windows driver adds; *windows* -- its assertions are about what the driver adds (a + Win32 code reaching its `ErrorCode` and back through `win32_code_of`, the real ring, the delivery, a worked example in + the README); or *both*, which is split. The classification is recorded beside the census, with the reason for each. + +**The search**, run at commit `8ae3780e` and recorded so it can be run again, not trusted: + +``` +git --no-pager grep -n -E "fail_next_flush|fail_next_commit" -- crates/win-durable-ioring +git --no-pager grep -n -w failed_seal -- crates/win-durable-ioring/src +``` + +The first finds each call; a call inside a helper was followed to the tests that call the helper with the second, since +`failed_seal` in `failures.rs` arms the seam for five tests. The hits that are not a use are the seam itself -- +`Dioring::fail_next_flush` in `dioring.rs` and `fail_next_commit` in `windows_backend.rs` -- and a comment in `Cargo.toml`. No +crate outside `win-durable-ioring` arms it. + +**The census and its classification.** *core*: the assertions are about how the core answers a failed flush and name nothing +the driver adds. *windows*: they are about what the driver adds. *both*: split, the Windows half staying. + +| File (under `crates/win-durable-ioring/src/`) | Test | Class | Why | +|---|---|---|---| +| `dioring/tests/failures.rs` | `a_failed_flush_is_reported_with_its_cause_and_holds_the_mark` | *both* | Windows half: the cause is relayed from the ring, `source` `"windows-ioring"` and the `HRESULT` read back through `as_ioring_error`. Core half: the suspects, no high-water mark, the epoch `Blocked`, a re-seal `AlreadySealed`, the inventory, one answer. | +| `dioring/tests/failures.rs` | `a_healed_failure_is_passed_at_the_first_seal_after_the_heal` | *core* | A write to a sealed epoch refused, then `Healed` before any `Durable` at the next seal and the inventory empty: CONTRACT.md's healing example, nothing Windows adds. | +| `dioring/tests/failures.rs` | `without_the_heal_a_later_seal_is_answered_blocked` | *core* | A later seal answered `Blocked` by the failure, then `Abandoned` and both epochs' `Durable`: how the core answers. | +| `dioring/tests/failures.rs` | `a_failed_flush_reaches_only_files_sharing_a_declared_domain` | *core* | Which writes a failure suspects, by declared domain. | +| `dioring/tests/failures.rs` | `a_resolution_naming_another_instances_failure_is_refused_whole` | *core* | A resolution naming a foreign token refused whole, nothing abandoned. The seam only makes a failure to hold; an import would do. | +| `dioring/tests/failures.rs` | `a_closed_failure_stays_and_its_token_can_be_taken_again` | *core* | A closed token, the inventory's `token_live`, `take_token`, then abandoning. | +| `dioring/tests/failures.rs` | `a_write_into_an_abandoned_epoch_is_refused_with_what_it_took` | *core* | Pushes into an abandoned or sealed epoch refused with their buffer and context. Its registered-buffer half needs the in-memory backend to take registered buffers, which `DI-18.2` checks. | +| `dioring/tests/failures.rs` | `an_armed_failure_is_used_by_its_own_file_only` | *windows* | It checks the seam itself: an armed failure is used by the flush of its own file and no other. The seam stays (DI-D-75). | +| `dioring/tests/ending.rs` | `a_failure_suspecting_a_write_holds_its_file_until_it_is_resolved` | *core* | `remove_target` refused while a failure suspects a write to the file, and allowed once it is abandoned. | +| `dioring/tests/ending.rs` | `closing_hands_back_the_unresolved_failures` | *core* | What `close` hands back: the unresolved failure and the lineage's high-water mark. `DI-18.2` checks that the in-memory driver's close hands back the same. | +| `delivery/tests.rs` | `a_failure_resolved_through_the_handle_is_delivered_with_what_follows_it` | *windows* | It checks `EntryDelivery`, the driver's delivery on the thread pool, and resolving through its handle from the handler; the failure is only the vehicle. | +| `dioring/tests/lineages.rs` | `a_failure_spanning_two_lineages_heals_in_each_at_its_own_seal` | *core* | A failure suspecting a write of each of two lineages, healed in each at its own seal (DI-D-41). | +| `dioring/tests/markings.rs` | `a_heal_reports_its_record_with_the_markings_its_failure_gained` | *core* | A healing failure gains a `Covered` marking for a later flushed write, and `Healed` reports it. | +| `dioring/tests/markings.rs` | `a_failed_flushs_code_reads_without_the_ring_crate` | *windows* | `win32_code_of` and `error_code_of` read the Win32 code back from the fault without the ring crate. | +| `dioring/tests/stamps.rs` | `an_instance_given_a_clock_stamps_a_failed_flush_with_its_reading_when_observed` | *core* | A failure's `observed` stamp is the clock's reading when it was observed, in the event and in the inventory. The core's test clock serves in place of the driver's interrupt-time one. | +| `windows_backend/tests.rs` | `an_armed_commit_failure_reports_the_code` | *windows* | The backend's own seam, `fail_next_commit`: only a commit is failed, its source and code, used once. | +| `crates/win-durable-ioring/README.md` | "Healing a failure" | *windows* | A worked example of the driver; the README's examples stay (DI-D-75). | +| `crates/win-durable-ioring/README.md` | "Failures and epochs are many-to-many" | *windows* | As above. | +| `crates/win-durable-ioring/README.md` | "Failures and epochs are many-to-many", its second ending (heal F1, abandon F2) | *windows* | As above; it arms the seam in hidden lines. | + +This is a record of the tree at `8ae3780e`. `DI-18.2` works from it, and re-runs the search first, since a test added since is +not in it. + +## Moved 2026-10-21 17:12:31 -04:00 -- DI-18.2: the core's failure tests, over the script + +### DI-18.2 -- Each *core* test that armed the Windows fault seam is moved to `durable-ioring` over the in-memory script with its name and assertions, and every sabotage entry the originals caught is still caught without them. *(completed 2026-10-21 17:12:31 -04:00)* + +The item as written: + +**DI-18.2** -- **Move each *core* test to `durable-ioring` over the script, with the same assertions.** The + census and its classification are [DI-18.1's record](COMPLETED-CHECKLIST.md#di-181). It goes + where the behaviour lives (`io_core/tests` or `durability/tests`), built on the in-memory backend's script and not on + a detached target. A *both* test keeps its Windows half where it is. Nothing is deleted yet: the moved test must pass + in its new place first. For each, the sabotage entries it was the catching test for, in either crate's manifest, are + still caught once it has moved, by it or by another test, which is run and not assumed. + +**What moved.** The search of [DI-18.1](COMPLETED-CHECKLIST.md#di-181) was re-run first and found the census unchanged. The +eleven *core* tests and the core half of the one *both* test are in `src/io_core/tests/flush_failures.rs`, each named as it +was and each saying which file it came from. Each flush fails by the script's step for the named file's first commit, which +is the seam's "fail the next flush of this file" for every test here, since in each the armed flush was the file's first. +Where the Windows harness supplied a value of its own the rig's stands in -- the operations' contexts, and the fault's source -- +and the stamp test drives a core over the hand-moved clock directly, since the rig's clock counts its own readings. The +registered-buffer half of the abandoned-epoch test runs on the in-memory backend's registered buffers, and the core's `close` +hands back what the driver's did. Nothing was deleted. + +**The sabotage check, run.** Both crates' manifests were swept in full at commit `306d8cfb`, with the moved tests and the +originals both present, so that each entry's record names every test that failed under it. `durable-ioring`'s manifest runs +both crates' suites; `win-durable-ioring`'s runs only its own, where the moved tests are not. Every entry in both behaved as +declared. Of the entries an original catches, every one is caught by some other test too, so deleting the originals loses none; +one of them -- `DI-3.2.7: close hands back no failure` -- is caught by no test but the original and its moved copy, so the move +is what keeps it caught. The entries whose record names no test were each read: those that failed to build are caught before any +test runs, and in the transcript of each that hung or crashed, every original reported `ok`, so none of them was caught by an +original either. + +## Moved 2026-10-21 17:36:11 -04:00 -- DI-18.3, and DI-M18 is complete + +### DI-18.3 -- The moved tests are deleted from `win-durable-ioring`, the *both* test keeps its Windows half, and every sabotage entry the originals caught is still caught. *(completed 2026-10-21 17:36:11 -04:00)* + +The item as written: + +**DI-18.3** -- **Delete the moved tests from `win-durable-ioring`.** What remains there is the *windows* set. The + seam itself and the README's examples stay ([DI-D-75](DESIGN-NOTES.md#di-d-75)). Both crates' tests pass, and the + sabotage entries checked in `DI-18.2` are still caught. + +The eleven *core* tests of [DI-18.1](COMPLETED-CHECKLIST.md#di-181)'s census are deleted from `win-durable-ioring`, with the +helpers and imports only they used. The *both* test keeps its Windows half -- the flush's failure relayed from the ring, its +source and its `HRESULT` -- as `a_failed_flush_is_reported_with_the_rings_cause`. Re-running the census search finds the seam +armed only by the *windows* set, that test's helper, and the README's examples, which stay with the seam +([DI-D-75](DESIGN-NOTES.md#di-d-75)). Both crates' tests pass. + +**The sabotage check, run.** Every entry [DI-18.2](COMPLETED-CHECKLIST.md#di-182) found an original catching, in either crate's +manifest, was swept again with the originals gone, with each manifest's controls beside them, from filtered copies of the two +manifests so as not to sweep the rest. Every entry behaved as declared: each is still caught, and each control survived. +`DI-3.2.7: close hands back no failure` is now caught by `io_core::tests::flush_failures::closing_hands_back_the_unresolved_failures` +alone, the moved copy. + +### DI-M18 is complete + +Its three items are archived: the census ([DI-18.1](COMPLETED-CHECKLIST.md#di-181)), the move +([DI-18.2](COMPLETED-CHECKLIST.md#di-182)) and the deletion ([DI-18.3](COMPLETED-CHECKLIST.md#di-183)). A failure test now lives +at the layer that owns what it checks: how the core answers a failed flush, in `durable-ioring` over its in-memory script; what +the Windows driver adds, in `win-durable-ioring` over its seam. + +## Moved 2026-10-21 17:41:53 -04:00 -- DI-19.1: the correlation id + +### DI-19.1 -- `CorrelationId`, a process-scoped `u64` from one relaxed atomic counter, minted only by `CorrelationId::for_pair`, which asks the sink first. *(completed 2026-10-21 17:41:53 -04:00)* + +The item as written: + +**DI-19.1** -- **A process-scoped correlation id in the core.** A `u64` minted from one static `AtomicU64` by a + `fetch_add` of one with relaxed ordering, in `durable-ioring`, minted only when a begin is built, so a sink that wants + nothing mints nothing. Tests: ids minted from many threads at once are all distinct, ids from one thread rise, and a + mint with no sink wanting an event takes none. + +Built in `src/correlation.rs` and exported from the crate. Nothing builds a begin yet, so the rule that an id is minted only +when a begin is built is carried by the one way to mint, which asks the sink whether it wants the kind; [DI-19.2](CHECKLIST.md) +calls it as it builds each begin. The choices are [DI-D-86](DESIGN-NOTES.md#di-d-86). + +## Moved 2026-10-21 18:18:31 -04:00 -- DI-19.2.1: the pair variants and the pairing checker + +### DI-19.2.1 -- The seal, flush and lock-wait pair variants and kinds are in `Diagnostic` beside the old ones, `Diagnostic::pairing` says which end of which pair an event is, and `PairingChecker` holds the pairing rule. *(completed 2026-10-21 18:18:31 -04:00)* + +The item as written: + +**DI-19.2.1** -- **The pair variants and kinds, and `PairingChecker`.** The new variants and kinds beside the old ones, + which stay until `DI-19.2.4` moves the sinks, so each step compiles. `PairingChecker`, public in `durable_ioring::oracle` + beside the conformance oracle: every end follows a begin of the same id and kind; no id is begun twice; nothing follows an + end; and at the stream's end no begin is left open but those a test names. Tested in memory against hand-built streams it + must accept and streams it must refuse, each rule in both directions. + +The variants, `SealOutcome`, `FlushOutcome`, `Edge` and `Pairing` are in `src/diagnostics.rs`; the checker and +`PairingViolation` in `src/oracle/pairing.rs`, exported from `durable_ioring::oracle`. Nothing emits a pair yet. The choices +are [DI-D-88](DESIGN-NOTES.md#di-d-88). + +## Moved 2026-10-21 18:29:12 -04:00 -- DI-19.2.2: the core's seal and flush pairs + +### DI-19.2.2 -- The core emits the seal and flush pairs, beside the old delay events until `DI-19.2.4`, each ending on every path its begin can leave by. *(completed 2026-10-21 18:29:12 -04:00)* + +The item as written: + +**DI-19.2.2** -- **The core emits the seal and flush pairs.** In place of `SealWaited`, `FlushIssued` and + `FlushAnswered`, each pair's id minted by `CorrelationId::for_pair` as its begin is built. An end on every path a begin can + leave by: an answer, a failure, a completion dropped unanswered, a lineage that ends or retires, and the core closed or + dropped; any that cannot be reached is documented at its definition. Tested over the in-memory backend and its script, + through the checker. + +Built in `src/durability.rs`, `src/durability/providers.rs` and `src/durability/lineages.rs`, and tested over the in-memory +backend and its script in `src/io_core/tests/pairs.rs`, whose sink hands every event to `PairingChecker`. The old events are +still emitted beside the pairs, not in their place as the item said: had the core stopped, the ETW sink would write no flush +events until it maps the pairs in `DI-19.2.4`, and the driver's elevated session test in CI would fail meanwhile. The choices +are [DI-D-89](DESIGN-NOTES.md#di-d-89). + +## Moved 2026-10-21 18:50:31 -04:00 -- DI-19.2.3: the lock-wait pair and the leaf-lock rule + +### DI-19.2.3 -- A lock wait is a pair, begun when the immediate try fails and ended as the lock is taken with it held, and `DiagnosticSink` states the leaf-lock rule the shipped sinks keep. *(completed 2026-10-21 18:50:31 -04:00)* + +The item as written: + +**DI-19.2.3** -- **The lock-wait pair, and the sink's leaf-lock contract.** `LockWatch` writes the begin when the + immediate try fails and the end on acquisition, with the lock held, and its timer goes. `DiagnosticSink`'s contract says a + sink is called with dioring's lock held, so its own synchronization is leaf-only: never taken in a way that can wait on + anything dioring's locks guard, and never held by a thread while it calls into an instance, a consumer holding a sink's + lock around such a call included. The shipped sinks are checked against it and the result recorded; a test sink that + records under a mutex is shown to keep to it. + +Built in `src/lock_watch.rs`, with the rule on `DiagnosticSink` in `src/diagnostics.rs`. The timer did not go here as the item +said: the old `LockWait`, which carries how long the wait was, is still reported until `DI-19.2.4` moves the sinks, and it +is the timer that measures that; it goes with the old event, as `DI-19.2.4` now says. + +**The shipped sinks, read against the rule on 2026-10-21.** `NoDiagnostics` takes nothing. `StderrDiagnostics` writes through +`eprintln!`, which takes the standard library's lock on standard error and nothing else; a consumer holding that lock -- a +`StderrLock` -- around a call on the instance would be the case the rule forbids. `FanOutDiagnostics` takes no lock of its own +and calls each sink it holds, so it keeps the rule when they do. `DebugOutputDiagnostics` takes no lock: it formats a line and +makes one call to `OutputDebugStringW`. `EtwDiagnostics` takes no lock to report; its shared registration's mutex is taken only +to build, drop and ask. The test sinks that record do so under a mutex taken only to record, and one test shows a wait's end +recorded that way while the watched lock is held. + +Three test sinks -- `durable-ioring`'s pump tests and two in `win-durable-ioring` -- panicked on any event but the old +`LockWait`; they now pass over the pairs until `DI-19.2.4` binds them to the checker. The pump tests' sink panicking on the +pair's begin had made two of them hang, waiting for a signal the panicking thread never sent; a sweep's baseline found it. +The choices are [DI-D-90](DESIGN-NOTES.md#di-d-90). + +## Moved 2026-10-21 19:51:07 -04:00 -- DI-19.2.4: the sinks follow the pairs and the old delay events go + +### DI-19.2.4 -- The sinks and the ETW provider's schema follow the pairs, with Start and Stop opcodes and a task for each kind, the driver's lock-wait tests bind to the checker, and the old delay events and the core's timing are gone. *(completed 2026-10-21 19:51:07 -04:00)* + +`Diagnostic::FlushIssued`, `FlushAnswered`, `SealWaited` and the old `LockWait` are removed with their kinds, and with them +the durability state's stamps, `StampClock::elapsed` and `LockTimer`; this is the major change to `durable-ioring`. The +standard-error and debugger lines carry each pair's id. `durable-ioring-events` writes the pairs as events 7 to 14 with +`win:Start` and `win:Stop` opcodes and a task per kind, retires ids 2 to 6, renders and holds the manifest, and was verified +as `DI-12.1.5` was: compiled, installed, traced and decoded by `tracerpt`, with the session tests run elevated. The driver's +lock-wait tests pass every event to `PairingChecker`, and a sabotage in the driver's manifest shows an end naming another +pair is caught there. Seven sabotages of the removed timing were deleted, the rest re-pointed, and the new claims swept with +their controls. The choices are [DI-D-91](DESIGN-NOTES.md#di-d-91) and +[DE-D-5](../durable-ioring-events/DESIGN-NOTES.md#de-d-5). + +## Moved 2026-10-21 19:59:39 -04:00 -- DI-19.2.5 and DI-19.2: the sweep, the sabotage and the record; the pairs complete + +### DI-19.2.5 -- The sweep for the delay events' old shape, sabotage showing the checker is the one place the pairing rule lives, and the record. *(completed 2026-10-21 19:59:39 -04:00)* + +The sweep covered the three crates' code, rustdoc, READMEs, manifests' reasons and design notes, and the rest of the +repository for the old names. Stale comments were corrected in the core's durability state and the driver's setup, its test +modules and its manifest's dependency comment, and an orphaned comment left by `DI-19.2.4` was removed. `DI-D-26`, `DI-D-51` +and `DI-D-77` carry status markers, the in-between bullets of `DI-D-88` and `DI-D-90` say they were overtaken, and DE-D-1's +scope names the pairs. Nothing outside the three crates named the old events. The driver's chosen-sink test now finishes a +`PairingChecker`. Sabotage changes the definition in each suite and is caught there, attributed by hand in the driver's. The +choices are [DI-D-92](DESIGN-NOTES.md#di-d-92). + +### DI-19.2 -- Begin and end events in the core, with the ETW provider and every sink following and the old delay events gone, and `PairingChecker` the one place the pairing rule lives. *(completed 2026-10-21 19:59:39 -04:00)* + +Done in five steps, each archived above: [DI-19.2.1](#di-1921), [DI-19.2.2](#di-1922), [DI-19.2.3](#di-1923), +[DI-19.2.4](#di-1924) and [DI-19.2.5](#di-1925). The shapes and the engineer's rulings are +[DI-D-87](DESIGN-NOTES.md#di-d-87); the choices of the steps are `DI-D-88` to `DI-D-92`. + +## Moved 2026-10-21 20:25:49 -04:00 -- DI-19.3: an event written with an explicit activity id + +### DI-19.3 -- `win-etw-sys` writes an event with the activity id and related activity id its caller gives, by `EventWriteTransfer`, and its session reads both back. *(completed 2026-10-21 20:25:49 -04:00)* + +`Registration::write_transfer` takes each id as optional, as the call's page does, and refuses nothing about them, since +the page calls no value invalid. A session's `Recorded` carries the activity and the related activity and is now +`#[non_exhaustive]`, a breaking change to `win-etw-sys`. The session test the item named writes a begin on one thread and its +end on another and reads the activity given on both; it and four others were run elevated and pass. The sabotages only a +session can catch are a manifest of their own, `sabotage-session.json`, swept elevated. The choices are +[WE-D-4](../win-etw-sys/DESIGN-NOTES.md#we-d-4). + +## Moved 2026-10-21 20:48:46 -04:00 -- DI-19.4, and DI-M19 is complete + +### DI-19.4 -- The ETW provider writes each pair in an activity `EventActivityIdControl` creates at the begin and the end repeats, and a program pairs a trace's dump and computes each duration. *(completed 2026-10-21 20:48:46 -04:00)* + +The item as written: + +**DI-19.4** -- **The ETW provider writes the activity id.** Waits on `DI-19.2` and `DI-19.3`. The Start and Stop + opcodes moved to `DI-19.2.4` (DI-D-87); this step writes each pair's activity id by the mapping the section above holds as + a working position, and is verified as `DI-12.1.5` was: compiled and installed, then a trace recorded and read by + `tracerpt`, which this time must show the begin and end paired and their duration computed. + +Both halves changed by the engineer's rulings before it was built ([DI-D-93](DESIGN-NOTES.md#di-d-93)). The working position +was set aside for ids `EventActivityIdControl` creates, since a reused process id would repeat a derived one; +`win-etw-sys` gained `create_activity_id` ([WE-D-5](../win-etw-sys/DESIGN-NOTES.md#we-d-5)) and the provider holds each pair's +id from its begin to its end ([DE-D-6](../durable-ioring-events/DESIGN-NOTES.md#de-d-6)). `tracerpt` does not pair records, so +the trace was dumped to CSV and paired by the example `pair_trace`, which found all four of `emit_every_event`'s pairs, each in +an activity of its own and no shorter than the wait made, the flushes ending on another thread than they began on. The session +tests of both crates were run elevated and pass, and both crates' session sabotage manifests were swept then, each entry caught +and each control surviving. + +### DI-M19 is complete + +Its items are archived: the correlation id ([DI-19.1](#di-191)), the begin and end events and the one checker +([DI-19.2](#di-192)), the explicit activity id in `win-etw-sys` ([DI-19.3](#di-193)) and the provider's activities +([DI-19.4](#di-194)). The rulings that opened it are [DI-D-80](DESIGN-NOTES.md#di-d-80). A delay is now a begin and an end +sharing an id, written by every sink, and in ETW one activity, so a duration is computed by whoever reads the trace. + +## Moved 2026-10-21 20:57:55 -04:00 -- DI-20.1: what Windows documents about a directory entry's durability + +### DI-20.1 -- What Windows documents about a directory entry's durability -- the pages for creating, flushing, renaming and deleting, and what they lead to -- is read and recorded with each page's date, silence as silence, and the experiment the open questions call for is described. *(completed 2026-10-21 20:57:55 -04:00)* + +The item as written: + +**DI-20.1** -- **Read what Windows documents about a directory entry's durability, and record it.** The pages for + `CreateFileW`, `FlushFileBuffers`, `MoveFileExW` (with `MOVEFILE_WRITE_THROUGH`), `SetFileInformationByHandle`'s rename + and disposition classes, and anything they lead to, each read for: whether flushing a new file makes its directory entry + durable; whether a directory handle may be flushed, and what that commits; and what a rename or a delete needs. Each + finding is recorded in [DESIGN-NOTES.md](DESIGN-NOTES.md) with its page and the date read, and silence is recorded as + silence. No `Nt*` page is a source ([the rule](../../.github/copilot-instructions.md)). If the pages leave a question open, + the experiment that would answer it is described, and is evidence, never a check. + +The findings are [DI-D-94](DESIGN-NOTES.md#di-d-94). Beyond the pages the item named, the reading followed `CreateFileW`'s +caching section to File Caching and Directory Handles, the rename and disposition classes to `FILE_INFO_BY_HANDLE_CLASS`, +`DeleteFileW`, `CreateDirectoryW`, `RemoveDirectoryW`, `ReplaceFileW`, the transacted forms and the alternatives to +Transactional NTFS, and the ring's own flush, which has no page on Learn. The experiment is described there and not built. + +## Moved 2026-10-21 21:21:09 -04:00 -- DI-M20 withdrawn: the durable ioring never names a file + +### DI-20.2 -- Withdrawn: the durable ioring neither creates, renames nor deletes a file, so a directory entry's durability is not a question for it. *(withdrawn 2026-10-21 21:21:09 -04:00)* + +The item as written: + +**DI-20.2** -- **Bring the findings to the engineer for a design discussion.** Whether dioring makes a directory + entry durable, whether a consumer must, and whether a directory is a target a seal can cover, are decided there; the work + that follows is queued from that discussion and not before it. + +The engineer, 2026-10-21, asked whether the durable ioring creates or deletes files: "no I didn't think it did either", and: "the 'directory' point I raised previously was mostly framing for what seems to feed in to m39.3". + +Investigated before the ruling: a file enters the durable ioring as an open handle (`Setup::files`, `add_file`) and leaves by +`remove_file`; its only operations are reads and writes, made durable by seals; and the Windows driver's `OpenedFile`, the +one place a file is created, is a `CreateFileW` wrapper that runs before the file reaches the ring. What a seal covers is +what was written and flushed through it. The design session's directory was the question of which flush domains a file +created in it will have, which root [M39.3](../../CHECKLIST.md) holds. The reasoning is [DI-D-95](DESIGN-NOTES.md#di-d-95). + +### DI-M20 is withdrawn + +[DI-20.1](#di-201) was completed, and its findings stay as [DI-D-94](DESIGN-NOTES.md#di-d-94), the record of what Windows +documents for a consumer that makes its own files durable. [DI-20.2](#di-202) is withdrawn above, and nothing was queued +from it. + +## Moved 2026-10-22 01:10:02 -04:00 -- DI-M22: findings of the review of the branch + +### DI-22.1 -- A fan-out hands every pair's end to every sink, so a sink that stopped wanting a kind is given the end that lets go of its pair. *(completed 2026-10-22 01:10:02 -04:00)* + +The item as written: + +- [x] **DI-22.1** -- **A fan-out hands every pair's end to every sink.** The ETW sink holds an activity for each pair from its + begin to its end, and a fan-out that asked `wants` of an end did not give it to the sink after its session stopped, so the + process's activity map kept the pair for the life of the process. + +What was done. Recorded as [DI-D-96](DESIGN-NOTES.md#di-d-96), which also records the standard producers are held to and that +sinks assume none of it, and that a first attempt, which kept which sinks each begin was given to, was withdrawn. The fan-out +holds no state. Tests: seven new in [diagnostics/tests.rs](src/diagnostics/tests.rs), over a sink whose answer to `wants` changes +while a pair is open. Evidence, in [sabotage.json](sabotage.json): two entries caught and a control. The one in the sink that +reads the leak, `a_pair_ended_through_a_fan_out_after_its_session_stopped_is_let_go`, is a session test. + +### DI-22.2 -- Text in an ETW event ends at its first zero, so a later field is not misread. *(completed 2026-10-22 01:10:02 -04:00)* + +The item as written: + +- [x] **DI-22.2** -- **A zero in the text of an ETW event's field ends the text there.** A zero in a lock's name, which is + followed by the pair, made a reader take the rest of the text as the pair. + +What was done. Recorded as [DE-D-7](../durable-ioring-events/DESIGN-NOTES.md#de-d-7), which amends +[DE-D-2](../durable-ioring-events/DESIGN-NOTES.md#de-d-2). Tests: five new in +[encode/tests.rs](../durable-ioring-events/src/encode/tests.rs), and one session test that decodes the event as a reader does. +Evidence, in [sabotage.json](../durable-ioring-events/sabotage.json): three entries caught and a control. + diff --git a/crates/durable-ioring/COMPONENT.md b/crates/durable-ioring/COMPONENT.md index c4715a72a..44150ee15 100644 --- a/crates/durable-ioring/COMPONENT.md +++ b/crates/durable-ioring/COMPONENT.md @@ -48,8 +48,9 @@ refused push names its cause, and the loop that waits it out is a maintained exa session planned it as a layer that contains an execution domain; it is built on `IoRing` directly instead, and the item that queued it, `M33+.5` in [CHECKLIST-io-domains.md](../../CHECKLIST-io-domains.md), has been transferred here. -- **It does not replace the `epoch_log` sample** ([DI-D-7](DESIGN-NOTES.md#di-d-7)). The two - coexist until this crate is proven, and the merge-or-delete decision is queued. +- **It does not replace the `epoch_log` sample**, which is a log, a consumer of a durable ring, and stays in + `windows-ioring-sys` as that crate's own ([DI-D-85](DESIGN-NOTES.md#di-d-85)). A sample log built on this crate, from + common practice in database and file-system logs, is queued as `DI-M21`. ## Design record diff --git a/crates/durable-ioring/CONTRACT.md b/crates/durable-ioring/CONTRACT.md index ab60c953a..23ccb5318 100644 --- a/crates/durable-ioring/CONTRACT.md +++ b/crates/durable-ioring/CONTRACT.md @@ -204,6 +204,11 @@ dioring binds to these obligations, never to how a backing happens to meet them shares fate with every target. - **Its geometry and capabilities are stated**: the alignment its offsets, lengths and buffers need, and what it offers beyond these obligations, such as per-write write-through. +- **Its access is stated**: whether it can be read, written, or both. An operation its access does not + cover is refused when it is pushed, as `AccessDenied`, and never reaches the target; a commit is not + one of them. A consumer may register the target under a typed `Key`, which declares the access + it expects: a declaration the stated access does not cover is refused at registration, with the + target handed back, and a declaration of less is accepted. - **A fault names its source**: the layer that reported it, and that layer's code where it gives one (see "Where every report comes from"). @@ -459,7 +464,7 @@ synthesizes ([DI-D-13](DESIGN-NOTES.md#di-d-13), [DI-D-30](DESIGN-NOTES.md#di-d- An instance has a **readiness signal**, of a type its driver chooses -- the ring trait's `Readiness` ([DI-D-52](DESIGN-NOTES.md#di-d-52) Q6); the Windows driver's is an auto-reset event it -owns, of which the consumer can take a duplicate, as a `win-sync-sys` `Event` +owns, of which the consumer can take a clone sharing its handle, as a `win-sync-sys` `Event` ([DI-D-28](DESIGN-NOTES.md#di-d-28), [DI-D-50](DESIGN-NOTES.md#di-d-50)). These rules bind every implementation of this contract and whoever waits on the signal, and the conformance module's `check_readiness` checks the instance's half of them, for an implementation whose signal is an diff --git a/crates/durable-ioring/DESIGN-NOTES.md b/crates/durable-ioring/DESIGN-NOTES.md index 84e0214ac..8c9af46da 100644 --- a/crates/durable-ioring/DESIGN-NOTES.md +++ b/crates/durable-ioring/DESIGN-NOTES.md @@ -18,7 +18,7 @@ appear here. | DI-D-4 | **Amended by [DI-D-49](#di-d-49): owning forms are the Windows backend's file target's concern; the core takes targets, not handles. `#![forbid(unsafe_code)]` is unchanged.** **Amended by [DI-D-29](#di-d-29): the owning form a consumer gives is `SharedHandle`, which dioring hands to the ring unchanged.** **No unsafe code, enforced by `#![forbid(unsafe_code)]` from the first commit.** The `epoch_log` sample needs `unsafe` only because it pushes through raw handles; this crate takes files in owning forms (`SharedHandle`, `RegisteredFile`, `flush_owned`) and never a `RawHandle`, which its per-write bookkeeping wants anyway for file identity. If a Win32 call is ever needed that no lower crate wraps safely, the safe wrapper is added to the lower crate (the workspace's fix-the-layer policy), never an `unsafe` block here. A build that fails on the `forbid` is the moment to raise it, not a lint to relax. | | DI-D-5 | **Two consumer classes, and a layering that serves both: this crate reports and resolves; retention is a layer above it.** Naive consumers want durability without expertise; expert consumers -- storage engines with their own logs -- want truthful reporting and no buffering imposed on them. So this crate owns, for both:

**(a)** a high-water mark that never reports a point durable while an earlier failure is unresolved -- Scenarios 1 and 2 in the session show a later successful flush reporting data durable that is gone, and an expert needs this guarantee as much as anyone;

**(b)** a resolution protocol by which the consumer, not this crate, decides what a failure means;

**(c)** an inventory, by identity, of the writes a failure puts in doubt -- a fact about what was issued, not a policy about what to do.

Retaining written data until durable, rewriting it, and replaying it are **policy, and belong to a separate future crate above this one**, which uses (b) and (c) rather than re-tracking every write. A naive consumer on this crate alone gets truthful durability and a precise loss report -- correct, if not graceful; the retention crate is what makes it graceful. That crate is a deliberate layer, not work deferred for want of a consumer, and is queued in [CHECKLIST.md](CHECKLIST.md). | | DI-D-6 | **Ordering overlapping writes is the consumer's responsibility; this crate tracks an identity per write and nothing more.** The engineer's decision. Two overlapping writes in flight together land in an unspecified order, and replaying suspect writes after a failure can clobber newer data if overlaps are not resolved newest-wins; both are the consumer's to handle, and the contract states the first as a requirement on the consumer. This crate does not track in-flight byte ranges, refuse overlaps, or resolve them. Whether the per-write identity carries an order usable for newest-wins resolution was left open here; it does -- `OpId` is ordered by push order ([DI-D-15](#di-d-15)). **Applied 2026-10-06 (the engineer: "drop the extents. The consumer may track them themselves via the OpId."):** a failure's `SuspectWrite` and the durability provider's request (DI-2.12) carry a write's `OpId`, never its offset or length. | -| DI-D-7 | **The `epoch_log` sample stays as it is until this crate is proven; then a merge-or-delete decision is taken.** The repository's duplicate-then-decide rule: this crate is a new path beside the sample, not a rewrite of it. The decision -- rebuild the sample on this crate, keep both, or retire one -- is queued in [CHECKLIST.md](CHECKLIST.md) so the duplication cannot become permanent by default. A defect the session found in the sample's own reasoning is queued separately in the ring crate, since it is wrong whatever happens here. | +| DI-D-7 | **Superseded by [DI-D-85](#di-d-85).** **The `epoch_log` sample stays as it is until this crate is proven; then a merge-or-delete decision is taken.** The repository's duplicate-then-decide rule: this crate is a new path beside the sample, not a rewrite of it. The decision -- rebuild the sample on this crate, keep both, or retire one -- is queued in [CHECKLIST.md](CHECKLIST.md) so the duplication cannot become permanent by default. A defect the session found in the sample's own reasoning is queued separately in the ring crate, since it is wrong whatever happens here. | | DI-D-8 | **Amended by [DI-D-9](#di-d-9): a durability point is now identified by the consumer's own ordered number, and what stays opaque is the mechanism, not the identity.** As first written: durability points are opaque to the consumer -- the consumer names and waits on a point without seeing how it is represented, so the epoch stays an implementation detail ([DI-D-2](#di-d-2)). That intent survives DI-D-9: which flushes are issued, under which commit strategy, and how the suspect window is tracked remain dioring's and invisible; only the *label* of a point became the consumer's. | | DI-D-9 | **Amended by [DI-D-27](#di-d-27): rule (c)'s "covered" now means named in a durability-provider call.** **Amended by [DI-D-19](#di-d-19): every rule below applies within one lineage. A write's epoch is a lineage plus an epoch id (formerly "epoch number"), and a seal covers only its own lineage's prefix.** **The durability model: the consumer tags each write with an epoch id of its own choosing, and `make_durable_through(n)` seals and requests durability for every epoch at or below `n`.** The engineer's decision, reached as a what-if and then adopted. Tags may be issued in any order, higher and lower intermixed; a journal transaction ID or log sequence number can be the tag directly. `make_durable_through(n)` seals a *prefix* of the number space, so "n durable implies every m < n durable" holds by construction and epochs above `n` remain open -- several open epochs come free, with no open or seal ceremony and no handles. Rules:

**(a)** once `n` is sealed, a write tagged at or below `n` is an API violation, refused before anything is reserved, as [D-28](../windows-ioring-sys/DESIGN-NOTES.md#d-28) treats every legality check;

**(b)** `make_durable_through(m)` with `m` at or below an already-sealed number is a no-op that reports `m`'s current state -- durable, pending, or failed -- rather than a single "already durable" code that would be false in two of the three cases;

**(c)** writes of still-open epochs that a commit's flush happens to cover are not reported durable, and may appear in a failure's suspect set with their tags -- they are simply not yet guaranteed.

Why it fits: a covering flush covers everything pushed before it whatever its tag, so the prefix is the physical truth of the ring and the tag is bookkeeping over it; and because rule (a) forces a rewrite of a failed epoch's data to carry a higher tag, healing composes with it without a special rule. Named in a storage idiom rather than a database one (prepare/commit): **"through" states the prefix property in the name**, and the report is `durable_through()` (the high-water mark the sample already calls by that name). Costs carried: a bare number compares across instances, losing a type-level guard on C-3's per-domain rule (since answered in the DI-2.1 draft by making the epoch type generic, so the consumer chooses comparability through types); sparse tags mean an ordered map of open epochs inside dioring; a consumer whose native sequence wraps (jbd2's 32-bit transaction IDs do) maps it onto a non-wrapping one. The tag's concrete type is [DI-2.1](CHECKLIST.md)'s. **Supersedes the explicit-handle model** explored in the session (open and seal as separate operations, positions assigned at seal), which this model makes unnecessary. | | DI-D-10 | **Amended by [DI-D-49](#di-d-49): the ring dioring owns is its backend; the consumer reaches the backend only through dioring.** **The consumer cannot put an operation on the ring that interferes with dioring's control of it.** The engineer's decision. dioring owns its ring outright: the consumer reaches it only through dioring's own operations -- reads, tagged writes, and durability requests -- and never through `IoRing`, `Batch`, a `RingScope`, or `windows-ioring-sys`' raw-SQE seam ([D-7](../windows-ioring-sys/DESIGN-NOTES.md#d-7)). Anything that would interfere -- a flush whose failure the suspect window would not hear, a cancel that silently changes a tagged write's fate, a registration that replaces what dioring registered -- either is not offered or is offered only as a dioring-mediated operation whose effect dioring accounts for. **Enforced by the types, not by documentation**: no public method returns a borrow through which an underlying ring operation can be pushed, which is the same shape as [D-43](../windows-ioring-sys/DESIGN-NOTES.md#d-43)'s `RingScope` and is checked by the borrow question in [DESIGN-INSTRUCTIONS.md](../windows-ioring-sys/DESIGN-INSTRUCTIONS.md). **No escape hatch, including for experts**, and this is consistent with OPTION INTEGRITY rather than an exception to it: that rule permits foreclosing an option on a demonstrated wrong answer, and an interfering operation produces exactly that -- a durability report that is false. | @@ -37,12 +37,12 @@ appear here. | DI-D-23 | **Amended by [DI-D-46](#di-d-46): the seal a heal waits for is the first made after it that reaches the highest epoch pushed in the lineage by then.** **Amended by [DI-D-43](#di-d-43): a seal that would close a cycle is refused too, since a seal decides which held writes reaching an epoch needs.** **Gate cycles are detected, and the push that would close one is refused.** The engineer's decision under DI-2.4 point 4, 2026-10-06: "I think we have to detect it. The alternative leads to a lot of extra work around enumeration, modification and cancellation of lineages that I don't really want to have to do." A gated write tagged `L:a` with gate `M:b` adds the dependency "L reaching a needs M reaching b"; because durability is a prefix, a cycle exists when M reaching b already needs, transitively, L reaching a or beyond. The push that would close a cycle is refused before anything is reserved, as every legality check is ([D-28](../windows-ioring-sys/DESIGN-NOTES.md#d-28)), with an error naming the cycle; a write gated on its own lineage at or above its own epoch is the one-edge case. Edges leave the graph when a gate releases or its epoch is abandoned. **Limits:** only gates dioring sees are covered -- a consumer that waits in its own code for one lineage before writing another can still deadlock itself -- and a lineage its holder never finishes strands gates on it without forming a cycle, which is DI-2.13's question. **For the implementation:** a heal takes effect only when the first seal made after it completes, so reaching a failed epoch can depend on reaching a later one; the search must not assume reaching `b` depends only on writes at or below `b`. Gated reads take no epoch and so cannot be part of a cycle. | | DI-D-24 | **Refined by DI-2.15: `Epoch` and every other shared type that carries an identity is generic over one `Identities` bundle -- the implementation's identity types plus the consumer's epoch-id type -- since an epoch holds a lineage; only the plain values stay concrete ([API.md](API.md) section 17).** **The contract is a trait, generic only, with identity types associated to each implementation.** The engineer's decision under DI-2.5, 2026-10-06: "I would prefer a trait", then, point by point, "yes associated types", "yes generic only. We will make things free functions when needed", "agreed", "yes", and on the readiness signal, "why wouldn't it have such a signal?". The contract is something layers above implement as well as consume -- the fault-injecting implementation (DI-3.3) and the retention layer (DI-5+.1) -- so it is a trait from the start rather than extracted later. **Identity types are associated types** (`Lineage`, `OpId`, `FailureId`, `FailureToken`): each embeds a private instance identity or a cell into its instance, so only its own implementation can mint it, and associated types keep that invariant per implementation; plain values -- `Epoch`, `FileKey`, `FlushDomain`, the options builders -- stay shared. **Generic only, not object-safe:** consumers write `D: DurableRing<...>`; construction stays on concrete types, and what cannot sit on the trait becomes free functions. **A core trait plus an extension trait for registered buffers**, since an implementation without a wioring may have none. **The trait carries a readiness signal**, set whenever an entry becomes available from any source -- a ring completion or a durability provider's out-of-band completion -- so the Model A and Model B front ends are written once over the trait; its form and wake-up rules are DI-2.14's. **dioring ships a conformance oracle** over the event stream, which every implementation's tests run, because a trait makes [CONTRACT.md](CONTRACT.md) bind implementations dioring does not own. **Consequence carried:** a public trait others implement cannot gain a required method without a major release, so additions come as default methods or extension traits. | | DI-D-25 | **Ending an instance -- `close()` or drop -- seals nothing, waits for everything outstanding, and `close()` hands back what is left.** The engineer's decisions under DI-2.7, 2026-10-06 (points 1-6: "yes"; then "add close here also", and on provider calls in flight: "the responsibilities w.r.t. outstanding calls to durability providers are similar to the responsibilities to around outstanding ring operations. so in the end we have to await them."). **No implicit durability:** ending issues no final seal or flush -- that would be policy, would block, and a failure would have nobody to report to. **It waits** for every kernel operation (the ring's own rundown, which leaks rather than frees buffers if it cannot prove quiescence, [D-4](../windows-ioring-sys/DESIGN-NOTES.md#d-4)) and for every durability-provider call in flight, because both may still be using memory or state the instance owns; dioring adds no other waiting, and a provider call that never completes means ending never completes, as a kernel operation that never completes would. **No cancellation notification to the provider:** considered, and the engineer doubts it is worthwhile ("perhaps there should be a cancellation notification but I doubt it's worthwhile"); no work is scheduled. **`close(self) -> Leftovers`** returns the gated operations the kernel never saw (with buffer and context), the unresolved failures, and every lineage with its final high-water mark; drop discards them. Nothing reported is undone. **Model A teardown** quiesces delivery before releasing anything its callback reaches ([D-13](../windows-ioring-sys/DESIGN-NOTES.md#d-13)), so no callback runs after ending returns. **While unwinding,** problems are traced, not panicked ([D-79](../windows-ioring-sys/DESIGN-NOTES.md#d-79)). A failure token's drop is its `close()` (DI-1.2). **Not an instance's rule:** ending a *lineage* need not wait -- the engineer: there is "not a lifetime issue and there is not a dependency around durability" -- which is DI-2.13's. Implementation constraint: the wait must not hold dioring's lock, or a provider completion that takes it deadlocks the end. | -| DI-D-26 | **Amended by [DI-D-51](#di-d-51): diagnostics go to a sink the consumer chooses; ETW is one sink, the Windows driver's default, and no longer the single surface.** **Amended by [DI-D-50](#di-d-50): the portable core reports through a tracing hook; the ETW provider is the Windows driver's.** **dioring publishes one ETW provider, manifest-based, as the single diagnostic surface for durability delays; it costs nothing when no session listens.** The engineer's decisions under DI-2.11, 2026-10-06: "delays are inevitable with shared resources. we should plan for etw logging for delays"; "I have two major concerns for delays: flushes and locks. Locks are in our control and flushes are up to the flush domains control"; "nothing" (cost when disabled); "manifest"; "we are generating events about durability delays, so we own it"; and "the etw events we publish give a surface for diagnosis of issues; clients don't want to search around across providers to try to correlate issues." Lineages and instances share submission-queue capacity, the deliverer and dioring's lock, so delays are made observable rather than promised away. **Primary events: lock waits and flushes.** Locks are timed by the engineer's pattern: try-lock; only on failure read the clock, block, and on acquiring record the wait; emit after releasing, so the event does not lengthen the hold it measures; one wrapper type, a name per lock; poisoning handled separately. Flushes are timed at the durability-provider boundary -- call issued, and each domain's outcome -- so an event names the slow domain; a provider's internals are its own to report. **Secondary:** a seal's other phases (waiting for covered writes, and for gated writes to be released), which the engineer doubts will be warranted. **One surface:** whatever dioring calls -- the wioring, a provider -- is timed at the boundary, so the wioring's own lock appears as a slow call into the ring and the ring crate publishes no provider (its `M31+.1` was withdrawn). **Nothing when disabled:** no clock read and no event built unless a session is listening. **Consequence carried:** manifest events decode by name only where the manifest is installed, an administrative step an application must take or document. What each event names, the time base (chosen from observed timelines), and the provider's name and GUID are DI-3.6's. | +| DI-D-26 | **Amended by [DI-D-80](#di-d-80) and [DI-D-91](#di-d-91): a delay is reported as a begin and an end sharing an id, and its length is the gap a reader measures between them; nothing is timed, so the lock pattern below reads no clock and its event is written before blocking and on acquiring, with the lock held.** **Amended by [DI-D-76](#di-d-76): the provider is named `durable-ioring-events`, lives in its own crate, and records without its manifest being installed.** **Amended by [DI-D-51](#di-d-51): diagnostics go to a sink the consumer chooses; ETW is one sink, the Windows driver's default, and no longer the single surface.** **Amended by [DI-D-50](#di-d-50): the portable core reports through a tracing hook; the ETW provider is the Windows driver's.** **dioring publishes one ETW provider, manifest-based, as the single diagnostic surface for durability delays; it costs nothing when no session listens.** The engineer's decisions under DI-2.11, 2026-10-06: "delays are inevitable with shared resources. we should plan for etw logging for delays"; "I have two major concerns for delays: flushes and locks. Locks are in our control and flushes are up to the flush domains control"; "nothing" (cost when disabled); "manifest"; "we are generating events about durability delays, so we own it"; and "the etw events we publish give a surface for diagnosis of issues; clients don't want to search around across providers to try to correlate issues." Lineages and instances share submission-queue capacity, the deliverer and dioring's lock, so delays are made observable rather than promised away. **Primary events: lock waits and flushes.** Locks are timed by the engineer's pattern: try-lock; only on failure read the clock, block, and on acquiring record the wait; emit after releasing, so the event does not lengthen the hold it measures; one wrapper type, a name per lock; poisoning handled separately. Flushes are timed at the durability-provider boundary -- call issued, and each domain's outcome -- so an event names the slow domain; a provider's internals are its own to report. **Secondary:** a seal's other phases (waiting for covered writes, and for gated writes to be released), which the engineer doubts will be warranted. **One surface:** whatever dioring calls -- the wioring, a provider -- is timed at the boundary, so the wioring's own lock appears as a slow call into the ring and the ring crate publishes no provider (its `M31+.1` was withdrawn). **Nothing when disabled:** no clock read and no event built unless a session is listening. **Consequence carried:** manifest events decode by name only where the manifest is installed, an administrative step an application must take or document. What each event names, the time base (chosen from observed timelines), and the provider's name and GUID are DI-3.6's. | | DI-D-27 | **Amended by [DI-D-50](#di-d-50): the built-in default provider issues one commit per target the seal touched, through the backend, and covered means a write's deciding commit succeeded.** **Amended by [DI-D-29](#di-d-29): a request names files as `SharedHandle`, not `SharedFile`.** **Seals are made durable by a built-in default provider and at most one consumer-supplied durability provider, which flushes by any means and answers per flush domain.** The engineer's decisions under DI-2.12, 2026-10-06: the provider shape ("I generally like this, it's what I expect dioring to be on top of the wioring"), "drop the extents", "one provider per instance seems too restrictive" then "do your clean form of b", and the assistant's stances on the rest ("seems right, let's move on"). **Built-in default plus one consumer provider:** the default -- each file synced through dioring's own wioring -- is the only code that touches that ring ([DI-D-10](#di-d-10)); the consumer's provider states at construction the domains it serves, and everything else, including files declared with no domains, goes to the default. Several providers are composed by a router dioring ships, itself a provider (DI-3.8). **The call:** one request per seal, made only after dioring has observed every named write complete, with an entry per domain naming its files (`FileKey`, `SharedFile`) and each file's `OpId`s -- identities, never extents -- and one completion handle per domain, consumed once with success or an error. **Any means:** the provider never issues or carries writes and never touches dioring's wioring; deduplicating across coupled domains or calls is its own business, and coupled domains dedupe only within one provider. **Outcomes:** a write is durable when every domain of its file has succeeded; a domain's success covers exactly the `OpId`s named for it and is the consumer's warranty; a domain's failure reaches the files whose sets contain that domain, plus unknown files -- narrower than [DI-D-21](#di-d-21)'s file-intersection rule, which still governs the default provider's per-file failures; a handle dropped uncompleted is a failure of its domain with its own cause; a call that never completes leaves the seal pending, with no timeout in dioring. **Call rules:** the call returns promptly, handles may complete on any thread at any time, the provider holds no reference to the instance, a completion only records the outcome and sets the readiness signal, and a provider panic propagates with its dropped handles counted as abandoned. **"Covered" is redefined** as named in a provider call made after the write's completion was observed, and covered successfully once every domain of its file reports success; it drives the seal's file set, suspect sets, `remove_file`'s `FileBusy`, and crediting (DI-6+.7). Writing this into CONTRACT.md and API.md is DI-2.16. | -| DI-D-28 | **Amended by [DI-D-58](#di-d-58): the signal is also set when a `pop` has a failure to report -- a doorbell that failed, or completions that could not be read -- not only after an entry is poppable.** **Amended by [DI-D-50](#di-d-50): the readiness signal's type is the driver's, an associated type of the contract; the Windows driver's is the `Event` below, and the rules are unchanged.** **Amended by `DI-3.2.2.1` (2026-10-07): the duplicate is a `win-sync-sys` `Event`, not a bare `OwnedHandle`, so a front end can give it to a thread-pool wait without `unsafe` -- which dioring forbids ([DI-D-4](#di-d-4)) and which a bare handle would need, since nothing in its type says it is not a mutex. The engineer created `win-sync-sys` for this.** **The trait's readiness signal is an auto-reset event its implementation owns, with the wioring's wake-up rules; dioring's is its own event, relayed from `EventDelivery`.** The engineer's decisions under DI-2.14, 2026-10-06: "relay to a second event" and "yes" to the rest. **Form:** an auto-reset event the implementation owns, the consumer receiving a duplicate, the shape of the wioring's `IoRing::completion_event` ([D-20](../windows-ioring-sys/DESIGN-NOTES.md#d-20)). **Wake-up rules**, the wioring's ([D-19](../windows-ioring-sys/DESIGN-NOTES.md#d-19), [D-21](../windows-ioring-sys/DESIGN-NOTES.md#d-21)) restated for the trait: an implementation sets the signal only after an entry is poppable, and at least on every empty-to-non-empty transition; a front end pops to `None` after every wake before waiting again; a wake with nothing to pop is normal; one waiter per instance, matching the single deliverer. **dioring's implementation** keeps `EventDelivery`, so the ring's arm-and-drain discipline stays in the ring crate where it is solved, and [DI-D-18](#di-d-18)'s binding to its callback contract (ring `M31.1`) stands: the callback records each ring completion into dioring's core under dioring's own lock, assuming no order among overlapping callbacks, then sets dioring's event; a provider completion sets it too. Rejected: making dioring's signal the ring's own event, set by hand -- one hop fewer, but it tied dioring's contract to the ring's event semantics. **Consequence:** in both delivery models the ring is drained by `EventDelivery`'s pool callbacks; Model A and Model B differ only in who delivers. **Conformance:** the oracle sees entries, not wakes, so a harness check -- push into an empty queue and assert the signal is set; drain; assert the next push sets it again -- runs beside it in every implementation's tests (built in `DI-3.2.2.1`). | +| DI-D-28 | **Amended by `win-sync-sys`' [WS-D-10](../win-sync-sys/DESIGN-NOTES.md#ws-d-10) (2026-10-18): the consumer's `Event` is a clone sharing the instance's handle, not a duplicate.** **Amended by [DI-D-58](#di-d-58): the signal is also set when a `pop` has a failure to report -- a doorbell that failed, or completions that could not be read -- not only after an entry is poppable.** **Amended by [DI-D-50](#di-d-50): the readiness signal's type is the driver's, an associated type of the contract; the Windows driver's is the `Event` below, and the rules are unchanged.** **Amended by `DI-3.2.2.1` (2026-10-07): the duplicate is a `win-sync-sys` `Event`, not a bare `OwnedHandle`, so a front end can give it to a thread-pool wait without `unsafe` -- which dioring forbids ([DI-D-4](#di-d-4)) and which a bare handle would need, since nothing in its type says it is not a mutex. The engineer created `win-sync-sys` for this.** **The trait's readiness signal is an auto-reset event its implementation owns, with the wioring's wake-up rules; dioring's is its own event, relayed from `EventDelivery`.** The engineer's decisions under DI-2.14, 2026-10-06: "relay to a second event" and "yes" to the rest. **Form:** an auto-reset event the implementation owns, the consumer receiving a duplicate, the shape of the wioring's `IoRing::completion_event` ([D-20](../windows-ioring-sys/DESIGN-NOTES.md#d-20)). **Wake-up rules**, the wioring's ([D-19](../windows-ioring-sys/DESIGN-NOTES.md#d-19), [D-21](../windows-ioring-sys/DESIGN-NOTES.md#d-21)) restated for the trait: an implementation sets the signal only after an entry is poppable, and at least on every empty-to-non-empty transition; a front end pops to `None` after every wake before waiting again; a wake with nothing to pop is normal; one waiter per instance, matching the single deliverer. **dioring's implementation** keeps `EventDelivery`, so the ring's arm-and-drain discipline stays in the ring crate where it is solved, and [DI-D-18](#di-d-18)'s binding to its callback contract (ring `M31.1`) stands: the callback records each ring completion into dioring's core under dioring's own lock, assuming no order among overlapping callbacks, then sets dioring's event; a provider completion sets it too. Rejected: making dioring's signal the ring's own event, set by hand -- one hop fewer, but it tied dioring's contract to the ring's event semantics. **Consequence:** in both delivery models the ring is drained by `EventDelivery`'s pool callbacks; Model A and Model B differ only in who delivers. **Conformance:** the oracle sees entries, not wakes, so a harness check -- push into an empty queue and assert the signal is set; drain; assert the next push sets it again -- runs beside it in every implementation's tests (built in `DI-3.2.2.1`). | | DI-D-29 | **Superseded by [DI-D-50](#di-d-50): the contract names targets by `IoTargetKey`, and a provider request names `IoTargetWrites { key, writes }`; no handle crosses the contract.** **Revised 2026-10-07: the type is `SharedHandle`, from the workspace crate `win-shared-os-owned-handle`; first decided, on 2026-10-06, as a crate-local `DurableFile`.** **A file is given to the contract as a `SharedHandle`: an owning, cheaply shared handle that lends itself.** The engineer, under DI-2.17 and after it. Details, and what the contract adds on top of the type, are in [the section below](#di-d-29-a-file-is-a-sharedhandle). | | DI-D-30 | **Amended by [DI-D-43](#di-d-43): a new gate on an ended lineage's epoch is refused as a lineage the instance cannot answer for, not as `GateAbandoned`; and a held write of the ended lineage ends as never issued too.** **Amended by [DI-D-40](#di-d-40): a lineage also ends when its last handle is released, and `end` takes the last handle by value.** **A lineage its holder will not finish is ended: every epoch of it not yet durable is abandoned, its handle refused at once, and it is retired once drained; nothing waits and no I/O is cancelled.** The engineer's decisions under DI-2.13, 2026-10-06: shutting down a lineage "does not have to wait because there is not a lifetime issue and there is not a dependency around durability"; "I believe that entire larger structures may be abandoned but not surgical I/O cancellation" (DI-2.6); then "close it" on the assistant's stances. **In v1.** **Everything not yet durable is abandoned**, sealed epochs whose flushes are in flight included; a flush that finishes afterwards reports nothing. **Dependants end, by existing rules:** gated operations on its epochs, in any lineage, end as `NeverIssued` and new gates on them are refused as `GateAbandoned` (contract guarantee 9, [DI-D-23](#di-d-23)) -- the case cycle detection cannot see, closed without new machinery. **No waiting, no cancellation:** operations in flight complete normally and are delivered, unlike ending an instance ([DI-D-25](#di-d-25)), which must wait because the instance owns their memory. **Ending retires:** the handle is refused at once and the lineage retired once its operations drain, so the consumer need not retry `retire_lineage`. **Shared failures:** a failure that also holds another lineage's writes stays unresolved for that lineage. **Reported** by a fifth synthesized entry, `LineageEnded { lineage, abandoned_through }`, in order with every other. **The default lineage cannot be ended**, as it cannot be retired. Operation cancel stays parked (DI-6+.4). The authoritative text is [CONTRACT.md](CONTRACT.md) "Ending a lineage" and [API.md](API.md) section 18. | -| DI-D-31 | **The Model A front end is `EntryDelivery`, built by `EntryDelivery::new(ring, on_entry, env)` from any `D: DurableRing`; the handler is `FnMut(EntryOf, &DeliveryHandle)`, and a failed construction hands back the ring and the handler.** The engineer's decision, 2026-10-07 ("write it in"), on the assistant's proposal under `DI-3.2.2.2`. **Name:** what `windows-ioring-sys`' `EventDelivery` is for a ring, one layer up -- it owns the instance and delivers on pool threads -- named for what it delivers rather than for this repository's "Model A". Model B has no type of its own, since its consumer uses the trait directly. **Constructor:** takes `ring: D` by value, because the front end owns the delivery ([DI-D-18](#di-d-18)), and is written once over the trait ([DI-D-24](#di-d-24)), so it serves dioring, the fault-injecting implementation and layers above alike. `env: Option<&mut CallbackEnviron>` selects a private pool or a callback priority, as `EventDelivery::new` does. Bound: `D: Send + 'static`. (`EntryOf: Send` was proposed with it and proved unnecessary in `DI-3.2.2.2`: a `Send` instance carries its queued entries across threads with it.) **Refusal:** `DeliverySetupError { error, ring, on_entry }`, by the rule that every refusal hands back what it took ([API.md](API.md) section 10); it fails when duplicating the readiness signal or creating the thread-pool wait fails. **Handler:** `FnMut`, because entries are delivered strictly one at a time (DI-2.3 point 8), so calls never overlap and a consumer keeps plain mutable state without its own synchronisation. Choosing `Fn + Sync` instead would have kept room to relax that serialisation, at the cost of making every consumer synchronise; the serialisation is a decided contract, so `FnMut` binds to it. **The handler receives a `&DeliveryHandle`**, a view that does not own the front end, rather than a reference to the owner: it is how the handler calls back in -- pushing the next write, say -- without the set-a-`Weak`-after-construction step `EventDelivery` leaves to its callers, and because it cannot keep the front end alive, a handler cannot cause the front end to be dropped from inside its own callback. The owner and the handle offer the same operations; which, and in what form, is still open under `DI-3.2.2.2`. **Handover:** entries already queued when the ring is handed over are delivered: `new` arms its wait and then sets the readiness event itself, since the signal may already have been consumed -- the guarantee `EventDelivery::new` had to earn (its `M26.12`), tested from the start here. Setting it needs no `unsafe` because the readiness duplicate is an `Event` ([DI-D-28](#di-d-28)). | +| DI-D-31 | **Amended by `win-sync-sys`' [WS-D-10](../win-sync-sys/DESIGN-NOTES.md#ws-d-10) (2026-10-18): setup clones the readiness signal rather than duplicating it, so it fails only when the instance cannot give the signal or the pool cannot create a wait.** **The Model A front end is `EntryDelivery`, built by `EntryDelivery::new(ring, on_entry, env)` from any `D: DurableRing`; the handler is `FnMut(EntryOf, &DeliveryHandle)`, and a failed construction hands back the ring and the handler.** The engineer's decision, 2026-10-07 ("write it in"), on the assistant's proposal under `DI-3.2.2.2`. **Name:** what `windows-ioring-sys`' `EventDelivery` is for a ring, one layer up -- it owns the instance and delivers on pool threads -- named for what it delivers rather than for this repository's "Model A". Model B has no type of its own, since its consumer uses the trait directly. **Constructor:** takes `ring: D` by value, because the front end owns the delivery ([DI-D-18](#di-d-18)), and is written once over the trait ([DI-D-24](#di-d-24)), so it serves dioring, the fault-injecting implementation and layers above alike. `env: Option<&mut CallbackEnviron>` selects a private pool or a callback priority, as `EventDelivery::new` does. Bound: `D: Send + 'static`. (`EntryOf: Send` was proposed with it and proved unnecessary in `DI-3.2.2.2`: a `Send` instance carries its queued entries across threads with it.) **Refusal:** `DeliverySetupError { error, ring, on_entry }`, by the rule that every refusal hands back what it took ([API.md](API.md) section 10); it fails when duplicating the readiness signal or creating the thread-pool wait fails. **Handler:** `FnMut`, because entries are delivered strictly one at a time (DI-2.3 point 8), so calls never overlap and a consumer keeps plain mutable state without its own synchronisation. Choosing `Fn + Sync` instead would have kept room to relax that serialisation, at the cost of making every consumer synchronise; the serialisation is a decided contract, so `FnMut` binds to it. **The handler receives a `&DeliveryHandle`**, a view that does not own the front end, rather than a reference to the owner: it is how the handler calls back in -- pushing the next write, say -- without the set-a-`Weak`-after-construction step `EventDelivery` leaves to its callers, and because it cannot keep the front end alive, a handler cannot cause the front end to be dropped from inside its own callback. The owner and the handle offer the same operations; which, and in what form, is still open under `DI-3.2.2.2`. **Handover:** entries already queued when the ring is handed over are delivered: `new` arms its wait and then sets the readiness event itself, since the signal may already have been consumed -- the guarantee `EventDelivery::new` had to earn (its `M26.12`), tested from the start here. Setting it needs no `unsafe` because the readiness duplicate is an `Event` ([DI-D-28](#di-d-28)). | | DI-D-32 | **`EntryDelivery`'s operations are `&self` methods on `DeliveryHandle` that mirror the trait's, the owner derefs to its handle, and no `&mut` to the instance is ever given out; ending quiesces the delivery first, and `into_inner` hands the instance back.** The two questions DI-D-31 left open, decided by the assistant while implementing `DI-3.2.2.2` on analytic grounds; **confirmed by the engineer, 2026-10-07** ("yes mirrored"; of ending by stopping delivery first, "I don't see how else it could work"). **Mirrored methods, not a closure over the instance:** a closure taking `&mut D` -- like a `DerefMut` scope -- lets safe code replace the instance while the wait stays armed on the old one's signal, and delivery then stops without an error; that is the ring crate's measured [D-43](../windows-ioring-sys/DESIGN-NOTES.md#d-43), whose record says outright that a closure does not close it. It would also let a caller `pop`, taking entries out of order, or take `readiness`, stealing wakes from the one waiter ([DI-D-28](#di-d-28)). So the handle offers every trait operation a consumer calls on a live instance, with the registered-span forms when `D: RegisteredBufferRing`, and registered bytes through closures over the bytes alone (`with_registered_buffer`, `with_registered_buffer_mut`); `pop` and `readiness` are withheld. [delivery.rs](src/delivery.rs) is the list. Each call takes the front end's lock on the instance for its duration; the handler is called with it released, under a lock of its own that only the deliverer takes. **The cost, owned:** the handle restates the trait's consumer operations, so each later step that adds one must add it here too; `DI-3.2`'s parent item says so, which is where a later step will read it. **Ending:** drop and `into_inner` both quiesce first -- disarm, wait for a delivery in progress, release the deliverer's reference -- before the instance is released ([DI-2.7](COMPLETED-CHECKLIST.md) point 5). Drop then drops the instance with any entry not yet delivered; `into_inner` returns it with those entries still queued, so it can be popped directly (Model B) or closed, which needs the instance by value. **Serialisation is structural:** the deliverer re-arms only after popping to `None`, and the readiness event is auto-reset and stays set until a wait takes it, so a wake that lands mid-drain starts a new delivery only after this one's last handler call. A failed `pop` ends the wake as `None` does. | | DI-D-33 | **Amended by [DI-D-40](#di-d-40): `epoch_state` for a lineage the instance does not have answers that explicitly, instead of panicking.** **Amended by [DI-D-34](#di-d-34): a failed flush is now observed as a failure and finishes its seal; only a write a consumer provider answers for still leaves one pending.** **One lineage's durability is an I/O-free state machine held under dioring's lock with the queue; the delivery callback records each completion there and pushes the flushes it makes due while still holding it.** Decided by the assistant while implementing `DI-3.2.3`, as the mechanism for [DI-D-9](#di-d-9) and [DI-D-22](#di-d-22); **confirmed by the engineer, 2026-10-07** ("all is true"), including one `Durable` per seal rather than one coalesced entry. **The machine** ([durability.rs](src/durability.rs)) decides and the relay acts, as [DI-D-18](#di-d-18)'s I/O-free core intends, so its rules are tested without a ring. A seal of n waits until no write of the lineage at or below n is in flight -- a flush issued before a write completes does not cover it, because coverage is by observation -- then names every completed write at or below n not yet named, and asks for one flush per file the built-in default answers for -- the default's unit is the file, because `IoRing`'s flush is a file handle's; a consumer provider is asked per flush domain instead ([DI-D-27](#di-d-27), `DI-3.2.6`) -- pushed with `FlushCoverage::Unordered` because it is issued only after what it covers has completed. Each write therefore belongs to exactly one seal, the first at or above its epoch, since later writes at or below a seal are refused. Seals may finish out of order; the high-water mark advances over finished seals in order, and one `Durable` is appended per seal as the mark passes it (guarantee 2). **Order on the queue:** a completion's `Op` entry is appended before anything it makes due, so a `Durable` follows the completions it covers whichever pool thread recorded them (guarantee 5). **Locks:** dioring's before the ring's (DI-2.3 point 10). A push holds dioring's lock across the ring push, so a write is recorded before its completion can be; the callback pushes due flushes while holding it, which the ring crate's [D-83](../windows-ioring-sys/DESIGN-NOTES.md#d-83) permits. A flush the ring refuses is kept and pushed at the next chance. **Reaching the ring from the callback without a cycle:** the delivery owns the callback, so the relay holds a `Weak` to it, upgraded only under dioring's lock and released before unlocking; dropping the instance first marks the core closed under that lock. So no callback holds the delivery once the instance ends, and it is dropped on the instance's own thread -- never inside one of its callbacks, where dropping it would wait for itself. **Left pending, not approximated:** a failed flush leaves its seal pending until `DI-3.2.4` reports it as `Failed`, and a write a consumer provider must answer for leaves its seal pending until `DI-3.2.6` asks the provider. **Not decided:** `epoch_state` for an epoch of a lineage the instance does not have; `EpochState` has no value for it, so dioring panics, documented on the method, while `make_durable_through` refuses with `InvalidInput` and `durable_through`/`sealed_through` answer `None`. Queued for the engineer under `DI-3.2.5`, where retired lineages raise the same question. | | DI-D-34 | **Confirmed by the engineer under `DI-3.9.1`, 2026-10-08, with three amendments recorded in [DI-D-46](#di-d-46): `import_failure` reports the domains it could not match; a heal waits for a seal that reaches the re-issue; and an inconsistency is panicked on where it is found.** **Amended by [DI-D-41](#di-d-41) and [DI-D-42](#di-d-42): a heal takes effect per lineage, a failure that suspects nothing heals at once, and `ImportScope::Lineage` reaches only that lineage's writes.** **Amended by [DI-D-36](#di-d-36): a suspect write that completes as failed after its failure was observed gains a nullifier.** **Amended by [DI-D-35](#di-d-35): a write tagged with an abandoned epoch is refused.** **Failures are held in the same I/O-free core as seals: every write is recorded, in push order, from its push until it is covered successfully, fails, or its epoch is passed by the high-water mark, and a failure's suspect set is the recorded writes its reach covers, frozen when it is observed.** Decided by the assistant while implementing `DI-3.2.4`, as the mechanism for [DI-D-12](#di-d-12) and [DI-D-21](#di-d-21), and confirmed by the engineer under `DI-3.9.1` (see the status above). **One transition** (DI-1.2 Q4): a failed flush and an import both go through `observe(cause, reach)`; the cause is data on the failure, and nothing about a failure depends on which it was. **Reach**, by the declared flush domains: a failed flush of a file declared with domains reaches files whose domains intersect them, and files declared with none; a file declared with none reaches every file; `ImportScope::Domains` by the same rule, with a domain no file declared reaching only the undeclared files; `ImportScope::Lineage` reaches every file while the default lineage is the only one, and nothing for another instance's. Built here rather than in `DI-3.2.5` because the suspect set is defined here: an instance-wide interim would over-report, and an abandon would then abandon epochs nothing put at risk. **What is recorded:** a write that failed leaves the record when its completion arrives, so a failure observed after that does not suspect it, while one observed during its flight keeps it (frozen); a write whose flush failed stays recorded, and so is suspected again by every later failure, until its epoch passes the mark -- which is CONTRACT.md's many-to-many example, A@41 in both failures. **An epoch passes** only when no unresolved failure contains it. **Abandon** records the failure's epochs as abandoned at once, reported by `epoch_state` from then on even for an open epoch; the mark still waits for every other failure containing them. **Heal** takes effect when the first seal made after it finishes with every flush successful -- not merely the first seal made, so a seal whose own flush fails does not count, and the heal waits for the next. Until then the failure is unresolved and in the inventory, and the pending heal holds its token: `token_live` is true and `take_token` answers `None`. **Answers:** a seal whose own flush fails is answered by that `Failed`; one whose flushes succeed while a failure holds an epoch at or below it is answered `Blocked` once, naming the earliest such failure observed; every seal still gets its `Durable` when the mark passes it ([DI-D-33](#di-d-33)), including those answered `Failed` or `Blocked`. **Resolve** refuses another instance's token, handing every token back in the order given, and applies the rest together, appending `Abandoned` entries and then what the change makes due. **Inconsistencies on a pool thread:** the core records an internal inconsistency instead of asserting, because most changes arrive on the delivery callback, where a panic aborts the process and names no test; `pop` asserts on it in debug builds, on the consumer's thread. Two sabotages were caught only by such an abort before this. **The fault seam:** `Dioring::fail_next_flush(file, code)`, behind the `fault-injection` feature, makes the next flush of `file` to complete report a Win32 error, by transforming its real completion through `windows-ioring-sys`' own seam, so the flush still reaches the kernel. A self dev-dependency enables it for every test build, so CONTRACT.md's worked examples, compiled as doctests, always run. **Not decided:** whether a write to an open epoch already abandoned should be refused; it is accepted, and the question is queued under `DI-3.2.5`, beside `GateAbandoned`. **CONTRACT.md corrected:** the many-to-many example's second ending said the mark moves past the abandoned epochs; by the contract's own resolution rule it does so once F1's heal takes effect, at the next seal, and the example now makes that seal. | @@ -53,7 +53,7 @@ appear here. | DI-D-39 | **Amended by [DI-D-50](#di-d-50): `MarkingKind::Short` is withdrawn and a nullifier carries an `IoFault`.** **Confirmed by the engineer under `DI-3.9.2`, 2026-10-08, with amendments recorded in [DI-D-47](#di-d-47): every report cites where it came from; a covered marking means covered successfully and cites who answered; and a nullifier's code is a plain `ErrorCode`, a code-less failed write being an inconsistency.** **Amended by [DI-D-42](#di-d-42): the oracle no longer requires a `Durable` that passes a healed failure to follow its `Healed` entry -- a heal takes effect per lineage ([DI-D-41](#di-d-41)), so one lineage's `Durable` may come first.** **How DI-D-36's markings and error codes are built.** Decided by the assistant while implementing `DI-3.2.4.2`, and reviewed by the engineer under `DI-3.9.2`. **A marking** is `Marking { write, observed, kind }`, with `MarkingKind` non-exhaustive -- `Nullified { code }`, `Short { transferred }`, `Covered` -- so new kinds can be added. **When one is made:** a nullifier or short marking at the suspect write's completion, which only a write still in flight when the failure was observed can have after it; short means fewer bytes than the write asked for, so the core now records each write's requested length at push. A covered marking when the write's **own file's** flush succeeds after the failure was observed. `DI-3.2.4.2`'s text said "in a seal that later succeeded", and that computes the wrong answer: a seal that fails on file A has still committed file B, whose writes are on the device. The stream cannot show seals per file either. A write is marked in every unresolved failure suspecting it, at one clock reading. **Where markings are read:** a `Marked` entry for each; `FailureInfo::markings`; and the final record, `Abandoned { markings }` or a new `Healed { failure, suspect, markings }`, emitted when a heal takes effect and before any `Durable` it lets through. `Failed` gets no field, since it would always be empty at observation. A failure abandoned or healed gains no more. **Error codes** are `ErrorCode`, an `HRESULT` newtype: the I/O ring reports every failure as one, and a Win32 code is the `HRESULT_FROM_WIN32` form. `ErrorCode::of(&io::Error)` reads the code from any error dioring reports, which answers [DI-D-34](#di-d-34)'s question of how a consumer reads it without the ring crate's `IoRingErrorExt`. The code is **not** stored beside the `io::Error` in `Outcome::Failed` or `Cause::Flush`: the error already holds it, and a second copy could disagree. It is stored in a nullifier, which has no error to read it from; that field is an `Option` because it is read from an `io::Error`, and it is `Some` for every completion the kernel reports. **The oracle** checks each marking against the write's own completion, keeps one stamp order across `Failed` and `Marked`, and checks that a final record carries exactly the markings reported. Because `Healed` exists, it now also requires a `Durable` that passes a failure to follow that failure's `Healed` or `Abandoned` entry. It does not check which markings *should* have been made: whether a write was in flight at observation, its length, and per-file flushes are not in the stream. | | DI-D-40 | **A lineage is held through a `Clone` handle, and ends when its last handle is released. Operations that act on a lineage take its handle; operations that name or ask about a lineage or epoch take the plain value, and say explicitly when it is not one the instance can answer for.** The engineer's decisions, 2026-10-08, reached in discussion and confirmed as "the coherent answer". **The handle** wraps an `Arc` in a newtype, is `Clone` and `#[must_use]`, and is an associated type of the contract beside `FailureToken`. It carries the lineage's `Copy` name, `Lineage`, which stays the identity used in entries, reports and persisted epochs. **Ending:** releasing the last handle ends the lineage ([DI-D-30](#di-d-30)), abandoning every epoch not yet durable: a holder that has let go of every handle will not finish it, and a panic unwinding past the last handle ends it the same way. `#[must_use]` warns only when a returned handle is discarded unused -- not on `let _ =`, nor for a handle dropped with the struct holding it -- so the documentation says plainly that releasing the last handle abandons pending work. `end(handle)` and `retire(handle)` take the handle by value and succeed only when it is the last, handing it back otherwise, so any existing handle proves its lineage live; `retire` is the non-destructive close, succeeding only when nothing is pending. **The default lineage:** construction returns its handle, and the instance keeps one reference itself. So the default never ends while the instance lives, and `end` or `retire` of it never succeeds: [DI-D-19](#di-d-19)'s and DI-D-30's rule that it cannot be ended or retired follows from ownership instead of being an exception. The instance hands out a clone on request. A handle reaches dioring through a `Weak`, so a handle that outlives its instance does nothing, and the instance's own reference makes no cycle. **The rule**, applied to every operation (the engineer: "apply the rule that makes them consistent, I don't want to mis-specify"): acting on a lineage -- pushing a write into it, sealing it, ending or retiring it -- takes its handle, with a run-time refusal kept for another instance's handle. Naming or asking -- a gate (which may name another lineage), `ImportScope::Lineage`, `epoch_state`, `durable_through`, `sealed_through`, and every entry and report -- takes the plain `Lineage` or `Epoch`, checked at run time, and answers "not a lineage this instance can answer for" explicitly, never by panicking. That settles [DI-D-33](#di-d-33)'s open question, and corrects two answers that were already inconsistent: `durable_through` and `sealed_through` answered `None` both for "nothing yet" and for "not this instance's", and must tell them apart; and `import_failure` naming a lineage the instance does not have made a failure that suspects nothing, and must refuse instead. A retired lineage answers as a foreign one does: the instance keeps no memory of it, as it keeps none of a resolved failure. **Why handles** (the engineer: "Should we register a lineage, get a lifetime managed type back and not pass arbitrary values around?"): the Rust API Guidelines' C-VALIDATE prefers ruling a bad argument out by its type to checking it. A handle makes an operation on a lineage that is not live impossible to express, while values that must outlive an instance -- epochs written into a consumer's own logs -- stay plain and are checked. Branded lifetimes, which would also rule out another instance's handle at compile time, were not taken: they tie handles to a scope, so handles could not be stored freely. **Provenance, amended 2026-10-14 after the review of PR 126:** the refusal of another instance's handle cannot rest on the instance named in the handle's `Lineage`, since `LineageHandle::detached` and `mint` are public (a fake in another crate needs them) and take that name as an argument; a handle made for a real lineage has a reference count of its own, so ending or retiring through it would abandon epochs while the genuine handles were live, and the genuine last release would then find the lineage gone and panic. The check is the home the handle reports to, which a driver compares with its own and which a caller holding no reference to the instance cannot name; a handle whose home is not the instance's is refused as `Foreign`, and handed back. No work is queued behind this. | | DI-D-41 | **Amended by [DI-D-46](#di-d-46): the releasing seal must also reach the highest epoch pushed in the lineage when the heal was made, which is where the re-issued writes are.** **A heal takes effect per lineage: a healed failure stops holding lineage L once L's first seal made after the heal finishes with every flush successful, and is resolved as healed -- its `Healed` entry emitted -- only once that has happened in every lineage it belongs to.** The engineer's decision, 2026-10-08, on the assistant's proposal: "let's go with per lineage for now. we can worry about more complex cases later if necessary." It generalises [DI-D-12](#di-d-12) (c)'s "the first seal made after the heal", which named no lineage once a failure could span several ([DI-D-19](#di-d-19)). **Why per lineage:** the consumer re-issues a failure's lost writes into each lineage they belonged to, and each lineage's re-issued writes are committed only by its own next seal. Letting one lineage's seal end the failure everywhere would let another lineage's mark pass epochs whose re-issued writes it had not committed -- a wrong answer, not a weaker one. **Its cost, accepted:** a failure one of whose lineages never seals again stays in the inventory, healing, indefinitely, while the lineages that did seal are released. **"For now":** the engineer left more complex cases for later, if they prove necessary; nothing names one yet, so no work is queued for them. Built by `DI-3.2.5.1`; `DI-3.2.5.2`'s cycle search follows the dependency it creates, reaching L:a waiting on L's next seal. | -| DI-D-42 | **How [DI-D-40](#di-d-40)'s handles and [DI-D-41](#di-d-41)'s per-lineage heals are built.** Decided by the assistant while implementing `DI-3.2.5.1`, to be confirmed by the engineer under `DI-3.9.3`. **Confirmed by the engineer, 2026-10-13 (`DI-9.1.1` to `DI-9.1.4`): a handle's release runs on the thread that drops the last copy; an ended lineage is not live from the moment it ends; heals take effect as soon as nothing can delay them; and the oracle drops DI-D-39's tightening, with the four ended-lineage rules. The oracle's weaker rule cannot check when a heal takes effect; making it able to is parked as [DI-6+.9](CHECKLIST.md), scheduling nothing now. **Ratified by the engineer, 2026-10-13 (`DI-9.1.5`): the shapes, so the whole row is confirmed.** **The tag:** a write and a seal take `Tag<'_, V>`, an epoch id with a borrowed handle, made by `LineageHandle::at(id)`; it borrows the handle, never the instance, so the lineage cannot end while a push or a seal that names it is under way, even if another thread drops a copy. **Construction** returns `(Dioring, LineageHandle)`, the default lineage's handle beside the instance. **Answers for a name the instance cannot answer for:** `durable_through`, `sealed_through` and `epoch_state` return `Result<_, UnknownLineage>` -- `epoch_state` no longer panics -- and `import_failure` returns `Result`, refusing a `Lineage` scope it cannot answer for and recording nothing. A foreign handle is refused as `PushRefusal::UnknownLineage` by a push and as `InvalidInput` by a seal. **Refusals:** `EndLineageRefusal` is `Foreign`, `Default` or `Shared`, and `RetireLineageRefusal` adds `Busy(LineageBusy)`; both errors hand the handle back, and are checked in that order. `LineageBusy.held_for_gate` is zero until gates exist (`DI-3.2.5.2`), and `in_flight` counts writes, clearing as their completions arrive rather than when they are popped. Retiring reports no entry: it abandons nothing. **An ended lineage is not live from the moment it ends**, though it stays recorded until its writes in flight complete: its name is answered as unknown, a flush of its that answers afterwards reports nothing, and a write of its that completes afterwards is let go -- still marking any failure that already suspected it, which a failure observed after the end cannot. **`LineageEnded.abandoned_through`** is the highest epoch above the lineage's mark that it sealed or wrote, `None` if there is none. **A failure belongs to no lineage when its suspect set is empty, and healing it takes effect at once** -- the per-lineage rule applied to zero lineages. With one lineage, such a heal used to wait for the next seal. **Ending a lineage a heal waits for releases the heal there**, so its `Healed` entry may follow `LineageEnded` directly. **The oracle** drops DI-D-39's tightening: a `Durable` may pass a failure once the consumer has healed it, because the stream cannot show which lineage's seal made the heal effective. It gains four rules for an ended lineage: it is reported ended once, no `Durable` or `Blocked` names it afterwards, no write into it is accepted, and no later failure suspects a write of it. **A handle's release** runs on whichever thread drops the last copy, and takes dioring's lock and then the ring's, as a completion callback does; a handle that outlives its instance releases nothing. **The core** keys each lineage's state by its sequence number, the default's being zero, and keeps writes and failures per instance; lifecycle lives in `durability/lineages.rs`. | +| DI-D-42 | **How [DI-D-40](#di-d-40)'s handles and [DI-D-41](#di-d-41)'s per-lineage heals are built.** Decided by the assistant while implementing `DI-3.2.5.1`, to be confirmed by the engineer under `DI-3.9.3`. **Confirmed by the engineer, 2026-10-13 (`DI-9.1.1` to `DI-9.1.4`): a handle's release runs on the thread that drops the last copy; an ended lineage is not live from the moment it ends; heals take effect as soon as nothing can delay them; and the oracle drops DI-D-39's tightening, with the four ended-lineage rules. The oracle's weaker rule cannot check when a heal takes effect; making it able to is parked as [DI-6+.9](PARKED.md), scheduling nothing now. **Ratified by the engineer, 2026-10-13 (`DI-9.1.5`): the shapes, so the whole row is confirmed.** **The tag:** a write and a seal take `Tag<'_, V>`, an epoch id with a borrowed handle, made by `LineageHandle::at(id)`; it borrows the handle, never the instance, so the lineage cannot end while a push or a seal that names it is under way, even if another thread drops a copy. **Construction** returns `(Dioring, LineageHandle)`, the default lineage's handle beside the instance. **Answers for a name the instance cannot answer for:** `durable_through`, `sealed_through` and `epoch_state` return `Result<_, UnknownLineage>` -- `epoch_state` no longer panics -- and `import_failure` returns `Result`, refusing a `Lineage` scope it cannot answer for and recording nothing. A foreign handle is refused as `PushRefusal::UnknownLineage` by a push and as `InvalidInput` by a seal. **Refusals:** `EndLineageRefusal` is `Foreign`, `Default` or `Shared`, and `RetireLineageRefusal` adds `Busy(LineageBusy)`; both errors hand the handle back, and are checked in that order. `LineageBusy.held_for_gate` is zero until gates exist (`DI-3.2.5.2`), and `in_flight` counts writes, clearing as their completions arrive rather than when they are popped. Retiring reports no entry: it abandons nothing. **An ended lineage is not live from the moment it ends**, though it stays recorded until its writes in flight complete: its name is answered as unknown, a flush of its that answers afterwards reports nothing, and a write of its that completes afterwards is let go -- still marking any failure that already suspected it, which a failure observed after the end cannot. **`LineageEnded.abandoned_through`** is the highest epoch above the lineage's mark that it sealed or wrote, `None` if there is none. **A failure belongs to no lineage when its suspect set is empty, and healing it takes effect at once** -- the per-lineage rule applied to zero lineages. With one lineage, such a heal used to wait for the next seal. **Ending a lineage a heal waits for releases the heal there**, so its `Healed` entry may follow `LineageEnded` directly. **The oracle** drops DI-D-39's tightening: a `Durable` may pass a failure once the consumer has healed it, because the stream cannot show which lineage's seal made the heal effective. It gains four rules for an ended lineage: it is reported ended once, no `Durable` or `Blocked` names it afterwards, no write into it is accepted, and no later failure suspects a write of it. **A handle's release** runs on whichever thread drops the last copy, and takes dioring's lock and then the ring's, as a completion callback does; a handle that outlives its instance releases nothing. **The core** keys each lineage's state by its sequence number, the default's being zero, and keeps writes and failures per instance; lifecycle lives in `durability/lineages.rs`. | | DI-D-43 | **A seal that would close a gate cycle is refused, as a push that would is; and the search keeps one bound per lineage over its held writes sorted by epoch, naming the cycle only once there is one.** The engineer's decision, 2026-10-08, choosing the first of three options the assistant set out -- refuse such a seal; split seals at gated epochs; or leave seal-closed cycles undetected as a limit. **Why a seal can close one:** which held writes a lineage must complete to reach epoch `b` depends on the seal that will cover `b`, whose extent the consumer chooses after the gated writes were accepted, and a pending heal waits for the next seal however far it reaches ([DI-D-41](#di-d-41)). A write at `M:4` gated on `L:9` and one at `L:9` gated on `M:3` close no cycle when pushed, yet sealing `M` through 5 deadlocks: reaching 3 needs that seal, which waits for the write at 4. Sealing through 3 first does not. **The engineer's direction on the search:** "we need to strongly lean towards the normal non-error case being fast"; reduce it to small identifiers, sorted lists and closures over the lineages and heals in flight; "once we find that we're in the non-success case, we can take more time to give the more detailed diagnostic data"; and do not shape the exploration to gather that data up front if that costs more than a trivialized algorithm. **As built** (`durability/gates.rs`): the *extent* of `L:y` is `y`, raised to the first seal of `L` at or above `y` and, while a healing failure holds an epoch of `L` at or below `y`, to the seal its heal waits for; a seal not yet made adds nothing, because it is checked when made. Needs are monotone in `y`, so the search keeps one bound per lineage, only raises it, and scans only the newly covered range of that lineage's held writes; each lineage's held writes are scanned once, in any order of arrival. A push gated on `M:b` searches from `M:b` for its own lineage needing its epoch. A seal searches once from every held write beneath it together -- if that never needs the lowest, none is on a cycle -- and only then from each alone. The cycle is named by a second, breadth-first search with a parent per step, run only on that path. A push with no gate, and a seal over no held write, search nothing. **Decided by the assistant while implementing `DI-3.2.5.2`, confirmed by the engineer under `DI-3.9.4`, 2026-10-13 (`DI-9.2.1`: a held write whose own epoch is abandoned ends as never issued naming that epoch, and the gate is named when both are abandoned; `DI-9.2.2`: a held write is outstanding for its lineage's seals and no failure suspects it until released; `DI-9.2.3`: `SealError` in place of `io::Error`; `DI-9.2.4`: the rest, ratified):** a gate naming an ended or retired lineage is refused as `UnknownLineage`, since [DI-D-42](#di-d-42) makes such a lineage one the instance cannot answer for; a held write whose own epoch is abandoned first ends as `NeverIssued` naming that epoch, since no write may join it ([DI-D-35](#di-d-35)), and the gate is named when both are; a held write is outstanding for its lineage's seals, as one in flight is, and no failure suspects it until it is released; `make_durable_through` returns `SealError::{UnknownLineage, GateCycle}` in place of an `io::Error`; a cycle is listed as each held write's epoch followed by its gate; and released operations are pushed at the consumer's next call on the instance -- normally the `pop` that returns the `Durable` releasing them -- because a registered-span push needs the registration and an owned push its buffer's bound, which a held owned operation carries as a function pointer made where the bound held. The oracle gains: a gated operation completes only after its gate is reported durable, `NeverIssued` comes only for one and names its gate or own epoch once reported abandoned, and no failure suspects a held write. | | DI-D-44 | **Amended by [DI-D-50](#di-d-50): a request names `IoTargetWrites` in place of a file and its handle.** **Amended by [DI-D-46](#di-d-46): an answer no request asked for is panicked on where it is found, not recorded.** **The consumer's provider is called from the consumer's own next call on the instance, with dioring's lock released; and its answers and failures are built from what [DI-D-27](#di-d-27) already decided.** Decided by the assistant while implementing `DI-3.2.6`, and confirmed by the engineer under `DI-3.9.5`, 2026-10-13 (`DI-9.3.1`, `DI-9.3.2`, `DI-9.3.3`), as amended above. **When the provider is called:** a seal that becomes ready to start queues its request in the core; the request is made by the consumer's next `pop`, `make_durable_through`, `resolve` or `end_lineage`, after the lock is released -- never by a completion callback on a pool thread, and never with the lock held. Two constraints force this: [DI-D-27](#di-d-27) lets a provider answer inside `make_durable`, and that answer takes the lock; and a provider panic propagates, which it can only do to a caller. Under entry delivery the `pop` is the deliverer's, so the call is made on its thread. Nothing is stranded: a seal starts at the seal call itself, or at the completion of its last write, which always queues an entry the consumer pops. **Building before calling:** every request in the queue is built before any is handed over, so a panic drops the rest, and each completion dropped unanswered is a failure of its domain, `Cause::ProviderAbandoned`. **A request:** one per seal, naming only the domains the provider serves that the seal's writes touched, in the order of their interned ids; each file in a domain, with the handle the instance was given; and each file's writes in push order. A seal that names no write the provider serves asks it nothing. **Coverage:** a write is covered successfully when its file's flush succeeded, if the default answers for the file, and every served domain of its file answered success; the seal finishes once every flush and every domain has answered. **A failure** of domain D is observed with `Reach::Domains([D])`, which is exactly DI-D-27's rule -- files whose domains contain D, and files declared with none -- so it needs no reach of its own; it is the seal's answer, and later answers for that seal cover writes without reporting anything more. **An answer that cannot be applied:** one for a lineage no longer live reports nothing, as a late flush does ([DI-D-42](#di-d-42)); one for a seal the lineage does not have, one for a seal not flushing, one for a domain the seal did not ask about, and a second answer for a domain are recorded as inconsistencies and change nothing. A seal reported durable is no longer kept, so an answer for it is one for a seal the lineage does not have. | | DI-D-45 | **Amended by [DI-D-51](#di-d-51): the `trace` feature it introduced is removed; an inconsistency is reported to the instance's diagnostic sink.** **Amended by [DI-D-46](#di-d-46): an internal inconsistency is no longer looked for at the end; it is traced and panicked on where it is found, and only traced while unwinding.** **How `remove_file`'s refusal and [DI-D-25](#di-d-25)'s ending are built: one ending path for `close()` and drop, which closes the core and takes what is left in one locked step, lets the provider go, and waits for the completions still out.** Decided by the assistant while implementing `DI-3.2.7`, and **confirmed by the engineer, 2026-10-08** ("di-3.2.8: confirmed!"). **`FileBusy`:** `in_flight` counts the file's consumer operations from their push to the record of their completion, so it clears as completions arrive, which may be before they are popped -- the rule [DI-D-42](#di-d-42) chose for `LineageBusy`; a seal's own flush is not counted, since a write it is for is still uncovered. `uncovered` is the file's completed writes not yet covered and outside every unresolved failure's suspect set, and `failures` every unresolved failure, healing ones included, whose frozen suspect set names a write to the file -- even one covered since, because [API.md](API.md) section 8 has every write in such a set hold its file. `remove_file` pushes no released operation, so one waiting to be pushed counts as held. A file given at construction comes back and its key is free at once, while the registration holds it open until the instance ends. **The ending step:** under the lock, the core is marked closed, the held operations taken in push order, the caller's snapshot read -- `close()` reads the failures and the live lineages -- and requests for the provider not yet made, flushes not yet pushed and released operations not yet pushed are discarded. A completion recorded afterwards is dropped with what it held: no entry, no readiness signal. **Letting the provider go:** the provider is dropped next, before the wait, because a provider that keeps completions in itself would otherwise answer them only when dropped -- after a wait they hold up. **The wait:** every completion is counted when it is made and counted down when it is answered or dropped, after the end included; ending waits on a condition variable until the count is zero, which releases the lock while it waits, as DI-D-25 requires. The kernel is waited for by the ring's rundown when the instance's fields are dropped. **While unwinding:** an internal inconsistency found at the end is recorded through `windows-threadpool-sys`' trace under target `dioring` -- compiled in by the crate's new `trace` feature -- and asserted, in debug builds, only when the thread is not already unwinding, as the ring crate's [D-79](../windows-ioring-sys/DESIGN-NOTES.md#d-79) does. **Model A:** `EntryDelivery::close` quiesces delivery and closes the instance, and `DeliveryHandle::remove_file` forwards. | @@ -62,7 +62,7 @@ appear here. | DI-D-48 | **Amended by [DI-D-49](#di-d-49): targets sit beneath dioring's core, served by a backend that runs their I/O; a layer such as a partition or VHDX driver is a target, not an implementation of the contract.** **The contract's unit of I/O and durability is a target: a placeholder defined only by its contractual obligations. Files leave the contract's vocabulary; a Win32 file object is one way to back a target, and a separate concern.** The engineer's decision, 2026-10-09 ("Let's switch to this. I think it's a much stronger position. Then focusing on the win32 file object as a target can be a separate concern"), on the engineer's own proposal ("I wonder if we should perhaps abstract away from 'files' entirely and instead have a placeholder object with only its contractual obligations called out") and its direction for the substrate ("Let's describe an interface and implement to it"). **Why:** the principal consumers -- a filesystem over a raw partition or over a dynamic VHDX driver that consumes dioring and itself produces the contract, and databases opening unbuffered handles -- sit on substrates of different strength, and buffered I/O "can only be done on a 'best effort' basis" (the engineer), so a contract stated over files either overpromises for a cached file or says nothing a raw volume could warrant. Stated over obligations, each kind of backing says how far it honours them, and dioring's guarantee is durable to the extent its targets honour theirs. **The obligations, as the assistant drafted them** (to be settled under `DI-3.10.1`; the name "target" is provisional): a write of bytes at an offset is accepted and its completion reported, transferred or failed; a commit that reports success warrants that every write it completed before the commit was issued is on stable media, and one that reports failure claims nothing for writes not yet covered; a target states which others' writes its commit's failure can affect (today's flush domains); every failure affecting it that it learns of is reported, and a later success never implies an earlier failure repaired; alignment constraints on offsets and lengths; optional capabilities such as per-write write-through. **Every cause is relayed** (the engineer: "it's true of all cases, we're always just relaying"): a cause names its source -- the layer below, which for dioring over Windows is the kernel and for the VHDX driver is a lower dioring, the consumer, or the provider -- which [DI-D-47](#di-d-47)'s provenance rule then governs; `Cause::Flush`'s "kernel-reported" is the first case of "reported by the layer below". **What is restated, under `DI-3.10`:** the file vocabulary through the trait and types (`FileKey`, `add_file`, `remove_file`, `FileBusy`, `FileOptions`), what a provider request names ([DI-D-29](#di-d-29)), the built-in default provider as a target's own commit ([DI-D-27](#di-d-27)), flush domains as a target's failure-sharing obligation ([DI-D-21](#di-d-21)), and CONTRACT.md throughout; those rows gain amendment markers as each is restated. Until then the code and those rows stand as built. **Open:** whether [D-44](../windows-ioring-sys/DESIGN-NOTES.md#d-44)'s spike-before-wrapping rule is revisited, or applies inside each backing's implementation rather than gating the design -- the assistant proposed the latter; the engineer has not ruled. | | DI-D-49 | **dioring is an algorithm: a portable, public state machine at the core, with targets and the backend that runs their I/O beneath it and a threaded driver above it. Layers are targets. The code is restated in place.** The engineer's decisions, 2026-10-09. **Portable** (the engineer: "the crate is 'durable-ioring', not 'win-durable-ioring', so perhaps it can be non-windows-only? ... There is no hard requirement for disabling non-windows building"; "if our dioring solution can be algorithmic only that's actually ideal"): the core names no platform type and is built and tested off Windows by CI's `portable` job, as `wtf-string`'s portable core already is. It supersedes [DI-D-3](#di-d-3), whose one retained consequence stands: the contract is written in terms of durability, not a platform's incidental behaviour. **Layering** (the engineer: "the state machine would be public ideally with the threaded driver on top, possibly as a separate 'win-' crate since it *is* windows specific"): the core takes calls and completions and returns what is due -- ring entries to issue, entries to deliver -- with no threads, I/O or clock reads of its own; a driver owns a backend, the lock, the notification and delivery, runs the core, and implements `DurableRing`. A consumer with its own ring-shaped substrate can embed the core directly. **Targets beneath the core** (the engineer, on the assistant's backend/target refinement: "seems OK", provided the I/O interface is "shaped like a ring with a sq/cq and a doorbell and notification mechanism"): a backend submits and completes I/O for the targets registered with it; a target carries obligations ([DI-D-48](#di-d-48)). **Layers are targets** (the engineer: "yes that seems like the right refinement. I didn't see why a partition driver would provide an entire dioring"): a partition is a window over another target, and a VHDX driver is a target whose commit succeeds once its own writes are durable in the order its format needs; lineages, failures and resolution live once, in the core at the top. **Restated in place** (the engineer), with today's tests and sabotages as the proof the Windows backend passes. **Assigned to the assistant:** `DI-3.10.1`'s design, as concrete proposals the engineer approves or rejects (the engineer: "I am suffering from 'design fatigue' at this moment and to make better progress you should make concrete proposals that I approve or disapprove"). **Open:** the driver's crate and name -- the workspace's [naming decision](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix) keeps `win-` for crates that raise a Windows API into safe Rust and gives a new facility its own name, so the assistant proposes a name under `DI-3.10.1`. | | DI-D-50 | **The target design (`DI-3.10.1`): `IoTarget` and its obligations, `DurableIoCore` and its backend, `FailureCause` and `IoFault`, and two crates.** **Amended by [DI-9.7.11](COMPLETED-CHECKLIST.md#di-9711): `ErrorCode` no longer has `from_win32` or `win32`; it carries and prints the number, and `win-durable-ioring` wraps a Win32 code into one (`error_code_from_win32`) and reads one back (`win32_code_of`), on `win-error-codes`' `HResult`.** Proposed by the assistant as P1-P13 and approved by the engineer, 2026-10-09 ("otherwise yes good. In-memory is a good basis for unit testing and driving"); the reasoning behind P2 is in the session log. **Names** (P1; the engineer chose the first two, and asked for names that are not "overly short"): `IoTarget`, `IoTargetKey`, `IoTargetBusy`, `IoTargetWrites`, `DurableIoCore`, `CoreActions`, `FailureCause`, `IoFault`; `add_target`, `remove_target` and `ErrorCode` keep their names. **Obligations** (P2): every write, read and commit completes exactly once; a write completes in full or with an `IoFault`, never short; for each write that completed successfully, the first commit submitted after its completion decides it -- success puts its bytes on stable media, failure ends every statement about it, and a later commit's success does not warrant it; a write that completes with a fault is never warranted; a target declares failure domains, meaning a failed commit may have lost any completed write not yet decided by a successful commit, on it or on a target sharing a domain, which dioring acts on; and a target states its alignment and capabilities. Covered is then a write whose deciding commit succeeded, and suspect one whose deciding commit failed or a sharing target's commit failed first -- dioring's behaviour is unchanged. **Short writes:** on Linux a backend handles a short count under a policy the client chooses when building it -- `Fault` by default, `Reissue { max_attempts }` for a client that handles `SIGXFSZ` (the engineer: "Can we just ask the client for how to proceed?" ... "yeah go with (i)"); asking per event would report the short completion after a seal may already be committing its epoch, and checking `getrlimit` first races `prlimit(2)`. The Windows backend never sees one. **Warranty class** (P3), reported by each target and informational only: `Complete`, `SharedDevice`, `BestEffort`; dioring's behaviour does not depend on it and `Durable` is not labelled with it ([DI-D-47](#di-d-47) (F)). **Failure sharing** (P4): a target declares its own domains; [DI-D-21](#di-d-21)'s and [DI-D-27](#di-d-27)'s rules are unchanged, a target with none shares fate with everything, and `FileOptions` goes. **Adding and removing** (P5): `add_target(key, target)` hands the target back on a duplicate key; `remove_target(key)` returns it, refused with `IoTargetBusy` for the holds that refuse `remove_file` today. **Provider requests** (P6) name `IoTargetWrites { key, writes }`; a provider keeps its own access to whatever backs a target. **The default provider** (P7) issues one commit per target the seal touched, through the backend. **Causes** (P8): `FailureCause::Commit { target, fault }`, `Imported`, `Provider`, `ProviderAbandoned`; `IoFault { source, code: Option, error }`, `source` naming the reporting layer; `ErrorCode` a portable integer whose meaning is the source's, an `HRESULT` from the Windows backend; `Outcome::Failed` carries an `IoFault`, and `Nullified { fault }`. This revises [DI-D-47](#di-d-47)'s ruling C: a code is optional again because a layer other than the kernel may have none. **A protocol breach** (P9) -- a completion twice, or for an entry never submitted -- is panicked on under [DI-D-46](#di-d-46), naming the backend; an ordinary fault, with or without a code, never is. **The ring** (P10): a backend is push, submit as the doorbell, pop, and a notification on every empty-to-non-empty completion transition; commits cover a whole target; completions are learned by waiting on the notification and draining; buffers go in owned and come back; registered buffers are a backend capability. **The core** (P11): `DurableIoCore` is public; its operations and `complete(cqe)` return `CoreActions { submit, deliver }`; its clock is a portable type parameter, tracing goes through a hook, and the driver's readiness is an associated type. **Crates** (P12): `durable-ioring`, portable -- the core, the contract trait and types, the `IoTarget` and backend traits, the in-memory backend, the oracle; and `win-durable-ioring` (the engineer's name) -- the threaded driver, the Windows backend, and the file and volume targets. **`DI-3.3`** (P13) is an in-memory backend in the portable crate whose targets and backend both fault on a script -- fail, hold, or complete late or out of order; refuse a submission; fill the queue; report a short count, to test a backend's policy -- public and deterministic. The 2026-10-05 purpose of a public second implementation of the contract is served by the target interface instead, which has two public backends. The fault seam behind `fault-injection` stays until the in-memory backend is proven, and its merge-or-delete decision is queued as `DI-3.11`. **The word is "flush domain"** (`FlushDomain`): what P2 and P4 call a target's failure domains keeps the contract's existing name (the engineer, on `DI-3.10.2`'s restatement: "let's call it a FlushDomain"). **`win-durable-ioring` is a recorded exception** to the workspace's [naming decision](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix), which gives `win-` only to crates raising a Windows API into safe Rust; the engineer chose an exception over amending the rule ("record this as an exception"), and it is recorded there. | -| DI-D-51 | **Diagnostics go to a sink the consumer chooses: a `DiagnosticSink` trait in the portable core, receiving typed events, with ETW one sink among several.** Proposed by the assistant as T1-T5 and approved by the engineer, 2026-10-09 ("approved"), on the engineer's point: "I think that we need another level of indirection for the tracing on Windows or maybe is it just in general? At least even a windows only consumer may not want to use ETW and may want to just have their own stderr based or OutputDebug based printing." **In general** (T1), because the core is portable: the core reports typed events -- [DI-D-26](#di-d-26)'s delay events and the inconsistency report of [DI-D-46](#di-d-46) -- so an ETW manifest maps each one-to-one and a text sink formats them as it likes. **Free when unwanted** (T2): a sink states which kinds it wants, and the core checks before building an event, so a sink that wants nothing costs a branch -- DI-D-26's "costs nothing when no session listens" carries over. **Sinks shipped** (T3): `NoDiagnostics`, `StderrDiagnostics` and `FanOutDiagnostics` in `durable-ioring`; `EtwDiagnostics` and `DebugOutputDiagnostics` (`OutputDebugStringW`) in `win-durable-ioring`; a consumer may implement its own. **Defaults** (T4): the Windows driver's is `EtwDiagnostics`, so a consumer who chooses nothing still gets DI-D-26's surface; any sink, `NoDiagnostics` included, can be passed when the driver is built; the core alone defaults to `NoDiagnostics`. **The `trace` feature goes** (T5): the inconsistency report goes through the sink, its panic message unchanged, and the dependency on `windows-threadpool-sys` for tracing with it. Built under `DI-3.10.3` (the trait, the portable sinks, the inconsistency report) and `DI-3.6` (the delay events and the Windows sinks). | +| DI-D-51 | **Amended by [DI-D-91](#di-d-91): the typed events are [DI-D-80](#di-d-80)'s begin and end pairs and the inconsistency report; DI-D-26's delay events are gone.** **Amended by [DI-D-76](#di-d-76): `EtwDiagnostics` is in the `durable-ioring-events` crate, not in `win-durable-ioring`, which depends on it for its default.** **Diagnostics go to a sink the consumer chooses: a `DiagnosticSink` trait in the portable core, receiving typed events, with ETW one sink among several.** Proposed by the assistant as T1-T5 and approved by the engineer, 2026-10-09 ("approved"), on the engineer's point: "I think that we need another level of indirection for the tracing on Windows or maybe is it just in general? At least even a windows only consumer may not want to use ETW and may want to just have their own stderr based or OutputDebug based printing." **In general** (T1), because the core is portable: the core reports typed events -- [DI-D-26](#di-d-26)'s delay events and the inconsistency report of [DI-D-46](#di-d-46) -- so an ETW manifest maps each one-to-one and a text sink formats them as it likes. **Free when unwanted** (T2): a sink states which kinds it wants, and the core checks before building an event, so a sink that wants nothing costs a branch -- DI-D-26's "costs nothing when no session listens" carries over. **Sinks shipped** (T3): `NoDiagnostics`, `StderrDiagnostics` and `FanOutDiagnostics` in `durable-ioring`; `EtwDiagnostics` and `DebugOutputDiagnostics` (`OutputDebugStringW`) in `win-durable-ioring`; a consumer may implement its own. **Defaults** (T4): the Windows driver's is `EtwDiagnostics`, so a consumer who chooses nothing still gets DI-D-26's surface; any sink, `NoDiagnostics` included, can be passed when the driver is built; the core alone defaults to `NoDiagnostics`. **The `trace` feature goes** (T5): the inconsistency report goes through the sink, its panic message unchanged, and the dependency on `windows-threadpool-sys` for tracing with it. Built under `DI-3.10.3` (the trait, the portable sinks, the inconsistency report) and `DI-3.6` (the delay events and the Windows sinks). | | DI-D-52 | **Amended by [DI-D-53](#di-d-53): pool delivery is a waker the ring crate provides beside a threadless backend, not `EventDelivery` owning the backend's ring; and by [DI-D-54](#di-d-54): a gated operation is put to the backend's `check` before it is held, and a submission of the core's own refused for a cause that is not retryable ends as failed.** **`DI-3.10.3.5`'s shape: the core emits a submission shape of its own; a refused push names its cause; delivery is factored out of the backend, under both of the ring crate's models.** The assistant's Q1-Q6, ruled by the engineer, 2026-10-10; the exchange is in the session log. **Submissions** (Q2; the engineer: "I feel confident we had already approved this" -- [DI-D-50](#di-d-50) P11's `CoreActions` implied it): the core holds no backend and does no I/O; it emits a portable submission -- a write, read or commit naming an `IoTargetKey`, an offset and a token, its buffer owned or a registered region -- and the backend maps the key to its own handle. **Traits** (Q3): `IoTarget` reports its flush domains, warranty class, alignment and capabilities; the backend has a target, buffer and notification type, `attach` and `detach`, and `push`, `submit` and `pop`; the driver gives the target to the backend and its description to the core. The engineer reviews the source as it lands and is not a gate on it ("I cannot be the blocking factor"). **Refusal** (Q4): a refused consumer operation is undone and hands its buffer back; a refused commit is kept and emitted again; nothing blocks inside the core or a callback. **A refusal names its cause** (the engineer: "ensure that the cause of the refusal is clear so that the caller can take the correct action (submit, drain) without having to probe for why the refusal occurred. At least, if the cause of the refusal is known, pass it through"): the backend reports a typed cause -- a submission needed, a drain needed, both, and an unknown cause carrying the backend's `IoFault`, the full set fixed with the code in `DI-3.10.3.5.1` -- the core keeps it with the refusal, and the driver passes it to the consumer unchanged, replacing `PushRefusal::Ring(io::Error)`. Each backend maps its own conditions; the Windows backend's mapping of `windows-ioring-sys`' `RingCondition`s is stated and tested in its step. A blocking push the driver could offer above refusal was proposed and is not ruled (`DI-3.10.3.5.8`). **Delivery** (Q5; "Yes but factored out hopefully? But the threadless vs. numa domain bound aspect seems like it should be retained"): the backend has no threads; who waits on its notification is a delivery layer in the driver, in the two models of the ring crate's [D-3](../windows-ioring-sys/DESIGN-NOTES.md#d-3) that [DI-D-18](#di-d-18) names -- pool delivery (Model A), with an optional callback environment so the pool may be bound to a NUMA node, and pumped delivery (Model B), in which the consumer's own thread waits and drains. As built, dioring had Model A only, on the default pool. **Readiness** (Q6): the ring trait's readiness becomes an associated type when the driver moves onto the core, ahead of the crate split. **Steps** (Q1): the assistant's to size; they are `DI-3.10.3.5.1` to `DI-3.10.3.5.8`. | | DI-D-53 | **Delivery wakes the driver, and the driver drains its backend: a backend stays threadless on every platform, and pool delivery is a waker beside it, built in the ring crate as a wake-only form of `EventDelivery`.** The assistant's R2, chosen by the engineer, 2026-10-11 ("yes, i thought r2 was better for this reason, its good to have a detailed analysis"), after asking which shape fits a Linux `io_uring` backend best; the analysis is in the session log. **Why** (the deciding argument): an `io_uring` backend is naturally threadless -- push and pop through shared memory, `io_uring_enter` the doorbell -- and its notification is an eventfd that something outside the ring waits on (epoll, a runtime's reactor, a thread), with no pool-callback primitive to own the ring; so a waker that tells an owner to drain, and an owner that pops its own backend under its own lock, is the one shape every platform shares: backend (`IoRing` / `io_uring`), notification (completion event / eventfd), waker (a thread-pool wait, optionally NUMA-bound / epoll, a runtime or a thread). [DI-D-18](#di-d-18)'s rule that no lock is shared across layers holds, because the waker only says "drain now". **Rejected: R1**, the Windows backend handing its ring to `EventDelivery` in a pool form, which puts delivery inside one platform's backend, gives every pool-form backend a `pop` that means nothing, and leaves each future platform to build its own. **What this obliges**: the ring crate gains the waker, keeping `EventDelivery`'s protocol -- the event attached unsignalled, the wait armed, then the setup signal; wake, re-arm, wake -- in one place, its [D-84](../windows-ioring-sys/DESIGN-NOTES.md#d-84) and `M33.1`; and the woken owner must drain to empty on every wake, because the event is an edge ([D-19](../windows-ioring-sys/DESIGN-NOTES.md#d-19)). This fixes [DI-D-52](#di-d-52)'s delivery layer: pool delivery is that waker, with the optional callback environment, and pumped delivery a thread the consumer owns. **Steps**: the ring crate's `M33.1` first, then `DI-3.10.3.5.6.1` and `DI-3.10.3.5.6.2`. | | DI-D-54 | **The assistant's choice, confirmed by the engineer under `DI-3.9.6`, 2026-10-13 (`DI-9.6.1`): what the backend refuses for good, the core ends. A gated operation is put to the backend's `IoBackend::check` before it is held, so it is refused at push for what an ungated one would be; and a submission of the core's own -- an operation its gate released, or a commit -- that the backend refuses for a cause that is not retryable completes with a `REFUSED` fault instead of being re-emitted.** Found while mapping the old driver onto the core for `DI-3.10.3.5.6.2`, 2026-10-11. **The defect it closes:** the core held a gated operation without asking whether the backend could ever issue it, and [DI-D-52](#di-d-52)'s rule kept every refused submission of its own for re-emission. A cause that cannot clear -- no target, an operation the backend cannot perform, one malformed for its target, a backend now unusable or unable to say -- therefore re-emitted forever: the operation never completed, its target stayed busy, and a seal waiting on it never resolved. Today's driver refused one such case up front (a span operation on an instance with no registration) and shared the rest ("no test reaches that"). **`check`** answers whether `push` would refuse for a cause that does not depend on the queues; a push it admits may still be refused for a full queue, or by a backend become unusable. The core asks only for an operation it will hold, and only after every check of its own, so the core's refusals keep their order and the backend's word comes last; an ungated operation is answered by its push. The Windows backend's `check` is its pre-ring refusal plus the registration's own span check, asked without pushing ([`windows-ioring-sys`' M34.1](../windows-ioring-sys/COMPLETED-CHECKLIST.md#m341)) -- the check its push leaves to the ring, so neither can drift from the other. **Ending:** `refused` keeps a submission whose cause `is_retryable` -- submit, drain, or wait for another submission -- and otherwise completes it as the backend would have with a fault: an operation as `Failed`, a write nullified as one its target failed is, a commit failing its seal's flush of its target. The fault names the source `REFUSED`, carries the backend's code where the cause has one, and its error is the `RefusalCause` itself, recovered by downcasting. `refused` returns what that makes due, and the driver acts on it in turn. **Unknown is treated as permanent:** a cause the backend could not name may clear, but retrying it blind is the hang this closes, and a failure is visible to the consumer, who can resolve it. **Rejected:** keeping every refusal and reporting a permanent one as a diagnostic, which leaves the hang in place; refusing at push only, which cannot reach a backend that becomes unusable between hold and release. | @@ -71,10 +71,19 @@ appear here. | DI-D-57 | **The split (`DI-8.2`): [DI-D-50](#di-d-50)'s P12 built, and what Rust's coherence rules made of it.** The plan is the engineer's, P12; what follows are the assistant's choices in carrying it out, confirmed by the engineer under `DI-9.7`, 2026-10-13 (`DI-9.7.5`, the portable crate's public surface for a driver; `DI-9.7.6`, the renames and the clock's bounds). **Where things went.** `durable-ioring` keeps the contract, the core (`DurableIoCore` and the durability state under it), the target and backend traits, the in-memory backend, the oracle, and the clock, fault and id types; it depends only on `io-buffers`, and builds for a non-Windows target. `win-durable-ioring` holds the threaded driver `Dioring`, the Windows backend and its file target, the three deliveries, the readiness `Event` and its harness check, the interrupt-time clock, and the worked examples, which moved to its [README.md](../win-durable-ioring/README.md) and run there as doctests; [CONTRACT.md](CONTRACT.md) links them. It re-exports the portable crate's API, so a Windows consumer names one crate. **What the orphan rule forced**, each a change to a public name: **(1)** a `win-time-sys` clock cannot be made a `StampClock` by a blanket impl in the driver, because the trait is the portable crate's and the clock the other crate's, so `TimeBase` is now `StampClock`; the default clock is a local `InterruptStamps`, and `FromClock` adapts any `win-time-sys` clock on interrupt time that keeps `Steady`'s promise. **(2)** The backend's notification is `CompletionEvent`, a local type, not a bare `OwnedHandle`, for the same reason: `Waitable` is the portable crate's and a handle std's. **(3)** `ErrorCode::of`, which reads the Windows ring's error type, is `error_code_of`, a function, because an inherent method cannot be added from another crate; `ErrorCode` itself and its HRESULT arithmetic stay portable. **(4)** `DioringIds` has no default time type, since the default was Windows' time; the driver names it `WindowsIds`. `LineageHandle::at` takes the time type as a parameter, normally inferred from the instance it is passed to. **What the portable crate shows a driver.** A `driver` module, documented as not the consumer's surface, re-exports what a driver builds on: the engine and its lock, the pump, `Home` and `Answer` and `DomainCall`, `InstanceId`, and two helpers the core and the driver both need. `Lineage` and `OpId` have public fields, since a driver mints them and a fake does; they identify, and an instance refuses another's. **Checked:** the portable crate builds all targets for `x86_64-unknown-linux-gnu`; its CI job is `DI-8.3`. **Not changed:** the contract, the core's behaviour and every test's assertions; the sabotage manifest is split in two, the portable one running both crates' tests, because the driver's tests also pin the core. **The clock's bounds (`DI-9.7.6`):** asked whether the portable clock wants bounds beyond `StampClock`, the answer is none, and the reasons are recorded so the question is not reopened without them. `StampClock` is `Send + 'static` and its `Instant` is `Copy + Ord + Debug + Send + Sync + 'static`. `Send` without `Sync` because the clock is only read under the engine's lock; `Ord` because the core and the oracle order stamps; `Copy` and `Debug` because the entries and records that hold a stamp are copied and printed; `Send + Sync` on the stamp because entries cross threads. `Hash`, `Clone` or `Sync` would exclude clocks that work and be used by nothing. **No marker for "never decreases":** a type cannot establish monotonicity, and implementing `StampClock` is already the implementor stating that promise; the Windows driver has a type-level guard exactly where a clock arrives from another crate that never made the promise to dioring, `FromClock` requiring `win-time-sys`' `Steady`. **What is not enforced:** nothing in the core checks that a reading did not go backwards. A clock that does yields stamps on failures and markings out of order and unreported; the conformance oracle checks one stamp order across `Failed` and `Marked`, so a test that reports to it sees a bad clock and a production instance does not. Only reported timestamps are affected: durability does not read stamps, and nothing orders by stamp alone ([DI-D-37](#di-d-37)). **Not done, and what it would take:** the core could remember the last reading and stamp the larger of it and the new one, which costs a comparison per failure or marking, needs no new bound since `Ord` is already required, and would make the ordering dioring's own guarantee rather than the clock's; the cost is a broken clock silently smoothed over unless a non-panicking diagnostic kind reports it, and [DI-D-46](#di-d-46)'s panic does not fit, a bad clock being the consumer's input and not dioring's defect. The engineer took the clock as it is; this schedules no work, and the option is recorded here as a working position, not a decision. | | DI-D-58 | **The doorbell is the engine's: a backend's push only queues, the engine rings once after the pushes it makes, and a doorbell that failed, or completions that could not be read, are told to the consumer once -- with no timer and no loop.** Raised by the review of PR 126, whose findings on the Windows backend's push, the engine's drain and `detach` were one gap, and decided by the engineer, 2026-10-13: "I don't like retry policies that end up needing timers. A one-time retry based on some one time condition that required a single fallback strategy is acceptable"; "we should more generally be forward only and if the caller gets stuck their remedy is to drop the thing they are working on whether it's a lineage, epoch, or a ring"; and, of making the push queue only, "this seems like the right fix to me". **What was wrong**: the Windows backend submitted inside every push, because the ring crate's batch submits as it drops ([D-5](../windows-ioring-sys/DESIGN-NOTES.md#d-5)). A push whose submit failed was accepted -- the ring holds the entry and cannot give it back, so `Refused` could not carry it -- with the failure kept in a flag that nobody was told of; and with nothing else in flight no completion would ever drive the drain that retried it, so a pool instance waiting on readiness waited for ever. It also made one submission per push, where the core already hands the engine batches. **The decision**: (1) a backend's `push` queues, and the ring crate gained `Batch::leave_queued` for it ([D-90](../windows-ioring-sys/DESIGN-NOTES.md#d-90)); (2) the engine rings the doorbell once at the end of `act`, which every path that pushes for the core passes through, and after a consumer's own write or read; (3) a push refused for want of room is rung for and pushed once more -- the one condition that retry is for, and the only retry the engine makes; (4) a doorbell that fails, in `act`, in `drain` or after a consumer's push, raises the readiness signal once, and the consumer's `pop` rings it again and reports the cause **only when it has no entry to return** (an entry is progress); (5) a drain whose backend cannot read its completions ends, keeps the first fault until a later read works, raises the signal once, and `pop` reports it once. **What it asks of the consumer**: nothing retries unprompted, so a cause that stays reaches the consumer on every pop, and its remedy is to drop the instance. A pool instance has no next wake to read a ring that could not be read -- the event is an edge, the wake that could not read took it, and the completion it left keeps the queue from going from empty to non-empty again -- so for it a fault is final; a pumped instance's next pump reads, since a pump drains before it waits. **Readiness amended**: [DI-D-28](#di-d-28)'s signal is also set when a `pop` has something to report; a wake with nothing to pop was already normal. **`detach`**: a backend that cannot take a queued submission back (the Windows ring cannot) issues it as pushed rather than faulting it, still completing exactly once; the core never detaches a target with an operation outstanding, so a driver does not meet this, and the contract in `IoBackend::detach` says so. **The memory backend** executes at its submit, so the engine's doorbell runs what it is pushed at once, and a pushed-and-unissued state exists there only while a doorbell fails. **Model A**: the handler an `EntryDelivery` is given receives a `DeliveryEvent`, an entry or the instance's own `pop` failure, because the wake that took the readiness edge is the only thing that told the consumer, and a failure collapsed into an empty queue left an accepted operation queued with nothing reaching the handler (the review of PR 126). A failure is told once per wake and the delivery goes on; the handler's remedy for one that stays is the same as everywhere here, to drop the delivery. **Tested**: the engine's tests over a scripted backend, the Windows backend's, and the driver's over real files with the ring crate's submit and call responders; the sabotage manifests carry the entries and the blind spots. | | DI-D-59 | **A panic or a fault is for a programming error, ours or a caller's -- an implementation of a trait that models behaviour included -- and never for normal error reporting; and the Windows backend's ring is an owned one, so a completion the ring held nothing for is not a case it has.** The engineer, 2026-10-14: "Panics and faults must occur only on programming errors on either our part or on the part of our callers (and that includes implementations of traits that model aspects of behavior). It is never acceptable behavior to have as part of normal error reporting to panic or fault the process. That said, if an inconsistency is found, a panic/fault is absolutely appropriate to prevent miscomputation downstream"; and of a ring the OS has driven into a state with no remedy but dropping it, "as long as this is diagnosable and attributable that is acceptable". [DI-D-46](#di-d-46) is the second half, which this states beside the first. **Applied**: a ring that cannot be read is reported to the consumer as an `IoFault` carrying its source, its `HRESULT` and the OS's own error ([DI-D-58](#di-d-58)), and the remedy is to drop the instance. `WindowsBackend::pop` returned that same kind of error for "a completion for an operation the backend did not push" -- which is not an OS state but an inconsistency in this crate's own bookkeeping, since the backend pushes only owned operations and waits for each registration -- so by this rule it should not have been an ordinary error. **It is now not a case**: the ring crate types a ring by whether it can have an operation in flight that it holds nothing for ([D-91](../windows-ioring-sys/DESIGN-NOTES.md#d-91)); the backend seals its ring with `into_owned` once its registrations are made and waited on, and an owned ring's pop returns what it held with no case of nothing, so the branch is deleted and not turned into a panic. If a registration were somehow still in flight at the seal, that is an inconsistency and the construction panics, naming it; no test can reach that, since making one needs a raw push and this crate forbids `unsafe`, so it is a declared blind spot. **What it leaves**: the converse rule has no checker; a panic reachable from a consumer's input or from a failure of the OS is the thing to look for in review. | -| DI-D-60 | **A file target is made from a file this crate opened, and from nothing else: how a file was opened is recorded by construction, never read back from a handle.** The engineer, 2026-10-14, closing `DI-10.1`. **The ruling that forced it:** "We may not call the Nt*() APIs from this repo", so that published code is not read as Microsoft endorsing an undocumented interface (the repository's instructions record it as "DOCUMENTED APIS ONLY"). An open handle's no-buffering and write-through modes are reported only by `NtQueryInformationFile(FileModeInformation)`, and no documented `GetFileInformationByHandleEx` class reports them, so [DI-D-29](#di-d-29)'s "no open-mode inspection" was not a choice to revisit but the only available answer, and a declaration of the class was the only other: the engineer, "We've investigated this problem before and the only real solution is to structurally require that handles have provenance that guarantees what we need in any given situation", the pattern `WaitableHandle` and the overlapped-io sealed endpoint types already follow. **The shape:** `OpenedFile` in `win-durable-ioring`, opened through `std::fs::OpenOptions` with `custom_flags` (no `unsafe`, no new crate, no native surface), and `B` a sealed `Buffering` marker, `Cached` or `Unbuffered`, that fixes what a target made from it can claim: `Cached` is `BestEffort`, needs no alignment and has no write-through; `Unbuffered` is `SharedDevice`, needs a sector's alignment and offers per-write write-through. `Win32FileTarget::new` takes an `OpenedFile` and nothing else, so `Complete`, which needs knowledge of what else shares the device, is never a file's. A consumer that needs the stronger warranty names `OpenedFile` in its own signature, and a cached file cannot be passed (a `compile_fail,E0308` doctest). **Foreign handles are disallowed in this version** (the engineer: "foreign handles: disallow for v1?", then the breaking change accepted): the setters `with_warranty`, `with_geometry` and `with_capabilities` and construction from a `SharedHandle` are removed, which is a breaking change to `win-durable-ioring` 0.1.0. What a handle opened elsewhere would need is queued as `DI-10.3`. **Write-through** is derived, the engineer leaving it to the assistant's judgement: the ring's per-write write-through flag fails with error 509 on a cached handle, including one opened with `FILE_FLAG_WRITE_THROUGH`, and completes on an unbuffered one (measured 2026-10-07; `the_caching_choice_reaches_the_kernel` and the unbuffered counterpart keep both), so the capability follows the buffering mode and the handle's own write-through flag is not opened. **Geometry** is stated, not queried: an unbuffered file starts at 4096, the largest sector size in general use, which every smaller one divides and so no device refuses, and `with_sector_size` lowers it for a device that allows less; the OS refuses an unaligned transfer on the first operation that needs more, as an error on its completion, never as silent damage. (`FileStorageInfo` would report the sector size by a documented call, but needs `unsafe` and a crate to put it in; it is not needed.) **Remote devices:** an unbuffered file is refused where the path's final location, which `std::fs::canonicalize` resolves through links and mapped drives, is a UNC share, in either of its forms: a remote device's report is the remote system's, not the device's, so the claim cannot hold. The decision is taken before anything is created, from the file's own location or the directory it would be created in, so the window is the time between that and the open; the refusal is an `io::ErrorKind::Unsupported` error carrying a `RemoteDevice`. A cached file is allowed on a share, claiming only `BestEffort`. **What a measurement showed and this does not change:** an unbuffered handle refuses even an unaligned *seek* (error 87), so a test that seeks and then writes fails at the seek. **Not decided here:** whether a handle opened elsewhere can be accepted at all, and under what attestation; `DI-10.3` carries it. | -| DI-D-61 | **A raw volume is a file target made from a volume this crate opened: one handle, one commit per volume, regions that are offsets the consumer allocates inside it, flush domains found from its disks, and never `Complete`.** The engineer, 2026-10-14 and 2026-10-15, closing `DI-10.2`. **Whole volume, not region targets** (the engineer: "the first is the intent"): the target is the volume, and "regions in it" are offsets the consumer chooses, so one commit per volume needs no change to the core; region targets over a shared handle, which the contract's "window over part of another" would be, would need commits coalesced across targets and are a different item. **The unsafe home** (the engineer: "unsafe home: sounds good, do it"): a new crate, [win-volume-sys](../win-volume-sys/DESIGN-NOTES.md#wv-d-1), over documented device controls only ([DI-D-60](#di-d-60)'s rule): `IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS`, `IOCTL_DISK_GET_DRIVE_GEOMETRY`, `FSCTL_LOCK_VOLUME` and `FSCTL_UNLOCK_VOLUME`. **Provenance by construction**, as for a file: `OpenedVolume` is opened here, by a `VolumePath` that can only be a drive letter or a volume GUID, and `Win32FileTarget::from_volume` takes it and nothing else. A volume that does not answer the extents control, which a remote volume and a handle that is not a volume do not, is refused, so a volume target is over this machine's own disks. **Alignment is the sector the device reports**, not a default: offsets and lengths, and the buffer's address when unbuffered. **Flush domains are found from the disks**, which the contract makes a backing's own obligation (its "Complete flush-domain declarations"): one per distinct disk, named `\\.\PhysicalDriveN` by `disk_flush_domain`, so a consumer declares the same bytes for a file on that disk and the two share a domain exactly when their bytes agree. A consumer's declaration on a volume adds to the found domains and never removes one, since a missing domain leaves a failure unreported. **The class is `SharedDevice` when unbuffered (`BestEffort` when cached, below) and never `Complete`** (the engineer: "your call. I don't feel strongly"): `Complete` is a device held exclusively with nothing else on it, and a volume's lock keeps other handles off the volume, not other writers off its disks, so a handle cannot show it, even locked and even with no other volume on the disks (an unmounted partition, a raw user of the disk, or another machine sharing a LUN is invisible to it). Claiming it would compute the wrong thing, which is the ground this repository removes an option on. What would change the answer is an attestation by a consumer that knows the disk is theirs alone, which is [`DI-10.3`](CHECKLIST.md)'s mechanism, not a new one; enumerating the machine's other volumes to rule out the visible sharers is parked in win-volume-sys as `WV-inf.2`, and would narrow the claim, not make it. **The lock** is `VolumeOptions::lock`: `FSCTL_LOCK_VOLUME` after the open, which flushes a file system and stops it writing, what a raw write to a mounted volume's own sectors needs and a volume with no file system does not, and which fails, holding nothing, if any other handle has the volume open. It is released when the handle closes, which for a target is when the instance ends. **A cached volume is allowed** (`OpenedVolume`: `BestEffort`, offsets and lengths aligned to the sector, buffers not), so the option exists though the default is unbuffered. **D-44** ([windows-ioring-sys](../windows-ioring-sys/DESIGN-NOTES.md#d-44)'s spike-before-wrapping rule, left open under [DI-D-48](#di-d-48)): the assistant's proposal, which the engineer did not contradict, is that it applies inside a backing's implementation and does not gate the design; a flush-failure spike on a volume needs a disk that fails on demand, which a virtual disk does not, so it stays `DI-4.1`'s. **Evidence:** the raw I/O, the lock and the durable write were run elevated on 2026-10-15 against a scratch virtual disk created with `diskpart` (the engineer approved the run and `sudo`), five tests that are ignored by default and that CI runs on its administrator account; everything that needs no privilege runs by default. The lock being taken is a declared survivor of the default sabotage sweep, since only those tests can tell it from a lock that was not. | -| DI-D-62 | **A whole physical disk is a file target made from a disk this crate opened by number: its own flush domain, sector-aligned, and never `Complete`.** The engineer, 2026-10-15, asking for the volume target to be repeated against the disk rather than a partition on it, and then "since we're operating at a somewhat fundamental level of storage, you should be precise". Recorded as `DI-10.4`; the request was not a checklist item. **Provenance by construction**, as for a file and a volume ([DI-D-60](#di-d-60), [DI-D-61](#di-d-61)): `OpenedDisk` is opened here, from a `DiskPath` that can only be a disk number, `\\.\PhysicalDriveN`, and `Win32FileTarget::from_disk` takes it and nothing else. **The disk is its own flush domain**, named by `disk_flush_domain` exactly as a volume on that disk names it, so a target on the disk and a target on a volume on it share one domain and a flush is not issued twice for the same device. A consumer's `with_domains` adds to it and never removes it, as for a volume. **Alignment is the sector the device reports**, through the same documented control as a volume (`IOCTL_DISK_GET_DRIVE_GEOMETRY`, in [win-volume-sys](../win-volume-sys/DESIGN-NOTES.md#wv-d-1)); `Buffering::volume_geometry` was renamed `sector_geometry` because it no longer describes only volumes. **The class is `SharedDevice` when unbuffered and `BestEffort` when cached, never `Complete`**, for the reason a volume's is: a handle to a disk cannot show that nothing else writes it. Only an attestation by a consumer that knows could claim more, and that is [DI-10.3](CHECKLIST.md). **Windows blocks a direct write under a mounted volume** (documented); a disk with volumes on it is written through them, or has them locked, or is taken offline first. The tests use a disk with no partition and no volume, so the blocking is documented here and not exercised. **The test helper is the safety, because the data is the disk's.** `ScratchDisk` creates and attaches an unpartitioned VHDX, then takes the disk number only from `diskpart`'s own report of the file it just made: exactly one `Filename:` equal to the canonical long path of that file, and exactly one `Associated disk#:` that is not 0. Anything else is a refusal, so no test writes to a disk it did not create. **Evidence**, an elevated run on 2026-10-15 of the five `raw_disk` tests on a 64 MiB scratch disk, all passing, with no disk or file left behind: `bytes_per_sector` answers on a physical-disk handle opened for reading and writing; the last sector is accepted and a transfer starting at the end transfers nothing; a per-write write-through request is accepted on the unbuffered handle. **Not verified:** the exact outcome of a transfer past the end, which the test pins only as "nothing transfers"; whether `bytes_per_sector` answers on a disk handle opened with no access; any disk that has a volume on it. **Scheduled work:** none beyond `DI-10.3`, which already carries the attestation question. | -| DI-D-63 | **A file is opened through `windows-namespace-request-sys`'s `OpenFile`: that crate carries every parameter of `CreateFileW` and decides none, and what an open means is decided here.** The engineer, 2026-10-15: "yes take that dependency, and do those checks", after it was found that the workspace already had a memory-safe, un-narrowed wrapper for the open calls ([windows-namespace-request-sys](../windows-namespace-request-sys/README.md): `OpenFile`, `OpenFileByIdentifier`, and the handle queries). A new unsafe crate for opening, or a widened [win-volume-sys](../win-volume-sys/README.md), would have duplicated working, tested code, and the mono-repo rule is to fix the layer. **The division:** the request crate takes raw access, share and flag words and a path, and adds nothing; `win-durable-ioring` states which of them an open may use ([DI-D-60](#di-d-60)'s provenance), so `FileOptions` and `Buffering` stay here and the crate keeps `forbid(unsafe_code)`. **What std did that `CreateFileW` alone does not, kept so that a caller sees no difference**, each with a test and a sabotage: a path longer than `MAX_PATH` is opened in its verbatim form (the request crate refuses an ordinary path past `MAX_PATH` by design, so the conversion is the load-bearing step, and the test shows the request alone refuses the same path); a file that is created and truncated is opened `OPEN_ALWAYS` and emptied once open, as std does to avoid `CREATE_ALWAYS`'s semantics (rust-lang/rust#115745); a new file keeps `FILE_FLAG_OPEN_REPARSE_POINT`; every file is shared for reading, writing and deletion; no access, or creating or truncating without write access, is `InvalidInput`. **The two checks the engineer asked for.** First, that a synchronous `perform` is acceptable for an open: it is one `CreateFileW` call on the calling thread, which is what `std::fs::OpenOptions` was, so nothing is added. Second, that the handle is plain and unassociated, which the request crate documents and its code shows (no completion-port association, no flag added or removed): every ring test of this crate, which now opens its files this way and drives them through the ring, passes, and that is the empirical check. **Differences found and kept honest:** a created-and-truncated file is emptied after the open and not atomically with it, so another handle can see the old content in between; std's source (1.98) has one route for the combination that overwrites in a single kernel call and one that is `OPEN_ALWAYS` plus a manual truncation, and which of them an open takes was not established here, so whether std was atomic is unknown; and an ordinary path is judged by `GetFullPathNameW` as the request crate documents (a legacy device name such as `CON` resolves to the device), which was not compared with std's handling here. **Not verified:** the handle's inheritability is not read back, since this crate forbids `unsafe` and has no safe query for it; it follows from `OpenFile` passing no security attributes, which `CreateFileW` documents as a non-inheritable handle. **Scheduled work:** `DI-13.5` and `DI-13.6` (the SDDL constructor and the `ReOpenFile` entry, which the request crate lacks; [NR-3](../windows-namespace-request-sys/CHECKLIST.md)), and `DI-14.5` (the remote check is still made on the path). | +| DI-D-60 | **A file target is made from a file this crate opened, and from nothing else: how a file was opened is recorded by construction, never read back from a handle.** The engineer, 2026-10-14, closing `DI-10.1`. **Its "not decided here", on foreign handles and on what could claim more than `SharedDevice`, is decided by [DI-D-68](#di-d-68).** **Amended by [DI-D-64](#di-d-64): the remote-device refusal below, which canonicalises the path before the open, moves to the opened handle and nowhere else ([DI-14.5](COMPLETED-CHECKLIST.md#di-145)); nothing is checked before the open.** **The ruling that forced it:** "We may not call the Nt*() APIs from this repo", so that published code is not read as Microsoft endorsing an undocumented interface (the repository's instructions record it as "DOCUMENTED APIS ONLY"). An open handle's no-buffering and write-through modes are reported only by `NtQueryInformationFile(FileModeInformation)`, and no documented `GetFileInformationByHandleEx` class reports them, so [DI-D-29](#di-d-29)'s "no open-mode inspection" was not a choice to revisit but the only available answer, and a declaration of the class was the only other: the engineer, "We've investigated this problem before and the only real solution is to structurally require that handles have provenance that guarantees what we need in any given situation", the pattern `WaitableHandle` and the overlapped-io sealed endpoint types already follow. **The shape:** `OpenedFile` in `win-durable-ioring`, opened through `std::fs::OpenOptions` with `custom_flags` (no `unsafe`, no new crate, no native surface), and `B` a sealed `Buffering` marker, `Cached` or `Unbuffered`, that fixes what a target made from it can claim: `Cached` is `BestEffort`, needs no alignment and has no write-through; `Unbuffered` is `SharedDevice`, needs a sector's alignment and offers per-write write-through. `Win32FileTarget::new` takes an `OpenedFile` and nothing else, so `Complete`, which needs knowledge of what else shares the device, is never a file's. A consumer that needs the stronger warranty names `OpenedFile` in its own signature, and a cached file cannot be passed (a `compile_fail,E0308` doctest). **Foreign handles are disallowed in this version** (the engineer: "foreign handles: disallow for v1?", then the breaking change accepted): the setters `with_warranty`, `with_geometry` and `with_capabilities` and construction from a `SharedHandle` are removed, which is a breaking change to `win-durable-ioring` 0.1.0. What a handle opened elsewhere would need is queued as `DI-10.3`. **Write-through** is derived, the engineer leaving it to the assistant's judgement: the ring's per-write write-through flag fails with error 509 on a cached handle, including one opened with `FILE_FLAG_WRITE_THROUGH`, and completes on an unbuffered one (measured 2026-10-07; `the_caching_choice_reaches_the_kernel` and the unbuffered counterpart keep both), so the capability follows the buffering mode and the handle's own write-through flag is not opened. **Geometry** is stated, not queried: an unbuffered file starts at 4096, the largest sector size in general use, which every smaller one divides and so no device refuses, and `with_sector_size` lowers it for a device that allows less; the OS refuses an unaligned transfer on the first operation that needs more, as an error on its completion, never as silent damage. (`FileStorageInfo` would report the sector size by a documented call, but needs `unsafe` and a crate to put it in; it is not needed.) **Remote devices:** an unbuffered file is refused where the path's final location, which `std::fs::canonicalize` resolves through links and mapped drives, is a UNC share, in either of its forms: a remote device's report is the remote system's, not the device's, so the claim cannot hold. The decision is taken before anything is created, from the file's own location or the directory it would be created in, so the window is the time between that and the open; the refusal is an `io::ErrorKind::Unsupported` error carrying a `RemoteDevice`. A cached file is allowed on a share, claiming only `BestEffort`. **What a measurement showed and this does not change:** an unbuffered handle refuses even an unaligned *seek* (error 87), so a test that seeks and then writes fails at the seek. **Not decided here:** whether a handle opened elsewhere can be accepted at all, and under what attestation; decided by [DI-D-68](#di-d-68). | +| DI-D-61 | **A raw volume is a file target made from a volume this crate opened: one handle, one commit per volume, regions that are offsets the consumer allocates inside it, flush domains found from its disks, and never `Complete`.** The engineer, 2026-10-14 and 2026-10-15, closing `DI-10.2`. **Amended by [DI-D-68](#di-d-68): a volume is never `Complete` by derivation, and is by a consumer's attestation.** **Whole volume, not region targets** (the engineer: "the first is the intent"): the target is the volume, and "regions in it" are offsets the consumer chooses, so one commit per volume needs no change to the core; region targets over a shared handle, which the contract's "window over part of another" would be, would need commits coalesced across targets and are a different item. **The unsafe home** (the engineer: "unsafe home: sounds good, do it"): a new crate, [win-volume-sys](../win-volume-sys/DESIGN-NOTES.md#wv-d-1), over documented device controls only ([DI-D-60](#di-d-60)'s rule): `IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS`, `IOCTL_DISK_GET_DRIVE_GEOMETRY`, `FSCTL_LOCK_VOLUME` and `FSCTL_UNLOCK_VOLUME`. **Provenance by construction**, as for a file: `OpenedVolume` is opened here, by a `VolumePath` that can only be a drive letter or a volume GUID, and `Win32FileTarget::from_volume` takes it and nothing else. A volume that does not answer the extents control, which a remote volume and a handle that is not a volume do not, is refused, so a volume target is over this machine's own disks. **Alignment is the sector the device reports**, not a default: offsets and lengths, and the buffer's address when unbuffered. **Flush domains are found from the disks**, which the contract makes a backing's own obligation (its "Complete flush-domain declarations"): one per distinct disk, named `\\.\PhysicalDriveN` by `disk_flush_domain`, so a consumer declares the same bytes for a file on that disk and the two share a domain exactly when their bytes agree. A consumer's declaration on a volume adds to the found domains and never removes one, since a missing domain leaves a failure unreported. **The class is `SharedDevice` when unbuffered (`BestEffort` when cached, below) and never `Complete`** (the engineer: "your call. I don't feel strongly"): `Complete` is a device held exclusively with nothing else on it, and a volume's lock keeps other handles off the volume, not other writers off its disks, so a handle cannot show it, even locked and even with no other volume on the disks (an unmounted partition, a raw user of the disk, or another machine sharing a LUN is invisible to it). Claiming it would compute the wrong thing, which is the ground this repository removes an option on. What would change the answer is an attestation by a consumer that knows the disk is theirs alone, which is the mechanism [DI-D-68](#di-d-68) builds; enumerating the machine's other volumes to rule out the visible sharers is parked in win-volume-sys as `WV-inf.2`, and would narrow the claim, not make it. **The lock** is `VolumeOptions::lock`: `FSCTL_LOCK_VOLUME` after the open, which flushes a file system and stops it writing, what a raw write to a mounted volume's own sectors needs and a volume with no file system does not, and which fails, holding nothing, if any other handle has the volume open. It is released when the handle closes, which for a target is when the instance ends. **A cached volume is allowed** (`OpenedVolume`: `BestEffort`, offsets and lengths aligned to the sector, buffers not), so the option exists though the default is unbuffered. **D-44** ([windows-ioring-sys](../windows-ioring-sys/DESIGN-NOTES.md#d-44)'s spike-before-wrapping rule, left open under [DI-D-48](#di-d-48)): the assistant's proposal, which the engineer did not contradict, is that it applies inside a backing's implementation and does not gate the design; a flush-failure spike on a volume needs a disk that fails on demand, which a virtual disk does not, so it stays `DI-4.1`'s. **Evidence:** the raw I/O, the lock and the durable write were run elevated on 2026-10-15 against a scratch virtual disk created with `diskpart` (the engineer approved the run and `sudo`), five tests that are ignored by default and that CI runs on its administrator account; everything that needs no privilege runs by default. The lock being taken is a declared survivor of the default sabotage sweep, since only those tests can tell it from a lock that was not. | +| DI-D-62 | **A whole physical disk is a file target made from a disk this crate opened by number: its own flush domain, sector-aligned, and never `Complete`.** **Amended by [DI-D-68](#di-d-68): a disk is never `Complete` by derivation, and is by a consumer's attestation.** The engineer, 2026-10-15, asking for the volume target to be repeated against the disk rather than a partition on it, and then "since we're operating at a somewhat fundamental level of storage, you should be precise". Recorded as `DI-10.4`; the request was not a checklist item. **Provenance by construction**, as for a file and a volume ([DI-D-60](#di-d-60), [DI-D-61](#di-d-61)): `OpenedDisk` is opened here, from a `DiskPath` that can only be a disk number, `\\.\PhysicalDriveN`, and `Win32FileTarget::from_disk` takes it and nothing else. **The disk is its own flush domain**, named by `disk_flush_domain` exactly as a volume on that disk names it, so a target on the disk and a target on a volume on it share one domain and a flush is not issued twice for the same device. A consumer's `with_domains` adds to it and never removes it, as for a volume. **Alignment is the sector the device reports**, through the same documented control as a volume (`IOCTL_DISK_GET_DRIVE_GEOMETRY`, in [win-volume-sys](../win-volume-sys/DESIGN-NOTES.md#wv-d-1)); `Buffering::volume_geometry` was renamed `sector_geometry` because it no longer describes only volumes. **The class is `SharedDevice` when unbuffered and `BestEffort` when cached, never `Complete`**, for the reason a volume's is: a handle to a disk cannot show that nothing else writes it. Only an attestation by a consumer that knows could claim more, and that is [DI-D-68](#di-d-68). **Windows blocks a direct write under a mounted volume** (documented); a disk with volumes on it is written through them, or has them locked, or is taken offline first. The tests use a disk with no partition and no volume, so the blocking is documented here and not exercised. **The test helper is the safety, because the data is the disk's.** `ScratchDisk` creates and attaches an unpartitioned VHDX, then takes the disk number only from `diskpart`'s own report of the file it just made: exactly one `Filename:` equal to the canonical long path of that file, and exactly one `Associated disk#:` that is not 0. Anything else is a refusal, so no test writes to a disk it did not create. **Evidence**, an elevated run on 2026-10-15 of the five `raw_disk` tests on a 64 MiB scratch disk, all passing, with no disk or file left behind: `bytes_per_sector` answers on a physical-disk handle opened for reading and writing; the last sector is accepted and a transfer starting at the end transfers nothing; a per-write write-through request is accepted on the unbuffered handle. **Not verified:** the exact outcome of a transfer past the end, which the test pins only as "nothing transfers"; whether `bytes_per_sector` answers on a disk handle opened with no access; any disk that has a volume on it. **Scheduled work:** none beyond [DI-10.5](CHECKLIST.md), which carries the attestation. | +| DI-D-63 | **A file is opened through `windows-namespace-request-sys`'s `OpenFile`: that crate carries every parameter of `CreateFileW` and decides none, and what an open means is decided here.** **Amended by [DI-D-64](#di-d-64): this crate reflects Win32 and not `std`, so the principle that a caller sees no difference from `std` no longer stands, and the list of what was kept for it is replaced below (`DI-13.7`).** The engineer, 2026-10-15: "yes take that dependency, and do those checks", after it was found that the workspace already had a memory-safe, un-narrowed wrapper for the open calls ([windows-namespace-request-sys](../windows-namespace-request-sys/README.md): `OpenFile`, `OpenFileByIdentifier`, and the handle queries). A new unsafe crate for opening, or a widened [win-volume-sys](../win-volume-sys/README.md), would have duplicated working, tested code, and the mono-repo rule is to fix the layer. **The division:** the request crate takes raw access, share and flag words and a path, and adds nothing; `win-durable-ioring` states which of them an open may use ([DI-D-60](#di-d-60)'s provenance), so `FileOptions` and `Buffering` stay here and the crate keeps `forbid(unsafe_code)`. **What the open is now** ([DI-D-64](#di-d-64)): the path is handed over as given (`DI-13.7.1`); the disposition is Win32's own, three of its five, and `FILE_FLAG_OPEN_REPARSE_POINT` is an explicit option (`DI-13.7.3`); the share mode is an option, starting at all three members (`DI-13.4`); every combination of the options is a request, so none is refused, until [DI-14.4](CHECKLIST.md) adds the refusals [the documented-contract rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract) allows; and the open is a build and a perform (`DI-13.7.4`). What `std` did that `CreateFileW` alone does not, and this crate once kept (the verbatim form past `MAX_PATH`, `OPEN_ALWAYS` plus an emptying for a created-and-truncated file, the reparse flag on a new file, and the refusal of a create or truncate without write access), is gone. **The two checks the engineer asked for.** First, that a synchronous `perform` is acceptable for an open: it is one `CreateFileW` call on the calling thread, so nothing is added, and `OpenRequest` lets a caller make it on another thread. Second, that the handle is plain and unassociated, which the request crate documents and its code shows (no completion-port association, no flag added or removed): every ring test of this crate, which now opens its files this way and drives them through the ring, passes, and that is the empirical check. **Left as the request crate documents:** an ordinary path is judged by `GetFullPathNameW` (a legacy device name such as `CON` resolves to the device). **Handle inheritability** follows from `OpenFile` passing no security attributes, which `CreateFileW` documents as a non-inheritable handle, and is read back (`DI-14.8.5`). **Scheduled work:** `DI-13.5` and `DI-13.6` (the SDDL constructor and the `ReOpenFile` entry, which the request crate lacks; [NR-3](../windows-namespace-request-sys/CHECKLIST.md)). **Where the remote check is made** is [DI-14.5](COMPLETED-CHECKLIST.md#di-145), in "What building DI-14.5 settled" below. | +| DI-D-64 | **The open surface, parameter by parameter (`DI-13.1`): which parameters of `CreateFileW`, `CreateFile2`, `OpenFileById` and `ReOpenFile` are exposed and as which type, which are excluded and why, and what each does to a target's claim.** The engineer, 2026-10-15 and 2026-10-16. This crate reflects Win32 and not `std`, so [DI-D-63](#di-d-63)'s parity with `std` is amended. The table, its sources, what was not verified and the engineer's rulings of 2026-10-16 on the calls the assistant had made are in [the section below](#di-d-64). **Amended by [DI-D-65](#di-d-65) for the data rights.** | +| DI-D-65 | **Data access is a type parameter of every handle this crate opens, and the rights set carries only the rest.** The engineer, 2026-10-16, on the observation that requiring a data right of the type while access is a parameter of the open is odd, and that the pattern should reach disks and volumes and not only a file in the ring. Four markers, `NoData`, `ReadOnly`, `WriteOnly` and `ReadWrite`; a file target is made only from a handle with a data right, at compile time. The ring sees it through [DI-D-66](#di-d-66). [Section below](#di-d-65). | +| DI-D-66 | **A target states its data access to the core, and a key can carry it.** The engineer, 2026-10-16: "it seems like IoTarget should have the same sort of type-based encoding of the access to the target that we just litigated in di-13.8", then "do both, schedule both". `IoTarget` gains a required `access()` read once into its description, the core refuses what the access does not cover, and a typed `Key` lets a wrong submission fail to compile. [Section below](#di-d-66). | +| DI-D-67 | **What a name reaches is the caller's responsibility: an open is not checked, refused, bounded or cancelled on account of where its name leads.** The engineer, 2026-10-18: "The responsibility is only on the caller." No check of a name can be complete, since object-manager links and intermediate reparse points redirect regardless of `FILE_FLAG_OPEN_REPARSE_POINT`; a check of the handle cannot reach an open that has not returned; and `CancelSynchronousIo` targets a thread rather than a call and is not guaranteed by its documentation. See [DI-D-67](#di-d-67). | +| DI-D-68 | **A handle opened elsewhere is not accepted by a file target in this version, and a consumer who knows a device is theirs alone can attest it, which is the only way a Win32 target is `Complete`.** The engineer, 2026-10-18, closing `DI-10.3`: "do the attestation approach", on the assistant's recommendation. Foreign handles stay out, with the residue a `ReOpenFile` cannot convert named; the attestation is a constructor for each of the three kinds of target that takes an unbuffered handle this crate opened. [Section below](#di-d-68). | +| DI-D-69 | **A file's identity is this crate's `FileId`: the volume serial and the 128-bit identifier `FileIdInfo` reports, read through the handle shared.** The assistant, implementing `DI-15.1`. The type is owned here and not the request crate's; it is read from any `SharedHandle` and from an `OpenedFile` of any access, and nothing is duplicated to read it. [Section below](#di-d-69). | +| DI-D-70 | **An open by identifier is `OpenedFile::open_by_id`, taking one of three identifier kinds, its own `ByIdOptions`, and any handle as the hint; a `FileId` is checked against the file opened.** The assistant, implementing `DI-15.2`. The checks are the path open's, by the same code; a sharing violation names the file by `OpenedBy`. Observed: a directory opens by identifier without the backup flag, which the engineer ruled allowed, [DI-D-72](#di-d-72). [Section below](#di-d-70). | +| DI-D-71 | **A reopen is `OpenedFile::reopen`, from any handle, with its own `ReopenOptions`; the result is a new file of the type asked for, judged by the path open's checks.** The assistant, implementing `DI-15.4`. A sharing violation names the file by `OpenedBy::Reopen`. Observed: every pair of an original and a reopen is refused or accepted as the page's sharing rule says; a volume and a disk do not reopen, unelevated. [Section below](#di-d-71). | +| DI-D-72 | **A directory reached by an open by identifier is allowed, and what it does is documented.** The engineer, 2026-10-19: "I think these results are 'harmless'. We can document our findings but there is no harm in allowing it to happen." A target made from one faults every read and write with the kernel's error and completes every commit; a faulted write is covered by no durability report, so the commits vouch for nothing. [Section below](#di-d-72). | ## DI-D-29: a file is a `SharedHandle` @@ -120,3 +129,1707 @@ is authoritative): **Considered and left out:** querying a file's volume and file id to detect the same file opened twice. A consumer can do it through the lent handle. + +## DI-D-64: the open surface, parameter by parameter + +**Amended by [DI-D-65](#di-d-65): the rows for zero access, `FILE_READ_DATA` and `FILE_WRITE_DATA`, and the generic rights are superseded for the data rights, which are a type and no longer members of the rights set.** + +**What is decided.** Which parameters of `CreateFileW`, `CreateFile2`, `OpenFileById` and `ReOpenFile` +this crate exposes and as which type, which it excludes and why, and what each does to a target's +claim. The engineer's rulings of 2026-10-15 and 2026-10-16 are applied as given; a row that is the +assistant's call and not a ruling says so in the last column and is listed again at the end for the +engineer to confirm. It amends [DI-D-63](#di-d-63), which made the open match `std`; the code follows in +[DI-13.7](COMPLETED-CHECKLIST.md#di-137). + +**Sources.** The Microsoft Learn pages for the four calls and for "File Access Rights Constants", read +on 2026-10-16. The `CreateFileW` remarks were read through the section on physical disks and volumes; +the sections after it (consoles, communications resources, tape drives, mailslots, pipes) were not read +for this table. + +**The rules the table applies.** + +- **This crate reflects Win32, not the Rust standard library.** A disposition, a share mode and an + access right are the call's own, as a typed set or an enum and never a word of flags to OR, with no + raw `u32` to construct one from. No default is added. A combination is refused ahead of the open only + as [the repository's rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract) (the engineer, + 2026-10-18) allows: where the documented contract calls it invalid on the arguments alone, with the + citation, or where this crate chooses not to support it, named as its own; never because a run showed + the kernel refuse or accept it. [DI-14.4](CHECKLIST.md) builds that check. Where a ruling excludes an + option it is unrepresentable in the types, so nothing is refused at run time on its account. +- **Paths are opaque.** A `Wtf16String`, passed through untouched ([DI-13.1 ruling (a)](COMPLETED-CHECKLIST.md#di-131)). +- **No check of the target before the open.** [DI-13.1 ruling (b)](COMPLETED-CHECKLIST.md#di-131) put one syntactic + check ahead of it; the engineer amended that on 2026-10-16, ruling that finding out late is + acceptable and that the check on the opened handle ([DI-14.5](COMPLETED-CHECKLIST.md#di-145)) is the only + one. The refusal of an unbuffered file on a remote device stays, since `OpenedFile` promises + `SharedDevice` and a remote file cannot keep it. It is made inside `OpenRequest::perform`, after the + call, where it can block with it. +- **What a target claims follows from how the file was opened** ([DI-D-60](#di-d-60)). The buffering + mode is the type: it decides the warranty class, the alignment and whether per-write write-through is + offered, and no other row changes the class. +- **`open` is a build and a perform** ([DI-13.7](COMPLETED-CHECKLIST.md#di-137)). The build is where a + refusal of the arguments is made ([DI-14.4](CHECKLIST.md)), and every validation of the result is made + on the opened handle. + +**Calls.** `CF` is `CreateFileW` and `CreateFile2`, which have the same parameters; `ID` is +`OpenFileById`; `RO` is `ReOpenFile`. Each row says which calls have the parameter. + +| Parameter (calls) | Disposition | Surface | What it does to a claim, or why it is excluded | Ruling | +|---|---|---|---|---| +| `CreateFileW` as the route | Exposed | The open goes through it | As [DI-D-63](#di-d-63) records. | engineer 2026-10-15 | +| `CreateFile2` as a second route | Excluded | None | Its parameters are `CreateFileW`'s, packed into `CREATEFILE2_EXTENDED_PARAMETERS` (attributes, flags, security quality of service, security attributes, template), so a second route adds a second set of behaviours to test and expresses nothing the first cannot. Its page documents two share-mode conditions that `CreateFileW`'s does not (exclusive access is granted only to a caller with write access, and a missing `FILE_SHARE_READ` fails a caller without it); whether that is a behavioural difference was not measured. | engineer 2026-10-16 (excluded; see Rulings below); assistant (the text) | +| `CreateFileTransacted` | Excluded | None | A transacted write is durable at the transaction's commit, so an epoch's warranty would mean nothing. | engineer 2026-10-15 | +| `OpenFileById` as a call | Exposed | `OpenedFile::open_by_id` and `request_by_id` ([DI-15.2](COMPLETED-CHECKLIST.md#di-152)), with an options type of its own, `ByIdOptions` ([DI-D-70](#di-d-70)) | Its page lists no creation disposition, takes no attributes, and marks the security-attributes parameter reserved. It lists SMB as not supported, so an open by ID on a remote share is the kernel's to refuse. So its options are its own type, carrying the access, share mode and flags it takes, and not `FileOptions`, whose disposition and attributes it has no place for. | engineer 2026-10-15; its own options type, engineer 2026-10-18 ("I would assume its own type") | +| `ReOpenFile` as a call | Exposed | `OpenedFile::reopen` and `request_reopen` ([DI-15.4](COMPLETED-CHECKLIST.md#di-154)), with an options type of its own, `ReopenOptions` ([DI-D-71](#di-d-71)) | Takes access, share mode and flags only; its page says the flags cannot include attributes, and that the original must have been created by `CreateFile`. Reopening with a different buffering mode is a new `OpenedFile` of the other type. | engineer 2026-10-15 | +| `lpFileName` (CF) | Exposed | `Wtf16String`, opaque | Passed through for Win32 to interpret: `\\?\`, long paths where the process opts in, `/` against `\`, a stream name, a device name. An open of a device is not stopped here, and where a name leads is the caller's responsibility ([DI-D-67](#di-d-67)); what such an `OpenedFile` claims is not decided: it gets a file's default geometry and no flush domains until [DI-14.6.2](CHECKLIST.md) decides it, from the measurements under "Measured for DI-14.6" below. Nothing about the path is checked before the open. An unbuffered file on a remote device is refused on the opened handle ([DI-14.5](CHECKLIST.md)), so a create refused there has already created the file, and the open has touched the network before it is refused; both are documented and not prevented. | engineer 2026-10-15, 2026-10-16 | +| `hVolumeHint` (ID) | Exposed | Any handle | The hint only tells the call which volume the ID is on and is never the file opened, so its mode reaches nothing the result claims: the opened handle's buffering and access are this crate's choice, and the checks run on it ([DI-14.5](COMPLETED-CHECKLIST.md#di-145)). The page accepts any file on the volume or share. The same reasoning as `hOriginalFile`'s ([DI-D-68](#di-d-68)). | engineer 2026-10-18 ("It should be any handle"), replacing the plan that took only what this crate opened | +| `lpFileId` (ID) | Exposed | The three kinds the `FILE_ID_DESCRIPTOR` page lists: `FileIdType` (a 64-bit `LARGE_INTEGER`), `ObjectIdType` (a `GUID`), and `ExtendedFileIdType` (a `FILE_ID_128`, "not supported before Windows 8 and Windows Server 2012") | The page was read for [DI-15.1](COMPLETED-CHECKLIST.md#di-151) and its list is the request crate's `OpenFileByIdentifier`'s, so the ruling stands as made. The page says the extended ID "is used on ReFS file systems"; an NTFS file's 16 bytes from `FileIdInfo` were observed to open as it (`D-31` in [the request crate's notes](../windows-namespace-request-sys/DESIGN-NOTES.md#d-31)). The identity this crate reads from a handle is the extended form, [DI-D-69](#di-d-69). | engineer 2026-10-15; the page read 2026-10-18 | +| `hOriginalFile` (RO) | Exposed | Any handle ([DI-D-68](#di-d-68)) | The reopened handle's mode is this crate's choice, recorded by its type, so the original's provenance is not what the claim rests on. The page says the original must have been created by `CreateFile`; what it cannot convert stays out ([DI-D-68](#di-d-68), ruled 2026-10-19). A handle that came from `OpenFileById` was observed to reopen, and a volume's and a disk's were observed not to, unelevated ([DI-D-71](#di-d-71)). | engineer 2026-10-18, [DI-D-68](#di-d-68) | +| `dwDesiredAccess` (CF, ID, RO): zero | **Superseded by [DI-D-65](#di-d-65):** the type `NoData`, excluded only from a file target | `NoData` | A zero-access open is valid Win32 (it queries attributes without access). A handle opened so cannot become a file target, which the `HasData` bound refuses at compile time and not when the first operation fails. | engineer 2026-10-16 ([DI-D-65](#di-d-65)) | +| `FILE_READ_DATA`, `FILE_WRITE_DATA` | **Superseded by [DI-D-65](#di-d-65):** the data access type, not members of the rights set | `ReadOnly`, `WriteOnly`, `ReadWrite` | Read and write operations. A target needs one of them, which is the `HasData` bound. | engineer 2026-10-15; engineer 2026-10-16 ([DI-D-65](#di-d-65)) | +| `FILE_APPEND_DATA` | Excluded as a member | None on its own | Its page says that without `FILE_WRITE_DATA` a write does not overwrite existing data, and the core addresses by offset, so an append-only handle would compute the wrong answer. With no member for it and no raw constructor, append-only access cannot be built; the bit is still carried by the generic rights below, each of which includes `FILE_WRITE_DATA`. | engineer 2026-10-15 (the rule); assistant (the representation) | +| `FILE_READ_ATTRIBUTES`, `FILE_WRITE_ATTRIBUTES` | Exposed | Members | None. | engineer 2026-10-15 | +| `FILE_READ_EA`, `FILE_WRITE_EA` | Exposed | Members | None. | standing | +| `FILE_EXECUTE` | Exposed | Member | None. | standing | +| `DELETE` | Exposed | Member | Allows delete and rename. | engineer 2026-10-15 | +| `READ_CONTROL`, `WRITE_DAC`, `WRITE_OWNER` | Exposed | Members | None. | engineer 2026-10-15 | +| `SYNCHRONIZE` | Exposed | Member | None. | standing | +| `ACCESS_SYSTEM_SECURITY` | Exposed | Member | A caller who lacks what the kernel requires for it gets the kernel's error, not a panic; which privilege that is was not measured here. | standing | +| `GENERIC_READ`, `GENERIC_WRITE`, `GENERIC_EXECUTE`, `GENERIC_ALL`, `FILE_ALL_ACCESS` | **Superseded for the data rights by [DI-D-65](#di-d-65):** not members of the rights set | Named constants of the same set, as ruled before [DI-D-65](#di-d-65) | These are the call's own documented values and the commonest ones. Each that includes a write right includes `FILE_WRITE_DATA`. Measured in `DI-13.3` on Windows 11 build 26300: `FILE_ALL_ACCESS`, which names the append bit itself, is refused by an unbuffered open with `ERROR_INVALID_PARAMETER`, while `GENERIC_WRITE` and `GENERIC_ALL` are taken unbuffered. The refusal is the kernel's and reaches the caller as it is; the constant stays and its documentation says so. **Corrected 2026-10-18 by [DI-14.4.1](CHECKLIST.md)'s reading:** this called the pair one "the kernel documents as incompatible". The pages `CreateFileW` is governed by do not say so; `ZwCreateFile`'s does, for its own flags, and this crate does not make that call. | engineer 2026-10-16; assistant | +| `FILE_ADD_FILE`, `FILE_ADD_SUBDIRECTORY`, `FILE_LIST_DIRECTORY`, `FILE_TRAVERSE`, `FILE_DELETE_CHILD`, `FILE_CREATE_PIPE_INSTANCE` | Excluded | None | These name the directory or pipe meaning of a right. A directory is opened only with `FILE_FLAG_BACKUP_SEMANTICS` (the page says so), which is not offered in v1, and a pipe is not a file; the bits shared with the file rights are already members. The directory rights are revisited when `FILE_FLAG_BACKUP_SEMANTICS` is offered. That the pipe right is never offered is a working position ("almost certainly never"), not a decision. | engineer 2026-10-16 (directory); assistant | +| `dwShareMode` (CF, ID, RO) | Exposed | A set of `FILE_SHARE_READ`, `FILE_SHARE_WRITE`, `FILE_SHARE_DELETE`, any subset including none | Does not change the class, the alignment or the offsets. The default stays today's all three; an exclusive open (none) is expressible, since a database that owns its file wants it. A sharing violation reaches the caller as a `SharingViolation` that keeps the kernel's error as its source ([DI-13.4](COMPLETED-CHECKLIST.md#di-134); see "What building DI-13.4 settled" below). | engineer 2026-10-15 (the set, the default, exclusive); assistant (the error's form) | +| `lpSecurityAttributes`: the security descriptor (CF) | Exposed | A `SecurityDescriptor` built from SDDL ([DI-13.5](CHECKLIST.md)), owned by it | The page says an existing file's descriptor is ignored on open, so a caller who passes one to an open of an existing file gets none applied; that is Win32's and is documented here. | engineer 2026-10-15 | +| `lpSecurityAttributes`: `bInheritHandle` (CF) | Not in v1 | None; the handle is never inheritable | An inheritable handle reaches child processes that can write through it, and no call this crate makes chooses inheritance. A ruling for v1, not a rejection. | engineer 2026-10-15 | +| `lpSecurityAttributes` (ID) | Not exposed | None | The page marks it reserved. It does not say whether the handle is inheritable, so a test reads that back: observed not inheritable ([DI-D-70](#di-d-70)). | assistant | +| `dwCreationDisposition` (CF) | Exposed, **narrowed for v1** | An enum of `CREATE_NEW`, `OPEN_EXISTING` and `OPEN_ALWAYS`. **`CREATE_ALWAYS` and `TRUNCATE_EXISTING` are excluded for v1 and are to be revisited** ([DI-6+.11](PARKED.md)) | Three of the call's five: the three that never change or empty a file that exists, so no data access is gated by the disposition. The two left out are unrepresentable, so nothing is refused at run time on their account. `create`, `create_new` and `truncate` go, and with them the `OPEN_ALWAYS` plus an emptying that [DI-D-63](#di-d-63) used; a caller empties a file through the lent handle. What an answer to the two needs is measured below. | engineer 2026-10-15 (the five); engineer 2026-10-16 (the two left out for v1, "just to simplify the matrix", to be revisited at a later date) | +| `FILE_ATTRIBUTE_ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM` (CF) | Exposed | Members of the attributes set, for a created file | None. The page says an existing file's attributes are generally combined with the flags and the supplied attributes ignored, and says applications should not change `OFFLINE` arbitrarily. `ID` ignores attributes and `RO` cannot take them, so those entries have none. | plan, [DI-14.3](CHECKLIST.md) | +| `FILE_ATTRIBUTE_TEMPORARY` (CF) | Exposed | Member | The page says it lets a file system hold data in the cache and avoid writing it back while memory allows, which is within `BestEffort` for a cached file. For an unbuffered file the pages do not say what it does, so the pair is not a contract refusal, and it is **supported** (the engineer, 2026-10-18, [DI-14.4.3](COMPLETED-CHECKLIST.md#di-1443)): the attribute concerns when cached data is written back, and an epoch's durability here comes from the flush this crate issues, never from write-back timing, so there is no analytic ground to narrow. | standing | +| `FILE_ATTRIBUTE_NORMAL` (CF) | Not a member | The empty set | The page says it is valid only alone and that any other attribute overrides it, so it is the absence of attributes and not one. What is passed for an empty set, zero or `NORMAL`, and whether it matters, is for [DI-14.3](CHECKLIST.md)'s tests. | engineer 2026-10-16; assistant | +| `FILE_ATTRIBUTE_ENCRYPTED` (CF) | Excluded | None | It brings a set of certificate problems this crate does not take on; the page says it is not supported on every edition. | engineer 2026-10-15 | +| `FILE_FLAG_NO_BUFFERING` (CF, ID, RO) | Exposed | The buffering type, `Unbuffered` | It is the type, not a separate option: the class `SharedDevice`, a sector's alignment of offset, length and buffer, per-write write-through, and a local device only. | [DI-D-60](#di-d-60) | +| `FILE_FLAG_WRITE_THROUGH` (CF, ID, RO) | Not in v1 | None | What it can warrant is unclear: the pages say that with `FILE_FLAG_NO_BUFFERING` it also requests a write-through of the disk's own cache, and without it the data is written to the system cache and flushed without delay. The per-write flag is a different mechanism and is unaffected, and is known to fail with error 509 on a cached handle even one opened with this flag. A ruling for v1, not a rejection. | engineer 2026-10-15 | +| `FILE_FLAG_RANDOM_ACCESS`, `FILE_FLAG_SEQUENTIAL_SCAN` (CF, ID, RO) | Exposed | Builder methods | None. Passed to an unbuffered file as well and documented there, not refused. The `CreateFileW` page says they have no effect "if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING", a sentence that can be read two ways, so what they do on an unbuffered file is shown by a test ([DI-14.1](CHECKLIST.md)). The page calls using both together self-defeating and does not refuse it, so neither does this crate. | engineer 2026-10-16 | +| `FILE_FLAG_DELETE_ON_CLOSE` (CF, RO) | Exposed | Builder method | The file is deleted when the last handle closes, so a durability report is about data that will not outlive the handle. The caller's choice. `ID` does not list it. | plan, [DI-14.2](CHECKLIST.md) | +| `FILE_FLAG_OPEN_REPARSE_POINT` (CF, ID, RO) | Exposed | Builder method, explicit | The handle is to the reparse point and not its target, so the remote check has to look at the target ([DI-14.5](CHECKLIST.md)). No longer set automatically on a new file. | engineer 2026-10-15 | +| `FILE_FLAG_OPEN_NO_RECALL` (CF, ID, RO) | Exposed | Builder method | None. Intended for remote storage systems. | plan, [DI-14.2](CHECKLIST.md); engineer 2026-10-16 | +| `FILE_FLAG_POSIX_SEMANTICS` (CF, RO) | Exposed | Builder method | None. The page says files made with it may not be reachable by older applications. `ID` does not list it. | plan, [DI-14.2](CHECKLIST.md); engineer 2026-10-16 | +| `FILE_FLAG_BACKUP_SEMANTICS` (CF, ID, RO) | Not in v1 | None | It serves backup and restore applications and is required to open a directory. A ruling for v1, with nothing against it. An open by identifier was observed to open a directory without it, though the `OpenFileById` page says it is required ([DI-D-70](#di-d-70)), and that is allowed ([DI-D-72](#di-d-72)). | engineer 2026-10-15; the directory by identifier, engineer 2026-10-19 | +| `FILE_FLAG_OVERLAPPED` (CF, ID, RO) | Exposed | Builder method, [DI-14.9](COMPLETED-CHECKLIST.md#di-149) | The pages say the system then does not maintain the file pointer and I/O is not serialised. The core addresses every operation by offset, so the pointer is not what it reads; what the ring does on such a handle was measured in [DI-14.7](COMPLETED-CHECKLIST.md#di-147). The run opened through `CreateFileW` only, which is all `FileOptions` reaches today. Earlier ruled out of v1 unless the ring needed it. | engineer 2026-10-15, 2026-10-16, 2026-10-18 | +| `FILE_FLAG_SESSION_AWARE` (CF) | Not in v1 | None | The page says it matters to processes in session 0 and is supported only on server editions; a small interest and hard to test. | engineer 2026-10-15 | +| `SECURITY_SQOS_PRESENT` and the `SECURITY_*` flags (CF, RO) | Excluded | None | The `ReOpenFile` page confines them to the client side of a named pipe, and the `CreateFileW` page does not say what they do for a file. A file target does not impersonate a server. | engineer 2026-10-15 | +| `FILE_FLAG_FIRST_PIPE_INSTANCE` | Excluded | None | It is for pipes and is not a parameter of these calls. | standing | +| `hTemplateFile` (CF) | Excluded | None | It copies attributes and extended attributes from another file onto a new one, and is ignored for an existing file. It would bring in, through a handle, the attributes this table excludes, and extended attributes this crate has no way to state or check. | engineer 2026-10-15; reason confirmed 2026-10-16 | +| The returned handle | Not exposed | A plain handle, owned | Unassociated with a completion port, as the request crate documents; no flag is added or removed. | [DI-D-63](#di-d-63) | + +**How the calls differ, from their pages.** `ID` takes no creation disposition and no attributes and has +no security descriptor; its flags are the buffering, caching, reparse, recall, overlapped, +write-through and backup ones, without `FILE_FLAG_DELETE_ON_CLOSE` or `FILE_FLAG_POSIX_SEMANTICS`. `RO` +takes access, share mode and flags, with `DELETE_ON_CLOSE` and `POSIX_SEMANTICS` among them and no +attributes. The typed entries for those two offer no attributes, which is the call's shape and not a +check. A flag a page does not list for a call is passed to it like any other: a page's silence is not +invalidity, so it is not refused, and what the kernel does with it is measured and recorded as +documentation, never made a check ([the documented-contract rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract)). + +**Not verified, and stated as such.** + +- Whether `CreateFile2` behaves differently from `CreateFileW` beyond what the pages say. +- Whether the handles `OpenFileById` and `ReOpenFile` return are inheritable: their pages do not say, and both were + observed not to be ([DI-D-70](#di-d-70), [DI-D-71](#di-d-71)). Kept here because an observation is not the page. +- What `FILE_ATTRIBUTE_TEMPORARY` does with `FILE_FLAG_NO_BUFFERING`. +- What an `OpenedFile` claims for a stream name or a device name. +- Whether `ReOpenFile` accepts a handle that came from `OpenFileById`: observed to ([DI-D-71](#di-d-71)); the + page's condition is that the original was created by `CreateFile`. +- Whether passing no attributes is the same as passing `FILE_ATTRIBUTE_NORMAL`. +- Anything the `CreateFileW` remarks say about devices other than physical disks and volumes. + +**Rulings of 2026-10-16 on the calls the assistant had made.** Each row above names who ruled on it; +the list is kept here so that the answers are in one place and are not asked again. + +- `CreateFile2` is not a second route: ruled. The engineer's reason was that it is available only to + apps on the unified app model. Its page lists desktop apps as a supported target, so that reason is + not recorded as a fact; the exclusion stands on the row's own reason. +- The generic rights and `FILE_ALL_ACCESS` are named constants and the set has no raw constructor: + ruled, and then superseded for the data rights by [DI-D-65](#di-d-65). The no-raw-constructor part + stands, with its cost: a right Win32 adds later cannot be expressed until the set gains it. +- The directory-only rights are not members until `FILE_FLAG_BACKUP_SEMANTICS` is offered: ruled. The + pipe right never being offered is a working position, not a decision. +- `FILE_ATTRIBUTE_NORMAL` is the empty set and not a member: ruled. +- `FILE_FLAG_OPEN_NO_RECALL` and `FILE_FLAG_POSIX_SEMANTICS` are exposed: ruled. +- The reason given for excluding `hTemplateFile`: ruled. + +**Zero access** (`dwDesiredAccess` of zero, which the engineer named "no access") was the one call not +ruled when the list above was written; it is resolved by [DI-D-65](#di-d-65), as the type `NoData`. + +**What building DI-13.4 settled** ([DI-13.4](COMPLETED-CHECKLIST.md#di-134)). None of these was a ruling. + +- `ShareMode` is a set of `READ`, `WRITE` and `DELETE` with `NONE` and `ALL`, built as `FileRights` is, with + no constructor from a raw mask and no `Default`: the empty set is the exclusive one and the set a file + is opened with is every member, so either would be a surprising answer from `default()`. + `FileOptions::share` replaces the mode; the default is `ALL`, which is what every file was opened with. +- The row said a sharing violation is the kernel's error, and the checklist item said it is a typed error + that names which side conflicted where the kernel says. The kernel does not say: the `CreateFileW` page + documents two conditions, an open asking for what the other does not share and an open not sharing what + the other holds, and fails both with `ERROR_SHARING_VIOLATION`; no documented call reports the other + open's access or sharing. So the error is a `SharingViolation`, with the kind `ResourceBusy`, that + carries what *this* open asked for (data access, rights, sharing, and the path or, since + [DI-D-70](#di-d-70) and [DI-D-71](#di-d-71), the identifier or the reopen it named the file by) and keeps the + kernel's own + error as its `source`, which satisfies both statements. It does not name a side. +- Measured on local NTFS, Windows 11 build 26300: every pair of opens over the three accesses a share mode + judges (read, write, delete) and the eight share modes on each side, 576 pairs, is accepted or refused + as the page's two conditions say. An open that asks for no data right and no other right is not + refused beside an exclusive one. +- `OpenedDisk` and `OpenedVolume` took std's default sharing and had no share mode of their own, because + they open through `std::fs::OpenOptions`; [DI-13.11](COMPLETED-CHECKLIST.md#di-1311) gave them one, below. +- `ReOpenFile` takes a share mode too, so [DI-15.4](CHECKLIST.md) takes a `ShareMode` and raises the same + `SharingViolation`. + +**What building DI-13.11 settled** ([DI-13.11](COMPLETED-CHECKLIST.md#di-1311)). None of these was a ruling. + +- `VolumeOptions::share` and a new `DiskOptions::share` take the same `ShareMode`, default all three, and a + conflict is the same `SharingViolation` with no right beyond the data access. A disk had no options + type; `OpenedDisk::open(path, options)` replaces `open(path)`, which breaks its callers, and is the + assistant's choice over a second method beside the first, so that a volume and a disk are opened alike. +- The volume's and the disk's open are one function, `raw_open::open`, so what a device open asks of the + kernel is one decision. The call is the same whatever the path names, so its tests open a file through + it: every pair of read, write and read-and-write opens with the eight share modes on each side matches + the documented rule, as a file's do. +- What is not reached by the local run: that `OpenedVolume::open` and `OpenedDisk::open` pass their + options' share mode to that function. A raw open needs an administrator and a scratch virtual disk, so + the exclusive volume and disk tests are the ignored ones that run in CI, and an open that asks for no + data right is not judged by the share mode, so a no-data handle cannot show it either. Run elevated on + 2026-10-16 ([DI-13.12](COMPLETED-CHECKLIST.md#di-1312)), both pass, and each wiring mutated to ignore the options + fails its test. The two sabotage entries still expect to survive, because the sweep runs no ignored test. + The first open of an exclusive volume was refused once in two runs as a sharing violation that was not + the test's, something else holding the volume just after it appeared; the volume tests open it again for + a bounded time, so that is the test's patience and not the library's. +- **The lock is not independent of the share mode.** Measured elevated on 2026-10-16 on a 64 MiB scratch + volume: a volume locked with `VolumeOptions::lock` refuses every other open that asks for a data right, + read-write or read-only, with `ERROR_ACCESS_DENIED` and not a `SharingViolation`, whether the holder + shared nothing or everything; an unlocked volume answers as the share mode says, a sharing violation + when exclusive and an open when shared. An open that asks for no data right is admitted in all four. + So a caller that locks sees the lock's refusal and never the share mode's. + +**What building DI-14.2 settled** ([DI-14.2](COMPLETED-CHECKLIST.md#di-142)). None of these was a ruling. + +- `FileOptions::delete_on_close`, `open_no_recall` and `posix_semantics` are the three flags of the rows + above, each a bool that sets its own bit, passed to a cached and an unbuffered file alike. Nothing is + added or checked for them here; which combinations the documented contract calls invalid, and so are + refused ahead of the open, is [DI-14.4](CHECKLIST.md)'s. +- **Delete-on-close is not refused for want of the right to delete.** **Corrected 2026-10-18 by + [DI-14.4.1](CHECKLIST.md)'s reading:** this said the `CreateFileW` page lists the right as needed; it does + not. The requirement is stated on `ZwCreateFile`'s page, a call this crate does not make (see "What the + reading pass of DI-14.4.1 found" below). Measured on Windows 11 build 26300, an open with the flag and no right to delete among + the rights succeeds and the file is deleted when the last handle closes. While the handle is open, a + later open that does not share deletion fails with a sharing violation, whatever it asks for, and one that + shares it is admitted, with the right to delete or without. +- `FILE_FLAG_OPEN_NO_RECALL` and `FILE_FLAG_POSIX_SEMANTICS` are measured only as far as an open with each + succeeds on a local file and the file is usable. What either does is not, since neither affects a target + and the measurement needs remote storage and a case-sensitive directory respectively. +- `FILE_FLAG_OVERLAPPED` is not offered by this item: the run in [DI-14.7](COMPLETED-CHECKLIST.md#di-147) + measured the ring on such a handle, and whether to offer it was left to [DI-14.9](COMPLETED-CHECKLIST.md#di-149). + +**What building DI-14.3 settled** ([DI-14.3](COMPLETED-CHECKLIST.md#di-143)). None of these was a ruling. + +- `FileAttributes` is a set of `ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM` and `TEMPORARY`, built as + `ShareMode` is, with no constructor from a raw mask; `FileOptions::attributes` replaces the set. The item + listed *encrypted* among them; the table excludes `FILE_ATTRIBUTE_ENCRYPTED` (engineer 2026-10-15), and the + table is what was followed. +- **The empty set and `FILE_ATTRIBUTE_NORMAL`** (the question the table left to this item): measured on + Windows 11 build 26300, a file created with no attributes and one created with `NORMAL` have the same + attributes, `ARCHIVE` among them. The empty set passes zero. +- **A file created `READONLY` with the right to write**: the create succeeds, the handle that made it can + write, and a later open for writing is refused with `ERROR_ACCESS_DENIED`. +- **An open of a file that exists leaves its attributes alone**, whatever attributes are asked for, with + `OpenExisting` or `OpenAlways`. +- **The security descriptor is on the request, not in `FileOptions`.** A descriptor owns memory and cannot be + `Copy`, and the options are a `Copy` description passed to several opens, so a field there would have ended + that for every caller. `OpenRequest::with_security_descriptor` takes the `SecurityDescriptor` the request + crate builds from SDDL, which the request owns and frees. It is the assistant's choice over a field in the + options, and a change of place later is a change to one method. +- **Inheritance is not an option.** The request passes `SECURITY_ATTRIBUTES` with `bInheritHandle` false when + it has a descriptor and none otherwise, which `CreateFileW` documents as a handle that cannot be inherited; + a test pins the first on the request. The opened handle's inheritability is read back through + `get_handle_information` of [win-handles-sys](../win-handles-sys/DESIGN-NOTES.md), which + [DI-14.8](COMPLETED-CHECKLIST.md#di-148) built for it: a created file's handle, with a descriptor and without, carries + neither flag. (When this was built the query was not available safely, and the read-back was queued.) +- **The descriptor is read back by behaviour and by query.** A file created with a DACL that denies read to + everyone refuses a later open for reading, and one created with a DACL that allows it admits one; a + descriptor given with an open of a file that exists leaves it as it was, as the page says. The descriptor + itself is read back through `get_security_info` of + [win-authorization-sys](../win-authorization-sys/DESIGN-NOTES.md), built for it by + [DI-14.8](COMPLETED-CHECKLIST.md#di-148): a created file's DACL reads back as the one it was given, cached and + unbuffered. (When this was built the query was not available safely, and the read-back was queued.) +- The security quality-of-service flags are excluded by the table, so none is offered. + +**What building DI-14.7 settled** ([DI-14.7](COMPLETED-CHECKLIST.md#di-147)). None of these was a ruling, and +none decides whether `FileOptions` offers `FILE_FLAG_OVERLAPPED`: that was [DI-14.9](COMPLETED-CHECKLIST.md#di-149)'s. + +Measured on Windows 11 build 26300, local NTFS (the volume of the temporary directory), through +`windows-ioring-sys` 0.7.1, by `win-durable-ioring`'s `tests/overlapped_evidence.rs`. Four handles, each +opened through the request crate's `OpenFile` with the flag in the raw flags or without it: cached and +unbuffered, overlapped and plain. The same sequence was driven through the ring on each: two writes (offsets 0 +and 4096), a flush, two reads back, a read at the end of the file, a read across it, a write through a handle +opened for reading only, a read at offset 1, four reads in one batch, and a read through a registered file. +The completion carries a result and a byte count, so those are what was compared; the status and information +values of the item's text are not separately visible through the safe API. + +- **The ring accepts a handle opened with the flag.** It registers one, and every step queued, submitted and + completed on it. +- **Every result is the same with the flag as without it, in each buffering mode.** The write is 4096 + bytes, the flush 0, the reads 4096; the read at the end of the file fails with `ERROR_HANDLE_EOF`, the write + through the read-only handle with `ERROR_ACCESS_DENIED`; the read across the end returns the 4096 bytes that + exist. The one result that differs is between the buffering modes and not between the flag's settings: a read + at offset 1 returns 4096 bytes on a cached handle and fails with `ERROR_INVALID_PARAMETER` on an unbuffered + one, with the flag or without it. +- **What differs is when a completion is there.** `bounded_pop.rs` in `windows-ioring-sys` records that a + handle opened without the flag completes inline, during the submit. In the first run every step on every + plain handle, and on the cached overlapped handle, was poppable as soon as the submit returned, and the + four reads were in each of 30 further runs. On the unbuffered overlapped handle the three data reads and + the registered read were not poppable then, in each of the 30; the writes, the flush, and the three + failing steps were, in the first run, which is the only one in which they were looked at. +- **Order of completion.** A batch of four reads completed in queue order on every plain handle and on the + cached overlapped handle in each of 30 runs; on the unbuffered overlapped handle it completed out of order + in 10 of 30 (`[3, 1, 2, 0]` in 7, `[2, 0, 3, 1]` in 3). +- **What the test asserts and what it does not.** It asserts every step's result on all four handles, that a + plain handle completes inline, and that a batch's completions are the four queued. It does not assert the + inline completion of an overlapped handle, which depends on the host, and so it cannot fail if the flag + stops being applied: the 30-run observation above is what shows it was. +- **Not reached:** a device that is not a disk file, a remote file, and a volume that is not NTFS; each is a + different row of [DI-13.1](COMPLETED-CHECKLIST.md)'s table, and no overlapped handle was opened on any. + +**What building DI-14.5 settled** ([DI-14.5](COMPLETED-CHECKLIST.md#di-145)). None of these was a ruling. + +- **Which documented handle queries were checked, and which was chosen.** Measured on Windows 11 build 26300 + through the request crate, on a file and a directory of a local volume and on the same through the + administrative share `\\localhost\C$`: + `GetFinalPathNameByHandleW` (the request crate's `QueryFinalPath`) answers `\\?\C:\...` for the local + handles and `\\?\UNC\localhost\C$\...` for the share's; `GetFileInformationByHandleEx` with + `FileRemoteProtocolInfo` (reached through `QueryFileInformation` and a raw class) succeeds for the share's + handles, reporting SMB 3.1.1, and fails with `ERROR_INVALID_PARAMETER` for the local ones. Neither needs more + than the right to read attributes. **The final path is the one used**, because it is what the check already + read (`canonicalize` is the same call made on a path), so which paths count as remote is unchanged and the + refusal still carries the location; and because the other answers by failing, which is the same + for a file system that does not implement the class and for a local one. `FileRemoteProtocolInfo` is not + rejected as an option, only not needed: it is the documented route to *which protocol* a remote file is on, + should a later item want that. +- **There is no window.** The check is a query of the handle the open returned, so a link swapped after the + open cannot change what was judged. It is made after the open, which the engineer ruled on 2026-10-16 is + acceptable; measured, an unbuffered `CreateNew` of a file on the share is refused and the file it created + is still there, as the item's text says. +- **A reparse point is judged by what the handle is.** A local link to a file on the share, opened unbuffered: + followed, the handle is the share's file and is refused; opened as the link (`open_reparse_point`), the handle + is the link's own, on the local volume, and is accepted. Neither is a refusal made on the link's target by + looking at the path, which was the item's concern about the check ahead of the open. A handle opened on a link + is the link, and the ruling that an unbuffered file must be local is a statement about the device the + handle's I/O goes to. **Ruled by the engineer, 2026-10-18 ([DI-14.10](COMPLETED-CHECKLIST.md#di-1410)):** a + handle opened on a link to a share is accepted, and is not refused for where the link leads. +- **A handle whose final path cannot be read is refused, as the crate's own narrowing.** As first built the + refusal was the final-path call's own error; [DI-14.6.2](COMPLETED-CHECKLIST.md#di-1462) names it + `UnknownLocation`, with that error as its source. See "What DI-14.6.2 settled" below. +- **What the unit tests reach and what they do not.** The location of a handle, the link cases on a local + volume and the unreadable final path run everywhere. The refusal itself, the created-before-refused file and + the link to a share need the administrative share and are ignored by default, as the earlier one was; they + were run with the ignored tests included, and pass. + +**What the reading pass of DI-14.4.1 found** ([DI-14.4.1](CHECKLIST.md)). Read 2026-10-18, under +[the documented-contract rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract), from the +sources the Learn pages are built from: `CreateFileW` (`ms.date` 08/16/2022), "Creating and Opening Files" +(07/08/2025), "File Buffering" (05/31/2025), "File Access Rights Constants" (05/31/2018) and "File Attribute +Constants" (09/23/2025). "Creating and Opening Files" was added to the item's three because the `CreateFileW` +page refers to it for the valid combinations of share mode and access and for special cases. The question +asked of every statement: is it a condition on a combination of the open's arguments, and does the page call +it invalid. + +*Conditions on the arguments alone that a page calls invalid.* Every one is already unrepresentable in this +crate's v1 surface, so none needs a check: + +| Condition | Where | Why it is already excluded | +|---|---|---| +| `FILE_ATTRIBUTE_NORMAL` "is valid only if used alone" | `CreateFileW`, attributes table; "File Attribute Constants" | `NORMAL` is not a member of `FileAttributes`; the empty set passes no attribute ([DI-14.3](COMPLETED-CHECKLIST.md#di-143)). | +| `FILE_FLAG_OPEN_REPARSE_POINT` "cannot be used with the `CREATE_ALWAYS` flag" | `CreateFileW`, flags table | `CREATE_ALWAYS` is not a `Disposition` in v1. | +| `TRUNCATE_EXISTING`: "The calling process must open the file with the `GENERIC_WRITE` bit set" | `CreateFileW`, `dwCreationDisposition` | `TRUNCATE_EXISTING` is not a `Disposition` in v1. Note for its return: the page names the `GENERIC_WRITE` bit, and this crate's data access asks for `FILE_WRITE_DATA`. | +| The creation dispositions "cannot be combined" | `CreateFileW`, `dwCreationDisposition` | `Disposition` is an enum. | + +The two v1 exclusions are recorded against their return in [DI-6+.11](PARKED.md), which is when the +first two rows of this kind become checks. + +*Conditions that depend on the target*, which no check ahead of the open can judge and which the kernel's +error answers: a share mode or access that conflicts with an open handle (`ERROR_SHARING_VIOLATION`); +delete-on-close against open handles that did not share deletion, and later opens that do not; a security +descriptor or attributes given for a file that exists (ignored); a security descriptor on a file system +without security; `FILE_FLAG_OPEN_REPARSE_POINT` on a path that is not a reparse point (ignored); a file +pending deletion (`ERROR_ACCESS_DENIED`); and the sections on directories, physical disks and volumes, +communications resources, consoles, mailslots and pipes, which state requirements on the arguments *when the +path names such a device*. Through `OpenedFile` the path is opaque, so those are conditions on the target. +They are not conditions on the target for `OpenedVolume` and `OpenedDisk`, whose path types name the device; +see the last paragraph. + +*Statements that are not invalidity*, and so pass through: random access with sequential scan, "self-defeating"; +sequential scan "should not be used if read-behind ... will be used", which is advice about the I/O and not +about the arguments; each hint "has no effect if the file system does not support cached I/O and +`FILE_FLAG_NO_BUFFERING`", which reads two ways and depends on the file system; `FILE_FLAG_WRITE_THROUGH` and +`FILE_FLAG_NO_BUFFERING` "are independent and may be combined"; no-buffering with overlapped, a statement +about performance; `FILE_ATTRIBUTE_TEMPORARY`, of which the pages say what it does to caching and say nothing +about an unbuffered file, supported ([DI-14.4.3](COMPLETED-CHECKLIST.md#di-1443)); and `FILE_APPEND_DATA` without `FILE_WRITE_DATA`, which +the rights page says changes what a write does, not that it is invalid. + +*Two conditions this crate had attributed to the `CreateFileW` pages are not on them.* That delete-on-close +needs the `DELETE` right, and that the append right is incompatible with no-buffering, are both stated on +`ZwCreateFile`'s page in the driver documentation (`ms.date` 12/14/2022): "If this flag is set, the DELETE +flag must be set in the *DesiredAccess* parameter", and `FILE_NO_INTERMEDIATE_BUFFERING` "is incompatible with +the *DesiredAccess* parameter's FILE_APPEND_DATA flag". This crate opens through `CreateFileW`, and the +[DOCUMENTED APIS ONLY rule](../../.github/copilot-instructions.md) keeps it from calling `ZwCreateFile`. Read as +the call's own contract, the rule makes neither a contract refusal: what `CreateFileW` does with its flags +on the way down is not documented on its pages, so the lower call's conditions describe the layer below and +not the call. The measurements stay what they were, as anecdote: delete-on-close is taken without the right +to delete, and `FILE_ALL_ACCESS` is refused unbuffered with `ERROR_INVALID_PARAMETER`. **Ruled by the +engineer, 2026-10-18 ([DI-14.4.2](COMPLETED-CHECKLIST.md#di-1442)):** the lower layer's page "is not direct +evidence", and is "good evidence to use for an experiment" whose findings are documented as such. Nothing is +refused on its account, so `OpenedFile`'s open has no check ahead of it. + +*Outside `OpenedFile`:* the "Physical Disks and Volumes" section says that "When opening a volume or floppy +disk, the *dwShareMode* parameter must have the `FILE_SHARE_WRITE` flag", and that the disposition must be +`OPEN_EXISTING`. For `OpenedVolume` the path is a volume by its type, so this is a condition on its +arguments, and `VolumeOptions::share` offers modes without `FILE_SHARE_WRITE`, including `ShareMode::NONE`, +which [DI-13.12](COMPLETED-CHECKLIST.md#di-1312) measured succeeding elevated. The disposition is already +`OPEN_EXISTING`, since the device open uses `std`'s with no create option. **Settled by the engineer's +amendment of 2026-10-18 ([DI-14.11](COMPLETED-CHECKLIST.md#di-1411)):** "If we have evidence that the +documentation is wrong ... the remedy is to not embody the documentation structurally." `DI-13.12`'s +exclusive-volume test is that evidence, so the share option stays as it is, and `VolumeOptions::share` +documents the page's requirement and the measurement beside it. The section states no share-mode requirement +for a physical disk. + +**Measured for DI-14.6: what an open handle reports about its own kind** ([DI-14.6.1](COMPLETED-CHECKLIST.md#di-1461)). +Windows 11 build 26300, one machine, local NTFS, 2026-10-18. Every handle came from the request crate's +`OpenFile`, which is `CreateFileW` with nothing added. These are measurements and not the contract: only +`GetFileType`'s return values are documented, so the rows are anecdote, and none of them is a refusal +([the rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract)). The pinned part is +[device_handle_evidence.rs](../windows-namespace-request-sys/tests/device_handle_evidence.rs), which asserts +`GetFileType`'s class and only whether `IOCTL_STORAGE_GET_DEVICE_NUMBER` succeeds, never which error a failure +carries. Both directions were checked by sabotage: an ioctl that fails on a volume turns the volume and disk +tests red, and one that always reports a number turns the file, directory, `NUL` and pipe tests red. + +| Handle | `GetFileType` | `IOCTL_STORAGE_GET_DEVICE_NUMBER` | `GetFileInformationByHandle` | `GetFinalPathNameByHandleW` | `FlushFileBuffers` | +|---|---|---|---|---|---| +| disk file | `DISK` | fails, 87 | succeeds | succeeds | succeeds | +| directory (backup semantics) | `DISK` | fails, 87 | succeeds | succeeds | fails, 5 | +| `\\.\NUL` | `CHAR` | fails, 1 | fails, 1 | fails, 87 | fails, 1 | +| `CONOUT$` | `CHAR` | fails, 1 | fails, 1 | fails, 1 | fails, 6 | +| `CONIN$` | `CHAR` | fails, 1 | fails, 1 | fails, 1 | succeeds | +| volume `\\.\C:`, access 0 | `DISK` | succeeds | fails, 1 | fails, 1 | fails, 5 | +| `\\.\PhysicalDriveN`, access 0 | `DISK` | succeeds | fails, 1 | fails, 1 | fails, 5 | +| named pipe, server end | `PIPE` | fails, 1 | succeeds (attributes 0x80) | fails, 1 | succeeds | +| named pipe, client end | `PIPE` | fails, 1 | succeeds (attributes 0x80) | fails, 1 | succeeds | + +- **`GetFileType` does not tell a file from a volume or a disk.** It answers `DISK` for all of them, so a + query of it alone cannot say whether a handle is a file. +- **The device-number control separated them, on this machine.** It succeeded on a volume and on a physical disk + and failed on a file, a directory, `NUL`, a console and a pipe, so over these handles the two sets were told apart + by it; no page says which devices answer it, so this is an observation and not a rule about them + ([WV-D-12](../win-volume-sys/DESIGN-NOTES.md#wv-d-12)). It succeeded + with no access requested on both, unelevated; the volume's reply carries a non-zero partition number, and the + disk opened from that reply's device number reports the same device number. Opening the volume or the disk + for `GENERIC_READ` was refused with 5 in this unelevated session. +- **`NUL`:** `CHAR`, and no file query succeeds on it. `FlushFileBuffers` fails with `ERROR_INVALID_FUNCTION` + and does not report success. A read returned end of file and a write succeeded, as [D-19](../windows-namespace-request-sys/DESIGN-NOTES.md#d-19) + recorded. +- **A pipe's two ends are alike** on every query tried. +- **A serial port was not measured, and no decision rests on it.** The three ports named on this machine are + Remote Desktop redirections of the client's ports: `QueryDosDevice` gives `COM3`, `COM4` and `COM22` the targets + `\Device\RdpDrPort\;COMn:2\tsclient\COMn`, so what an open waits on is the client's port at that moment. `COM2` is + an ACPI port in an error state with no DOS device. In three earlier runs `CreateFileW` on `COM3` had not returned + after 15, 25 and 45 seconds, and the process was killed each time; later it returned `ERROR_ACCESS_DENIED` after + about 80 ms, and `COM22` returned `ERROR_FILE_NOT_FOUND` at once. `COM4`, 2026-10-18, blocked in `CreateFileW` + for about 5.2 seconds in every run and then failed with 31. `CancelSynchronousIo` on that open, from a second + thread that held `THREAD_TERMINATE`, returned `TRUE` and did not shorten it: the call itself returned only when + the open did, about 4.2 seconds after it was made at one second, and after the whole open when made at once. + No port opened, so `GetFileType`, `FlushFileBuffers`, the device control and a cancel of a blocked serial read were + not measured. What is decided does not depend on them: a cached open of a handle that is not a disk file is + accepted and an unbuffered one with no final path is refused ([DI-14.6.2](COMPLETED-CHECKLIST.md#di-1462)), and + what this crate does about a blocking open is [DI-D-67](#di-d-67). A machine with a port that opens could measure + them. + +**What DI-14.6.2 settled** ([DI-14.6.2](COMPLETED-CHECKLIST.md#di-1462)). The engineer, 2026-10-18, "implement +di-14.6.2", after the question of what refusing is worth had been discussed and not ruled; the positions below are +the assistant's, taken on that instruction, and stand until amended. + +- **A cached open of a handle that is not a disk file is accepted, and nothing is added for it.** No + `CreateFileW` page makes it invalid ([the rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract)), + where a name leads is the caller's ([DI-D-67](#di-d-67)), and a cached file claims only best effort, which a + device does not falsify. A pipe's or `NUL`'s `FlushFileBuffers` result is not a durability statement, and + the claim made does not rest on it. +- **An unbuffered open of one is refused, and the refusal is the crate's own.** An unbuffered file claims + `WarrantyClass::SharedDevice`, that its writes reach a device shared with other writers, which `NUL`, a + pipe and a console cannot keep. Removing the refusal, which the discussion of 2026-10-18 had leaned + toward, would give an unbuffered `NUL` that claim, so it stays. What + changed is that it is no longer an accident: before, it followed from `GetFinalPathNameByHandleW` failing on + a handle with no final path, and was reported as that call's error, which says nothing of why the open was + refused. It is now `UnknownLocation`, kind `io::ErrorKind::Unsupported`, with the call's error as its source, + and its documentation says it is this crate's narrowing and not a condition `CreateFileW` documents. It sits + beside `RemoteDevice`, which is the same kind of refusal for a remote file. +- **What the refusal says it knows.** That the final path could not be read, and so the handle is not known to + be a file on a local volume. It does not say what the handle is: the cause is any failure of that read, and + the [measurements](#di-d-64) show that `NUL`, a console, a pipe, a volume and a physical disk all fail it. `GetFileType` was not added to name the kind in the error; it separates `CHAR`, `PIPE` + and `DISK` and does not separate a file from a volume or disk, and an accessor that returned a class is a + separate decision, not made here. +- **A volume or disk opened through `OpenedFile` unbuffered is refused for the same reason**, since neither has + a final path; they are opened as such by `OpenedVolume` and `OpenedDisk`, whose path types name the device. +- **Tested** with a real unbuffered and a real cached open of `NUL`. Three sabotages: an unreadable location + accepted, the refusal reported as the call's error, and a cached open refused. The last is refused by a + `const` assertion at build, before any test runs, which is the stronger rung. +- **Not decided here:** a serial port, which was not measured and which nothing rests on (above), and the open's + documentation of [DI-D-67](#di-d-67), which is [DI-14.6.4](COMPLETED-CHECKLIST.md#di-1464). + +**What DI-14.12 built was withdrawn: `HandleKindQuery` and `HandleKind` are removed.** +**Withdrawn by [DI-D-73](#di-d-73).** [DI-14.12](COMPLETED-CHECKLIST.md#di-1412) was an observer over any handle that +asked `GetFileType`, then `IOCTL_STORAGE_GET_DEVICE_NUMBER`, then `IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS`, and named the +pattern of answers `DiskFile`, `Volume` or `PhysicalDisk`; the engineer approved the sketch on 2026-10-18 ("I like it, +build it. We can tweak it if we don't like it"). What it answered with, observed on 2026-10-18 on one machine with +one physical disk, stays as a measurement: the system volume answered the device-number control with type 7 and +partition 4 and the extents control with one extent, and `\\.\PhysicalDrive0` answered the device-number control with +type 7 and partition 0 and the extents control with `ERROR_INVALID_FUNCTION`. + + +**What DI-16.1 changed: the request crate is handed the handle already held** ([DI-16.1](COMPLETED-CHECKLIST.md#di-161)). +The request crate's entries hold a `SharedHandle` ([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32)), so +nothing here duplicates a handle it holds to ask a question of it. + +- **`HandleKindQuery::of` took `&SharedHandle`**, and the query was later removed ([DI-D-73](#di-d-73)). +- **An opened file's final path is asked of the handle it holds.** `final_location` takes the `SharedHandle` and gives + the request a clone. +- **`OpenedVolume` and `OpenedDisk` were checked and had nothing to change**: they hold a `SharedHandle` and never hand + one to the request crate. +- **What is tested.** A duplicate in `final_location` is not observable, since the request is made and dropped inside + it and only the path comes back; its sabotage is a declared survivor. The kind query, whose handle a caller held and + so could compare with the share, was the one place a duplicate was observable, and is removed. + +**Measured for DI-13.7: which access each disposition accepts.** Windows 11 build 26300, local NTFS, through +the request crate's `OpenFile` (which is `CreateFileW` with nothing added), cached and unbuffered, with +[disposition_measurements.rs](../win-durable-ioring/examples/disposition_measurements.rs); every disposition +against ten access masks, the file present except for `CREATE_NEW`. Cached and unbuffered agree except where +stated. + +| Access | `CREATE_NEW`, `CREATE_ALWAYS`, `OPEN_EXISTING`, `OPEN_ALWAYS` | `TRUNCATE_EXISTING` | +|---|---|---| +| `FILE_WRITE_DATA` | accepted; `CREATE_ALWAYS` empties the file | `ERROR_INVALID_PARAMETER` | +| `FILE_READ_DATA \| FILE_WRITE_DATA` | as above | `ERROR_INVALID_PARAMETER` | +| `FILE_WRITE_DATA` with `FILE_WRITE_ATTRIBUTES`, `FILE_WRITE_EA`, both, or `DELETE` | as above | `ERROR_INVALID_PARAMETER` | +| `FILE_WRITE_DATA \| FILE_APPEND_DATA` | as above cached; every disposition `ERROR_INVALID_PARAMETER` unbuffered | `ERROR_INVALID_PARAMETER` | +| `GENERIC_WRITE` | accepted; `CREATE_ALWAYS` empties the file | accepted; empties the file | +| `FILE_READ_DATA` only | accepted; `CREATE_ALWAYS` empties the file | `ERROR_INVALID_PARAMETER` | +| no access | accepted; `CREATE_ALWAYS` empties the file | `ERROR_INVALID_PARAMETER` | + +`TRUNCATE_EXISTING` is accepted with the literal `GENERIC_WRITE` and with no other mask tried, including +`GENERIC_WRITE` less its append, synchronize and read-control bits. `CREATE_ALWAYS` emptied the file under +every mask, a read-only one and none included. The engineer ruled on 2026-10-16 that v1 offers neither, +which is the row above; the two questions the table raises, how an enum would offer `TRUNCATE_EXISTING` +and whether a handle typed not to write may be opened to overwrite, are not answered and go with the +revisit. + +**Scheduled work:** none of its own. Each row's work is the item it names: +[DI-13.3](CHECKLIST.md) (rights), [DI-13.4](CHECKLIST.md) (share mode), [DI-13.7](COMPLETED-CHECKLIST.md#di-137) +(the open, the disposition and the two-phase shape), [DI-14.1](CHECKLIST.md) to [DI-14.7](CHECKLIST.md) +(flags, attributes, security, combinations, the remote check, devices, the overlapped evidence) and +[DI-15.1](CHECKLIST.md) to [DI-15.4](CHECKLIST.md) (identity and reopening). + +## DI-D-65: data access is a type parameter, and the rights set carries the rest + +**Amends [DI-D-64](#di-d-64) for the data rights.** The rows there for zero access, `FILE_READ_DATA`, +`FILE_WRITE_DATA` and the generic rights carry the marker. + +**What is decided.** The engineer, 2026-10-16, after the form below was shown: "ok this seems usable, +let's use it". The prompt was the engineer's observation that, with access a parameter of the open, +demanding a data right of the type was odd, and that the pattern should reach every handle this crate +opens and not only a file in the ring. + +**The types.** Four sealed markers name the data access a handle was opened with; two axes, read and +write, give `NoData`, `ReadOnly`, `WriteOnly` and `ReadWrite`. + +```rust +pub trait DataAccess: Sealed + Copy + Debug + Send + Sync + 'static { + const READ: bool; // the markers live in durable-ioring (DI-D-66); the rights they ask + const WRITE: bool; // of Win32 are derived from these in win-durable-ioring + type WithRead: DataAccess; // NoData -> ReadOnly, WriteOnly -> ReadWrite, the rest unchanged + type WithWrite: DataAccess; +} +pub trait HasData: DataAccess { const ACCESS: Access; } // ReadOnly, WriteOnly, ReadWrite +pub trait CanRead: HasData {} // ReadOnly, ReadWrite +pub trait CanWrite: HasData {} // WriteOnly, ReadWrite + +pub struct FileOptions; // .read(), .write() move A +pub struct OpenedFile; +pub struct OpenedDisk; +pub struct OpenedVolume; +// Win32FileTarget::new, from_disk and from_volume take A: HasData; the target stays one type. +``` + +`FileRights` keeps the rights that gate nothing this crate offers: attributes, extended attributes, +`FILE_EXECUTE`, `DELETE`, the security rights and `SYNCHRONIZE`. `FILE_READ_DATA`, `FILE_WRITE_DATA`, +the `GENERIC_*` constants and `FILE_ALL_ACCESS` leave it. No marker or set carries `FILE_APPEND_DATA`, +so [DI-D-64](#di-d-64)'s rule that append-only access is unrepresentable holds. + +**Where the line between type and set is drawn, and why.** A right is a type if it gates something this +crate's own API does with the handle: a file target is made only from a handle with a data right, and +the ring's read, write and commit are what a target performs. A right that gates only a call the +consumer makes on the lent handle stays in `FileRights` and is judged by the kernel. The measurement is +in [access_rights_measurements.rs](../win-durable-ioring/examples/access_rights_measurements.rs), which +prints a table for a cached and an unbuffered open of nine sets, using safe `std` calls on a duplicate +of the handle. On a local file system: a read needed the read-data right; a write, a length change and +a flush needed the write-data right; setting a time needed the write-attributes right; and the size +query succeeded for every set tried, including those without the read-attributes right, where the +`GetFileSizeEx` page says it may depend on the directory's permissions. From the documentation and not +measured: `DELETE` gates rename and delete-on-close through `SetFileInformationByHandle`, and +`SetFileValidData` needs `GENERIC_WRITE` and a privilege. If this crate later offers a method gated by +one of those, that right becomes an axis, and the cost of one more axis is in +[DI-13.8](COMPLETED-CHECKLIST.md#di-138). + +**What it fixes.** + +- The refusal of a handle with no data right, made when the request was built, is a compile error: a + `NoData` handle has no conversion to a file target. +- A raw disk or volume could be opened only for reading and writing, since + [opened_disk.rs](../win-durable-ioring/src/windows_backend/opened_disk.rs) and + [opened_volume.rs](../win-durable-ioring/src/windows_backend/opened_volume.rs) passed both; either can + now be opened for reading only, and a geometry query can be made on a handle with no data right. +- `ReOpenFile` ([DI-15.4](CHECKLIST.md)) changes the access of a handle, and the change is a change of + `A`. +- `FILE_ALL_ACCESS` named the append bit, and an unbuffered open of it was measured to be refused with + `ERROR_INVALID_PARAMETER` ([DI-D-64](#di-d-64)); no marker carries the bit, so that open cannot be + asked for. + +**What the type does not reach, and what then reaches it** ([DI-D-66](#di-d-66)). The ring addresses a target by `IoTargetKey(pub u64)`, a number the +consumer chooses, and `Win32FileTarget` is not generic, so a compile-time check ends where the target is +built. A write submitted to a read-only target reached the kernel and failed with its error, which the +test `a_write_through_a_read_only_handle_fails` then expected. [DI-D-66](#di-d-66) decided what replaced +that: the target states its data access to the core, as it states its warranty and geometry, so the core +refuses the write at the push and hands its buffer back, and the test changed with it. + +**What building it settled** ([DI-13.8](COMPLETED-CHECKLIST.md#di-138)). None of these was a ruling; each is +the choice the code made, with the measurement. + +- `read()` and `write()` ask for `FILE_READ_DATA` and `FILE_WRITE_DATA` alone, and no generic right. Reading, + writing, extending, flushing and every test through the ring pass with them. +- **`TRUNCATE_EXISTING` is refused with `ERROR_INVALID_PARAMETER` when the access holds + `FILE_WRITE_DATA` and not `GENERIC_WRITE`**, measured on Windows 11 build 26300, which is what the + `CreateFileW` page says of that disposition. A typed write access cannot ask for `GENERIC_WRITE`, + which is one of the two reasons `TRUNCATE_EXISTING` is not among the dispositions offered for v1 + (see the `dwCreationDisposition` row above; [DI-6+.11](PARKED.md) revisits it). +- A type parameter written in a path takes its default when it is left off, so + `OpenedFile::::open(path, FileOptions::new().read())` names `ReadWrite` and does not compile. + A file opened with other options writes the parameter or leaves it to be inferred with `_`: + `OpenedFile::::open(path, options)`. A disk and a volume have no options to infer it + from, so it is named at the call, `OpenedDisk::::open(path, options)`. +- `VolumeOptions::lock` is offered with any data access. The `FSCTL_LOCK_VOLUME` page names no right + the handle needs, and whether the kernel wants one was not measured. + +**Not verified.** What a raw disk and a raw volume do for each marker, which needs an administrator and +runs only where the test run has one, in CI; and whether `ReOpenFile` can widen a handle's access, which +is a test in [DI-15.4](COMPLETED-CHECKLIST.md#di-154): it was observed to, from read-only to read-write, where the +file's security allows ([DI-D-71](#di-d-71)). + +**Scheduled work.** [DI-13.8](COMPLETED-CHECKLIST.md#di-138), and [DI-13.9](COMPLETED-CHECKLIST.md#di-139) and +[DI-13.10](COMPLETED-CHECKLIST.md#di-1310) from [DI-D-66](#di-d-66). Amended by this decision: [DI-13.7](COMPLETED-CHECKLIST.md#di-137), [DI-15.2](CHECKLIST.md) and +[DI-15.4](CHECKLIST.md). + +## DI-D-66: a target states its data access to the core, and a key can carry it + +**Extends [DI-D-65](#di-d-65) to the ring.** The data access of a handle is a type where this crate opens +it; here the same access reaches the core, which is where a submission is judged. + +**What is decided.** The engineer, 2026-10-16, on being shown that the type ends where the target is built: +"it seems like IoTarget should have the same sort of type-based encoding of the access to the target that +we just litigated in di-13.8", and then, offered the two layers below, "do both, schedule both". + +**Why the trait does not take an associated type.** A ring holds one `IoBackend::Target`, and addresses +what it holds by `IoTargetKey(pub u64)`, a number the consumer picks. `Win32FileTarget` is one type for every +file, volume and disk, and the buffering mode is already read into its fields when it is made and not carried +as a parameter. An associated type on `IoTarget` would let a ring hold targets of one access only, so a +ring with a read-write log file and a read-only snapshot could not be built. The access is therefore a +type where the target is constructed and a stated fact after it, as the buffering mode is. + +**Layer 1 -- the target states it** ([DI-13.9](COMPLETED-CHECKLIST.md#di-139)). + +```rust +// durable-ioring (portable): the markers and traits of DI-D-65 live here, so that MemoryTarget and +// any other backend use them. What a marker asks of Win32 stays in win-durable-ioring. +pub enum Access { ReadOnly, WriteOnly, ReadWrite } // the stated fact; a target has data + +trait IoTarget { /* existing */ fn access(&self) -> Access; } // required, no default +struct IoTargetDescription { /* existing */ access: Access } // read once, as the rest is +RefusalCause::AccessDenied // additive, the enum is non_exhaustive +``` + +`IoBackend::check` refuses a write to a target that cannot write and a read to one that cannot read. It +already runs before a gated operation is held ([DI-D-54](#di-d-54)), so a gated write to a read-only +target is refused at the consumer's push and not when its gate releases. The method has no default, +because a default would state `ReadWrite` for a target that never said so. + +**Layer 2 -- a key carries it** ([DI-13.10](COMPLETED-CHECKLIST.md#di-1310)), additive. + +```rust +pub struct Key(IoTargetKey, PhantomData A>); // the same number; the consumer's declaration +trait TargetKey: Sealed { fn untyped(&self) -> IoTargetKey; fn declared(&self) -> Option; } +trait WriteKey: TargetKey {} // IoTargetKey, and Key where A: CanWrite +trait ReadKey: TargetKey {} // IoTargetKey, and Key where A: CanRead +fn add_target(&mut self, key: K, target) // the registration check +fn write_with(&mut self, file: K, ..) // and write, read, read_with, + // and the four registered ones +``` + +`IoTargetKey` implements both traits, so a call with an untyped key compiles as it does now and is judged +at runtime by layer 1. A `Key` does not implement `WriteKey`, so a write through it does not +compile. Registration checks the key's declared access against the target's and refuses a mismatch; +that check is at runtime because the target is erased, and it is the only runtime step a typed key adds. +Entries, errors and events keep the untyped key. + +**What building it settled** ([DI-13.10](COMPLETED-CHECKLIST.md#di-1310)). The four submit methods, and the four +registered-buffer ones beside them, are generic over `WriteKey` and `ReadKey` without harm to a caller +that holds the ring through `DurableRing`: the trait was already generic-only ([DI-D-24](#di-d-24)), and +every call that passed an `IoTargetKey` compiled unchanged. The two traits are supertraits of a sealed +`TargetKey`, so no outside type can stand for a key that was never checked against what it claims. +Registration is `add_target` and construction. `AddTargetError` gained a `cause` (`KeyInUse`, or +`AccessNotCovered { declared, actual }`), and construction's `TargetSetup` gained `declared` and a `new` +that reads it off the key, with `SetupRefusal::AccessNotCovered`; a construction that fails hands each file +back with what its key declared. A key that declares less than its target states is accepted, as a narrower +view, and the ring still judges an untyped push by the target. + +**What is the engineer's and what is the assistant's.** + +| Choice | Who | +|---|---| +| The target states its access to the core, as it states its warranty and geometry | engineer 2026-10-16 | +| A typed key as well, built with the first layer and scheduled beside it | engineer 2026-10-16 | +| No associated type on `IoTarget`; the access is stated, not carried as a type parameter | assistant; the reason is above | +| `access()` is required, with no default | assistant | +| The markers and traits move to `durable-ioring`, the Windows rights staying in the Windows crate | assistant | +| The names `Access`, `AccessDenied` and `Key` | assistant | +| The four submit methods become generic over `WriteKey` and `ReadKey`; untyped keys keep working | assistant | +| A commit to a read-only target needs no rule: a commit is issued only to a target with writes, and a read-only one has none | assistant | +| `Key` does not exist | assistant | +| `WriteKey` and `ReadKey` are marker traits over a sealed `TargetKey` that carries the methods, not traits with a method each | assistant | +| The registered-buffer methods take the same keys as the four submit methods | assistant | +| `AddTargetError` gains a `cause`, and `TargetSetup` gains `declared`; both break a struct literal | assistant | +| A key declaring less than its target states is accepted | assistant | + +A row that is the assistant's stands until amended; it is not a list awaiting confirmation. + +**What changes in what exists.** The test `a_write_through_a_read_only_handle_fails` changes: the write is +refused at the push, with its buffer handed back, and no longer fails after the kernel. A required +method on a published trait breaks every other implementor; the implementors in this workspace are +`MemoryTarget`, `Win32FileTarget` and the test targets. + +**Scheduled work.** [DI-13.9](COMPLETED-CHECKLIST.md#di-139) and [DI-13.10](COMPLETED-CHECKLIST.md#di-1310). + +## DI-D-67: what a name reaches is the caller's responsibility + +**What is decided.** The engineer, 2026-10-18: "The responsibility is only on the caller." An open of a +name may reach something whose open does not return. This crate does not check the name for it, does not +refuse it, and does not bound or cancel the open. Where a name leads, and whether its open can block, is +the caller's to know. + +**The hazard.** Measured on one machine for [DI-14.6.1](COMPLETED-CHECKLIST.md#di-1461), in +[DI-D-64](#di-d-64) under "Measured for DI-14.6": `CreateFileW` on `COM3` had not returned after 15, 25 +and 45 seconds, and a later run of the same open returned `ERROR_ACCESS_DENIED` at once. The open itself +blocked, so no handle existed to judge. The machine's three named ports are Remote Desktop redirections +of the client's ports (`QueryDosDevice` gives each a `\Device\RdpDrPort\` target), so what the open waits +on is the client's port ([DI-D-64](#di-d-64), "Measured for DI-14.6"). + +**Why no check of the name can be complete.** A name can be redirected to a device by three routes, and +only part of one is under an open's control: + +- *A file-system reparse point.* `FILE_FLAG_OPEN_REPARSE_POINT` stops reparse at the final component + only; a junction or symbolic link in an intermediate directory is still followed. No documented Win32 + flag was found that refuses reparse along the whole path; the lower layer's is out of reach under the + [DOCUMENTED APIS ONLY rule](../../.github/copilot-instructions.md). +- *An object-manager link*: a drive letter, `subst`, `DefineDosDevice`, the `\??\` namespace. These are + resolved before a file system sees the name, so no reparse flag applies. `DefineDosDevice` in the + caller's own session needs no privilege. +- *A device named directly*: `\\.\COM3`, or `COM3`. The caller wrote it. + +So restricting the hazard to opens that follow reparse points does not separate the names that carry +it from those that do not. The engineer, 2026-10-18, on that: "The problem is that you don't know which +names may carry the hazard." + +**Why a check of the handle does not answer it.** A check of the handle after the open can answer a documented +question about it, as [DI-14.5](COMPLETED-CHECKLIST.md#di-145) does for a remote file. It cannot say in general what a +name reached, which no documented call answers ([DI-D-73](#di-d-73)), and it cannot reach an open that has not +returned. + +**Why no bounded open.** Read 2026-10-18: `CancelSynchronousIo` (`ms.date` 12/05/2018) and "Canceling +Pending I/O Operations" (05/31/2018). + +- *Documented as a use, never as a guarantee.* The second page's motivating example is an open "blocked + because the call is to a very slow device", and its sample cancels a sequence that begins with + `CreateFileA`. It also says "There is no guarantee that underlying drivers correctly support + cancellation", and the function's page says that "other operations can continue toward completion + before they are actually canceled", and that a cancelled operation may complete normally. +- *It targets a thread, not a call.* The page warns that "the wrong call in a series of calls could get + canceled", and of the race on a shared thread. The engineer, 2026-10-18: "it could end up cancelling + something other than the target API. There is no synchronization with the thread unless you build some + gating with every API that you think you may need to cancel." +- *Measured on a redirected serial port, 2026-10-18:* an open that blocks about 5.2 seconds and then fails with 31 + was not shortened by `CancelSynchronousIo` from a second thread, which returned `TRUE` and itself returned only + when the open did ([DI-D-64](#di-d-64), "Measured for DI-14.6"). One driver, and not a guarantee either way. +- *The gating such an open would need:* a thread of its own making that one call and no other, a cancel + repeated until the thread reports done (a cancel issued before the call enters the kernel finds nothing), + and a thread lost for good when the driver does not cancel. +- *The open is the case with no handle.* After the open, `CancelIoEx` names a handle and an `OVERLAPPED`. + The open has neither, which leaves only the thread. `IoRing` has no open operation, so the ring does not + change this. + +**Which opens take no name.** Recorded as fact, not as advice to a caller. An open by file identity +([DI-15.2](CHECKLIST.md)) is relative to a volume handle and has no name to redirect, and an identity can +be read from enumerating a directory without opening each entry. A path beginning `\\?\Volume{GUID}\` +does not pass through drive letters or `subst`; the `Volume{GUID}` link is the mount manager's, and +intermediate reparse points are still followed. + +**What this does not decide.** Whether an open that has returned a handle that is not a disk file is +refused, or classified and returned as such, and what an `OpenedFile` on such a handle claims, stay +[DI-14.6.2](CHECKLIST.md)'s: those concern what a durability claim on such a handle would mean, not the +blocking open. **Settled by [DI-14.6.2](COMPLETED-CHECKLIST.md#di-1462)** ([DI-D-64](#di-d-64), "What DI-14.6.2 +settled"), which also names the unbuffered open of `NUL`'s refusal as this crate's own. +A serial-port measurement was queued as `DI-14.6.3` and is not made: no decision rests on it, and what was +observed of it is in [DI-D-64](#di-d-64). + +**Scheduled work.** [DI-14.6.4](CHECKLIST.md): the open's documentation states this responsibility, since +a caller cannot carry one it is not told of. + +## DI-D-68: a handle opened elsewhere is not accepted in this version, and a device's exclusivity can be attested + +**What is decided.** The engineer, 2026-10-18, closing `DI-10.3` ("do the attestation approach", on the assistant's +recommendation, which the engineer had asked for): + +1. **Foreign handles are not accepted by a file target in this version.** This is the state + [DI-D-60](#di-d-60) left, now ruled and not merely parked. The blocker is not the lack of a consumer: an open + handle's no-buffering and write-through modes are reported only by an undocumented call, which this repository + does not make ([DOCUMENTED APIS ONLY](../../.github/copilot-instructions.md)), so a foreign handle's class could + only be a consumer's declaration, and no ruling has been made on the `unsafe` form of one. What a + handle from elsewhere would need is [DI-10.6](COMPLETED-CHECKLIST.md#di-106). + + **Ruled 2026-10-19, closing `DI-10.6`:** the engineer, "I say it's not accepted at this time." A consumer's + declaration of a foreign handle's mode is not accepted in any form, safe or `unsafe`, capped or not, so the + residue below stays out. The assistant had laid out a safe declaration capped at `BestEffort`, under which + every misdeclaration it could find fails loudly; that is a record of the option, not a plan. Nothing is + scheduled: revisiting it is a new item, raised by the engineer. +2. **A consumer who knows a device is theirs alone can attest it, and an attested target is `Complete`.** It is + the only way a Win32 target is `Complete`: [DI-D-60](#di-d-60), [DI-D-61](#di-d-61) and [DI-D-62](#di-d-62) + stand that a handle cannot show it, and are amended only in that the claim can be made by someone who can. + [DI-10.5](CHECKLIST.md) builds it. + +**Why "no" costs little: most of what a foreign handle is wanted for has a route that needs no attestation.** +`windows-namespace-request-sys`'s `ReOpenFile` entry takes any handle and the caller chooses its access, sharing and +flags, so the handle it returns has a mode this crate chose and recorded, which is [DI-D-60](#di-d-60)'s +requirement. Its tests include adding no-buffering to a handle opened without it. +[DI-15.4](CHECKLIST.md) reopens an owned `OpenedFile` and is amended to take any handle. + +**What `ReOpenFile` does not reach.** Measured 2026-10-18 on Windows 11 build 26300, unelevated, by a throwaway +script over `CreateFileW` and `ReOpenFile`, not kept. The page is the authority and these are anecdote. + +| The foreign handle | A reopen of it | +|---|---| +| Opened with sharing of none | Fails, error 32, for read, for read with no-buffering, and for write | +| Opened with read sharing | Reopens for read, with and without no-buffering; fails for write, error 32 | +| Opened with write sharing | Failed for all three with error 161, which was not chased | +| Opened with all sharing | Reopens for read, for read with no-buffering and for write | +| To a file whose ACL then denied the caller read, held from before | Fails for read, for read with no-buffering and for write, error 5 | + +The last row is the broker case: access is checked against the caller afresh, so a handle granted for a file the +caller could not open itself cannot be converted. Restrictive sharing is the same, and the page says the object +must have been created by `CreateFile`, which leaves out pipes and devices; a volume's and a disk's handles, which +`CreateFile` did create, were observed not to reopen either ([DI-D-71](#di-d-71)). That residue, and nothing else, is +what [DI-10.6](COMPLETED-CHECKLIST.md#di-106) decided stays out. + +**What the attestation is.** It claims one thing, that nothing else writes the device, and that the flush domains the +target reports are all of them; it does not change which domains are reported ([DI-D-61](#di-d-61): a declaration adds +and never removes). It is a constructor for each kind of target, taking only a handle this crate opened **unbuffered**: +`Win32FileTarget::new_attested_exclusive`, `from_volume_attested_exclusive` and `from_disk_attested_exclusive`. A cached handle +cannot be attested, by its type: `BestEffort` is about the cache reporting success for data already lost, which what +else writes the device does not change, so there is nothing a consumer could truthfully attest. + +**Why not `unsafe`: the assistant's position, which stands until the engineer amends it.** `DI-10.3` as first written +argued for `unsafe` because a wrong attestation is silent and a durability layer's worst failure is silent. The class +is information only: [DI-D-50](#di-d-50) P3, "dioring behaves identically for every class and does not label a +`Durable` with it", and the core's only use of it is to describe the target. A wrong attestation therefore +mislabels a report and changes nothing the layer does. In this workspace `unsafe` marks a memory-safety contract +(`assume_overlapped`, `assume_waitable`), and the attestation is not one. The name carries the claim at every call +site instead, and the documentation says what is claimed and that the layer behaves the same for every class. Changing +the constructors to `unsafe fn` is a breaking change to what [DI-10.5](CHECKLIST.md) adds, and is not one before it +is released. + +**Scheduled work.** [DI-10.5](CHECKLIST.md) (the attestation), [DI-10.6](COMPLETED-CHECKLIST.md#di-106) (the residue, +decided 2026-10-19: not accepted), and [DI-15.4](CHECKLIST.md) amended to reopen from any handle. + +## DI-D-69: a file's identity is the volume serial and the 128-bit identifier, read through the handle shared + +**What is decided.** The assistant, implementing [DI-15.1](COMPLETED-CHECKLIST.md#di-151): + +1. **`FileId` is this crate's type**, in `win-durable-ioring`'s `windows_backend`: a `u64` volume serial number and + a 16-byte identifier, with `Eq` and `Hash` over both. It is converted field by field from the request crate's + `FileIdInfo` and does not expose it, so what an identity is here is stated here and does not move when that crate + does ([Design Autonomy](../../.github/copilot-instructions.md)). The identifier is carried as its bytes and is not + read as an integer, which would impose a byte order the call does not state. +2. **The form is the 128-bit one, because it is what `GetFileInformationByHandleEx`'s `FileIdInfo` class reports.** + The `FILE_ID_INFO` page: "The file identifier and the volume serial number uniquely identify a file on a single + computer." As an identifier for `OpenFileById` it is `ExtendedFileIdType`, which the `FILE_ID_DESCRIPTOR` page + ties to ReFS and which an NTFS file's bytes were observed to open as; that the open succeeds is + [DI-15.2](COMPLETED-CHECKLIST.md#di-152)'s to test, not this decision's to assume (it does, [DI-D-70](#di-d-70)). The 64-bit `FileIdType` and `ObjectIdType` are + the other two kinds [DI-D-64](#di-d-64)'s `lpFileId` row exposes; they are not forms of `FileId`. +3. **It is read through the handle shared.** `FileId::of(&SharedHandle)` hands the request crate's `QueryFileIdInfo` + a clone of the `SharedHandle`, and `OpenedFile::file_id` calls it with the file's own, so no handle is duplicated + ([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32) in the request crate's notes). It runs on the + calling thread; a caller who wants it elsewhere performs the request crate's entry where it chooses. +4. **Any handle is asked, and any data access reads it.** `of` takes any `SharedHandle`, as the volume hint of + [DI-15.2](CHECKLIST.md) takes any handle, and `file_id` is on `OpenedFile` for every `A`, `NoData` included: + the call needs no data right. A handle that is not on a file system is refused with the kernel's code, unaltered; + the page lists no codes, so none is promised. + +**Observed, on Windows 11 build 26300 on NTFS, by the tests.** The identity is the same through two opens, a cached +and an unbuffered open, an open with no data access, a clone, a `SharedHandle` kept after its `OpenedFile` was +dropped, a rename made while the file was open and a change of its contents, and a hard link; two files on one +volume have one serial and different identifiers; a directory has an identity of its own; an event is refused with +an OS code. These are anecdote, and the page is the authority. + +**Scheduled work.** [DI-15.2](CHECKLIST.md), amended in the same change to open by all three kinds of identifier, +`FileId` among them. + +## DI-D-70: an open by identifier, and the check a FileId needs + +**What is decided.** The assistant, implementing [DI-15.2](COMPLETED-CHECKLIST.md#di-152), within the engineer's +rulings of 2026-10-15 and 2026-10-18 recorded in [DI-D-64](#di-d-64)'s `OpenFileById`, `hVolumeHint` and `lpFileId` +rows: + +1. **`FileIdentifier` names the three kinds**: `Extended(FileId)` for `ExtendedFileIdType`, `FileId64(u64)` for + `FileIdType`, and `ObjectId([u8; 16])` for `ObjectIdType`. A `FileId` converts into one. The object ID is carried as + the bytes of the `GUID` as Windows lays one out, which is how `FSCTL_CREATE_OR_GET_OBJECT_ID` reports it and + `fsutil objectid` prints it, and is turned into the request crate's `u128` at the boundary: that value is the + `GUID` read as `GUID::from_u128` reads one, so the three little-endian fields are turned around. A test opens a + file by the object ID `fsutil` created for it, which is what shows the reading is right. +2. **`ByIdOptions` carries what the page lists and [DI-D-64](#di-d-64) exposes**: the data access `A`, the other + rights, the share mode, and the reparse-point, no-recall, overlapped, random-access and sequential-scan flags. No + disposition, attributes or security, which the page does not take; no delete-on-close or POSIX semantics, which it + does not list; no backup semantics or write-through, which are not in v1 for any open. The buffering flag is `B`'s. +3. **Built and performed in two steps**, as an open by path is (`DI-13.7.4`): `OpenedFile::request_by_id` builds a + `ByIdRequest`, which is `Send` and can be performed more than once, and `open_by_id` runs the two together. The + hint is any `impl Into`, held shared. +4. **An open by `Extended` reads the opened file's identity and refuses it if it is not the one asked for**, with + `IdentityMismatch` as an `InvalidInput` error, and the handle is closed. The call takes the volume from the hint + and never sees the `FileId`'s serial, so without the check a hint on another volume opens whatever has that + identifier there and returns it as the file asked for: a wrong answer, and a silent one. The cost is one + `FileIdInfo` query on each open by `FileId`. `FileId64` and `ObjectId` name no volume, so there is nothing to + compare and no check. +5. **The checks on the opened handle are the path open's, made by the same function** (`OpenedFile::admit`), so the + two cannot drift: for `Unbuffered`, the remote check and `UnknownLocation`. No test reaches them by identifier, + since the page lists SMB as not supported and the call opens only a file on a file system; the sabotage manifest + declares that survivor. +6. **`SharingViolation` names the file by `OpenedBy`**, a path or an identifier (and a reopen, since + [DI-D-71](#di-d-71)), in place of the `path` field it had. + It was not in a release, so its shape changed rather than a second error being added for the same condition. + +**Observed, on Windows 11 build 26300 on NTFS, unelevated, by the tests.** These are anecdote, and the pages are the +authority. + +- The file's own handle, its directory, and the volume opened asking for no access are each a hint that works. +- The 64-bit ID `GetFileInformationByHandle` reports, the object ID `fsutil objectid create` made, and an identity + from a directory listing (`FileIdExtdDirectoryInfo`, read through `windows-file-enumeration-sys`) each open the file. +- An identifier that names nothing fails with error 87; an event as the hint, with error 6. +- The handle is not inheritable; the page does not say. +- **A directory opens by identifier without `FILE_FLAG_BACKUP_SEMANTICS`**, for every data access and in either + buffering mode, though the page says the flag is required to obtain a handle to a directory; an open of the same + directory by path without it fails with error 5. So an `OpenedFile`, and a target made from one, can be a + directory, which nothing before could make. Nothing refuses it, which the engineer ruled is right + ([DI-D-72](#di-d-72)). +- **A file deleted by name while held open still opens by identifier.** The page says that a file "pending deletion as + a result of a previous call to `DeleteFile`" fails with `ERROR_ACCESS_DENIED`; what state `std::fs::remove_file` + left the file in was not examined. Nothing here depends on it. + +**Scheduled work.** [DI-15.5](COMPLETED-CHECKLIST.md#di-155), whether an open by identifier that reaches a directory +is refused, which [DI-D-72](#di-d-72) answers. + +## DI-D-71: a reopen, from any handle, into a file of the type asked for + +**What is decided.** The assistant, implementing [DI-15.4](COMPLETED-CHECKLIST.md#di-154), within the engineer's +rulings recorded in [DI-D-64](#di-d-64)'s `ReOpenFile` and `hOriginalFile` rows and in [DI-D-68](#di-d-68): + +1. **`OpenedFile::::reopen(original, options)`**, with `request_reopen` to build it and perform it elsewhere, + as the other two opens are. The original is any `impl Into`: an owned file passes + `file.handle().clone()`, and a handle opened elsewhere passes itself. It is held shared, not duplicated. The + result is a new `OpenedFile` of the type asked for; nothing of the original's type carries over, including a + sector size given with `with_sector_size`, since a handle from elsewhere has none to give. +2. **`ReopenOptions` carries what the page lists and [DI-D-64](#di-d-64) exposes for a reopen**: the data access, + the other rights, the share mode, and the reparse-point, delete-on-close, no-recall, POSIX, overlapped, + random-access and sequential-scan flags. No disposition or attributes, which the page does not take; no backup + semantics or write-through, not in v1; not the named-pipe security flags, since what is reopened here is a file. + It is a third options type beside `FileOptions` and `ByIdOptions` because each call takes a different set, and + a field a call would ignore is a setting that silently does nothing. +3. **The checks are the path open's, by the same function** (`OpenedFile::admit`), and a reopen reaches them: an + unbuffered reopen of `NUL` is refused as `UnknownLocation`, and of a file on a share as `RemoteDevice`. +4. **A sharing violation is a `SharingViolation` naming the file by `OpenedBy::Reopen`.** The original handle is the + caller's own and is not carried. + +**Observed, on Windows 11 build 26300 on NTFS, unelevated except where said, by the tests.** These are anecdote, and +the page is the authority. + +- Every pair of an original opened by path and a reopen of it, over the three accesses a share mode judges and the + eight share modes on each side, was accepted or refused as the page's rule says, judged by the share-mode tests' + own statement of that rule. Every refusal was `ERROR_SHARING_VIOLATION`. The error 161 that + [DI-D-68](#di-d-68)'s throwaway script saw for an original shared for writing did not appear. +- A reopen changes the buffering mode in either direction, narrows read-write to read-only beside the writer, and + widens read-only to read-write where the file's security allows, which answers [DI-D-65](#di-d-65)'s open + question. A handle opened by `std`, one opened with no data + access, and one from `OpenFileById` each reopen. Delete-on-close given to a reopen deletes the file when its last + handle closes. The handle is not inheritable. +- A cached reopen of `NUL` succeeds. A volume's handle and a disk's were refused (codes 87 and 31) whatever either + side asked for, though the page's condition, created by `CreateFile`, holds for both. Elevated, with a data access, + was not measured. So an `OpenedVolume` or `OpenedDisk` reopened in place was not built: on what was observed, the + call does not do it. +- On an account that reaches `\\localhost\C$`, a cached reopen of a file on the share succeeds and an unbuffered one + is refused as `RemoteDevice`; that test is ignored by default, as the open's own is. + +**Scheduled work.** None. + +## DI-D-72: a directory reached by an open by identifier is allowed + +**What is decided.** The engineer, 2026-10-19, closing [DI-15.5](COMPLETED-CHECKLIST.md#di-155) after the +measurements below: "I think these results are 'harmless'. We can document our findings but there is no harm in +allowing it to happen." Nothing checks whether an `OpenedFile` reached by identifier is a directory, and none is +refused for being one; the behaviour is documented on `OpenedFile::open_by_id` and here. The assistant, asked +whether it disagreed, agreed on the ground of [CONTRACT.md](CONTRACT.md)'s first guarantee: a write that completed +with a fault is covered by no durability report. Every write to a directory faults, so a directory target is a +target whose writes all fail, which the core already handles for a file whose writes are refused, and a commit +that succeeds on it vouches for nothing. + +**Measured on Windows 11 build 26300 on NTFS, unelevated.** Directly, by a throwaway script over documented calls +on a directory with one child: opened by path with `FILE_FLAG_BACKUP_SEMANTICS`, or by identifier without it, the +two behave the same. `ReadFile` fails with error 1 (`ERROR_INVALID_FUNCTION`); `WriteFile` fails with error 1, or +87 for an unaligned write on an unbuffered handle, or 5 without the write right; `FlushFileBuffers` succeeds with +the write right and fails with error 5 without it; `GetFileSizeEx` reports 0. Through the ring, by +[a test](../win-durable-ioring/src/windows_backend/tests/directory.rs): a directory registers at construction and +attaches later; every write and read completes with a fault carrying `HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)`; +every commit completes successfully; and the directory's contents are unchanged. Backup semantics changes nothing +about I/O, only whether the handle can be had by path. These are anecdote; no page describes I/O on a directory +handle. + +**What a consumer should know, and the documentation says.** + +- On a directory, `FILE_READ_DATA` and `FILE_WRITE_DATA` are the same bits as `FILE_LIST_DIRECTORY` and + `FILE_ADD_FILE`, so the handle an `OpenedFile<_, ReadWrite>` of a directory lends carries those rights. +- What a flush of a directory makes durable is not stated by the `FlushFileBuffers` page, which speaks of files; a + commit that succeeds on a directory target is not a statement that the directory's entries are on stable media. + +**Scheduled work.** None. + +## DI-D-73: there is no query for what kind of handle a handle is + +**What is decided.** The engineer, 2026-10-20, on the review of PR #155 and what [DI-17.2](COMPLETED-CHECKLIST.md#di-172) +asked: "this query is a bug farm, we will never get it right because the question as stated is not answerable within +the Windows API set as far as I know. Remove the API and if that is the only way to obtain that type, the type." +`HandleKindQuery` and `HandleKind` are removed from `win-durable-ioring`, along with the types and the dependency that +existed only for them. It had not been released, so nothing is withdrawn from a published version. Withdraws +[DI-14.12](COMPLETED-CHECKLIST.md#di-1412)'s build. + +**Why the question has no answer.** The query turned the success or refusal of two controls into a taxonomy. Neither +the Win32 nor the WDK page for `IOCTL_STORAGE_GET_DEVICE_NUMBER` says which devices answer it, and the page for +`IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS` names only SMB 3.0, which does not support it, and the Cluster Shared Volume +File System, which does ([WV-D-12](../win-volume-sys/DESIGN-NOTES.md#wv-d-12)). What a handle that refuses a control is, +no page says either. Every route that was named ended in some part of the storage stack's own word: the `device_type` +the control reports, which both a volume and a physical disk reported as 7; the disk and volume device interfaces +matched by device number, which is an inference over a number; or the kind recorded at the open, which is not an +answer for a handle this crate did not open, nor for a name whose reach is the question +([DI-D-67](#di-d-67)). The part that is documented, `GetFileType`'s five values, is the request crate's +`QueryFileType`, and each control's own result is `win-volume-sys`'s; a caller who wants either asks it directly, and +what the query added was the taxonomy over them, which is the part nobody can stand behind. + +**What stays.** `QueryFileType` in the request crate, and `storage_device_number` and `volume_disk_extents` in +`win-volume-sys`, each of which reports one documented call's result as the call gave it. `OpenedVolume` and +`OpenedDisk` still record what they were opened as, which is a fact of their construction and not a classification. +`DiskExtent` stays re-exported, as `OpenedVolume::disk_extents` returns it; `StorageDeviceNumber` and `FileType` are no +longer re-exported, because `HandleKind` was the only way this crate gave one. + +**Measured, and kept as measurement.** The 2026-10-18 observations of what a handle answered, recorded under +"Measured for DI-14.6" and above, stand as measurements and are no longer the ground of anything. + +**Scheduled work.** None. The sabotage entries for the module, nine in `win-durable-ioring`'s manifest, are removed with +it. + +## DI-D-74: the in-memory backend's targets and the backend itself fault on a script + +**What is decided.** [DI-D-50](#di-d-50) P13 made the in-memory backend public and deterministic, and the engineer +approved it on 2026-10-09 ("In-memory is a good basis for unit testing and driving"). Its fault script is +[DI-11.1](COMPLETED-CHECKLIST.md#di-111), built as below; the shape is the assistant's and stands as built until +amended. A `FaultScript` is a list of steps, each a `When` and a `Fault`, given to a `MemoryBackend` with +`with_script`. It answers the problem P13 names: a flush failure cannot be produced on a healthy machine, so +a consumer's failure paths, and the core's, had nothing to run against but a target detached underneath them. + +**Where a step applies, and how it counts.** Each fault belongs to one point in the backend: a refusal to a push +the backend would otherwise admit; a failure, a short count, a hold or a late completion to a submission the doorbell +executes; a refused doorbell to a doorbell; a failed pop to a pop. A `When` names the operation (write, read or commit), +the target, and which occurrence: the nth, or the nth and every one after, or every one. **Every step that matches a +point counts it, whether or not an earlier step fired**, so a step's count does not depend on the steps before it; +when several fire for one submission the earliest in the script is applied. The counts belong to the backend and not +to the script, so one script drives many backends, each from the start. Nothing is random and nothing depends on +timing: a run with a script is the same run every time. + +**What each fault does.** + +- `Refuse(cause)`: the push comes back unchanged with that cause, so a queue that is not full can be refused as full. + **A refusal the backend would have made is made, first, and is not counted**, so a script never hides a real + refusal and never has its count spent by one. `check` is not scripted: it answers what a target and an operation + admit, which a transient scripted refusal is not. +- `Fail(fault)` and `FailAfterApplying(fault)`: a write or a commit either does not reach its target, which is a + flush that failed with every byte still undecided, or reaches it and is reported failed, which is the other + direction of the same lie. Both are provided because [DI-D-50](#di-d-50) P2 treats a failed commit as ending every + statement about the writes it covered, whether or not they reached the media, and a consumer's path is + not the same for the two. +- `Short(count)`: a write or a read transfers at most that many bytes and completes successfully with the count. + **A short write breaks the obligation that a write completes in full or with a fault** (P2), which the conformance + oracle refuses; it is provided as a source of short counts, which a backend's short-write policy exists to handle + and which the in-memory backend has no policy for. The policy is not built here. A short read is an ordinary one. +- `Hold` and `Late(n)`: the operation executes at the doorbell as it would, and its completion is withheld, until + the test calls `release` or `release_all`, or until `n` doorbells later. Holding and releasing in an order the test + chooses is how completions arrive late and out of order. **A held completion counts against the completion queue**, + so a completion always has room when it is released, as it does for one that is ready. +- `RefuseDoorbell(cause)` and `FailPop(fault)`: the backend itself fails. A refused doorbell issues nothing and + leaves what was queued; a failed pop takes nothing and leaves what was ready. Neither names an operation or a + target, which a doorbell and a pop do not have. + +**A script a run could only ignore is refused where it is written.** A step that names `Short` for commits, a +refused doorbell or a failed pop that names an operation or a target, and an occurrence counted from zero, panic in +`FaultScript::on` and `When::nth` and `When::from`, since a run would otherwise do nothing and say nothing. + +**What the core is tested with.** The core's tests had made a commit fail by detaching the target from the backend. They +now also drive it with a script ([scripted.rs](src/io_core/tests/scripted.rs)): a commit that +fails, a write whose completion is held, completions out of order, a refusal with its cause. Verified by the tests in +`memory/script/tests.rs`, by those, and by sabotage entries in [sabotage.json](sabotage.json), with a control. Older +entries in the manifest were re-pointed at the lines the change moved, with the faults they inject unchanged. + +**Not scripted, and the reason.** `notification()` takes `&self`, and a script's counts need `&mut`, so scripting it +means interior mutability in every backend for one rare fault; `attach` and `detach` have no failure type to +script. Both are open to the engineer's ruling and nothing is scheduled by this note. + +**Scheduled work.** None. [DI-D-75](#di-d-75) is the merge-or-delete decision this made possible. + +## DI-D-75: both paths for fault injection stay + +**What is decided.** The engineer, 2026-10-20, asked whether the Windows backend's fault seam should be kept now that +the in-memory backend has a fault script ([DI-D-74](#di-d-74)): "I think it's a good idea to keep both paths for fault +injection." So `fail_next_flush` on `Dioring` and `fail_next_commit` on `WindowsBackend`, behind `win-durable-ioring`'s +`fault-injection` feature, stay beside the script, and neither is merged into the other or deleted. This closes +[DI-11.2](COMPLETED-CHECKLIST.md#di-112), the merge-or-delete decision the repository's duplicate-then-decide rule +required of them. + +**What the two are, as the assistant put them to the engineer.** They are two layers and not two copies. The script +drives the portable core through `MemoryBackend`, so everything about how the core answers a failed flush is produced +without a device. The Windows seam runs a failing flush through the Windows driver and the real ring on a real file: the +commit still reaches the kernel and only the answer changes, so a Win32 code goes through `error_code_from_win32` and +is read back by `win32_code_of`, which `MemoryBackend` never touches. `Dioring` is the only `DurableRing` this +repository ships, and the in-memory backend cannot drive it, so the seam is also what lets `win-durable-ioring`'s +README examples fail a flush. The seam is one mechanism: `fail_next_flush` forwards to `fail_next_commit`, which sits on +`windows-ioring-sys`' own seam. Neither produces a failure of the kernel's own, which is `DI-4.1`'s open problem. + +**Then asked, and queued.** Whether the tests of `win-durable-ioring` that only check how the core answers a failure, and +not what the Windows driver adds, should move to the portable crate and use a script. The assistant proposed it and the +engineer, 2026-10-20, asked for it to be queued. + +**Scheduled work.** Done: [DI-18.1](COMPLETED-CHECKLIST.md#di-181) to [DI-18.3](COMPLETED-CHECKLIST.md#di-183): classify the tests that arm the seam, move the ones that check +the core to the script, and delete them from `win-durable-ioring`. The seam and the README's examples stay. + +## DI-D-76: the delay events' sinks: one provider in its own crate, a debugging crate beside it, and no manifest needed to record + +**Amended 2026-10-20: the provider and its crate are named `durable-ioring-events`, not `win-dioring` (item 6).** + +**What is decided.** The assistant put four open questions of [DI-12.1](CHECKLIST.md) to the engineer on 2026-10-20 (crate +names, the provider's identity, installing the manifest, identity per event), and the engineer answered them in order: + +1. On the crate for `OutputDebugStringW`, proposed as `win-debug-sys`: "how about win-debugging-sys?" The crate is + `win-debugging-sys`. +2. On the provider's identity: "win-dioring, I'd put it in its own crate." The provider was named `win-dioring` (see 6), and it + is a crate of its own and not part of `win-durable-ioring`. +3. On installing the manifest: "correct you don't actually have to install the manifest to record events. record as + stated." So the provider emits its events whether or not its manifest is installed; the manifest is what decodes an + event's names and fields for a reader, and installing it is a step for whoever reads the trace, not for the + application that writes it. This corrects [DI-D-26](#di-d-26), which carried it as an administrative step an + application must take or document. +4. On identity per event, proposed as the instance plus what the event is about (a lock wait names the lock; a flush + names the lineage, the epoch and the domain): "ok". +5. On the item itself, too coarse for one commit and gated in parts: "do the restructuring". [DI-12.1](CHECKLIST.md) + is now `DI-12.1.1` to `DI-12.1.6`. +6. On the provider's name, after the scope of `win-dioring` was asked about and it was found to read as broader than + the dioring family: "Change the name of the event source to durable-ioring-events, replacing dashes with underscores + as appropriate." The provider is named `durable-ioring-events`. The assistant took "event source" to be the provider + and its crate together, as 2 had them, so the crate is `durable-ioring-events` and its library is + `durable_ioring_events`. The provider's name keeps its dashes: it is a label in the manifest, and the underscores + are for Rust identifiers. + +**The assistant's reading of 2, a working position and not a decision.** `durable-ioring-events` is both the provider's name and +the name of the new crate that holds the provider, its manifest and `EtwDiagnostics`, which [DI-D-51](#di-d-51) had in +`win-durable-ioring`. That crate depends on `durable-ioring` for the sink trait and on a wrapper of the event-tracing +functions, and `win-durable-ioring` depends on it for its default sink. The wrapper's crate name is `win-etw-sys`: the +assistant proposed it and the engineer, 2026-10-20, answered "win-etw-sys is good". The provider's GUID is generated once when the crate is built and is +recorded in that crate's notes, because changing it later breaks every session that enabled it. + +**What the placement rule says about the wrapper, so the step does not assume it.** The Learn snapshot +([WIN32-LEARN-MAP-DATA.txt](../../WIN32-LEARN-MAP-DATA.txt)) files `OutputDebugString` under *Diagnostics > Debugging and +Error Handling > Basic Debugging > Debugging Reference > Debugging Functions*. It files `evntprov.h`'s macros under +*Windows Events > Event Tracing > Event Tracing Reference > Event Tracing Macros*, and does not list the provider functions +(`EventRegister`, `EventWrite`, `EventWriteTransfer` and the rest) at all, so the step reads their pages and records +where they are documented before it names a section. + +**Scheduled work.** [DI-12.1.1](CHECKLIST.md) to `DI-12.1.6`. + +## DI-D-77: how the delay events are built and timed + +**Superseded in part by [DI-D-91](#di-d-91):** `StampClock::elapsed`, `SealWaited`, the lock watch's timer and the report after the guard is released are gone, and so is the driver's interrupt-time timing of waits; the epoch rendered through `Debug`, the naming of a flush part, the lock being taken by `try_lock` first, and the watched lock being the engine's slot stand ([DI-D-80](#di-d-80)). + +**What is decided.** The choices [DI-12.1.1](COMPLETED-CHECKLIST.md#di-1211) left to its implementation, made by the +assistant under [DI-D-26](#di-d-26) and [DI-D-76](#di-d-76). None is an answer the engineer was asked for, and each is +open to a ruling. + +- **The epoch is rendered through its `Debug` form.** A seal's events carry the epoch it is through as + `fmt::Arguments`, built from `EpochId`'s existing `Debug` bound, so the trait gained no bound and an event allocates + nothing. A sink that wants a structured epoch has only text; that is the cost of the bound being left alone. +- **What a flush part is named by.** A file by its key. A provider's domain by the name the consumer gave it, shown as + text with invalid bytes replaced, and held by the event as the bytes. +- **Flush and seal durations are measured by the instance's own `StampClock`**, which gained a required method, + `elapsed(earlier, later)`, because a reading cannot be subtracted without knowing its unit. This breaks every + implementor of the trait, which is why the change is a major one for `durable-ioring`. A reading is taken only for a + kind the sink `wants`, and what was stamped is dropped when its answer arrives, when its lineage retires, and with + the instance. +- **One secondary event for a seal's wait.** `SealWaited` is reported once, when a seal that was waiting for the writes + at or below it is issued. Writes in flight and writes held for a gate block a seal alike, and the event does not + tell them apart. A seal that did not wait reports nothing. +- **Lock waits are timed outside the engine.** The sink and the clock live inside the engine, which sits behind the lock + being timed, so the core cannot time its own wait. A `LockWatch` holds its own handles to the sink and to a timer, + and a lock taken through it is `try_lock`ed first: an immediate acquisition reads no clock and builds no event, and + only a wait the sink wants is timed. The report is made after the guard is released. A poisoned lock is taken as the + panic left it, as `lock` does ([DI-D-46](#di-d-46)). +- **The Windows driver times its lock waits in interrupt time whatever clock the instance stamps with** + ([DI-D-37](#di-d-37)), because the watch is built from the driver's own clock and not from the instance's, which + the core owns. +- **The lock watched is the engine's slot.** Every path that reaches the engine takes it: a consumer's call, a pump or + a pool drain, and the hub a lineage handle or a provider's answer goes through. The Windows hub's other mutex counts + unanswered provider completions and is held for an increment, and it is not watched. The engineer, 2026-10-20: "no, + since it's only held for an increment I don't think there is a need, but add comments that if there were more content + added with the mutex held, that might warrant it." The comment is on the field. + +**Scheduled work.** None beyond [DI-12.1.2](CHECKLIST.md) onward, which carry these events to the sinks. + +## DI-D-78: the debugger sink writes the standard-error sink's line, escapes a NUL, and wants everything + +The choices [DI-12.1.3](COMPLETED-CHECKLIST.md#di-1213) left to its implementation, made by the assistant under +[DI-D-51](#di-d-51) and open to a ruling. + +- **The line is the standard-error sink's.** `DebugOutputDiagnostics` writes `dioring:`, a space and the event's text, + as `StderrDiagnostics` does, and both take the prefix from one constant, `LINE_PREFIX`, so a reader of either sees the + same line. The constant is exported from the portable crate, and so is `FlushPart`, which `Diagnostic::FlushAnswered` + carries and which [DI-12.1.1](COMPLETED-CHECKLIST.md#di-1211) had left unnameable by a sink. +- **A NUL in the text is written as `\0`.** The call's string ends at its first NUL, and `win-debugging-sys` refuses a string + that has one ([WD-D-2](../win-debugging-sys/DESIGN-NOTES.md#wd-d-2)). An event's text can carry one: a flush domain's + name is bytes the consumer chose, and an inconsistency's detail is any text. A sink has no way to report a failure + of its own, so it escapes the NUL and sends the line; nothing else in the text is changed, so a `\0` already there is + indistinguishable from an escaped NUL, which is the cost of the choice. +- **It wants every kind, always.** A line is built for each event whether or not a debugger is attached, because the call + gives no way to tell and the page says it "does nothing" when there is none. Asking `IsDebuggerPresent` first would + decide for the consumer that a system debugger, or one attached later, is not wanted. A consumer who does not want the cost + chooses another sink, or leaves this one out of a fan-out. +- **The emission is observed by a debugger, once.** The sink's test starts the test binary as a debugged process through + `win-debugging-sys`' `test-util` feature ([WD-D-3](../win-debugging-sys/DESIGN-NOTES.md#wd-d-3)), which is the one copy of + that harness: this crate forbids unsafe code ([DI-D-4](#di-d-4)), so it could not carry one. + +**Scheduled work.** None beyond [DI-12.1.4](CHECKLIST.md) onward. + +## DI-D-79: the driver's default sink is `EtwDiagnostics`, built for each instance, and an instance that cannot register carries on + +The choices [DI-12.1.6](COMPLETED-CHECKLIST.md#di-1216) left to its implementation, made by the assistant under +[DI-D-51](#di-d-51) T4 and open to a ruling. + +- **The default is `EtwDiagnostics::new()`, built when an instance is built with `Setup::diagnostics` as `None`.** Each such + instance holds a sink, and the sinks of a process share one registration of the provider, made by the first and given back + when the last is dropped ([DE-D-4](../durable-ioring-events/DESIGN-NOTES.md#de-d-4)). So building instances does not use up + a process's registrations, and an instance that ends gives its part back. +- **An instance whose provider could not be registered still builds, and writes nothing.** `EtwDiagnostics::new` carries on + with no registration, as the page for `EventRegister` says production code should, so a diagnostic surface that failed to + start never fails the driver. A consumer who wants to know builds the sink with `try_new` and passes it. +- **`EtwDiagnostics` is re-exported from `win-durable-ioring`**, so a consumer can name the default, or build one to pass, with + no direct dependency on the provider's crate. +- **The cost of the default is a test, per kind of event.** With no session listening the sink wants no kind, so the core + builds no event and reads no clock ([DI-12.1.1](COMPLETED-CHECKLIST.md#di-1211)); that is what makes it a default. +- **What shows it.** A process that builds an instance with no sink registers the provider and gives it back with the + instance, and one with a sink of its own does not (`tests/default_diagnostics.rs`); a sink given replaces the default and + receives the instance's events; and an instance with none writes its flush events to a session that is listening, which needs + privilege, so that test is ignored by default and CI runs it with `--include-ignored`. It was run elevated on 2026-10-20 and + passed five times in a row. + +**Scheduled work.** None. + +## DI-D-80: durations come from paired begin and end events with correlation ids, never from a component's own timing + +**What is decided.** The engineer, 2026-10-21, on what the routing provider ([DI-12.2](CHECKLIST.md)) should report: "I +don't think that the core router should implement any of this timing infrastructure, it should come in via the +diagnostics and tracing. ETW has the ability to pair begin/end events and then the analysis tools will compute times from +those events. We can depend on other diagnostic sinks to do similar if we can simply establish (a) correlation IDs and (b) +reliable begin/end pairings." And on the ids: "our correlation ids are scoped to a process and so may be u64s generated from +an atomic u64 using relaxed increments." + +- **The router measures nothing.** A duration is for the sink's tools to compute from a begin and an end, and not for a + component to compute and report, so the router adds no timing event and keeps no start time. +- **A correlation id is a process-scoped `u64`**, minted from one atomic with relaxed increments. +- **What it takes.** A correlation id on a begin and its end, and a pairing that holds on every path a begin can leave by. + +**What was found, in the assistant's reading of the shipped events.** [DI-12.1](COMPLETED-CHECKLIST.md#di-121)'s events have +no id; a seal's flush is issued once and answered once for each part, so an answer has no begin of its own; and +`win-etw-sys`' write uses `EventWrite`, which takes the current thread's activity id, which two events on different threads do +not share, and a provider's answer may come from any thread. These are why the work is more than the id. + +**Ruled, 2026-10-21, by the engineer.** +- **There are pairs for seals and for the weightier actions such as a flush, and a sink may decline any pair.** The seal's + pair is the one the engineer first named as trivially declined, and the engineer then made the point general: "sinks may + decline any pair, to be clear". "For the portable ring, I assume that flushes are modeled as asynchronous operations by the + provider (since they are with Linux iouring) so the begin/end pairing is important there." +- **An end carries no `elapsed`:** "I say no, and this is something we can choose to enhance in the sinks rather than pushing + it into the router." A sink that wants a duration computes it itself. +- **The pairs are a seal, a flush of a file or a domain, and a lock wait**, in answer to the assistant's three candidates: + "those seem like the required ones". + +- **A seal's pair runs from its being asked for to its becoming durable or failing.** The engineer, 2026-10-21, to the + assistant's two candidates: "asked-for to durable-or-failed". The flush pairs lie inside it, and the wait before the first + flush is the gap before its begin. +- **A lock wait's end is written with the lock held, and a sink's synchronization must not interfere with dioring's.** The + engineer, 2026-10-21: "The lock wait end is written with the lock held. Any synchronization within the diagnostics + platform must not interfere with the synchronization in the durable-ioring." This reverses [DI-D-77](#di-d-77)'s report + after the guard is released, which is how the report avoided lengthening the hold it measured; the cost is that the sink's + time is inside the hold, which `DiagnosticSink`'s existing rule that a sink "returns promptly and never calls back into the + instance" already bounds. The rule that the sink's locks are leaves is the assistant's statement of the second sentence, and + is open to the engineer's correction. + +**Superseded by [DI-D-93](#di-d-93).** **A working position, not a decision.** The engineer, 2026-10-21, on the 128-bit activity id: "true, perhaps we use the +process id as the upper 64 bits and the u64 as the lower?" It is held as a position until [DI-19.4](CHECKLIST.md) builds it, +and a process id reused within one trace is a case the assistant has not settled. + +**Scheduled work.** [DI-M19](COMPLETED-CHECKLIST.md#di-m19----paired-begin-and-end-events-with-correlation-ids), completed. The router +reports no timing, and nothing else: a domain it does not route is panicked on, the panic its only report +([DI-D-83](#di-d-83)). + +## DI-D-81: a provider registered with the router serves exactly the domains its own `domains()` returns + +**What is decided.** The engineer, 2026-10-21, agreeing to the assistant's lean: the routing provider +([DI-12.2](CHECKLIST.md)) is given providers and no domains, and routes to each the domains it declares. A registration +cannot narrow a provider to a subset of what it declares, and cannot name a domain the provider does not. + +**Why.** A flush domain is the consumer's exact bytes ([DI-D-21](#di-d-21)), minted by the layer that maps the consumer's +storage onto domains (root [M39](../../CHECKLIST.md)). Whatever builds a provider must be handed those same bytes, so it +knows what the provider serves when it builds it, and a provider built with its domains says so through `domains()`. A +second list at registration would be a second statement of the same fact, free to disagree with the first. Narrowing a +provider, or renaming domains for a provider whose identifiers differ from the consumer's, can be added later as another +way to register, beside this one; neither has been asked for. The model of how a consumer would use this, from which the +argument came, is in +[the design session](../../design-sessions/DESIGN-SESSION-2026-10-21-router-domains-and-paired-events.md#the-real-world-model). + +**Scheduled work.** [DI-12.2.1](CHECKLIST.md), which builds the router this way. + +## DI-D-82: how the router is built, and what its refusal names + +The choices [DI-12.2.1](COMPLETED-CHECKLIST.md#di-1221) left to its implementation, made by the assistant under +[DI-D-81](#di-d-81) and open to a ruling. + +- **A builder, consumed by `build`.** `RouterBuilder::register` takes a provider by value and reads nothing; `build` reads each + provider's `domains()` once, in registration order, and the router answers its own `domains()` from what it read. So a + provider's declaration is read once, as the trait says of the instance's read of the router's. +- **Registrations are numbered from 0 in the order made**, and the refusal, `RoutedTwice`, names the domain and the two + registrations that claimed it. A provider that declares one domain twice is refused the same way, with both numbers its + own, rather than by a refusal of a second kind: the fault is the same, a domain the router would not know where to send. +- **The first repeat in reading order is the one refused.** The build reads registrations in order and each one's domains in + the order it gave them, and stops at the first domain met a second time, so the providers after it are not asked for + their domains. A consumer with several repeats sees them one build at a time. +- **The router's domains are the registrations in order, each in its own order.** That is the order the core interns them in, + and the order a consumer reading them back sees. +- **`RoutedTwice` is dioring's own refusal**, of the rule [DI-2.12](COMPLETED-CHECKLIST.md#di-212) point 1 sets, and not a + prediction of the core's `DuplicateDomain`, which it makes unreachable for a router: the union it builds has no repeat. + +**Scheduled work.** None: [DI-12.2.2](COMPLETED-CHECKLIST.md#di-1222) implemented `DurabilityProvider` for the router, with +its dispatch ([DI-D-83](#di-d-83)). + +## DI-D-83: how the router dispatches, and how it fails a domain it does not route + +**Amended by the engineer, 2026-10-21: a domain the router does not route is panicked on, and the panic is its only report; the assistant's first choice, failing it with `Unrouted`, is withdrawn.** + +The choices [DI-12.2.2](COMPLETED-CHECKLIST.md#di-1222) left to its implementation, made by the assistant under +[DI-D-81](#di-d-81) and open to a ruling. + +- **`domains()` is the trait's, not an inherent method.** The router's union, built in [DI-D-82](#di-d-82), is answered + through `DurabilityProvider::domains`, so a consumer that reads it names the trait, as it would for any provider. +- **The request is split before any provider is called**, into one list of parts per registration, each in the request's + order; then each registration with a part is called once, in registration order. Nothing is buffered beyond the call: + each part's completion goes on as it came, so its answer reaches the instance directly, from whatever thread gives it. +- **A panic is not caught.** It propagates out of `make_durable`, as the trait says, and the unwinding drops the parts not + yet handed on, whose completions then answer as abandoned; the provider that panicked drops its own the same way. So a + panic fails exactly the domains of the providers it stopped, and the ones already called answer as they would have. +- **A domain no provider serves is panicked on, and the panic is the router's only report.** The engineer, 2026-10-21: "I + agree, these exogenous messages should panic and it's the only report." The instance sends a provider only the domains it + read from it at build, and the router's domains and its map of owners come from the same single read of each provider, + so such a domain needs a driver that builds requests wrongly, a caller outside an instance, or a provider that forwards a + router domains it does not serve: a caller breaking the protocol, which [DI-D-46](#di-d-46) panics on where it is found + and [DI-D-59](#di-d-59) keeps out of ordinary error reporting. The panic names the domain and comes before any provider is + called, so the whole request is dropped and every domain in it abandoned. The router reports to no diagnostic sink: it has + none, and no instance number to give one, which settles [DI-12.2.3](COMPLETED-CHECKLIST.md#di-1223). The assistant's + first choice, failing the domain with an `Unrouted` error inside an `io::Error`, made the breach look like an ordinary + provider failure and let it persist; it was withdrawn before release. + +**Scheduled work.** None. + +## DI-D-84: the routing provider, as shipped + +**What is decided.** dioring ships `RoutingProvider`, the router [DI-2.12](COMPLETED-CHECKLIST.md#di-212) point 1 decided on +(where it was numbered `DI-3+.8`; it was built as [DI-12.2](COMPLETED-CHECKLIST.md#di-122)). It is portable, lives in +`durable-ioring`, and reaches a Windows consumer through `win-durable-ioring`'s re-export of that crate. Its rules are three +decisions, each made as it was built: what a registered provider serves ([DI-D-81](#di-d-81)), how it is built and what its +refusal names ([DI-D-82](#di-d-82)), and how it dispatches and what it does with a domain it does not route +([DI-D-83](#di-d-83)). It measures nothing ([DI-D-80](#di-d-80)) and reports to no diagnostic sink. + +- **What composing providers costs is documented, not enforced.** A file whose domains belong to two providers is in both + calls, and is durable when both succeed, as the core decides for any file and any provider. Coupled domains -- two names + whose flushes one physical flush would cover -- are deduplicated by nothing when split across providers, so each provider + flushes for its own; the router is correct either way, and a consumer who wants one flush registers them with one provider. +- **What shows it.** The router is tested alone against fake providers and a fake completion home in `durable-ioring`, and + through a real instance in `win-durable-ioring`: a seal touching two providers is one call to each with only its own + domain; a file spanning both is durable only when both succeed, and fails when either does; one failing fails only the + files in its domains; a domain neither serves is the default's; and a panic in the first abandons both its domains and the + second's, which is never called. Sabotage entries in each crate's manifest patch the router, and the driver's run only its + own suite, so each crate's tests are shown to catch them on their own. + +**Scheduled work.** None. + +## DI-D-85: no merge-or-delete with the `epoch_log` sample; a sample log is built on this crate from common practice + +**Supersedes [DI-D-7](#di-d-7).** + +**What happened.** The engineer, 2026-10-21: the durable ring was what he meant to be developing as an example; "there was a +miscommunication between me and well another instance of you and instead the epoch_log example was developed which was +interesting. it took a while for me to realize the miscommunication because the terminology I was using ("epochs", +"durability") seemed to be hitting but the fact that it was turning into a log instead of a ring just didn't dawn on me until +basically it was done. so I preserved it because while a common log driven by epoch based durability notations is useful it's +not what I wanted at the time". DI-D-7 recorded the sample as a path this crate duplicated, to be merged or deleted once the +crate was proven; the sample is a log, which is a consumer of a durable ring rather than a copy of one, and only its +hand-written durability overlaps this crate. + +**What is decided.** +- **No merge-or-delete decision ties the sample to this crate.** `DI-12.3`, which queued one, is withdrawn. The engineer: + "I don't think you need to really refer back to epoch_log at all other than incidentally". The sample stays in + `windows-ioring-sys` as that crate's own; what becomes of it is that crate's question, and its known defect is queued there + regardless (`M29` in [windows-ioring-sys' CHECKLIST.md](../windows-ioring-sys/CHECKLIST.md)). That the sample's fate is no + longer this crate's to decide is the assistant's reading of the ruling, open to correction. +- **A sample log is built on this crate, from a survey of common practice in database and file-system logs** -- "Just + survey common practices for database/filesystem [...] logs and provide it as a sample" -- and not from the `epoch_log` + sample's design. +- **Once it proves out it is a candidate to promote** to a library ("I agree once the sample proves out it's a good + candidate to promote"), which is parked behind the sample. +- **Priority:** "I don't see it as being important to me personally in the immediate future." The work is queued, not + deferred. + +**Scheduled work.** [DI-M21](CHECKLIST.md#di-m21----a-sample-log-on-durable-ioring), and `DI-21+.1` in +[PARKED.md](PARKED.md#di-m21----promoting-the-sample-log-parked-behind-di-m21). + +## DI-D-86: how a correlation id is minted + +The choices [DI-19.1](COMPLETED-CHECKLIST.md#di-191) left to its implementation, made by the assistant under +[DI-D-80](#di-d-80) and open to a ruling. + +- **One way to mint, which asks the sink.** `CorrelationId::for_pair(sink, kind)` mints only if the sink wants `kind`, and + returns `None` otherwise, having taken nothing from the counter. Asking is the rule's single home, so a begin cannot be + given an id its sink did not want, and the core cannot forget to ask. +- **Public, and exported from the crate.** A sink reads an event's id, so it names the type; minting is public too, for a + driver that builds a pair of its own. An id minted by anyone comes from the same counter and is as unique as any other. +- **The counter starts at 1**, so zero is never the first id; it wraps on overflow, as `fetch_add` does, and nothing checks + for that. +- **Relaxed ordering**, as the engineer ruled: an id must differ from every other, which the counter's single order of + modification gives, and it orders nothing else. + +**Scheduled work.** [DI-19.2](CHECKLIST.md), which mints an id as it builds each begin. + +## DI-D-87: the pairs' shapes, the ETW opcodes with them, and the core's duration code removed + +**What is decided.** The engineer, 2026-10-21, on the assistant's proposal for restructuring +[DI-19.2](CHECKLIST.md): + +- **The ETW schema's Start and Stop opcodes come with the pairs** ("b"). Changing `Diagnostic` breaks + `durable-ioring-events` in the same commit, so its schema changes in `DI-19.2.4` once, with the opcodes, rather than + twice; `DI-19.4` keeps writing the activity id. +- **The shapes** ("looks good!"). The kinds are `Seal`, `Flush`, `LockWait` and `Inconsistency`; `FlushIssued`, + `FlushAnswered` and `SealWaited` go, a seal's wait being the gap between its begin and its first flush's begin. + `SealBegan { instance, lineage, through, id }` and `SealEnded { ..., id, outcome }`, the outcome how the seal was answered + -- durable, failed, or blocked by an earlier failure -- or ended unanswered, by its lineage ending or its instance being + dropped. `FlushBegan { instance, lineage, through, part, id }` and `FlushEnded { ..., id, outcome }`, succeeded, failed or + abandoned. `LockWaitBegan { instance, lock, id }` and `LockWaitEnded { instance, lock, id }`. An end repeats its begin's + identity fields, which pairing does not need and a reader of one line does. +- **The core's duration code is removed** ("remove the clock"): `StampClock::elapsed` and the lock watch's `LockTimer`, + which nothing uses once the ends carry no `elapsed`. The assistant reads the ruling as that code and not `StampClock` + itself, which still stamps writes and failures; that reading is open to correction. +- **`PairingChecker` is public in `durable_ioring::oracle`** ("good"), beside the conformance oracle, so each crate's sink + tests bind to the one definition: every end follows a begin of the same id and kind, no id is begun twice, nothing follows + an end, and at the stream's end no begin is left open but those a test names. + +**Scheduled work.** [DI-19.2.1](CHECKLIST.md) to `DI-19.2.5`, and `DI-19.4`, narrowed. + +## DI-D-88: which events pair is defined once, on `Diagnostic`, and the checker holds the rest + +The choices [DI-19.2.1](COMPLETED-CHECKLIST.md#di-1921) left to its implementation, made by the assistant under +[DI-D-87](#di-d-87) and open to a ruling. + +- **`Diagnostic::pairing` is the one definition of which events are the ends of which pairs.** It returns the pair's kind, + its id, and whether the event is the begin or the end, or `None`. The checker reads it, and the ETW sink will read it to + choose a Start or a Stop opcode, so neither restates which variants pair; a variant added later is placed once. +- **The checker also holds that an end repeats its begin's identity**: the instance, and the lineage and epoch, the part, or + the lock. DI-D-87 promises it, and the checker is the one place a stream can be held to it. It compares the identity as + text, so an epoch rendered by its own `Debug` is compared as the reader sees it. +- **The finish is bidirectional**: every pair open must be named as left open, and every pair named must be open, so a test + cannot excuse a pair that never began or has ended. +- **A violation is the first one met**, and the checker is not meant to go on past it; a test stops there. +- **Ids a test chooses** come from a constructor that exists only under `cfg(test)`, so a test can say which pair it means; + everything else mints through `CorrelationId::for_pair` ([DI-D-86](#di-d-86)). + +**Scheduled work.** [DI-19.2.2](CHECKLIST.md) onward, which emit the pairs and bind each sink's tests to the checker. + +## DI-D-89: where the core's seal and flush pairs begin and end + +The choices [DI-19.2.2](COMPLETED-CHECKLIST.md#di-1922) left to its implementation, made by the assistant under +[DI-D-87](#di-d-87) and open to a ruling. + +- **A seal's pair begins when the seal is accepted as new**, in the same call, and not for a seal answered `AlreadySealed`, + which makes none. Its id is kept on the seal, and taken as the pair ends, so it ends once. +- **It ends at the seal's first answer.** `Failed`, when one of its own flush's parts fails; `Blocked`, when an earlier + failure holds it; `Durable`, when the mark passes it unanswered. A seal answered `Failed` or `Blocked` that later becomes + durable, after a heal or an abandonment, ends nothing more: its pair said its answer already. +- **A flush pair is one part**: the default's flush of one file, or the provider's part for one domain, from its issue to its + answer. A domain is reported by the consumer's name for it, which the core is now given with the domains the provider + serves; a served domain always has one. +- **Ending a lineage ends its pairs**: each flush still out as abandoned, and each seal unanswered as ended unanswered. The + flush's answer, when it comes, reports nothing, as it did. **Retiring a lineage cannot leave a pair open**, since a seal with + flushes out holds uncovered writes and a lineage holding any is refused retirement. +- **Closing or dropping the core ends every pair still open**, in its durability state's `Drop`: the seals unanswered and the + flushes abandoned. So a pair ends on every path, including a panic that unwinds through the instance. +- **The old delay events are still emitted beside the pairs**, until [DI-19.2.4](CHECKLIST.md) moves the sinks and removes + them; stopping them here would leave the ETW sink writing no flush events in between. They were removed there + ([DI-D-91](#di-d-91)). + +**Scheduled work.** None beyond [DI-19.2.3](CHECKLIST.md) onward. + +## DI-D-90: the lock-wait pair, and the rule that a sink's locks are a leaf beneath dioring's + +The choices [DI-19.2.3](COMPLETED-CHECKLIST.md#di-1923) left to its implementation, made by the assistant under +[DI-D-80](#di-d-80) and [DI-D-87](#di-d-87) and open to a ruling. + +- **One question decides the wait.** When the immediate try fails, the watch asks for a pair id, which asks the sink once + whether it wants lock waits. With none, the lock is taken with no clock read and no event; with one, the begin is written, + the wait is timed, the end is written with the lock taken, and the old wait is reported after the guard is let go. The pair + and the old wait share a kind, so the one answer is right for both. +- **The rule on `DiagnosticSink`** is the engineer's "any synchronization within the diagnostics platform must not interfere + with the synchronization in the durable-ioring", stated as the assistant reads it: a sink's lock is never taken in a way + that can wait on anything dioring's locks guard, and never held by a thread while it calls into an instance, including a + consumer holding a sink's lock around such a call. That reading is open to correction. +- **The timer stays until the old wait goes**, in [DI-19.2.4](CHECKLIST.md), since it is what measures that wait. Both + went there ([DI-D-91](#di-d-91)), so the first bullet's timing and old wait describe the state between the two steps. + +**Scheduled work.** None beyond [DI-19.2.4](CHECKLIST.md) and `DI-19.2.5`. + +## DI-D-91: the old delay events and the core's timing are gone, and the driver's lock-wait tests bind to the checker + +The choices [DI-19.2.4](COMPLETED-CHECKLIST.md#di-1924) left to its implementation, made by the assistant under +[DI-D-87](#di-d-87) and open to a ruling. + +- **Removed**: `Diagnostic::FlushIssued`, `FlushAnswered`, `SealWaited` and the old `LockWait`, their kinds, the stamps the + durability state kept to time them, `StampClock::elapsed`, and the lock watch's `LockTimer`. The kinds are `Inconsistency`, + `Seal`, `Flush` and `LockWait`. The durability state's provider answer no longer takes the domain's name, which only the + old event used. This is a major change to `durable-ioring`. +- **The lock watch is a sink and an instance number.** A wait is one `try_lock`; when it fails, one `for_pair` decides + whether the pair is written, and with it the begin is written before blocking and the end with the lock taken, as + [DI-D-90](#di-d-90) placed them. Nothing reads a clock. +- **The driver's lock-wait tests bind to `PairingChecker`**: their sink passes every event to it, and records each edge with + the thread that wrote it. A test asserts that its own thread wrote a begin and then an end, and does not finish the whole + stream, because the pool's own thread may still be inside a wait of its own when the test's ends, which would make a finish + race. The checker still holds every event it is given, so an end that names another pair is caught there and nowhere else, + which a sabotage in the driver's manifest shows. +- **The ETW provider's schema follows** ([DE-D-5](../durable-ioring-events/DESIGN-NOTES.md#de-d-5)). + +**Scheduled work.** None beyond [DI-19.2.5](CHECKLIST.md). + +## DI-D-92: the pairing rule's one home is shown by changing it, and the driver's chosen-sink test binds to it + +The choices [DI-19.2.5](COMPLETED-CHECKLIST.md#di-1925) left to its implementation, made by the assistant under +[DI-D-87](#di-d-87) and [DI-D-88](#di-d-88), and open to a ruling. + +- **What shows the rule has one home is a change to the definition, in each suite that binds to it.** The sabotage harness + patches one file per entry, so a defect and a weakened checker cannot be injected together; instead the definition is + changed -- `Diagnostic::pairing` made to call a seal's end a begin, and the checker made to refuse an end of its begin's + own kind -- and each crate's suite must fail because of it. The core's, the driver's and the provider's manifests carry + the first; the driver's carries the second, with a control. A suite that held its own copy of the rule would pass. +- **The catch is attributed, not only counted.** A suite failing is not enough when other tests could fail for another + reason, so the driver's catch of the seal change was injected by hand and its panics read: every one was at the checker's + binding in the chosen-sink test. The provider's suite holds the rule through its table's edges, which a test holds equal + to `pairing` for an event of every variant ([DE-D-5](../durable-ioring-events/DESIGN-NOTES.md#de-d-5)). +- **The driver's chosen-sink test passes every event to `PairingChecker` and finishes it** once the instance has ended, with + nothing left open, so the seal and flush pairs a real instance writes are held to the rule through the driver, as the lock + waits already were ([DI-D-91](#di-d-91)). +- **What the sweep left alone is history**: the milestone's own preamble, a quoted question in + [DE-D-1](../durable-ioring-events/DESIGN-NOTES.md#de-d-1), the dated check in DE-D-3, the design sessions and the archives. + Decisions it found stating the old shape as current carry a status marker at their title instead of being rewritten: + [DI-D-26](#di-d-26), [DI-D-51](#di-d-51), [DI-D-77](#di-d-77), and the in-between bullets of [DI-D-88](#di-d-88) and + [DI-D-90](#di-d-90). + +**Scheduled work.** None; [DI-19.3](CHECKLIST.md) and `DI-19.4` follow. + +## DI-D-93: a pair's ETW activity id is created by the platform, and a trace's pairing is verified by a program + +**The activity id** ("go ahead with your "c" option", the engineer, 2026-10-21). The working position of +[DI-D-80](#di-d-80), the process id over the pair id, lets a process id reused within one trace give a later process's pair an +earlier one's activity. The assistant offered three ways: keep it and document the collision; mix in something of each process +instance, which narrows the collision without removing it; or have the ETW provider create each pair's activity id with +`EventActivityIdControl`, which its page guarantees unique across every process of the system until it restarts, hold it from +the begin to the end, and keep the pair id in the payload. The third was chosen. How the provider does it is +[DE-D-6](../durable-ioring-events/DESIGN-NOTES.md#de-d-6), and the call is [WE-D-5](../win-etw-sys/DESIGN-NOTES.md#we-d-5). The +portable core is unchanged: it has the `u64`, and the activity is the ETW sink's. + +**The verification** ("dump the events to xml or csv. Write code to load the file and do the pairing in Rust", the engineer, +2026-10-21). `DI-19.4` asked that `tracerpt` show each begin and end paired with a duration, which `tracerpt` does not do: it +dumps records. So the trace is dumped to CSV and read by the example `pair_trace` in `durable-ioring-events`, which pairs by +activity, checks task and pair id, and computes each duration; its tests run over the recorded dump. + +**Scheduled work.** None; [DI-M19](COMPLETED-CHECKLIST.md#di-m19----paired-begin-and-end-events-with-correlation-ids) is +complete. + +## DI-D-94: what Windows documents about a directory entry's durability + +**DI-M20 withdrawn by [DI-D-95](#di-d-95); kept as the record of what the pages say, for a consumer that makes its own files durable.** + +**Findings, not a decision.** Read by the assistant for [DI-20.1](COMPLETED-CHECKLIST.md#di-201), 2026-10-21; what follows +from them was to be [DI-20.2](COMPLETED-CHECKLIST.md#di-202)'s discussion with the engineer. Each page is named with the `ms.date` it carries. No +`Nt*` or driver-kit page is a source. Silence is recorded as silence. + +### Whether flushing a new file makes its directory entry durable + +- **`FlushFileBuffers`** (2018-12-05): "writes all the buffered information for a specified file to the device or pipe". + It speaks of the file, of a communications device, of a pipe and of a volume. It says nothing of the directory that + names the file. **Silent.** +- **`CreateFileW`** (2022-08-16), under `FILE_FLAG_NO_BUFFERING`: "the file metadata may still be cached (for example, when + creating an empty file). To ensure that the metadata is flushed to disk, use the FlushFileBuffers function." The page + counts a file's creation among the metadata a flush of that file covers. It does not say whether the entry in the parent + directory is part of "the file metadata". +- **File Caching** (2018-05-31): "File system metadata is always cached. Therefore, to store any metadata changes to disk, + the file must either be flushed or be opened with FILE_FLAG_WRITE_THROUGH." Which file's flush stores a change to a + directory's entries is not said. +- **`CreateFileW`**, under caching behavior: "A write-through request via FILE_FLAG_WRITE_THROUGH also causes NTFS to flush + any metadata changes, such as a time stamp update or a rename operation, that result from processing the request." The + sentence names NTFS only, and names a rename among the metadata changes a write-through request flushes. +- **The ring's flush**, which the built-in provider issues: `BuildIoRingFlushFile` has no page among the ring API's pages + on Learn (the `ioringapi` index lists none), so neither does its `FILE_FLUSH_MODE`, whose modes name metadata. What + [windows-ioring-sys](../windows-ioring-sys/src) says of the modes comes from the header and from inference, and it says + so. **Silent** on Learn. + +### Whether a directory handle may be flushed, and what that commits + +- **`CreateFileW`**: `FILE_FLAG_BACKUP_SEMANTICS` -- "You must set this flag to obtain a handle to a directory. A directory + handle can be passed to some functions instead of a file handle." +- **Directory Handles** (2018-05-31): "You can pass a directory handle to the following functions", listing `BackupRead`, + `BackupWrite`, `GetFileTime`, `ReadDirectoryChangesW` and `SetFileTime`. `FlushFileBuffers` is not among them. The page + does not say the list is complete. +- **`FlushFileBuffers`**: the handle "must have the GENERIC_WRITE access right"; directories are not mentioned. **Silent** + on whether a directory handle may be flushed and on what such a flush commits. +- **Measured, not documented** ([DI-D-72](#di-d-72), 2026-10-19): `FlushFileBuffers` on a directory handle succeeded with + the write right and failed with error 5 without it, and a commit through the ring on a directory target completed + successfully. Neither says what reached stable media. +- **A volume**: "To flush all open files on a volume, call FlushFileBuffers with a handle to the volume. The caller must + have administrative privileges." The page says "open files". **Silent** on directories, and on files no longer open. + +### What a rename or a delete needs + +- **`MoveFileExW`** (2018-12-05), `MOVEFILE_WRITE_THROUGH`: "The function does not return until the file is actually moved + on the disk. Setting this value guarantees that a move performed as a copy and delete operation is flushed to disk before + the function returns. The flush occurs at the end of the copy operation." and "This value has no effect if + MOVEFILE_DELAY_UNTIL_REBOOT is set." The guarantee is stated for a move done as a copy and a delete; for a rename within a + volume the page has the first sentence and no guarantee. +- **`SetFileInformationByHandle`** (2018-12-05) with `FileRenameInfo`, and **`FILE_RENAME_INFO`** (2018-12-05): nothing + about durability. `FILE_RENAME_INFO` names a `Flags` member used with `FileRenameInfoEx` and lists no flag values. + **Silent.** +- **`ReplaceFileW`** (2018-12-05): `REPLACEFILE_WRITE_THROUGH` -- "This value is not supported." The page for the + alternatives to Transactional NTFS (2018-05-31) offers `ReplaceFile` for an update that is "completely applied or not + applied at all" and says nothing of durability. +- **`DeleteFileW`** (2023-12-15): "The DeleteFile function marks a file for deletion on close." and "The use of POSIX delete + causes the file to be deleted while handles remain open." Nothing about durability. **Silent.** +- **`FILE_DISPOSITION_INFO`** (2018-12-05): nothing about durability. `FILE_INFO_BY_HANDLE_CLASS` (2018-12-05) lists + `FileDispositionInfoEx` with no description, and `FILE_DISPOSITION_INFO_EX` has no page among the Win32 API pages, so its + flags are documented only in driver-kit material, which is not a source here. **Silent.** +- **`CreateDirectoryW`** (2018-12-05) and **`RemoveDirectoryW`** (2023-12-15): nothing about durability. **Silent.** +- **Transactional NTFS**: the operations have transacted forms (`MoveFileTransactedW`, `DeleteFileTransacted`, + `CreateFileTransacted`, among others). `MoveFileTransactedW`'s page (2018-12-05), the one read, opens with "Microsoft + strongly recommends developers utilize alternative means" and "TxF may not be available in future versions of Microsoft + Windows." + +### Questions the pages leave open, and the experiment that would bear on them + +Open: whether flushing a new file -- by `FlushFileBuffers` or by the ring's default flush -- makes its directory entry +survive a power loss; whether flushing a directory handle does; whether a rename on a handle opened write-through, or by +`MoveFileExW` with `MOVEFILE_WRITE_THROUGH` within a volume, does; and the same for a delete. + +The experiment: a virtual machine whose disk is a virtual hard disk file. The guest creates, renames or deletes an entry, +applies one candidate -- none, a flush of the file, the ring's flush, a flush of the directory handle, a flush of the +volume, a write-through handle, or `MOVEFILE_WRITE_THROUGH` -- and tells the host, which turns the machine off at once. On +the next boot the guest records which entries exist. Many trials per candidate, on NTFS and on ReFS. Such a power-off +discards the guest's memory but not writes the host has accepted, so it measures what the guest's file system committed, +not what a device's volatile cache would lose. A loss under a candidate shows the candidate insufficient on that +configuration; no loss shows only that none was seen. Its results are evidence and never a check: a refusal ahead of a call +rests on the documented contract, never on what a run showed +([DESIGN-NOTES.md](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract)). + +**Scheduled work.** None; DI-M20 is withdrawn ([DI-D-95](#di-d-95)), and the experiment is not built. + +## DI-D-95: a directory entry's durability is not the durable ioring's question, and DI-M20 is withdrawn + +The engineer, 2026-10-21, asked whether the durable ioring creates or deletes files: "no I didn't think it did either", and: "the 'directory' point I raised previously was mostly framing for what seems to feed in to m39.3". + +**Why.** The durable ioring never names a file. A file enters as an open handle and leaves by `remove_file`; the operations +are reads and writes, made durable by sealing an epoch, which flushes what was written ([API.md](API.md) sections 8 and 15). +It has no create, rename, delete or directory operation, and the Windows driver's `OpenedFile`, the one place a file is +created, is a `CreateFileW` wrapper that runs before the file is given to the ring ([DI-D-60](#di-d-60)). So what a seal +covers is what was written and flushed through the ring; whether a file's name survives is decided by calls the consumer made +before, and is the consumer's, with [DI-D-94](#di-d-94) as the record of what Windows documents about it. + +**Where the question came from.** The design session's directory was the engineer's framing of how a database's storage is +laid out: a layer outside the crate maps a directory to the flush domains a file created in it will have. That is root +[M39.3](../../CHECKLIST.md), which predicts a directory's domains and confirms each file's by its handle. The assistant read a +second question into it -- whether the new file's directory entry is durable -- from the practice on Linux of flushing the +directory, and queued it as DI-M20 without asking whether the durable ioring had any part in making a name. + +**A direction not taken, recorded so it is not lost.** The durable ioring could take namespace operations -- create, rename, +delete -- as operations tagged with an epoch, so that sealing an epoch would cover a file's existence with its writes. It was +named in the discussion and not asked for; nothing is queued for it. + +**Scheduled work.** None. + +## DI-D-96: a producer owes matched pairs, a sink assumes none, and a fan-out hands every end to every sink + +The engineer, 2026-10-22, on two findings of a review of the branch: a stateless fan-out and a tolerant sink, and the +standard each side is held to. The details are the assistant's and open to a ruling. + +**Producers are held to the high standard.** The core and its drivers, which report the pairs, owe a begin for every end and +an end for every begin: no end without a prior begin, and every begin eventually ended, by the core's own close, drop or end +of a lineage if not otherwise. [DI-D-87](#di-d-87)'s `PairingChecker` is that standard written once, and the core's tests +bind to it. + +**Sinks make no assumption that the standard was met where they stand.** What a sink wants can change while a pair is open (a +trace session stops or starts), and a router or an event filter between a correct producer and a sink -- a fan-out today, a +routing feature later -- can deliver an end whose begin the sink was never given, with nothing wrong in the producer. So a sink +tolerates an end with no begin as it tolerates a begin with no end, decides for itself what to record of either, filters what +it would over-log, and lets go whatever it held for a pair at the pair's end. `DiagnosticSink::report`'s documentation says so. +`EtwDiagnostics` writes such an end in an activity of its own ([DE-D-6](../durable-ioring-events/DESIGN-NOTES.md#de-d-6)). + +**The fan-out is stateless and does not ask `wants` of an end.** It hands an event that is not a pair's end to the sinks that +want its kind now, as before, and a pair's end to every sink it holds, in order. Before this, an end was asked of `wants` +like any event, so a sink that had stopped wanting the kind between the begin and the end -- the ETW sink, when a session +stopped -- was not given the end that lets it release its pair, and the process's activity map kept an entry for each such +pair for the life of the process. The first attempt kept, per open pair, which sinks its begin was given to and sent the end to +exactly those; it was withdrawn on the ruling above, since it made the router carry a guarantee that the sinks are not to rely +on, and a state of its own to keep in step with the producers'. + +**Not decided here:** whether a sink that wants nothing should be given an end at all by a router that knows it was never given +the begin. A sink filters either way, so nothing waits on it. + +**Scheduled work.** None; [DI-22.1](COMPLETED-CHECKLIST.md#di-221) is this. + diff --git a/crates/durable-ioring/PARKED.md b/crates/durable-ioring/PARKED.md new file mode 100644 index 000000000..f83d623fb --- /dev/null +++ b/crates/durable-ioring/PARKED.md @@ -0,0 +1,165 @@ +# Parked: durable-ioring + +Work that is not in scope now: gated on a deliverable (`M{n}+`), on the horizon (`M-inf`), or deferred by a +ruling. It lives here and not in [CHECKLIST.md](CHECKLIST.md), which holds only what is in scope, under the +workspace's rule "Parked work lives in `PARKED.md`, never in `CHECKLIST.md`" in +[copilot-instructions.md](../../.github/copilot-instructions.md). Every item keeps its ID. A section parked +behind a milestone is pointed to from that milestone in [CHECKLIST.md](CHECKLIST.md) by an `UNBLOCKS PARKED +WORK` callout. + +## DI-M5+ -- The retention layer (parked behind DI-M12) + +**Gate met 2026-10-21: `DI-M12` is complete. Each item here now waits for the engineer to choose it for a milestone.** + +- [ ] **DI-5+.1** -- **A separate crate that retains written data until it is durable and can rewrite + it after a failure**, built on DI-1.2's resolution protocol and the inventory, and resolving + overlapping suspect writes newest-wins, and resolving at the granularity of individual buffers it + can reissue (DI-1.2 Q6). The layer [DI-D-5](DESIGN-NOTES.md#di-d-5) moves out of + this crate; its first natural consumers are log-less designs such as FAT. Needs its own name and + component when it begins. + +## DI-M6+ -- Deliberately coarse (parked behind DI-M12) + +**Gate met 2026-10-21: `DI-M12` is complete. Each item here now waits for the engineer to choose it for a milestone.** + +Left coarse on purpose, per the workspace's resolution-gradient rule: each needs a working +implementation before it is answerable. + +- [ ] **DI-6+.1** -- **Measure alternating rings**, which stretch "one durability sequence per + instance" to two `IoRing`s, and the conditions under which dioring's own use of a ring barrier + beats completion-gated flushes (DI-6+.5). The consumer-facing strategy comparison is gone: + [DI-D-22](DESIGN-NOTES.md#di-d-22) offers the consumer no choice. + +- [ ] **DI-6+.5** -- **Choose the flush mechanism dynamically.** The engineer, 2026-10-06: "as + perf optimizations we may want to note if specialized flush domains are in use at all, or which + are in a given ring so that we may apply a different synchronization strategy dynamically." + [DI-D-22](DESIGN-NOTES.md#di-d-22) keeps the consumer from choosing; this is dioring choosing + for itself, per seal, where a ring barrier adds no coupling of its own. Working position, coarse: + - *State to keep:* whether any file declares domains at all (if none does, every file + intersects every other, so no lineages are separable and the barrier cannot break the + expectation), and per interned domain a count of operations outstanding on the ring, so a seal + can tell whether everything it would wait on intersects its own domains. + - *What the barrier buys:* the flush can go into the same submission as the writes it covers, + with no round trip -- the case of a log append followed by a commit on one file. + - *What it still costs when allowed:* it waits on unrelated earlier work, including reads and + the lineage's own later epochs, and N flushes run one after another, so it suits few files + on a quiet ring. Which policy wins is workload-dependent, so this item restores a + measurement for DI-6+.1 to carry, and DI-2.11's events record which mechanism each seal used. + - *The common case:* the engineer, 2026-10-06: "at least "naive" meaning no files with any flush + domains set will be common." That is the case where the barrier is always admissible, decided + by one flag rather than per-domain counts, so it is the first case to build and measure -- + the choice there is purely one of performance. + Applies to the default provider (DI-2.12), the only one that uses dioring's wioring. + Gated on DI-3.2. + +- [ ] **DI-6+.11** -- **Revisit `CREATE_ALWAYS` and `TRUNCATE_EXISTING`, which v1 leaves out** (the engineer, + 2026-10-16, "just to simplify the matrix", to be revisited at a later date). Parked, neither added nor + dropped. What an answer needs is in [DI-D-64](DESIGN-NOTES.md#di-d-64): `TRUNCATE_EXISTING` was + accepted only with the literal `GENERIC_WRITE`, and `CREATE_ALWAYS` emptied a file under every access + mask including a read-only one, so offering either means deciding what a handle typed not to write may + be opened to do. It is answerable once the three dispositions of [DI-13.7.3](COMPLETED-CHECKLIST.md#di-137) are in use. + **Added 2026-10-18 by [DI-14.4.1](COMPLETED-CHECKLIST.md#di-1441):** offering either brings in a + documented condition on the arguments that is then a check under + [the documented-contract rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract): + `FILE_FLAG_OPEN_REPARSE_POINT` "cannot be used with the `CREATE_ALWAYS` flag", and `TRUNCATE_EXISTING` + needs "the `GENERIC_WRITE` bit" in the access, which this crate's data access does not ask for + ([DI-D-64](DESIGN-NOTES.md#di-d-64), "What the reading pass of DI-14.4.1 found"). + +- [ ] **DI-6+.6** -- **Make the default provider cheaper than per-file sync.** From DI-2.12 point 1; + the engineer, 2026-10-06, asked these be recorded as "optimizations we should strongly consider", + then: "this is a performance feature that we will tackle during development and if we can't make + it safe, we will omit it. It is not required for shipping." Gated on DI-3.2. Candidates: + - *One device sync per flush domain, for declared cache-free files.* Write-through and no + buffering "are good for the filesystem cache but don't issue the flush at the device level, and + fua is largely ignored nowadays" (the engineer), so such files still need a device sync. The + consumer declares in `FileOptions` that a handle was opened `FILE_FLAG_NO_BUFFERING` with + `FILE_FLAG_WRITE_THROUGH`, and dioring issues one syncing flush per flush domain, on one of its + declared files. **The open safety question, settled during development or the feature is + omitted:** Windows does not document that a flush on one file commits the device cache behind + the others; if adopted, that becomes part of the consumer's warranty for the domain, not an + assumption of dioring's. Also open: the representative flush should use the default mode, since + a cheaper mode on a clean file may not send the device sync. A failed flush puts the whole + domain at risk, as DI-D-21 already scopes. + - *One flush per volume*, opt-in: `FlushFileBuffers` on a volume handle flushes "all open files on + a volume", but "The caller must have administrative privileges" and it flushes other processes' + files too. Whether IoRing's flush accepts a volume handle is unverified. + - *A cheaper flush mode per file* (`Data`, `MinMetadata`), chosen in `FileOptions`. + +- [ ] **DI-6+.8** -- **A completion fence.** From DI-2.6 point 3; parked, neither added nor dropped, + because neither the engineer nor the assistant sees a use yet ("I assume the use of it would be + the next layer up"). What it would be: a consumer-posted entry delivered by `pop` only once every + operation pushed before it has completed -- a completion sequencing point, saying nothing about + durability. Submission order needs no marker, since `OpId` is ordered by push order + ([DI-D-15](DESIGN-NOTES.md#di-d-15)). dioring would deliver the fence from its own bookkeeping + rather than a drain-flagged `Nop`, which would wait on everything on the ring -- the coupling + [DI-D-22](DESIGN-NOTES.md#di-d-22) avoided -- and could therefore scope it to a lineage or a set + of files. A likely user is the retention layer (DI-5+.1), releasing buffers at a fence. + +- [ ] **DI-6+.7** -- **Credit one provider call to other seals.** From DI-2.4 point 3, which settled + the contract: a call must make the sealing lineage's named writes durable, and making more durable + is "not incorrect, that's a performance issue" (the engineer, 2026-10-06). The optimization: a + call names every completed, not-yet-durable write on the files it covers, whichever lineage made + it, so its success can be credited and a later seal -- or one arriving while the call is in + flight, for writes that completed before it was issued -- needs no call of its own. Crediting is + safe only for writes the call named, since the provider's warranty covers what it was asked + about; coverage nobody asked for is never credited. It adds no failure exposure (those writes + share the files, so DI-D-21 already puts them at risk) and no coupling between separable + lineages, which share no files. Applies to every provider, so it lives in dioring's core. + Gated on DI-3.2. + +- [ ] **DI-6+.4** -- **A dioring-mediated cancel.** Not in v1 (the engineer's lean): cancel + guarantees no response time, so it does not deliver what shutdown or deadlines want, and draining + serves shutdown already. When it is taken up, the accounting is that a cancelled write's outcome is + indeterminate, so it joins the suspect set and its epoch cannot be reported durable until resolved. + The v1 design must not foreclose adding it. + +- [ ] **DI-6+.9** -- **Let the oracle check when a heal takes effect, by a per-lineage entry in the + stream.** From `DI-9.1.4`, which accepted the oracle's weaker rule + ([DI-D-42](DESIGN-NOTES.md#di-d-42)). The stream records when a seal is accepted, not when it + starts, so the oracle cannot tell a `Durable` from a seal that started after a heal from one that + started before it and finished after; it accepts both, and the core's own tests are what pin the + timing. Working position, not a decision: an entry such as `HealEffective { failure, lineage }`, + reported when a heal takes effect in a lineage, would let the oracle require it before a `Durable` + passes that failure's epochs there. It adds a public entry kind that every consumer's `match` sees, + and it changes [CONTRACT.md](CONTRACT.md) and the shared oracle together. Gated on a driver or a + consumer whose own tests need the oracle to check this, since that is what would show what the + entry should carry. + +- [ ] **DI-6+.10** -- **Fault seams in `win-sync-sys` and `windows-threadpool-sys`, and a safe `Mutex`, so + the Windows driver's remaining declared gaps can be tested.** From `DI-9.8.1`. Creating the readiness + event, the pool and signalled deliveries' thread-pool waits and events, and duplicating the notification + fail only when the process runs out of handles or memory, which a test cannot cause without breaking its + own harness; the ring crate has a seam for such failures (its `kernel-seam`) and neither of these crates + does. The notification's wait ending abandoned needs a `Mutex` to abandon, which is + [WS-1+.3](../win-sync-sys/CHECKLIST.md). Working position, not a decision: each is a change to a crate that + is not this one, and what the seam should look like is shaped by the first failure it has to produce. + Gated on a consumer, or this crate's own review, wanting those paths tested. + +## DI-M21+ -- Promoting the sample log (parked behind DI-M21) + +- [ ] **DI-21+.1** -- **Promote the sample log to a library, if it proves out.** The engineer, 2026-10-21: "I agree once the + sample proves out it's a good candidate to promote". Gated on [DI-21.3](CHECKLIST.md#di-m21----a-sample-log-on-durable-ioring), + the sample itself; what "proves out" means, and where a library would live, are for the engineer when it is done. + +## DI-M-inf -- The horizon (parked, no gating deliverable) + +- [ ] **DI-15.3** -- **Opening relative to a directory handle.** **Deferred by the engineer, 2026-10-19: "let's + defer any relative opening work at this time."** Parked from `DI-M15` with no gate; it is picked up when the + engineer brings it back. The item as written: confirm that no documented Win32 call opens a name relative to a + directory handle (the only route the assistant knows is `NtCreateFile` with a root directory, which the + repository's rule forbids), and if none does, record the substitute: list the directory through its handle, take + the child's identity, and open it by identity ([DI-15.2](COMPLETED-CHECKLIST.md#di-152)). What was found before + the deferral, so it is not redone: + - `GetFileInformationByHandleEx` takes the handle, the class, the buffer and its size, and nothing else, so its + directory classes (`FileIdExtdDirectoryInfo` and its restart form) return every entry and cannot be narrowed to + a name. `windows-file-enumeration-sys` filters after the read, by its own compiled patterns. + - `FindFirstFileExW` takes a name with wildcards, but by path, which walks the directory's name again and so + reopens the race a relative open exists to avoid. `NtQueryDirectoryFile`, which filters on a handle, is an + `Nt*` call. + - The substitute differs from `openat` in three ways a decision would have to state: it pins the object found + when the directory was listed, not the one the name reaches at the open; it cannot create a file, since + `OpenFileById` has no disposition; and finding one name costs reading entries until it appears, the whole + directory at worst. + - Not yet checked against the documentation: `CreateFile2`'s extended parameters, and the documented `FSCTL_*` + controls. A directory opens by identity without the backup flag ([DI-D-70](DESIGN-NOTES.md#di-d-70)), which + the engineer ruled allowed ([DI-D-72](DESIGN-NOTES.md#di-d-72)), so the substitute reaches directories too. diff --git a/crates/durable-ioring/PLANS.md b/crates/durable-ioring/PLANS.md index 4778a29d4..30df222a9 100644 --- a/crates/durable-ioring/PLANS.md +++ b/crates/durable-ioring/PLANS.md @@ -3,3 +3,4 @@ | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| | [CHECKLIST.md](CHECKLIST.md) | in progress | A ring with durability scheduled into its work, layered on `windows-ioring-sys`'s `IoRing`. The contract ([CONTRACT.md](CONTRACT.md)) and the API shape ([API.md](API.md)) are designed; implementations, the retention layer above this crate, and the flush-failure spike follow. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-SESSION-2026-10-05-epoch-ring.md](../../design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md) | +| [PARKED.md](PARKED.md) | parked | Work not in scope now: gated on a milestone, on the horizon, or deferred by a ruling. Each section names its gate. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/durable-ioring/sabotage.json b/crates/durable-ioring/sabotage.json index 3ebe00f11..8c0400f75 100644 --- a/crates/durable-ioring/sabotage.json +++ b/crates/durable-ioring/sabotage.json @@ -138,8 +138,8 @@ "file": "src/durability.rs", "expect": "caught", "why": "The prefix property (guarantee 2): Durable through 2 covers 1, so it must wait for 1. Caught by the I/O test whose first seal a provider holds, and by the unit tests.", - "find": [" while let Some(seal) = self.lineages.get(&lineage).and_then(|s| s.seals.front()) {\n if !matches!(seal.stage, SealStage::Done { .. })\n || self.blocking(lineage, seal.through).is_some()\n {\n break;\n }\n let through = seal.through;\n let state = self.lineages.get_mut(&lineage).expect(\"found above\");\n state.seals.pop_front();\n"], - "replace": [" while let Some(seal) = self.lineages.get(&lineage).and_then(|s| s.seals.back()) {\n if !matches!(seal.stage, SealStage::Done { .. })\n || self.blocking(lineage, seal.through).is_some()\n {\n break;\n }\n let through = seal.through;\n let state = self.lineages.get_mut(&lineage).expect(\"found above\");\n state.seals.pop_back();\n"] + "find": [" while let Some(seal) = self.lineages.get(&lineage).and_then(|s| s.seals.front()) {\n if !matches!(seal.stage, SealStage::Done { .. })\n || self.blocking(lineage, seal.through).is_some()\n {\n break;\n }\n let through = seal.through;\n let state = self.lineages.get_mut(&lineage).expect(\"found above\");\n let popped = state.seals.pop_front().expect(\"found above\");\n"], + "replace": [" while let Some(seal) = self.lineages.get(&lineage).and_then(|s| s.seals.back()) {\n if !matches!(seal.stage, SealStage::Done { .. })\n || self.blocking(lineage, seal.through).is_some()\n {\n break;\n }\n let through = seal.through;\n let state = self.lineages.get_mut(&lineage).expect(\"found above\");\n let popped = state.seals.pop_back().expect(\"found above\");\n"] }, { "name": "DI-3.2.3: a write a provider serves does not hold its seal", @@ -1017,9 +1017,9 @@ "name": "DI-3.10.3.3: a fan-out gives each event to every sink", "file": "src/diagnostics.rs", "expect": "caught", - "why": "A fan-out hands an event only to the sinks that want its kind, so a sink's answer to wants means the same behind a fan-out as alone.", - "find": [" for sink in self.sinks.iter().filter(|sink| sink.wants(kind)) {\n"], - "replace": [" for sink in self.sinks.iter().filter(|_| kind == kind) {\n"] + "why": "A fan-out hands an event that is not a pair's end only to the sinks that want its kind, so a sink's answer to wants means the same behind a fan-out as alone.", + "find": [" if is_end || sink.wants(kind) {\n"], + "replace": [" if is_end || kind == kind {\n"] }, { "name": "DI-3.10.3.3: a fan-out wants an event only when every sink does", @@ -1042,8 +1042,8 @@ "file": "src/memory.rs", "expect": "caught", "why": "DI-D-52: a refusal names its cause. A backend that admits more than its completion queue holds never tells the caller to drain.", - "find": [" let drain = self.queued.len() + self.completed.len() >= self.completion_queue_size;\n"], - "replace": [" let drain = self.queued.len() >= self.completion_queue_size;\n"] + "find": [" let outstanding = self.queued.len() + self.completed.len() + self.held.len();\n"], + "replace": [" let outstanding = self.queued.len();\n"] }, { "name": "DI-3.10.3.5.1: a full submission queue is reported as needing a drain", @@ -1074,8 +1074,8 @@ "file": "src/memory.rs", "expect": "caught", "why": "A read may complete short, and the count is the consumer's to handle; reporting the buffer's length claims bytes that were not read.", - "find": [" count\n"], - "replace": [" into.len()\n"] + "find": [" into[..count].copy_from_slice(&available[..count]);\n count\n"], + "replace": [" into[..count].copy_from_slice(&available[..count]);\n into.len()\n"] }, { "name": "DI-3.10.3.5.1: a buffer's address is not checked", @@ -1090,16 +1090,16 @@ "file": "src/memory.rs", "expect": "caught", "why": "Buffers go in owned and come back (DI-D-50 P10), on a fault as on a success.", - "find": [" } => Some(buffer),\n"], - "replace": [" } => {\n drop(buffer);\n None\n }\n"] + "find": [" } => Some(buffer),\n"], + "replace": [" } => {\n drop(buffer);\n None\n }\n"] }, { "name": "CONTROL: the memory backend tests emptiness by length", "file": "src/memory.rs", "expect": "survives", "why": "The manifest checking itself on the new code: an empty queue has length zero, so this is an equivalent change. If it comes back caught, something is reacting to more than behaviour.", - "find": [" let was_empty = self.completed.is_empty();\n"], - "replace": [" let was_empty = self.completed.len() == 0;\n"] + "find": [" if was_empty && !self.completed.is_empty() {\n"], + "replace": [" if was_empty && self.completed.len() != 0 {\n"] }, { "name": "DI-3.10.3.5.1: needing both is not reported as needing a drain", @@ -1346,7 +1346,7 @@ "file": "src/io_core/provider.rs", "expect": "caught", "why": "DI-D-27: the durability core asks the provider only for the domains it serves; without them a served target's seal waits for an answer never requested.", - "find": [" core.durability.serve(Arc::clone(&served));\n"], + "find": [" core.durability\n .serve(Arc::clone(&served), core.served_names.clone());\n"], "replace": [""] }, { @@ -1513,7 +1513,7 @@ "file": "src/memory.rs", "expect": "caught", "why": "A queued or finished operation may name a registered region, which replacing the buffers invalidates; the builder refuses it as a caller's programming error. Caught by the two should_panic tests, one with an operation queued and one with a completion waiting.", - "find": [" self.queued.is_empty() && self.completed.is_empty(),\n"], + "find": [" self.queued.is_empty() && self.completed.is_empty() && self.held.is_empty(),\n"], "replace": [" true,\n"] }, { "name": "CONTROL: the engine tests a pass's pops by comparison", @@ -1600,15 +1600,15 @@ "file": "src/pump.rs", "expect": "caught", "why": "The wait is for the completions the drain then takes. Drained first, a pump woken by a raising returns having taken nothing, and the entries are there only for the next one.", - "find": [" let waited = self.notification.wait(timeout);", " let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain);"], - "replace": [" let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain);", " let waited = self.notification.wait(timeout);"] + "find": [" let waited = self.notification.wait(timeout);", " let ready = self.locked().as_mut().is_some_and(Engine::drain);"], + "replace": [" let ready = self.locked().as_mut().is_some_and(Engine::drain);", " let waited = self.notification.wait(timeout);"] }, { "name": "DI-7.3: a pump waits holding the engine's lock", "file": "src/pump.rs", "expect": "caught", "why": "The wait is for something another thread does, and doing it takes the lock: a push, a provider's answer, a handle's release. Held through the wait, nothing can wake the pump but its own timeout.", - "find": [" let waited = self.notification.wait(timeout);", " let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain);"], + "find": [" let waited = self.notification.wait(timeout);", " let ready = self.locked().as_mut().is_some_and(Engine::drain);"], "replace": [" let mut held = lock(&self.engine);", " let waited = self.notification.wait(timeout);", " let ready = held.as_mut().is_some_and(Engine::drain);"] }, { @@ -1616,8 +1616,8 @@ "file": "src/pump.rs", "expect": "caught", "why": "A delivery that cannot wait still delivers what has completed, and says why it could not wait. Skipping the drain makes a broken notification stop an instance delivering at all.", - "find": [" let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain);"], - "replace": [" let ready = waited.is_ok() && lock(&self.engine).as_mut().is_some_and(Engine::drain);"] + "find": [" let ready = self.locked().as_mut().is_some_and(Engine::drain);"], + "replace": [" let ready = waited.is_ok() && self.locked().as_mut().is_some_and(Engine::drain);"] }, { "name": "DI-7.3: a pump reports the drain's result inverted", @@ -1688,15 +1688,15 @@ "file": "src/pump.rs", "expect": "survives", "why": "The manifest checking itself on the new module: map_or(false, ..) is is_some_and(..), so this must survive; if it comes back caught, something is matching incidental text.", - "find": [" let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain);"], - "replace": [" let ready = lock(&self.engine).as_mut().map_or(false, Engine::drain);"] + "find": [" let ready = self.locked().as_mut().is_some_and(Engine::drain);"], + "replace": [" let ready = self.locked().as_mut().map_or(false, Engine::drain);"] }, { "name": "DI-7.5: a pump does not drain before it waits", "file": "src/pump.rs", "expect": "caught", "why": "The notification is an edge: the completions that raised it may have been taken by another waiter on the same event, and waiting for a raising that has happened blocks while they sit in the backend.", - "find": [" if lock(&self.engine).as_mut().is_some_and(Engine::drain) {"], + "find": [" if self.locked().as_mut().is_some_and(Engine::drain) {"], "replace": [" if false {"] }, { @@ -1704,8 +1704,561 @@ "file": "src/pump.rs", "expect": "caught", "why": "A first drain that delivered returns that it did, which sets the readiness event and tells the consumer to pop.", - "find": [" if lock(&self.engine).as_mut().is_some_and(Engine::drain) {"], - "replace": [" if !lock(&self.engine).as_mut().is_some_and(Engine::drain) {"] + "find": [" if self.locked().as_mut().is_some_and(Engine::drain) {"], + "replace": [" if !self.locked().as_mut().is_some_and(Engine::drain) {"] + }, + { + "name": "DI-13.9: a write-only access is said to be able to read", + "file": "src/access.rs", + "expect": "caught", + "why": "Whether an access can read is what the backends ask, so a write-only access that said it could read would let a read through to a target that cannot serve it.", + "find": [" matches!(self, Self::ReadOnly | Self::ReadWrite)\n"], + "replace": [" matches!(self, Self::ReadOnly | Self::ReadWrite | Self::WriteOnly)\n"] + }, + { + "name": "DI-13.9: the read-only marker names the read and write access", + "file": "src/access.rs", + "expect": "caught", + "why": "A target made from a read-only handle states what the marker names, so a marker that named another access would state more than the handle was opened with.", + "find": [" const ACCESS: Access = Access::ReadOnly;\n"], + "replace": [" const ACCESS: Access = Access::ReadWrite;\n"] + }, + { + "name": "DI-13.9: a description states a different access than its target", + "file": "src/io_target.rs", + "expect": "caught", + "why": "What the core decides with is the description, taken once, so a description that did not carry the target's access would leave the refusal to a backend that has already been told otherwise.", + "find": [" access: self.access(),\n"], + "replace": [" access: Access::ReadWrite,\n"] + }, + { + "name": "DI-13.9: a refusal for access ends an operation with some other kind of error", + "file": "src/backend.rs", + "expect": "caught", + "why": "The fault a refused operation of the core's own ends with names the cause as the error's kind, and a refusal for access is permission denied.", + "find": [" RefusalCause::AccessDenied => io::ErrorKind::PermissionDenied,\n"], + "replace": [" RefusalCause::AccessDenied => io::ErrorKind::Other,\n"] + }, + { + "name": "DI-13.9: the in-memory backend lets a write through to a target that cannot write", + "file": "src/memory.rs", + "expect": "caught", + "why": "A write is covered by a target that can write.", + "find": [" SubmissionOp::Write { .. } => target.access.can_write(),\n"], + "replace": [" SubmissionOp::Write { .. } => target.access.can_read(),\n"] + }, + { + "name": "DI-13.9: the in-memory backend lets a read through from a target that cannot read", + "file": "src/memory.rs", + "expect": "caught", + "why": "A read is covered by a target that can read.", + "find": [" SubmissionOp::Read { .. } => target.access.can_read(),\n"], + "replace": [" SubmissionOp::Read { .. } => target.access.can_write(),\n"] + }, + { + "name": "DI-13.9: the in-memory backend refuses a commit to a target that cannot write", + "file": "src/memory.rs", + "expect": "caught", + "why": "A commit is not covered or refused by access: it is issued only to a target with writes, and a target that cannot write has none, so no rule is made for it.", + "find": [" SubmissionOp::Commit => true,\n"], + "replace": [" SubmissionOp::Commit => target.access.can_write(),\n"] + }, + { + "name": "DI-13.9: the in-memory backend does not refuse what the access does not cover", + "file": "src/memory.rs", + "expect": "caught", + "why": "An operation the access does not cover is refused as AccessDenied.", + "find": [" if !covered {\n return Err(RefusalCause::AccessDenied);\n"], + "replace": [" if false && !covered {\n return Err(RefusalCause::AccessDenied);\n"] + }, + { + "name": "DI-13.9 control: a target states its access through a binding", + "file": "src/memory.rs", + "expect": "survives", + "why": "A control: the same value returned through a local, which no test can tell apart.", + "find": [" fn access(&self) -> Access {\n self.access\n }\n"], + "replace": [" fn access(&self) -> Access {\n let access = self.access;\n access\n }\n"] + }, + { + "name": "DI-13.10: a key declaring more than its target states is admitted", + "file": "src/key.rs", + "expect": "caught", + "why": "Registration refuses a key whose declaration the target does not cover. Admitting exactly those it does cover, and refusing the rest, is the one runtime step a typed key adds.", + "find": [" Some(declared) if !actual.covers(declared) => {\n"], + "replace": [" Some(declared) if actual.covers(declared) => {\n"] + }, + { + "name": "DI-13.10: a typed key declares some other access than its marker names", + "file": "src/key.rs", + "expect": "caught", + "why": "What a key declares is what its marker says; a key that always declared read and write would be admitted by every target that can do both and refused by none it should be.", + "find": [" Some(A::ACCESS)\n"], + "replace": [" Some(Access::ReadWrite)\n"] + }, + { + "name": "DI-13.10: an untyped key declares an access", + "file": "src/key.rs", + "expect": "caught", + "why": "An untyped key declares nothing, so any target registers under it and the core judges what is pushed.", + "find": [" fn declared(&self) -> Option {\n None\n }\n"], + "replace": [" fn declared(&self) -> Option {\n Some(Access::ReadWrite)\n }\n"] + }, + { + "name": "DI-13.10: an access covers what it cannot do", + "file": "src/access.rs", + "expect": "caught", + "why": "An access covers another when it can do everything the other can, so joining the two axes with an or covers a key that declares one axis the target lacks.", + "find": ["(self.can_read() || !other.can_read()) && (self.can_write() || !other.can_write())"], + "replace": ["(self.can_read() || !other.can_read()) || (self.can_write() || !other.can_write())"] + }, + { + "name": "DI-13.10 control: a typed key's number is taken through a binding", + "file": "src/key.rs", + "expect": "survives", + "why": "A control: the same number returned through a local, which no test can tell apart.", + "find": [" fn untyped(&self) -> IoTargetKey {\n self.key\n }\n"], + "replace": [" fn untyped(&self) -> IoTargetKey {\n let key = self.key;\n key\n }\n"] + }, + { + "name": "DI-11.1: a failed write is applied to the target anyway", + "file": "src/memory.rs", + "expect": "caught", + "why": "Fail must leave the target as it was; applying it and then reporting the fault is FailAfterApplying, which is a different and separately tested fault.", + "find": [" Some(Fault::Fail(fault)) => (\n Completion {\n token: submission.token,\n result: Err(fault),\n buffer: owned_buffer(submission.op),\n },\n None,\n ),\n"], + "replace": [" Some(Fault::Fail(fault)) => {\n let mut completion = self.execute(submission, None);\n completion.result = Err(fault);\n (completion, None)\n }\n"] + }, + { + "name": "DI-11.1: a write failed after applying is reported as having succeeded", + "file": "src/memory.rs", + "expect": "caught", + "why": "The fault is the point of the variant: a write whose bytes are in the target and which is reported failed.", + "find": [" if completion.result.is_ok() {\n completion.result = Err(fault);\n }\n"], + "replace": [" let _ = fault;\n"] + }, + { + "name": "DI-11.1: a short count is ignored", + "file": "src/memory.rs", + "expect": "caught", + "why": "A short write is what the fault exists to produce; ignoring the count completes it in full.", + "find": [" Some(Fault::Short(count)) => (self.execute(submission, Some(count)), None),\n"], + "replace": [" Some(Fault::Short(_)) => (self.execute(submission, None), None),\n"] + }, + { + "name": "DI-11.1: a short count larger than the buffer transfers the count", + "file": "src/memory.rs", + "expect": "caught", + "why": "The transfer is at most the count and never more than the buffer holds; max would slice past the end and panic.", + "find": [" short.map_or(length, |count| length.min(position(count)))\n"], + "replace": [" short.map_or(length, |count| length.max(position(count)))\n"] + }, + { + "name": "DI-11.1: a held completion is delivered at once", + "file": "src/memory.rs", + "expect": "caught", + "why": "Holding is withholding the completion until the test releases it; delivering it at once is an ordinary completion.", + "find": [" Some(Fault::Hold) => (self.execute(submission, None), Some(None)),\n"], + "replace": [" Some(Fault::Hold) => (self.execute(submission, None), None),\n"] + }, + { + "name": "DI-11.1: a late completion of zero doorbells is held", + "file": "src/memory.rs", + "expect": "caught", + "why": "Zero is an ordinary completion; held with a count of zero it would be aged below zero by the next doorbell.", + "find": [" Some(Fault::Late(0)) => (self.execute(submission, None), None),\n"], + "replace": [""] + }, + { + "name": "DI-11.1: a late completion is aged by two doorbells at a time", + "file": "src/memory.rs", + "expect": "caught", + "why": "A completion late by two doorbells is released after two, and not after one.", + "find": [" *left -= 1;\n"], + "replace": [" *left = left.saturating_sub(2);\n"] + }, + { + "name": "DI-11.1: a held completion takes no room in the completion queue", + "file": "src/memory.rs", + "expect": "caught", + "why": "A held completion is outstanding: if it took no room, a completion might have nowhere to go when it is released.", + "find": [" let outstanding = self.queued.len() + self.completed.len() + self.held.len();\n"], + "replace": [" let outstanding = self.queued.len() + self.completed.len();\n"] + }, + { + "name": "DI-11.1: the notification is raised when the completions were not empty", + "file": "src/memory.rs", + "expect": "caught", + "why": "The notification is raised on a transition from none to some; raising it on every release or doorbell into a non-empty queue wakes a waiter for nothing, and not raising it into an empty one loses the wakeup.", + "find": [" if was_empty && !self.completed.is_empty() {\n"], + "replace": [" if !was_empty && !self.completed.is_empty() {\n"] + }, + { + "name": "DI-11.1: a scripted refusal is never consulted at the push", + "file": "src/memory.rs", + "expect": "caught", + "why": "The refusal is applied at the push; consulted at the wrong point it never fires.", + "find": [" if let Some(Fault::Refuse(cause)) = self.script.consult(Point::Push, probe) {\n"], + "replace": [" if let Some(Fault::Refuse(cause)) = self.script.consult(Point::Execute, probe) {\n"] + }, + { + "name": "DI-11.1: a step counts only when no earlier step has fired", + "file": "src/memory/script.rs", + "expect": "caught", + "why": "Every matching step counts every occurrence, so a step's count does not depend on the steps before it.", + "find": [" self.seen[index] = self.seen[index].saturating_add(1);\n"], + "replace": [" if firing.is_none() {\n self.seen[index] = self.seen[index].saturating_add(1);\n }\n"] + }, + { + "name": "DI-11.1: from fires one occurrence late", + "file": "src/memory/script.rs", + "expect": "caught", + "why": "From n is the nth match and every one after it.", + "find": [" Occurrence::From(n) => seen >= n,\n"], + "replace": [" Occurrence::From(n) => seen > n,\n"] + }, + { + "name": "DI-11.1: nth fires on every match from the nth", + "file": "src/memory/script.rs", + "expect": "caught", + "why": "Nth is that match and no other.", + "find": [" Occurrence::Nth(n) => seen == n,\n"], + "replace": [" Occurrence::Nth(n) => seen >= n,\n"] + }, + { + "name": "DI-11.1: a step ignores the target it names", + "file": "src/memory/script.rs", + "expect": "caught", + "why": "A step that names a target fires only for operations on it.", + "find": [" self.op.is_none_or(|wanted| op == Some(wanted))\n && self.target.is_none_or(|wanted| target == Some(wanted))\n"], + "replace": [" self.op.is_none_or(|wanted| op == Some(wanted))\n"] + }, + { + "name": "DI-11.1 control: the count limit is taken through a match", + "file": "src/memory.rs", + "expect": "survives", + "why": "A control: the same limit by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" short.map_or(length, |count| length.min(position(count)))\n"], + "replace": [" match short {\n Some(count) => length.min(position(count)),\n None => length,\n }\n"] + }, + { + "name": "DI-12.1.1: every lock is reported as waited for, held or not", + "file": "src/lock_watch.rs", + "expect": "caught", + "why": "An immediate acquisition is a try_lock and nothing else (DI-D-26, DI-D-87): no id minted and no event. Reporting every one as a wait builds two events on the path the pattern exists to keep free, and reports waits that never happened.", + "find": [" let guard = match mutex.try_lock() {"], + "replace": [" let guard = match Err::, TryLockError>>(TryLockError::WouldBlock) {"] + }, + { + "name": "DI-12.1.1: a wait's pair is minted though no sink wants it", + "file": "src/lock_watch.rs", + "expect": "caught", + "why": "A sink that does not want lock waits must cost a blocked acquisition no id and no event (DI-D-87).", + "find": [" let Some(id) = CorrelationId::for_pair(&*self.sink, DiagnosticKind::LockWait)"], + "replace": [" let Some(id) = CorrelationId::for_pair(&crate::diagnostics::StderrDiagnostics, DiagnosticKind::LockWait)"] + }, + { + "name": "DI-19.2.4: a lock wait's end is written before the lock is taken", + "file": "src/lock_watch.rs", + "expect": "caught", + "why": "The end says the lock was taken (DI-D-80): written first, it closes the pair while the thread is still waiting, and a wait that never ends reads as one that did.", + "find": [ + " let taken = mutex.lock().unwrap_or_else(PoisonError::into_inner);", + " // Written with the lock held, as the engineer ruled (DI-D-80): the sink's own", + " // synchronization is a leaf beneath it, which `DiagnosticSink` requires.", + " self.sink.report(&Diagnostic::LockWaitEnded {", + " instance: self.instance,", + " lock: name,", + " id,", + " });", + " taken" + ], + "replace": [ + " self.sink.report(&Diagnostic::LockWaitEnded {", + " instance: self.instance,", + " lock: name,", + " id,", + " });", + " mutex.lock().unwrap_or_else(PoisonError::into_inner)" + ] + }, + { + "name": "DI-12.1.3: the line prefix drops its space", + "file": "src/diagnostics.rs", + "expect": "caught", + "why": "The prefix is what a reader of a line, and every sink that writes one, relies on being the same: `dioring:`, a space, and the event.", + "find": ["pub const LINE_PREFIX: &str = \"dioring: \";"], + "replace": ["pub const LINE_PREFIX: &str = \"dioring:\";"] + }, + { + "name": "DI-12.2.1: a domain claimed twice is not refused", + "file": "src/router.rs", + "expect": "caught", + "why": "A domain routed to two providers is refused at build (DI-2.12 point 1): with no refusal the router would serve it twice and the core would refuse the whole instance with less to go on.", + "find": [" vacant.insert(registration);"], + "replace": [" drop(vacant);"] + }, + { + "name": "DI-12.2.1: the refusal names the second registration twice", + "file": "src/router.rs", + "expect": "caught", + "why": "The refusal names both registrations, so a consumer can find the two that claim the domain; naming one twice reads as a provider that declared it twice.", + "find": [" first: *owner.get(),"], + "replace": [" first: registration,"] + }, + { + "name": "DI-12.2.1: the union is read in reverse registration order", + "file": "src/router.rs", + "expect": "caught", + "why": "The router's domains are the registrations in order, each in its own order, which is what a consumer reading them back, and the core interning them, see.", + "find": [" .flat_map(|route| route.domains.iter().cloned())"], + "replace": [" .rev().flat_map(|route| route.domains.iter().cloned())"] + }, + { + "name": "DI-12.2.1 control: the routes are gathered without a capacity", + "file": "src/router.rs", + "expect": "survives", + "why": "A control: the same routes gathered without reserving room first, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let mut routes = Vec::with_capacity(self.providers.len());"], + "replace": [" let mut routes = Vec::new();"] + }, + { + "name": "DI-12.2.2: every part goes to the first registration", + "file": "src/router.rs", + "expect": "caught", + "why": "Each domain's part goes to the provider that serves it (DI-D-81); a part given to another provider is flushed by something that never said it could.", + "find": [" parts[registration].push(part);"], + "replace": [" parts[0].push(part);"] + }, + { + "name": "DI-12.2.2: a provider with no part is called anyway", + "file": "src/router.rs", + "expect": "caught", + "why": "One call per provider that has parts: a provider called with an empty request is asked for work it was never given, and a provider that rejects that would fail a seal it had no part in.", + "find": [" if !domains.is_empty() {"], + "replace": [" if domains.len() < usize::MAX {"] + }, + { + "name": "DI-12.2.2: an unrouted domain is sent to the first registration instead of panicked on", + "file": "src/router.rs", + "expect": "caught", + "why": "A domain no provider serves is a caller breaking the protocol and is panicked on where it is found (DI-D-46, DI-D-83); sending it to a provider hands work to something that never said it could do it, and lets the breach persist.", + "find": [" let Some(®istration) = self.owners.get(&part.domain) else {"], + "replace": [" let Some(®istration) = self.owners.get(&part.domain).or(Some(&0)) else {"] + }, + { + "name": "DI-12.2.2 control: the per-provider parts are made by counting", + "file": "src/router.rs", + "expect": "survives", + "why": "A control: the same empty parts made by counting the routes instead of walking them, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" self.routes.iter().map(|_| Vec::new()).collect();"], + "replace": [" (0..self.routes.len()).map(|_| Vec::new()).collect();"] + }, + { + "name": "DI-19.1: an id is minted whether or not the sink wants the pair", + "file": "src/correlation.rs", + "expect": "caught", + "why": "An id is minted only for a pair a sink wants (DI-D-80), so a sink that wants nothing costs the counter nothing.", + "find": [" sink.wants(kind)"], + "replace": [" (sink.wants(kind) || kind == kind)"] + }, + { + "name": "DI-19.1: the counter is not advanced", + "file": "src/correlation.rs", + "expect": "caught", + "why": "Every id differs from every other the process minted, which a counter that does not move breaks at the second mint.", + "find": [" .then(|| CorrelationId(counter.fetch_add(1, Ordering::Relaxed)))"], + "replace": [" .then(|| CorrelationId(counter.fetch_add(0, Ordering::Relaxed)))"] + }, + { + "name": "DI-19.1: the counter starts at zero", + "file": "src/correlation.rs", + "expect": "caught", + "why": "The first id is 1, as the documentation says.", + "find": ["const FIRST: u64 = 1;"], + "replace": ["const FIRST: u64 = 0;"] + }, + { + "name": "DI-19.1 control: the counter is advanced with sequential consistency", + "file": "src/correlation.rs", + "expect": "survives", + "why": "A control: a stronger ordering than relaxed, which mints the same distinct ids and no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" .then(|| CorrelationId(counter.fetch_add(1, Ordering::Relaxed)))"], + "replace": [" .then(|| CorrelationId(counter.fetch_add(1, Ordering::SeqCst)))"] + }, + { + "name": "DI-19.2.1: an end of another kind than its begin is accepted", + "file": "src/oracle/pairing.rs", + "expect": "caught", + "why": "A pair's begin and end are one kind, so a sink wants both or neither (DI-D-87); an end of another kind is a different pair's, or a defect.", + "find": [" if begun.kind != pairing.kind {"], + "replace": [" if begun.kind != pairing.kind && false {"] + }, + { + "name": "DI-19.2.1: an end that does not repeat its begin's identity is accepted", + "file": "src/oracle/pairing.rs", + "expect": "caught", + "why": "An end repeats its begin's identity fields (DI-D-87), so a reader of one line knows what ended; the checker is where that is held.", + "find": [" if begun.subject != subject {"], + "replace": [" if begun.subject != subject && false {"] + }, + { + "name": "DI-19.2.1: an ended pair is forgotten", + "file": "src/oracle/pairing.rs", + "expect": "caught", + "why": "Nothing of a pair follows its end; a checker that forgets the end lets a second end, or a begin again, pass as something else.", + "find": [" self.ended.insert(id);"], + "replace": [" let _ = id;"] + }, + { + "name": "DI-19.2.1: a pair named as left open need not be open", + "file": "src/oracle/pairing.rs", + "expect": "caught", + "why": "The finish is bidirectional: every open pair is named, and every named pair is open, so a test cannot excuse a pair that never began.", + "find": [" Some(&id) => Err(PairingViolation::NamedNotOpen { id }),"], + "replace": [" Some(_) => Ok(()),"] + }, + { + "name": "DI-19.2.1: a lock wait's end is taken for a begin", + "file": "src/diagnostics.rs", + "expect": "caught", + "why": "Which events open and close a pair is defined once, by Diagnostic::pairing (DI-D-87), which the checker and the ETW sink both read.", + "find": [" | Self::LockWaitEnded { id, .. } => (*id, Edge::End),"], + "replace": [" | Self::LockWaitEnded { id, .. } => (*id, Edge::Begin),"] + }, + { + "name": "DI-19.2.1 control: the open pairs are gathered by cloning", + "file": "src/oracle/pairing.rs", + "expect": "survives", + "why": "A control: the same ids gathered by clone instead of copy, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let mut open: Vec = self.open.keys().copied().collect();"], + "replace": [" let mut open: Vec = self.open.keys().cloned().collect();"] + }, + { + "name": "DI-19.2.2: a seal answered durable leaves its pair open", + "file": "src/durability.rs", + "expect": "caught", + "why": "A seal's pair ends at its first answer (DI-D-87); one left open is a begin with no end, which the pairing checker refuses.", + "find": [" popped.pair,"], + "replace": [" None,"] + }, + { + "name": "DI-19.2.2: a seal answered blocked leaves its pair open", + "file": "src/durability.rs", + "expect": "caught", + "why": "Blocked is an answer, so it ends the seal's pair; left open, the pair would end later as durable, saying the wrong answer.", + "find": [" let pair = state.seals[index].pair.take();"], + "replace": [" let pair: Option = None;"] + }, + { + "name": "DI-19.2.2: a seal whose flush failed ends as durable", + "file": "src/durability.rs", + "expect": "caught", + "why": "A failure of a seal's own flush is its answer, and its pair ends saying so.", + "find": [" SealOutcome::Failed,"], + "replace": [" SealOutcome::Durable,"] + }, + { + "name": "DI-19.2.2: a lineage that ends leaves its pairs open", + "file": "src/durability/lineages.rs", + "expect": "caught", + "why": "An end is reported on every path a begin can leave by (DI-D-87); a lineage that ends answers nothing more, so its pairs end with it.", + "find": [" self.end_pairs(Some(lineage));"], + "replace": [" let _ = lineage;"] + }, + { + "name": "DI-19.2.2: the core dropped leaves its pairs open", + "file": "src/durability.rs", + "expect": "caught", + "why": "An instance closed or dropped answers nothing more, so every pair still open ends as it does.", + "find": [" self.end_pairs(None);"], + "replace": [" let _ = self;"] + }, + { + "name": "DI-19.2.2 control: the abandoned flushes are ordered by a stable sort", + "file": "src/durability.rs", + "expect": "survives", + "why": "A control: the same order by a stable sort, which no test can tell apart since the keys are distinct. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" flushes.sort_unstable_by_key(|((key, seq, _), pair)| (*key, *seq, pair.id));"], + "replace": [" flushes.sort_by_key(|((key, seq, _), pair)| (*key, *seq, pair.id));"] + }, + { + "name": "DI-19.2.3: a lock wait's begin is not reported", + "file": "src/lock_watch.rs", + "expect": "caught", + "why": "A wait is a pair whose begin is written when the immediate try fails (DI-D-87); an end with no begin is what the pairing checker refuses.", + "find": [" self.sink.report(&Diagnostic::LockWaitBegan {"], + "replace": [" let _ = &(Diagnostic::LockWaitBegan {"] + }, + { + "name": "DI-19.2.3: a lock wait's end is not reported", + "file": "src/lock_watch.rs", + "expect": "caught", + "why": "The end is written as the lock is taken, with it held (DI-D-80); a wait whose end never comes is a pair left open.", + "find": [" self.sink.report(&Diagnostic::LockWaitEnded {"], + "replace": [" let _ = &(Diagnostic::LockWaitEnded {"] + }, + { + "name": "DI-19.2.3 control: the taken guard is declared with its type spelled", + "file": "src/lock_watch.rs", + "expect": "survives", + "why": "A control: the same binding with its type written out, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let taken = mutex.lock().unwrap_or_else(PoisonError::into_inner);"], + "replace": [" let taken: MutexGuard<'_, T> = mutex.lock().unwrap_or_else(PoisonError::into_inner);"] + }, + { + "name": "DI-19.2.4: a provider's abandoned domain ends as failed", + "file": "src/durability/providers.rs", + "expect": "caught", + "why": "A flush's end says how it ended (DI-D-87): a domain its provider abandoned did not fail, and an end that says it did sends a reader after a failure that did not happen.", + "find": [" Err(FailureCause::ProviderAbandoned { .. }) => FlushOutcome::Abandoned,"], + "replace": [" Err(FailureCause::ProviderAbandoned { .. }) => FlushOutcome::Failed,"] + }, + { + "name": "DI-19.2.4: a provider's failed domain ends as succeeded", + "file": "src/durability/providers.rs", + "expect": "caught", + "why": "A failed domain whose end says it succeeded hides the failure the pair exists to show.", + "find": [" Err(_) => FlushOutcome::Failed,"], + "replace": [" Err(_) => FlushOutcome::Succeeded,"] + }, + { + "name": "DI-19.2.5: the one definition calls a seal's end a begin", + "file": "src/diagnostics.rs", + "expect": "caught", + "why": "DI-19.2.5: Diagnostic::pairing is the one definition of which events pair (DI-D-88). Changing it must fail this suite through the code that binds to it; a suite that held a copy of the rule would pass.", + "find": [ + " | Self::LockWaitBegan { id, .. } => (*id, Edge::Begin),", + " Self::SealEnded { id, .. }", + " | Self::FlushEnded { id, .. }" + ], + "replace": [ + " | Self::LockWaitBegan { id, .. }", + " | Self::SealEnded { id, .. } => (*id, Edge::Begin),", + " Self::FlushEnded { id, .. }" + ] + }, + { + "name": "DI-22.1: a pair's end is asked of wants", + "file": "src/diagnostics.rs", + "expect": "caught", + "why": "A sink that stopped wanting a kind while a pair was open is not given the end, so a sink that holds something for the pair from its begin never lets it go: the ETW sink's activity map grew by a pair for each session stop. DI-D-96.", + "find": [" if is_end || sink.wants(kind) {\n"], + "replace": [" if sink.wants(kind) {\n"] + }, + { + "name": "DI-22.1: a begin is taken for an end", + "file": "src/diagnostics.rs", + "expect": "caught", + "why": "The edge decides which events skip the question. Reading a begin as an end gives every sink every begin, wanted or not, and asks the end.", + "find": [" .is_some_and(|pairing| pairing.edge == Edge::End);\n"], + "replace": [" .is_some_and(|pairing| pairing.edge == Edge::Begin);\n"] + }, + { + "name": "DI-22.1 control: the end is read by another spelling", + "file": "src/diagnostics.rs", + "expect": "survives", + "why": "A control: `map_or(false, ..)` is `is_some_and(..)`, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" .is_some_and(|pairing| pairing.edge == Edge::End);\n"], + "replace": [" .map_or(false, |pairing| pairing.edge == Edge::End);\n"] } ] } diff --git a/crates/durable-ioring/src/access.rs b/crates/durable-ioring/src/access.rs new file mode 100644 index 000000000..d9729eac5 --- /dev/null +++ b/crates/durable-ioring/src/access.rs @@ -0,0 +1,210 @@ +// Copyright (c) 2026 Mike Grier +//! What a target can do with the data it addresses (DI-13.9, DI-D-66). +//! +//! Two axes decide it: whether the data can be read and whether it can be written. They give four +//! markers, [`NoData`], [`ReadOnly`], [`WriteOnly`] and [`ReadWrite`], which a backend's handles carry +//! as a type parameter where they are opened (`win-durable-ioring`'s `OpenedFile` is one), and +//! which the traits here sort: [`HasData`] for the three that can read or write, [`CanRead`] and +//! [`CanWrite`] for those that can do each. +//! +//! A target is one type per backend and is held by a ring under a number, so it cannot carry the +//! marker as a type parameter without letting a ring hold targets of one access only. It states its +//! access instead, as a value of [`Access`], read once when it is given to an instance, as it states +//! its warranty and its geometry. A [`HasData`] marker names the value that stands for it, which is +//! how a target built from a typed handle states the access that handle was opened with. +//! +//! What a marker asks of a platform is the backend's: `win-durable-ioring` turns [`DataAccess::READ`] +//! and [`DataAccess::WRITE`] into the rights it opens with. + +use std::fmt; + +mod sealed { + pub trait Sealed {} +} + +/// What a target can do with its data, stated by the target. A target has data to address, so there +/// is no value for neither: a handle that can do neither is not a target +/// ([`NoData`] is not [`HasData`]). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Access { + /// It can be read and not written. + ReadOnly, + /// It can be written and not read. + WriteOnly, + /// It can be read and written. + ReadWrite, +} + +impl Access { + /// Whether the data can be read. + #[must_use] + pub const fn can_read(self) -> bool { + matches!(self, Self::ReadOnly | Self::ReadWrite) + } + + /// Whether the data can be written. + #[must_use] + pub const fn can_write(self) -> bool { + matches!(self, Self::WriteOnly | Self::ReadWrite) + } + + /// Whether everything `other` can do, this can: what a target must be able to do for a key + /// that declares `other` to be registered under it. + #[must_use] + pub const fn covers(self, other: Self) -> bool { + (self.can_read() || !other.can_read()) && (self.can_write() || !other.can_write()) + } +} + +impl fmt::Display for Access { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::ReadOnly => "read only", + Self::WriteOnly => "write only", + Self::ReadWrite => "read and write", + }) + } +} + +/// The access a handle was opened with, as a type. Sealed: there are four markers, one for each +/// combination of reading and writing. +pub trait DataAccess: sealed::Sealed + Copy + fmt::Debug + Send + Sync + 'static { + /// Whether this access can read the data. + const READ: bool; + + /// Whether this access can write the data. + const WRITE: bool; + + /// This access with reading added. + type WithRead: DataAccess; + + /// This access with writing added. + type WithWrite: DataAccess; +} + +/// A [`DataAccess`] that can read or write, so that a target can be made from it, and the [`Access`] +/// a target made from it states. [`NoData`] is not one: +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{HasData, NoData}; +/// +/// fn needs_data() {} +/// needs_data::(); +/// ``` +/// +/// ``` +/// use durable_ioring::{Access, HasData, ReadOnly, ReadWrite, WriteOnly}; +/// +/// assert_eq!(ReadOnly::ACCESS, Access::ReadOnly); +/// assert_eq!(WriteOnly::ACCESS, Access::WriteOnly); +/// assert_eq!(ReadWrite::ACCESS, Access::ReadWrite); +/// ``` +pub trait HasData: DataAccess { + /// What a target made from a handle with this access states. + const ACCESS: Access; +} + +/// A [`DataAccess`] that can read. [`WriteOnly`] and [`NoData`] are not one: +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{CanRead, WriteOnly}; +/// +/// fn needs_read() {} +/// needs_read::(); +/// ``` +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{CanRead, NoData}; +/// +/// fn needs_read() {} +/// needs_read::(); +/// ``` +pub trait CanRead: HasData {} + +/// A [`DataAccess`] that can write. [`ReadOnly`] and [`NoData`] are not one: +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{CanWrite, ReadOnly}; +/// +/// fn needs_write() {} +/// needs_write::(); +/// ``` +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{CanWrite, NoData}; +/// +/// fn needs_write() {} +/// needs_write::(); +/// ``` +pub trait CanWrite: HasData {} + +/// No data right: a handle that can address a file or device without reading or writing it, which is +/// enough to query it. It cannot be made into a target. +#[derive(Clone, Copy, Debug)] +pub struct NoData; + +/// The right to read the data and not to write it. +#[derive(Clone, Copy, Debug)] +pub struct ReadOnly; + +/// The right to write the data and not to read it. +#[derive(Clone, Copy, Debug)] +pub struct WriteOnly; + +/// The rights to read and to write the data. +#[derive(Clone, Copy, Debug)] +pub struct ReadWrite; + +impl sealed::Sealed for NoData {} +impl sealed::Sealed for ReadOnly {} +impl sealed::Sealed for WriteOnly {} +impl sealed::Sealed for ReadWrite {} + +impl DataAccess for NoData { + const READ: bool = false; + const WRITE: bool = false; + type WithRead = ReadOnly; + type WithWrite = WriteOnly; +} + +impl DataAccess for ReadOnly { + const READ: bool = true; + const WRITE: bool = false; + type WithRead = ReadOnly; + type WithWrite = ReadWrite; +} + +impl DataAccess for WriteOnly { + const READ: bool = false; + const WRITE: bool = true; + type WithRead = ReadWrite; + type WithWrite = WriteOnly; +} + +impl DataAccess for ReadWrite { + const READ: bool = true; + const WRITE: bool = true; + type WithRead = ReadWrite; + type WithWrite = ReadWrite; +} + +impl HasData for ReadOnly { + const ACCESS: Access = Access::ReadOnly; +} + +impl HasData for WriteOnly { + const ACCESS: Access = Access::WriteOnly; +} + +impl HasData for ReadWrite { + const ACCESS: Access = Access::ReadWrite; +} + +impl CanRead for ReadOnly {} +impl CanRead for ReadWrite {} + +impl CanWrite for WriteOnly {} +impl CanWrite for ReadWrite {} + +#[cfg(test)] +mod tests; diff --git a/crates/durable-ioring/src/access/tests.rs b/crates/durable-ioring/src/access/tests.rs new file mode 100644 index 000000000..5db24408e --- /dev/null +++ b/crates/durable-ioring/src/access/tests.rs @@ -0,0 +1,168 @@ +// Copyright (c) 2026 Mike Grier + +use std::any::TypeId; + +use super::{ + Access, CanRead, CanWrite, DataAccess, HasData, NoData, ReadOnly, ReadWrite, WriteOnly, +}; + +fn id() -> TypeId { + TypeId::of::() +} + +/// One row for each marker: whether it reads and writes, and what adding each axis makes of it. +struct Row { + name: &'static str, + reads: bool, + writes: bool, + with_read: TypeId, + with_write: TypeId, +} + +fn row(name: &'static str) -> Row { + Row { + name, + reads: A::READ, + writes: A::WRITE, + with_read: id::(), + with_write: id::(), + } +} + +fn rows() -> [Row; 4] { + [ + row::("NoData"), + row::("ReadOnly"), + row::("WriteOnly"), + row::("ReadWrite"), + ] +} + +#[test] +fn an_access_covers_what_it_can_do_and_nothing_that_it_cannot() { + use Access::{ReadOnly as R, ReadWrite as RW, WriteOnly as W}; + let table = [ + (R, R, true), + (R, W, false), + (R, RW, false), + (W, R, false), + (W, W, true), + (W, RW, false), + (RW, R, true), + (RW, W, true), + (RW, RW, true), + ]; + for (held, wanted, covers) in table { + assert_eq!(held.covers(wanted), covers, "{held} covers {wanted}"); + } +} + +#[test] +fn each_marker_reads_and_writes_as_it_is_named() { + let expected = [ + ("NoData", false, false), + ("ReadOnly", true, false), + ("WriteOnly", false, true), + ("ReadWrite", true, true), + ]; + for (row, (name, reads, writes)) in rows().iter().zip(expected) { + assert_eq!(row.name, name); + assert_eq!((row.reads, row.writes), (reads, writes), "{name}"); + } +} + +#[test] +fn adding_an_axis_gives_the_marker_that_has_it_and_what_it_already_had() { + let markers = [ + (id::(), false, false), + (id::(), true, false), + (id::(), false, true), + (id::(), true, true), + ]; + let marker = |reads: bool, writes: bool| { + markers + .iter() + .find(|(_, r, w)| *r == reads && *w == writes) + .map(|(marker, _, _)| *marker) + .expect("one marker for each combination") + }; + for row in rows() { + assert_eq!( + row.with_read, + marker(true, row.writes), + "{} + read", + row.name + ); + assert_eq!( + row.with_write, + marker(row.reads, true), + "{} + write", + row.name + ); + } +} + +#[test] +fn the_four_markers_are_four_different_combinations_of_reading_and_writing() { + let rows = rows(); + for (index, row) in rows.iter().enumerate() { + for other in &rows[index + 1..] { + assert_ne!( + (row.reads, row.writes), + (other.reads, other.writes), + "{} and {}", + row.name, + other.name + ); + } + } +} + +fn has_data() -> Access { + A::ACCESS +} +fn can_read() {} +fn can_write() {} + +#[test] +fn a_marker_with_data_names_the_access_a_target_made_from_it_states() { + assert_eq!(has_data::(), Access::ReadOnly); + assert_eq!(has_data::(), Access::WriteOnly); + assert_eq!(has_data::(), Access::ReadWrite); +} + +#[test] +fn the_stated_access_reads_and_writes_as_the_marker_it_stands_for_does() { + fn agrees() { + assert_eq!(A::ACCESS.can_read(), A::READ); + assert_eq!(A::ACCESS.can_write(), A::WRITE); + } + agrees::(); + agrees::(); + agrees::(); +} + +#[test] +fn an_access_can_read_and_write_as_it_is_named() { + assert!(Access::ReadOnly.can_read() && !Access::ReadOnly.can_write()); + assert!(!Access::WriteOnly.can_read() && Access::WriteOnly.can_write()); + assert!(Access::ReadWrite.can_read() && Access::ReadWrite.can_write()); +} + +#[test] +fn an_access_prints_what_it_allows() { + assert_eq!(Access::ReadOnly.to_string(), "read only"); + assert_eq!(Access::WriteOnly.to_string(), "write only"); + assert_eq!(Access::ReadWrite.to_string(), "read and write"); +} + +#[test] +fn the_capability_traits_are_implemented_for_the_markers_that_have_the_capability() { + can_read::(); + can_read::(); + can_write::(); + can_write::(); +} + +// What a marker lacks is refused at compile time, and those refusals are the `compile_fail` doctests +// on the three traits in `access.rs`. diff --git a/crates/durable-ioring/src/backend.rs b/crates/durable-ioring/src/backend.rs index 92a625467..b69cb6ab1 100644 --- a/crates/durable-ioring/src/backend.rs +++ b/crates/durable-ioring/src/backend.rs @@ -121,6 +121,9 @@ pub enum RefusalCause { /// target's geometry does not admit, or a region outside its registered buffer. Retrying does /// not help. Invalid, + /// The target's access does not cover the operation: a write to a target that cannot write, or a + /// read from one that cannot read (DI-D-66). Retrying does not help. + AccessDenied, /// The backend can take no more submissions, ever; carries what it reported. Unusable(IoFault), /// The backend could not say why; carries what it reported. @@ -161,6 +164,7 @@ impl fmt::Display for RefusalCause { Self::UnknownTarget => f.write_str("no target is attached under that key"), Self::Unsupported => f.write_str("the backend cannot perform it"), Self::Invalid => f.write_str("the submission is malformed for its target"), + Self::AccessDenied => f.write_str("the target's access does not cover the operation"), Self::Unusable(fault) => write!(f, "the backend is unusable: {}", fault.error), Self::Unknown(fault) => write!(f, "refused for a reason not known: {}", fault.error), } @@ -194,6 +198,7 @@ pub(crate) fn refusal_fault(cause: RefusalCause) -> IoFault { RefusalCause::Unsupported => io::ErrorKind::Unsupported, RefusalCause::Invalid => io::ErrorKind::InvalidInput, RefusalCause::UnknownTarget => io::ErrorKind::NotFound, + RefusalCause::AccessDenied => io::ErrorKind::PermissionDenied, _ => io::ErrorKind::Other, }; IoFault { diff --git a/crates/durable-ioring/src/backend/tests.rs b/crates/durable-ioring/src/backend/tests.rs index dbe884469..e16891819 100644 --- a/crates/durable-ioring/src/backend/tests.rs +++ b/crates/durable-ioring/src/backend/tests.rs @@ -25,6 +25,7 @@ fn table() -> Vec<(RefusalCause, bool, bool, bool)> { (RefusalCause::UnknownTarget, false, false, false), (RefusalCause::Unsupported, false, false, false), (RefusalCause::Invalid, false, false, false), + (RefusalCause::AccessDenied, false, false, false), (RefusalCause::Unusable(fault()), false, false, false), (RefusalCause::Unknown(fault()), false, false, false), ] @@ -78,6 +79,11 @@ fn a_refusal_fault_carries_its_cause_and_the_backends_code() { (RefusalCause::UnknownTarget, io::ErrorKind::NotFound, None), (RefusalCause::Unsupported, io::ErrorKind::Unsupported, None), (RefusalCause::Invalid, io::ErrorKind::InvalidInput, None), + ( + RefusalCause::AccessDenied, + io::ErrorKind::PermissionDenied, + None, + ), (RefusalCause::Busy, io::ErrorKind::Other, None), ( RefusalCause::Unusable(coded()), diff --git a/crates/durable-ioring/src/contract.rs b/crates/durable-ioring/src/contract.rs index 727f3f90a..732de6968 100644 --- a/crates/durable-ioring/src/contract.rs +++ b/crates/durable-ioring/src/contract.rs @@ -13,6 +13,7 @@ use std::io; use io_buffers::{IoBuf, IoBufMut, RegisteredSpan}; use crate::io_target::IoTarget; +use crate::key::{ReadKey, TargetKey, WriteKey}; use crate::types::{ AddTargetError, DurabilityRequest, EndLineageError, Entry, Epoch, EpochState, FailureInfo, ImportScope, Imported, IoTargetKey, Leftovers, LineageInfo, PushError, ReadOptions, @@ -87,14 +88,15 @@ pub trait DurableRing { /// a `Win32FileTarget`. type Target: IoTarget; - /// A duplicate of the readiness signal (DI-D-28): one the instance owns, set after an entry - /// becomes poppable and at least whenever the queue goes from empty to non-empty, and when a - /// [`pop`](Self::pop) has a failure to report (DI-D-58). One waiter; pop until `None` after - /// every wake; a wake with nothing to pop is normal. + /// The readiness signal (DI-D-28), as a value the caller holds: a second holder of the + /// instance's signal, or a duplicate where the implementation's handles cannot be shared. It + /// is set after an entry becomes poppable and at least whenever the queue goes from empty to + /// non-empty, and when a [`pop`](Self::pop) has a failure to report (DI-D-58). One waiter; pop + /// until `None` after every wake; a wake with nothing to pop is normal. /// /// # Errors /// - /// The error from making the duplicate. + /// The error from making the value, where making it can fail. fn readiness(&mut self) -> io::Result; /// Give the instance a target after construction, under a key unique within the instance. What @@ -103,10 +105,12 @@ pub trait DurableRing { /// /// # Errors /// - /// An [`AddTargetError`] handing the target back, when one is already present under `key`. - fn add_target( + /// An [`AddTargetError`] handing the target back, when one is already present under `key`, or + /// when `key` is a [`Key`](crate::Key) that declares more access than the target states + /// (DI-13.10); the error's `cause` says which. An untyped [`IoTargetKey`] declares nothing. + fn add_target( &mut self, - key: IoTargetKey, + key: K, target: Self::Target, ) -> Result<(), AddTargetError>; @@ -168,9 +172,9 @@ pub trait DurableRing { /// # Errors /// /// As [`write_with`](Self::write_with). - fn write( + fn write( &mut self, - file: IoTargetKey, + file: K, offset: u64, buffer: Self::Buffer, tag: Tag<'_, Self::Ids>, @@ -192,9 +196,9 @@ pub trait DurableRing { /// A [`PushError`] handing back the buffer and the context, for a file the instance was not /// given, a lineage -- the write's or the gate's -- it cannot answer for, a gate already /// abandoned, a gate that would close a cycle (DI-D-23), or a ring that refused the push. - fn write_with( + fn write_with( &mut self, - file: IoTargetKey, + file: K, offset: u64, buffer: Self::Buffer, tag: Tag<'_, Self::Ids>, @@ -209,9 +213,9 @@ pub trait DurableRing { /// # Errors /// /// As [`read_with`](Self::read_with). - fn read( + fn read( &mut self, - file: IoTargetKey, + file: K, offset: u64, buffer: Self::Buffer, context: Self::Context, @@ -232,9 +236,9 @@ pub trait DurableRing { /// A [`PushError`] handing back the buffer and the context, for a file the instance was not /// given, a gate's lineage it cannot answer for, a gate already abandoned, or a ring that /// refused the push. - fn read_with( + fn read_with( &mut self, - file: IoTargetKey, + file: K, offset: u64, buffer: Self::Buffer, context: Self::Context, @@ -387,9 +391,9 @@ pub trait RegisteredBufferRing: DurableRing { /// # Errors /// /// As [`write_registered_with`](Self::write_registered_with). - fn write_registered( + fn write_registered( &mut self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, tag: Tag<'_, Self::Ids>, @@ -405,9 +409,9 @@ pub trait RegisteredBufferRing: DurableRing { /// /// A [`PushError`] handing back the context, as for [`DurableRing::write_with`], and for an /// instance built without registered buffers or a span the registration does not contain. - fn write_registered_with( + fn write_registered_with( &mut self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, tag: Tag<'_, Self::Ids>, @@ -420,9 +424,9 @@ pub trait RegisteredBufferRing: DurableRing { /// # Errors /// /// As [`read_registered_with`](Self::read_registered_with). - fn read_registered( + fn read_registered( &mut self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, context: Self::Context, @@ -435,9 +439,9 @@ pub trait RegisteredBufferRing: DurableRing { /// # Errors /// /// As [`write_registered_with`](Self::write_registered_with), less the epoch. - fn read_registered_with( + fn read_registered_with( &mut self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, context: Self::Context, diff --git a/crates/durable-ioring/src/correlation.rs b/crates/durable-ioring/src/correlation.rs new file mode 100644 index 000000000..120f2171f --- /dev/null +++ b/crates/durable-ioring/src/correlation.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2026 Mike Grier +//! Correlation ids (DI-D-80): what pairs a begin event with its end, so a sink's analysis computes +//! the time between them and nothing in dioring measures it. +//! +//! An id is a `u64` from one counter for the whole process, so it is unique among the ids the +//! process has minted and says nothing about any other process's. It is minted only for a pair a +//! sink wants: [`CorrelationId::for_pair`] asks the sink first, so a sink that wants nothing costs +//! the counter nothing. + +use std::fmt; +use std::sync::atomic::{AtomicU64, Ordering}; + +use crate::diagnostics::{DiagnosticKind, DiagnosticSink}; + +#[cfg(test)] +mod tests; + +/// The process's counter. Relaxed is enough: an id must differ from every other, which the +/// counter's single modification order gives, and orders nothing else (DI-D-80). +static NEXT: AtomicU64 = AtomicU64::new(FIRST); + +/// The first id a counter mints. +const FIRST: u64 = 1; + +/// What pairs a begin event with its end: unique among the ids this process has minted. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct CorrelationId(u64); + +impl CorrelationId { + /// A new id for a pair of events of `kind`, if `sink` wants that kind, from the process's + /// counter; `None` if it does not, and then nothing is minted. Asked once, when the begin is + /// built, so the begin and its end carry the same id. + /// + /// The counter starts at 1 and wraps on overflow, as `fetch_add` does. + #[must_use] + pub fn for_pair(sink: &dyn DiagnosticSink, kind: DiagnosticKind) -> Option { + mint_for(&NEXT, sink, kind) + } + + /// The id's number. + #[must_use] + pub const fn get(self) -> u64 { + self.0 + } + /// An id with the number a test chose, which no counter minted. + #[cfg(test)] + pub(crate) const fn chosen(n: u64) -> Self { + Self(n) + } +} + +impl fmt::Display for CorrelationId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// [`CorrelationId::for_pair`], from `counter`: the process's, or a test's own. +fn mint_for( + counter: &AtomicU64, + sink: &dyn DiagnosticSink, + kind: DiagnosticKind, +) -> Option { + sink.wants(kind) + .then(|| CorrelationId(counter.fetch_add(1, Ordering::Relaxed))) +} diff --git a/crates/durable-ioring/src/correlation/tests.rs b/crates/durable-ioring/src/correlation/tests.rs new file mode 100644 index 000000000..44118cdb3 --- /dev/null +++ b/crates/durable-ioring/src/correlation/tests.rs @@ -0,0 +1,169 @@ +// Copyright (c) 2026 Mike Grier +//! Correlation ids (DI-19.1): distinct across threads, rising on one, and minted only for a pair a +//! sink wants. + +use std::collections::HashSet; +use std::sync::atomic::AtomicU64; +use std::thread; + +use super::{CorrelationId, FIRST, mint_for}; +use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink, NoDiagnostics}; + +/// A sink that wants only the kinds it was given. +struct Wants(Vec); + +impl DiagnosticSink for Wants { + fn wants(&self, kind: DiagnosticKind) -> bool { + self.0.contains(&kind) + } + + fn report(&self, _: &Diagnostic<'_>) {} +} + +fn everything() -> Wants { + Wants(vec![ + DiagnosticKind::Inconsistency, + DiagnosticKind::LockWait, + DiagnosticKind::Seal, + DiagnosticKind::Flush, + ]) +} + +#[test] +fn a_counter_mints_from_one() { + let counter = AtomicU64::new(FIRST); + let id = mint_for(&counter, &everything(), DiagnosticKind::LockWait).expect("wanted"); + assert_eq!(id.get(), 1); +} + +#[test] +fn ids_minted_on_one_thread_rise() { + let counter = AtomicU64::new(FIRST); + let sink = everything(); + let ids: Vec = (0..100) + .map(|_| { + mint_for(&counter, &sink, DiagnosticKind::Flush) + .expect("wanted") + .get() + }) + .collect(); + assert!(ids.windows(2).all(|pair| pair[0] < pair[1]), "{ids:?}"); + assert_eq!( + ids, + (1..=100).collect::>(), + "one apart, from the first" + ); +} + +#[test] +fn ids_from_the_process_counter_on_one_thread_rise() { + let sink = everything(); + let first = CorrelationId::for_pair(&sink, DiagnosticKind::LockWait).expect("wanted"); + let second = CorrelationId::for_pair(&sink, DiagnosticKind::LockWait).expect("wanted"); + assert!(first < second, "{first} then {second}"); +} + +#[test] +fn ids_minted_on_many_threads_at_once_are_all_distinct() { + const THREADS: usize = 8; + const EACH: usize = 1_000; + let counter = AtomicU64::new(FIRST); + let ids: Vec = thread::scope(|scope| { + let minting: Vec<_> = (0..THREADS) + .map(|_| { + scope.spawn(|| { + let sink = everything(); + (0..EACH) + .map(|_| { + mint_for(&counter, &sink, DiagnosticKind::Seal) + .expect("wanted") + .get() + }) + .collect::>() + }) + }) + .collect(); + minting + .into_iter() + .flat_map(|handle| handle.join().expect("a minting thread")) + .collect() + }); + let distinct: HashSet = ids.iter().copied().collect(); + assert_eq!(distinct.len(), THREADS * EACH, "no id minted twice"); + let expected: HashSet = (1..=(THREADS * EACH) as u64).collect(); + assert_eq!(distinct, expected, "none skipped"); +} + +#[test] +fn the_process_counter_gives_distinct_ids_to_many_threads() { + let ids: Vec = thread::scope(|scope| { + let minting: Vec<_> = (0..4) + .map(|_| { + scope.spawn(|| { + let sink = everything(); + (0..250) + .map(|_| { + CorrelationId::for_pair(&sink, DiagnosticKind::Flush).expect("wanted") + }) + .collect::>() + }) + }) + .collect(); + minting + .into_iter() + .flat_map(|handle| handle.join().expect("a minting thread")) + .collect() + }); + let distinct: HashSet = ids.iter().copied().collect(); + assert_eq!(distinct.len(), ids.len()); +} + +#[test] +fn a_sink_that_wants_nothing_takes_no_id() { + let counter = AtomicU64::new(FIRST); + for kind in everything().0 { + assert_eq!(mint_for(&counter, &NoDiagnostics, kind), None); + } + let next = mint_for(&counter, &everything(), DiagnosticKind::LockWait).expect("wanted"); + assert_eq!(next.get(), FIRST, "nothing was taken before"); +} + +#[test] +fn a_sink_that_wants_some_kinds_takes_ids_only_for_those() { + let counter = AtomicU64::new(FIRST); + let sink = Wants(vec![DiagnosticKind::Flush]); + assert_eq!(mint_for(&counter, &sink, DiagnosticKind::LockWait), None); + let wanted = mint_for(&counter, &sink, DiagnosticKind::Flush).expect("wanted"); + assert_eq!(mint_for(&counter, &sink, DiagnosticKind::Seal), None); + let again = mint_for(&counter, &sink, DiagnosticKind::Flush).expect("wanted"); + assert_eq!( + (wanted.get(), again.get()), + (1, 2), + "the declined kinds took none between" + ); +} + +#[test] +fn the_process_counter_takes_nothing_for_a_sink_that_wants_nothing() { + for kind in everything().0 { + assert_eq!(CorrelationId::for_pair(&NoDiagnostics, kind), None); + } +} + +#[test] +fn the_counter_wraps_as_fetch_add_does() { + let counter = AtomicU64::new(u64::MAX); + let sink = everything(); + let last = mint_for(&counter, &sink, DiagnosticKind::LockWait).expect("wanted"); + let wrapped = mint_for(&counter, &sink, DiagnosticKind::LockWait).expect("wanted"); + assert_eq!((last.get(), wrapped.get()), (u64::MAX, 0)); +} + +#[test] +fn an_id_shows_as_its_number() { + let counter = AtomicU64::new(41); + let id = mint_for(&counter, &everything(), DiagnosticKind::LockWait).expect("wanted"); + assert_eq!(id.to_string(), "41"); + assert_eq!(id.get(), 41); + assert_eq!(format!("{id:?}"), "CorrelationId(41)"); +} diff --git a/crates/durable-ioring/src/diagnostics.rs b/crates/durable-ioring/src/diagnostics.rs index 157b20b36..bd98a90bb 100644 --- a/crates/durable-ioring/src/diagnostics.rs +++ b/crates/durable-ioring/src/diagnostics.rs @@ -8,16 +8,116 @@ use std::fmt; use std::sync::Arc; +use crate::correlation::CorrelationId; +use crate::types::{FlushDomain, IoTargetKey}; + #[cfg(test)] pub(crate) mod tests; /// What a [`Diagnostic`] is about: what a sink answers [`DiagnosticSink::wants`] for, before the -/// event is built. Open to further kinds -- the delay events of `DI-12.1` among them. +/// event is built and before any clock is read for it. Open to further kinds. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum DiagnosticKind { /// dioring found its own state inconsistent (DI-D-46). Inconsistency, + /// A wait for one of an instance's locks that was not immediate: a pair (DI-D-26, DI-D-87). + LockWait, + /// A seal, from its being asked for to its being answered: a pair (DI-D-80, DI-D-87). + Seal, + /// One part of a seal's flush, from its issue to its answer: a pair (DI-D-80, DI-D-87). + Flush, +} + +/// Which end of a pair an event is (DI-D-80). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Edge { + /// The pair's first event. + Begin, + /// The pair's last event: nothing of the pair follows it. + End, +} + +/// What makes an event one end of a pair: the pair's kind, its id, and which end. The one +/// definition of which events pair, which the pairing checker and a sink that marks the ends +/// both read (DI-D-87). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Pairing { + /// The pair's kind, which its begin and its end share. + pub kind: DiagnosticKind, + /// The pair's id, which its begin and its end share. + pub id: CorrelationId, + /// Which end this is. + pub edge: Edge, +} + +/// How a seal's pair ended (DI-D-87). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum SealOutcome { + /// The seal was answered durable. + Durable, + /// The seal was answered with a failure of its own flush. + Failed, + /// The seal was answered blocked by an earlier failure. + Blocked, + /// The seal was never answered: its lineage ended, or its instance was closed or dropped. + Ended, +} + +impl fmt::Display for SealOutcome { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Durable => "durable", + Self::Failed => "failed", + Self::Blocked => "blocked", + Self::Ended => "ended unanswered", + }) + } +} + +/// How a flush's pair ended (DI-D-87). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum FlushOutcome { + /// The part answered success. + Succeeded, + /// The part answered a failure. + Failed, + /// The part was never answered: its completion was dropped, its lineage ended, or its + /// instance was closed or dropped. + Abandoned, +} + +impl fmt::Display for FlushOutcome { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Succeeded => "succeeded", + Self::Failed => "failed", + Self::Abandoned => "abandoned", + }) + } +} + +/// Which part of a seal's flush an event is about. +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum FlushPart<'a> { + /// The built-in default's flush of one target. + File(IoTargetKey), + /// The consumer's provider's part for one flush domain. + Domain(&'a FlushDomain), +} + +impl fmt::Display for FlushPart<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::File(key) => write!(f, "file {}", key.0), + Self::Domain(domain) => { + write!(f, "domain {:?}", String::from_utf8_lossy(domain.bytes())) + } + } + } } /// One event dioring reports. Open to further kinds, so match it with a wildcard arm. @@ -35,6 +135,77 @@ pub enum Diagnostic<'a> { /// What was in hand when it was found: lineage, seal or epoch, operation, target or domain. detail: fmt::Arguments<'a>, }, + /// A seal was asked for: the begin of its pair (DI-D-87). + SealBegan { + /// The instance, by the number it was minted with. + instance: u64, + /// The lineage, by its place among the instance's lineages: the default is 0. + lineage: u64, + /// The epoch the seal is through, as its identity renders itself. + through: fmt::Arguments<'a>, + /// The pair's id. + id: CorrelationId, + }, + /// A seal was answered, or will never be: the end of its pair (DI-D-87). + SealEnded { + /// The instance, by the number it was minted with. + instance: u64, + /// The lineage, by its place among the instance's lineages: the default is 0. + lineage: u64, + /// The epoch the seal is through, as its identity renders itself. + through: fmt::Arguments<'a>, + /// The pair's id. + id: CorrelationId, + /// How it ended. + outcome: SealOutcome, + }, + /// One part of a seal's flush was issued: the begin of its pair (DI-D-87). + FlushBegan { + /// The instance, by the number it was minted with. + instance: u64, + /// The lineage, by its place among the instance's lineages: the default is 0. + lineage: u64, + /// The epoch the seal is through, as its identity renders itself. + through: fmt::Arguments<'a>, + /// Which part. + part: FlushPart<'a>, + /// The pair's id. + id: CorrelationId, + }, + /// One part of a seal's flush answered, or will never: the end of its pair (DI-D-87). + FlushEnded { + /// The instance, by the number it was minted with. + instance: u64, + /// The lineage, by its place among the instance's lineages: the default is 0. + lineage: u64, + /// The epoch the seal is through, as its identity renders itself. + through: fmt::Arguments<'a>, + /// Which part. + part: FlushPart<'a>, + /// The pair's id. + id: CorrelationId, + /// How it ended. + outcome: FlushOutcome, + }, + /// An acquisition of one of an instance's locks found it held, and waits: the begin of its + /// pair (DI-D-87). + LockWaitBegan { + /// The instance, by the number it was minted with. + instance: u64, + /// Which lock: a name per lock. + lock: &'static str, + /// The pair's id. + id: CorrelationId, + }, + /// The waiting acquisition took the lock, and holds it: the end of its pair (DI-D-87). + LockWaitEnded { + /// The instance, by the number it was minted with. + instance: u64, + /// Which lock: a name per lock. + lock: &'static str, + /// The pair's id. + id: CorrelationId, + }, } impl Diagnostic<'_> { @@ -43,8 +214,30 @@ impl Diagnostic<'_> { pub fn kind(&self) -> DiagnosticKind { match self { Self::Inconsistency { .. } => DiagnosticKind::Inconsistency, + Self::SealBegan { .. } | Self::SealEnded { .. } => DiagnosticKind::Seal, + Self::FlushBegan { .. } | Self::FlushEnded { .. } => DiagnosticKind::Flush, + Self::LockWaitBegan { .. } | Self::LockWaitEnded { .. } => DiagnosticKind::LockWait, } } + + /// Which end of which pair the event is, or `None` for an event that is not one. + #[must_use] + pub fn pairing(&self) -> Option { + let (id, edge) = match self { + Self::SealBegan { id, .. } + | Self::FlushBegan { id, .. } + | Self::LockWaitBegan { id, .. } => (*id, Edge::Begin), + Self::SealEnded { id, .. } + | Self::FlushEnded { id, .. } + | Self::LockWaitEnded { id, .. } => (*id, Edge::End), + Self::Inconsistency { .. } => return None, + }; + Some(Pairing { + kind: self.kind(), + id, + edge, + }) + } } impl fmt::Display for Diagnostic<'_> { @@ -58,18 +251,83 @@ impl fmt::Display for Diagnostic<'_> { f, "instance {instance} found its own state inconsistent: {what} ({detail})" ), + Self::SealBegan { + instance, + lineage, + through, + id, + } => write!( + f, + "instance {instance} lineage {lineage} seal through {through} (pair {id}): began" + ), + Self::SealEnded { + instance, + lineage, + through, + id, + outcome, + } => write!( + f, + "instance {instance} lineage {lineage} seal through {through} (pair {id}): {outcome}" + ), + Self::FlushBegan { + instance, + lineage, + through, + part, + id, + } => write!( + f, + "instance {instance} lineage {lineage} seal through {through}: flush of {part} \ + (pair {id}) began" + ), + Self::FlushEnded { + instance, + lineage, + through, + part, + id, + outcome, + } => write!( + f, + "instance {instance} lineage {lineage} seal through {through}: flush of {part} \ + (pair {id}) {outcome}" + ), + Self::LockWaitBegan { instance, lock, id } => { + write!( + f, + "instance {instance} (pair {id}): waits for its {lock} lock" + ) + } + Self::LockWaitEnded { instance, lock, id } => { + write!(f, "instance {instance} (pair {id}): took its {lock} lock") + } } } } /// Where an instance's diagnostics go (DI-D-51). Called from whatever thread observed the event, /// with dioring's lock held, so a sink returns promptly and never calls back into the instance. +/// +/// Its own synchronization is a leaf beneath dioring's (DI-D-80): a lock a sink takes is never +/// taken in a way that can wait on anything dioring's locks guard, and is never held by a thread +/// while it calls into an instance -- a consumer that holds a sink's lock around a call on the +/// instance included, since that call may report to the sink under the instance's lock. A lock +/// wait's end is reported with the lock just taken (DI-D-87), so a sink that broke this could +/// deadlock against the instance it reports on. pub trait DiagnosticSink: Send + Sync { /// Whether the sink wants events of `kind`. Asked before each event is built, so answering /// `false` costs the core a branch and nothing more. fn wants(&self, kind: DiagnosticKind) -> bool; - /// Report `event`, which is of a kind [`wants`](Self::wants) answered `true` for. + /// Report `event`, which is of a kind [`wants`](Self::wants) answered `true` for, **except a pair's end**. + /// + /// A pair's end is reported whatever a sink wants, because a sink may hold something for the pair from its begin and the + /// end is what lets it go. **A sink makes no assumption that its begins and ends match (DI-D-96):** what it wants can change + /// while a pair is open, and a router or a filter between the core and a sink can deliver an end whose begin the sink was + /// never given, from a client that did nothing wrong. So a sink tolerates an end with no begin, as it tolerates a begin that + /// is never ended, and what it records of one is its own decision; it filters what it would over-log, and releases what it + /// held for a pair at the pair's end. fn report(&self, event: &Diagnostic<'_>); } @@ -85,7 +343,11 @@ impl DiagnosticSink for NoDiagnostics { fn report(&self, _: &Diagnostic<'_>) {} } -/// A sink that writes every event as a line on standard error, prefixed `dioring:`. +/// What a sink that writes an event as a line puts before it: the sinks that ship each write `dioring:`, a +/// space, and the event's text. +pub const LINE_PREFIX: &str = "dioring: "; + +/// A sink that writes every event as a line on standard error, prefixed [`LINE_PREFIX`]. #[derive(Clone, Copy, Debug, Default)] pub struct StderrDiagnostics; @@ -95,11 +357,17 @@ impl DiagnosticSink for StderrDiagnostics { } fn report(&self, event: &Diagnostic<'_>) { - eprintln!("dioring: {event}"); + eprintln!("{LINE_PREFIX}{event}"); } } -/// A sink that hands each event to every sink it holds that wants its kind, in order. +/// A sink that hands each event to every sink it holds that wants its kind, in order, and a pair's end to every sink it +/// holds. +/// +/// **A pair's end is not asked of `wants`.** Whether a sink wants a kind can change between a pair's begin and its end, as a +/// trace session stops or starts, and a sink that holds something for a pair from its begin has to be given the end that lets +/// it go. So an end goes to every sink, in order, and each decides what to do with it, including that it never saw the begin +/// (DI-D-96). The fan-out holds no state of its own about which pairs are open. #[derive(Clone, Default)] pub struct FanOutDiagnostics { sinks: Vec>, @@ -128,8 +396,13 @@ impl DiagnosticSink for FanOutDiagnostics { fn report(&self, event: &Diagnostic<'_>) { let kind = event.kind(); - for sink in self.sinks.iter().filter(|sink| sink.wants(kind)) { - sink.report(event); + let is_end = event + .pairing() + .is_some_and(|pairing| pairing.edge == Edge::End); + for sink in &self.sinks { + if is_end || sink.wants(kind) { + sink.report(event); + } } } } diff --git a/crates/durable-ioring/src/diagnostics/tests.rs b/crates/durable-ioring/src/diagnostics/tests.rs index 8fee65005..9547290c7 100644 --- a/crates/durable-ioring/src/diagnostics/tests.rs +++ b/crates/durable-ioring/src/diagnostics/tests.rs @@ -4,8 +4,10 @@ use std::sync::{Arc, Mutex}; use super::{ - Diagnostic, DiagnosticKind, DiagnosticSink, FanOutDiagnostics, NoDiagnostics, StderrDiagnostics, + Diagnostic, DiagnosticKind, DiagnosticSink, FanOutDiagnostics, FlushPart, NoDiagnostics, + StderrDiagnostics, }; +use crate::types::{FlushDomain, IoTargetKey}; /// A sink that keeps each event it is given, as text, and wants only what it was told to. #[derive(Default)] @@ -81,6 +83,90 @@ fn an_event_is_of_its_kind() { assert_eq!(event.kind(), DiagnosticKind::Inconsistency); } +#[test] +fn each_pair_event_is_of_its_kind() { + use crate::correlation::CorrelationId; + let id = CorrelationId::chosen(1); + let seal = Diagnostic::SealBegan { + instance: 1, + lineage: 0, + through: format_args!("3"), + id, + }; + let flush = Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("3"), + part: FlushPart::File(IoTargetKey(2)), + id, + }; + let lock = Diagnostic::LockWaitEnded { + instance: 1, + lock: "engine", + id, + }; + assert_eq!(seal.kind(), DiagnosticKind::Seal); + assert_eq!(flush.kind(), DiagnosticKind::Flush); + assert_eq!(lock.kind(), DiagnosticKind::LockWait); +} + +#[test] +fn a_flush_end_names_its_part_its_pair_and_how_it_ended() { + use super::FlushOutcome; + use crate::correlation::CorrelationId; + let id = CorrelationId::chosen(4); + let file = Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(7)), + id, + outcome: FlushOutcome::Failed, + }; + assert_eq!( + file.to_string(), + "instance 2 lineage 1 seal through 9: flush of file 7 (pair 4) failed" + ); + let domain = FlushDomain::new(b"ssd".to_vec()); + let part = Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id, + outcome: FlushOutcome::Succeeded, + }; + assert_eq!( + part.to_string(), + "instance 2 lineage 1 seal through 9: flush of domain \"ssd\" (pair 4) succeeded" + ); +} + +#[test] +fn a_domain_whose_name_is_not_text_is_shown_with_the_replacement_character() { + let domain = FlushDomain::new(vec![0xff, b'a']); + assert_eq!( + FlushPart::Domain(&domain).to_string(), + "domain \"\u{fffd}a\"" + ); +} + +#[test] +fn a_line_is_prefixed_dioring_colon_and_a_space() { + assert_eq!(super::LINE_PREFIX, "dioring: "); +} + +#[test] +fn stderr_diagnostics_wants_every_pair_kind() { + for kind in [ + DiagnosticKind::LockWait, + DiagnosticKind::Seal, + DiagnosticKind::Flush, + ] { + assert!(StderrDiagnostics.wants(kind), "{kind:?}"); + } +} + #[test] fn a_fan_out_reports_to_every_sink_that_wants_the_kind_in_order() { let first = Recording::wanting(); @@ -115,3 +201,337 @@ fn a_fan_out_debugs_by_its_count() { let fan = FanOutDiagnostics::new(vec![Arc::new(NoDiagnostics), Arc::new(StderrDiagnostics)]); assert_eq!(format!("{fan:?}"), "FanOutDiagnostics { sinks: 2 }"); } + +#[test] +fn each_end_of_a_pair_says_its_kind_id_and_edge_and_nothing_else_is_a_pair() { + use super::{Edge, FlushOutcome, Pairing, SealOutcome}; + use crate::correlation::CorrelationId; + let id = CorrelationId::chosen(42); + let domain = FlushDomain::new(b"log".as_slice()); + let pairing = |event: &Diagnostic<'_>| event.pairing(); + let expect = |kind, edge| Some(Pairing { kind, id, edge }); + assert_eq!( + pairing(&Diagnostic::SealBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + id + }), + expect(DiagnosticKind::Seal, Edge::Begin) + ); + assert_eq!( + pairing(&Diagnostic::SealEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + id, + outcome: SealOutcome::Durable + }), + expect(DiagnosticKind::Seal, Edge::End) + ); + assert_eq!( + pairing(&Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id + }), + expect(DiagnosticKind::Flush, Edge::Begin) + ); + assert_eq!( + pairing(&Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::File(IoTargetKey(3)), + id, + outcome: FlushOutcome::Abandoned + }), + expect(DiagnosticKind::Flush, Edge::End) + ); + assert_eq!( + pairing(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id + }), + expect(DiagnosticKind::LockWait, Edge::Begin) + ); + assert_eq!( + pairing(&Diagnostic::LockWaitEnded { + instance: 1, + lock: "engine", + id + }), + expect(DiagnosticKind::LockWait, Edge::End) + ); + assert_eq!( + pairing(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("y") + }), + None + ); +} + +#[test] +fn every_end_of_a_pair_shows_its_id_and_how_it_ended() { + use super::{FlushOutcome, SealOutcome}; + use crate::correlation::CorrelationId; + let id = CorrelationId::chosen(17); + let lines = [ + Diagnostic::SealBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + id, + } + .to_string(), + Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + id, + outcome: SealOutcome::Blocked, + } + .to_string(), + Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + id, + outcome: SealOutcome::Ended, + } + .to_string(), + Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(4)), + id, + } + .to_string(), + Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(4)), + id, + outcome: FlushOutcome::Failed, + } + .to_string(), + Diagnostic::LockWaitBegan { + instance: 2, + lock: "engine", + id, + } + .to_string(), + Diagnostic::LockWaitEnded { + instance: 2, + lock: "engine", + id, + } + .to_string(), + ]; + for line in &lines { + assert!(line.contains("(pair 17)"), "{line}"); + assert!(line.starts_with("instance 2"), "{line}"); + } + assert!(lines[0].ends_with("began")); + assert!(lines[1].ends_with("blocked")); + assert!(lines[2].ends_with("ended unanswered")); + assert!(lines[3].contains("flush of file 4") && lines[3].ends_with("began")); + assert!(lines[4].ends_with("failed")); + assert!(lines[5].contains("waits for its engine lock")); + assert!(lines[6].contains("took its engine lock")); +} + +// --- a pair's end goes to every sink, and each filters (DI-D-96) --- + +/// A sink that keeps each event it is given, and whose answer to `wants` a test changes while a pair is open. +struct Switched { + wants: std::sync::atomic::AtomicBool, + seen: Mutex>, +} + +impl Switched { + fn new(wants: bool) -> Arc { + Arc::new(Self { + wants: std::sync::atomic::AtomicBool::new(wants), + seen: Mutex::new(Vec::new()), + }) + } + + fn set(&self, wants: bool) { + self.wants.store(wants, std::sync::atomic::Ordering::SeqCst); + } + + fn seen(&self) -> Vec { + self.seen.lock().expect("the record").clone() + } +} + +impl DiagnosticSink for Switched { + fn wants(&self, _: DiagnosticKind) -> bool { + self.wants.load(std::sync::atomic::Ordering::SeqCst) + } + + fn report(&self, event: &Diagnostic<'_>) { + self.seen + .lock() + .expect("the record") + .push(event.to_string()); + } +} + +fn lock_begin(n: u64) -> Diagnostic<'static> { + Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id: crate::correlation::CorrelationId::chosen(n), + } +} + +fn lock_end(n: u64) -> Diagnostic<'static> { + Diagnostic::LockWaitEnded { + instance: 1, + lock: "engine", + id: crate::correlation::CorrelationId::chosen(n), + } +} + +fn seal_begin(n: u64) -> Diagnostic<'static> { + Diagnostic::SealBegan { + instance: 1, + lineage: 0, + through: format_args!("e"), + id: crate::correlation::CorrelationId::chosen(n), + } +} + +fn seal_end(n: u64) -> Diagnostic<'static> { + Diagnostic::SealEnded { + instance: 1, + lineage: 0, + through: format_args!("e"), + id: crate::correlation::CorrelationId::chosen(n), + outcome: super::SealOutcome::Durable, + } +} + +fn two() -> (Arc, Arc, FanOutDiagnostics) { + let (a, b) = (Switched::new(true), Switched::new(true)); + let fan = FanOutDiagnostics::new(vec![a.clone(), b.clone()]); + (a, b, fan) +} + +#[test] +fn an_end_is_given_to_a_sink_that_stopped_wanting_the_kind_after_the_begin() { + let (a, b, fan) = two(); + fan.report(&lock_begin(1)); + a.set(false); + fan.report(&lock_end(1)); + assert_eq!( + a.seen().len(), + 2, + "it may hold something for the pair: {:?}", + a.seen() + ); + assert_eq!( + b.seen(), + a.seen(), + "and the sink that never stopped saw both" + ); +} + +#[test] +fn an_end_is_given_to_a_sink_that_began_wanting_the_kind_after_the_begin_for_it_to_filter() { + let (a, b, fan) = two(); + a.set(false); + fan.report(&lock_begin(1)); + a.set(true); + fan.report(&lock_end(1)); + assert_eq!( + a.seen().len(), + 1, + "the begin was not given to it, and the end is, so that it can decide: {:?}", + a.seen() + ); + assert!(a.seen()[0].contains("took its"), "{:?}", a.seen()); + assert_eq!(b.seen().len(), 2); +} + +#[test] +fn an_end_is_given_to_a_sink_that_wants_nothing_and_a_begin_is_not() { + let (a, b, fan) = two(); + a.set(false); + b.set(false); + fan.report(&seal_begin(1)); + assert!( + a.seen().is_empty() && b.seen().is_empty(), + "no begin for a sink that does not want the kind" + ); + fan.report(&seal_end(1)); + assert_eq!(a.seen().len(), 1); + assert_eq!(b.seen().len(), 1); +} + +#[test] +fn every_kind_of_pair_end_is_given_to_every_sink() { + let (a, b, fan) = two(); + a.set(false); + b.set(false); + for end in [lock_end(1), seal_end(2)] { + fan.report(&end); + } + assert_eq!(a.seen().len(), 2); + assert_eq!(b.seen(), a.seen()); +} + +#[test] +fn an_end_reaches_the_sinks_in_their_order() { + let order = Arc::new(Mutex::new(Vec::::new())); + struct Ordered(usize, Arc>>); + impl DiagnosticSink for Ordered { + fn wants(&self, _: DiagnosticKind) -> bool { + false + } + fn report(&self, _: &Diagnostic<'_>) { + self.1.lock().expect("order").push(self.0); + } + } + let fan = FanOutDiagnostics::new( + (0..4) + .map(|n| Arc::new(Ordered(n, order.clone())) as Arc) + .collect(), + ); + fan.report(&lock_end(1)); + assert_eq!(*order.lock().expect("order"), [0, 1, 2, 3]); +} + +#[test] +fn an_event_that_is_not_a_pair_still_goes_to_the_sinks_that_want_its_kind_now() { + let (a, b, fan) = two(); + b.set(false); + fan.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("y"), + }); + assert_eq!(a.seen().len(), 1); + assert!( + b.seen().is_empty(), + "no end is involved, so what it wants decides" + ); +} + +#[test] +fn a_begin_is_given_only_to_the_sinks_that_want_its_kind_now() { + let (a, b, fan) = two(); + b.set(false); + fan.report(&seal_begin(1)); + assert_eq!(a.seen().len(), 1); + assert!(b.seen().is_empty()); +} diff --git a/crates/durable-ioring/src/driver.rs b/crates/durable-ioring/src/driver.rs index a99a9a0d6..2405ba3b8 100644 --- a/crates/durable-ioring/src/driver.rs +++ b/crates/durable-ioring/src/driver.rs @@ -10,7 +10,8 @@ pub use crate::domains::first_repeat; pub use crate::durability::inconsistent; -pub use crate::engine::{Engine, EngineGuard, Slot, lock, present}; +pub use crate::engine::{Engine, EngineGuard, Slot, lock, present, present_watched}; pub use crate::ids::{Home, InstanceId}; +pub use crate::lock_watch::{ENGINE_LOCK, LockWatch, WatchedGuard}; pub use crate::provider::{Answer, DomainCall}; pub use crate::pump::{Pumped, PumpedDelivery, PumpedSetup}; diff --git a/crates/durable-ioring/src/durability.rs b/crates/durable-ioring/src/durability.rs index 81043a814..0a5904f84 100644 --- a/crates/durable-ioring/src/durability.rs +++ b/crates/durable-ioring/src/durability.rs @@ -54,7 +54,7 @@ //! domain through [`providers`]; a failed or abandoned domain is observed as a failed flush is, with //! its own reach: the files whose declared domains contain it, and every file declared with none. -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; use std::fmt; use std::ops::Bound; use std::sync::Arc; @@ -62,12 +62,15 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::clock::{Instant, StampClock}; use crate::contract::EpochId; -use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink}; +use crate::correlation::CorrelationId; +use crate::diagnostics::{ + Diagnostic, DiagnosticKind, DiagnosticSink, FlushOutcome, FlushPart, SealOutcome, +}; use crate::domains::DomainId; use crate::ids::{DioringIds, FailureId, FailureToken, InstanceId, Lineage, OpId}; use crate::types::{ - Entry, Epoch, Failed, FailureCause, FailureInfo, IoFault, IoTargetKey, Marking, MarkingKind, - Resolution, ResolveError, ResolveRefusal, SuspectSet, SuspectWrite, + Entry, Epoch, Failed, FailureCause, FailureInfo, FlushDomain, IoFault, IoTargetKey, Marking, + MarkingKind, Resolution, ResolveError, ResolveRefusal, SuspectSet, SuspectWrite, }; mod files; @@ -393,6 +396,9 @@ struct Seal { stage: SealStage, /// Answered on the queue already, by `Failed` or `Blocked`; `Durable` is appended regardless. answered: bool, + /// Its pair's id while the pair is open: taken as the pair ends, at the seal's first answer, + /// so it ends once (DI-D-87). + pair: Option, } /// Where an unresolved failure stands. @@ -535,8 +541,74 @@ pub(crate) struct Durability { /// The domains the consumer's provider serves, sorted; empty without one. served: Arc<[DomainId]>, clock: K, - /// Where an inconsistency is reported before the panic (DI-D-51). + /// Where an inconsistency is reported before the panic (DI-D-51), and the seal and flush pairs + /// (DI-D-80). diagnostics: Arc, + /// Each part of a seal's flush whose pair is open, kept only while the sink wants the pair, and + /// removed as the pair ends (DI-D-87). + flush_pairs: HashMap<(Key, u64, Part), FlushPair>, + /// The consumer's name for each domain the provider serves, which a flush pair reports. + served_names: HashMap, +} + +/// An open flush pair: its id, and the seal it is part of. +struct FlushPair { + id: CorrelationId, + through: E, +} + +/// `part`, as a flush pair reports it, the domain by the name `names` gives it. +fn flush_part(part: Part, names: &HashMap) -> Option> { + match part { + Part::File(file) => Some(FlushPart::File(file)), + Part::Domain(domain) => names.get(&domain).map(FlushPart::Domain), + } +} + +/// Report the end of a seal's pair, if it has one open. +fn end_seal( + sink: &dyn DiagnosticSink, + instance: u64, + lineage: Key, + through: &E, + pair: Option, + outcome: SealOutcome, +) { + if let Some(id) = pair { + sink.report(&Diagnostic::SealEnded { + instance, + lineage, + through: format_args!("{through:?}"), + id, + outcome, + }); + } +} + +/// Report the end of a flush part's pair. +fn end_flush( + sink: &dyn DiagnosticSink, + instance: u64, + lineage: Key, + pair: &FlushPair, + part: FlushPart<'_>, + outcome: FlushOutcome, +) { + sink.report(&Diagnostic::FlushEnded { + instance, + lineage, + through: format_args!("{:?}", pair.through), + part, + id: pair.id, + outcome, + }); +} + +/// One part of a seal's flush, as the core keys it while it is out. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(super) enum Part { + File(IoTargetKey), + Domain(DomainId), } impl Durability { @@ -559,6 +631,8 @@ impl Durability { served: Arc::from([]), clock, diagnostics, + flush_pairs: HashMap::new(), + served_names: HashMap::new(), } } @@ -790,11 +864,21 @@ impl> state.sealed_through = Some(through); let seq = state.next_seal; state.next_seal += 1; + let pair = CorrelationId::for_pair(&*self.diagnostics, DiagnosticKind::Seal); + if let Some(id) = pair { + self.diagnostics.report(&Diagnostic::SealBegan { + instance: self.instance.number(), + lineage, + through: format_args!("{through:?}"), + id, + }); + } state.seals.push_back(Seal { seq, through, stage: SealStage::Waiting, answered: false, + pair, }); Sealing::Submitted(self.advance(Due::default())) } @@ -826,6 +910,12 @@ impl> return due; } }; + let outcome = if result.is_ok() { + FlushOutcome::Succeeded + } else { + FlushOutcome::Failed + }; + self.part_answered(lineage, seq, Part::File(file), outcome); match result { Ok(()) => self.cover(lineage, seq, &mut due), Err(fault) => { @@ -1109,8 +1199,16 @@ impl> } let through = seal.through; let state = self.lineages.get_mut(&lineage).expect("found above"); - state.seals.pop_front(); + let popped = state.seals.pop_front().expect("found above"); state.durable_through = Some(through); + end_seal( + &*self.diagnostics, + self.instance.number(), + lineage, + &through, + popped.pair, + SealOutcome::Durable, + ); // Only writes whose flush failed are left at or below a finished seal; once the mark // passes them, no later failure can reach back to them (guarantee 3). self.writes @@ -1128,6 +1226,15 @@ impl> if let Some(by) = self.blocking(lineage, through) { let state = self.lineages.get_mut(&lineage).expect("counted above"); state.seals[index].answered = true; + let pair = state.seals[index].pair.take(); + end_seal( + &*self.diagnostics, + self.instance.number(), + lineage, + &through, + pair, + SealOutcome::Blocked, + ); due.events.push(Event::Blocked { lineage, through, @@ -1201,6 +1308,28 @@ impl> ok: false, }) .collect(); + let issued = files + .iter() + .map(|file| Part::File(file.file)) + .chain(parts.iter().map(|part| Part::Domain(part.domain))); + for part in issued { + let Some(id) = CorrelationId::for_pair(&*self.diagnostics, DiagnosticKind::Flush) + else { + break; + }; + let Some(shown) = flush_part(part, &self.served_names) else { + continue; + }; + self.diagnostics.report(&Diagnostic::FlushBegan { + instance: self.instance.number(), + lineage, + through: format_args!("{through:?}"), + part: shown, + id, + }); + self.flush_pairs + .insert((lineage, seq, part), FlushPair { id, through }); + } if !parts.is_empty() { due.calls.push(ProviderCall { lineage, @@ -1262,4 +1391,94 @@ impl> self.lineages.remove(&lineage); } } + + /// Part `part` of `lineage`'s seal `seq` answered with `outcome`: end its pair, and, if it + /// failed, the seal's, whose answer the failure is. + pub(super) fn part_answered( + &mut self, + lineage: Key, + seq: u64, + part: Part, + outcome: FlushOutcome, + ) { + if let Some(pair) = self.flush_pairs.remove(&(lineage, seq, part)) + && let Some(shown) = flush_part(part, &self.served_names) + { + end_flush( + &*self.diagnostics, + self.instance.number(), + lineage, + &pair, + shown, + outcome, + ); + } + if outcome == FlushOutcome::Succeeded { + return; + } + if let Some(seal) = self + .lineages + .get_mut(&lineage) + .and_then(|state| state.seals.iter_mut().find(|seal| seal.seq == seq)) + { + let pair = seal.pair.take(); + end_seal( + &*self.diagnostics, + self.instance.number(), + lineage, + &seal.through, + pair, + SealOutcome::Failed, + ); + } + } +} + +impl Durability { + /// End every pair of `lineage` still open, or of every lineage: its seals' unanswered, and its + /// flushes' abandoned. For a lineage that ends, and for the instance's state as it is dropped. + pub(super) fn end_pairs(&mut self, only: Option) { + let instance = self.instance.number(); + let mut flushes: Vec<((Key, u64, Part), FlushPair)> = self + .flush_pairs + .extract_if(|(key, ..), _| only.is_none_or(|only| *key == only)) + .collect(); + flushes.sort_unstable_by_key(|((key, seq, _), pair)| (*key, *seq, pair.id)); + for ((lineage, _, part), pair) in &flushes { + if let Some(shown) = flush_part(*part, &self.served_names) { + end_flush( + &*self.diagnostics, + instance, + *lineage, + pair, + shown, + FlushOutcome::Abandoned, + ); + } + } + for (&lineage, state) in &mut self.lineages { + if only.is_some_and(|only| only != lineage) { + continue; + } + for seal in &mut state.seals { + let pair = seal.pair.take(); + end_seal( + &*self.diagnostics, + instance, + lineage, + &seal.through, + pair, + SealOutcome::Ended, + ); + } + } + } +} + +impl Drop for Durability { + /// Every pair still open ends as its instance does (DI-D-87): a seal unanswered, a flush + /// abandoned. + fn drop(&mut self) { + self.end_pairs(None); + } } diff --git a/crates/durable-ioring/src/durability/lineages.rs b/crates/durable-ioring/src/durability/lineages.rs index 3c301f765..0598ffffe 100644 --- a/crates/durable-ioring/src/durability/lineages.rs +++ b/crates/durable-ioring/src/durability/lineages.rs @@ -75,6 +75,11 @@ impl> .filter(above) .max(); let abandoned_through = sealed.max(written); + self.end_pairs(Some(lineage)); + let state = self + .lineages + .get_mut(&lineage) + .expect("live, checked above"); state.ended = true; state.seals.clear(); self.writes diff --git a/crates/durable-ioring/src/durability/providers.rs b/crates/durable-ioring/src/durability/providers.rs index 96638822d..3e1b109a1 100644 --- a/crates/durable-ioring/src/durability/providers.rs +++ b/crates/durable-ioring/src/durability/providers.rs @@ -11,12 +11,15 @@ use std::sync::Arc; -use super::{DomainPart, Due, Durability, Key, MarkingKind, Reach, SealStage, Stage, V, Write}; +use super::{ + DomainPart, Due, Durability, Key, MarkingKind, Part, Reach, SealStage, Stage, V, Write, +}; use crate::clock::{Instant, StampClock}; use crate::contract::EpochId; +use crate::diagnostics::FlushOutcome; use crate::domains::DomainId; use crate::ids::OpId; -use crate::types::{FailureCause, IoTargetKey}; +use crate::types::{FailureCause, FlushDomain, IoTargetKey}; /// Name `op`, a write to `file` declared with `domains`, in the request for each of those domains /// the provider serves. @@ -49,8 +52,10 @@ pub(super) fn name( impl> Durability { - /// The domains the consumer's provider serves, sorted, as interned when the instance was built. - pub(crate) fn serve(&mut self, domains: Arc<[DomainId]>) { + /// The domains the consumer's provider serves, sorted, as interned when the instance was built, + /// and the consumer's name for each, in the same order, which a flush pair reports. + pub(crate) fn serve(&mut self, domains: Arc<[DomainId]>, names: Vec) { + self.served_names = domains.iter().copied().zip(names).collect(); self.served = domains; } @@ -104,6 +109,12 @@ impl> ); return due; } + let ended = match &outcome { + Ok(()) => FlushOutcome::Succeeded, + Err(FailureCause::ProviderAbandoned { .. }) => FlushOutcome::Abandoned, + Err(_) => FlushOutcome::Failed, + }; + self.part_answered(lineage, seal, Part::Domain(domain), ended); match outcome { Ok(()) => self.cover(lineage, seal, &mut due), Err(cause) => { diff --git a/crates/durable-ioring/src/durability/tests.rs b/crates/durable-ioring/src/durability/tests.rs index 537ae4149..60c59a7f9 100644 --- a/crates/durable-ioring/src/durability/tests.rs +++ b/crates/durable-ioring/src/durability/tests.rs @@ -62,11 +62,12 @@ impl DerefMut for One { impl One { fn with_clock(clock: K) -> Self { - Self(Durability::new( - InstanceId::next(), - clock, - Arc::new(NoDiagnostics), - )) + Self::reporting(clock, Arc::new(NoDiagnostics)) + } + + /// A core reporting to `sink`. + fn reporting(clock: K, sink: Arc) -> Self { + Self(Durability::new(InstanceId::next(), clock, sink)) } fn sealed_through(&self) -> Option { @@ -669,7 +670,10 @@ fn a_write_a_provider_must_answer_for_leaves_its_seal_pending() { default: false, provider: true, }; - lineage.serve(Arc::from([DomainId(1)])); + lineage.serve( + Arc::from([DomainId(1)]), + vec![crate::types::FlushDomain::new(b"d1".as_slice())], + ); let write = ops.push_in(&mut lineage, 1, A, only, &[1]); lineage.completed(write, FULL); assert!( @@ -687,7 +691,10 @@ fn a_file_both_answer_for_is_flushed_and_still_waits_for_the_provider() { default: true, provider: true, }; - lineage.serve(Arc::from([DomainId(1)])); + lineage.serve( + Arc::from([DomainId(1)]), + vec![crate::types::FlushDomain::new(b"d1".as_slice())], + ); let write = ops.push_in(&mut lineage, 1, A, both, &[1, 2]); lineage.completed(write, FULL); assert_eq!(submitted(lineage.seal(1)).flushes, [flush(1, A)]); diff --git a/crates/durable-ioring/src/durability/tests/providers.rs b/crates/durable-ioring/src/durability/tests/providers.rs index 4cb2c230c..88000cf26 100644 --- a/crates/durable-ioring/src/durability/tests/providers.rs +++ b/crates/durable-ioring/src/durability/tests/providers.rs @@ -38,7 +38,12 @@ const BY_BOTH: MarkingKind = MarkingKind::Covered { }; fn serve(lineage: &mut Lineage, ids: &[u32]) { - lineage.serve(ids.iter().map(|&d| DomainId(d)).collect()); + lineage.serve( + ids.iter().map(|&d| DomainId(d)).collect(), + ids.iter() + .map(|d| crate::types::FlushDomain::new(format!("d{d}").into_bytes())) + .collect(), + ); } /// The single request a change asked of the provider. diff --git a/crates/durable-ioring/src/engine.rs b/crates/durable-ioring/src/engine.rs index f32941006..7dec8905c 100644 --- a/crates/durable-ioring/src/engine.rs +++ b/crates/durable-ioring/src/engine.rs @@ -24,6 +24,7 @@ use crate::clock::StampClock; use crate::contract::EpochId; use crate::ids::{DioringIds, OpId}; use crate::io_core::{CoreActions, DurableIoCore}; +use crate::lock_watch::WatchedGuard; use crate::provider::ProviderRequest; use crate::types::{ DurabilityRequest, Epoch, IoFault, IoTargetKey, PushError, ReadOptions, SealError, WriteOptions, @@ -252,7 +253,7 @@ pub fn lock(engine: &Mutex) -> MutexGuard<'_, T> { } /// The engine, locked, while delivery runs: its slot is full until delivery stops. -pub struct EngineGuard<'a, T>(MutexGuard<'a, Option>); +pub struct EngineGuard<'a, T>(WatchedGuard<'a, Option>); /// The engine in a locked slot. /// @@ -260,6 +261,17 @@ pub struct EngineGuard<'a, T>(MutexGuard<'a, Option>); /// /// If the slot is empty: it is taken only as delivery stops, and nothing locks it after. pub fn present(guard: MutexGuard<'_, Option>) -> EngineGuard<'_, T> { + present_watched(WatchedGuard::unwatched(guard)) +} + +/// The engine in a slot locked through a [`LockWatch`](crate::lock_watch::LockWatch), whose wait, +/// if the lock was contended, is reported when the lock is acquired, before the guard is returned, +/// and not when the guard is dropped: the pair does not include the time the lock is held. +/// +/// # Panics +/// +/// As [`present`]. +pub fn present_watched(guard: WatchedGuard<'_, Option>) -> EngineGuard<'_, T> { assert!( guard.is_some(), "the engine is taken only as delivery stops" diff --git a/crates/durable-ioring/src/io_core/provider.rs b/crates/durable-ioring/src/io_core/provider.rs index bfd22fc1d..9aaa6b476 100644 --- a/crates/durable-ioring/src/io_core/provider.rs +++ b/crates/durable-ioring/src/io_core/provider.rs @@ -57,7 +57,8 @@ impl DurableIoCore { .collect(); named.sort_unstable_by_key(|(id, _)| *id); core.served_names = named.into_iter().map(|(_, domain)| domain).collect(); - core.durability.serve(Arc::clone(&served)); + core.durability + .serve(Arc::clone(&served), core.served_names.clone()); core.served = served; Ok(core) } diff --git a/crates/durable-ioring/src/io_core/tests.rs b/crates/durable-ioring/src/io_core/tests.rs index 3d3616ea7..fc9a1f7ad 100644 --- a/crates/durable-ioring/src/io_core/tests.rs +++ b/crates/durable-ioring/src/io_core/tests.rs @@ -24,10 +24,13 @@ use crate::types::{ }; mod failures; +mod flush_failures; mod gates; mod lineages; +mod pairs; mod provider; mod refusals; +mod scripted; const KEY: IoTargetKey = IoTargetKey(1); const OTHER: IoTargetKey = IoTargetKey(2); @@ -62,6 +65,14 @@ impl Rig { rig } + /// A rig over a backend that does what `script` says, with the key attached. + fn scripted(script: crate::memory::FaultScript) -> Self { + let mut rig = Self::over(Core::new(Ticks::default(), Arc::new(NoDiagnostics))); + rig.backend = MemoryBackend::new(nz(8), nz(16)).with_script(script); + rig.attach(KEY, MemoryTarget::new()); + rig + } + /// `core`, with no target, over a backend with room to spare. fn over(core: Core) -> Self { Self { diff --git a/crates/durable-ioring/src/io_core/tests/flush_failures.rs b/crates/durable-ioring/src/io_core/tests/flush_failures.rs new file mode 100644 index 000000000..16f544925 --- /dev/null +++ b/crates/durable-ioring/src/io_core/tests/flush_failures.rs @@ -0,0 +1,605 @@ +// Copyright (c) 2026 Mike Grier +//! A failed flush, as the core answers it (DI-18.2): the tests that armed `win-durable-ioring`'s fault +//! seam only to make a flush fail, and assert nothing the Windows driver adds, moved here with their +//! names and assertions. Each flush fails by the in-memory backend's script -- the named file's first +//! commit -- rather than by the seam. Their census and the reason each is the core's are in +//! `DI-18.1`'s record in this crate's `COMPLETED-CHECKLIST.md`; the file each came from is named on it. +//! +//! Where the Windows harness supplied its own value, the rig's stands in: the contexts are the rig's, +//! and the fault's source is the script's. + +use std::sync::Arc; + +use super::{Core, KEY, OTHER, Rig, V, nz}; +use crate::backend::{IoBackend, RegisteredRegion, SubmissionData}; +use crate::diagnostics::NoDiagnostics; +use crate::ids::{FailureId, FailureToken}; +use crate::io_core::{CoreActions, DurableIoCore}; +use crate::io_target::IoTarget; +use crate::memory::{ + Fault, FaultScript, MemoryBackend, MemoryTarget, ScriptOp, When, scripted_fault, +}; +use crate::oracle::ConformanceOracle; +use crate::test_support::{ManualClock, Ticks}; +use crate::types::{ + DurabilityRequest, Entry, Epoch, EpochState, Failed, FailureCause, FlushDomain, MarkingKind, + OpKind, PushRefusal, RemoveTargetError, Resolution, ResolveRefusal, WriteOptions, +}; + +const GIVEN: crate::types::IoTargetKey = KEY; +const ADDED: crate::types::IoTargetKey = OTHER; + +type RigEntry = Entry, &'static str>; + +/// A script that fails `target`'s first commit: the seam's "fail the next flush of this file", for a +/// test whose armed flush is the file's first. +fn first_flush_fails(target: crate::types::IoTargetKey) -> FaultScript { + FaultScript::new().on( + When::any().op(ScriptOp::Commit).target(target).nth(1), + Fault::Fail(scripted_fault("the flush failed")), + ) +} + +/// A rig whose backend fails `fails`' first flush and has `registered` registered, with `targets` +/// attached, each declared with its domains. +fn rig_with( + fails: crate::types::IoTargetKey, + registered: Vec>, + targets: &[(crate::types::IoTargetKey, &[&str])], +) -> Rig { + let mut rig = Rig::over(Core::new(Ticks::default(), Arc::new(NoDiagnostics))); + rig.backend = MemoryBackend::new(nz(8), nz(16)) + .with_registered(registered) + .with_script(first_flush_fails(fails)); + for (key, domains) in targets { + rig.attach( + *key, + MemoryTarget::new() + .with_domains(domains.iter().map(|d| FlushDomain::new(d.as_bytes()))), + ); + } + rig +} + +/// A rig with `GIVEN` alone, whose first flush fails. +fn one() -> Rig { + rig_with(GIVEN, Vec::new(), &[(GIVEN, &[])]) +} + +/// The next entry, after running the backend, once the oracle has accepted it. +fn next_entry(rig: &mut Rig) -> RigEntry { + rig.run(); + let entry = rig.core.pop().expect("an entry"); + rig.oracle + .observe(&entry) + .expect("the oracle accepts the entry"); + entry +} + +/// The next entry, which must be a completion. +fn next_op(rig: &mut Rig) { + match next_entry(rig) { + Entry::Op(_) => {} + other => panic!("expected a completion, got {other:?}"), + } +} + +fn next_failed(rig: &mut Rig) -> Failed { + match next_entry(rig) { + Entry::Failed(failed) => failed, + other => panic!("expected Failed, got {other:?}"), + } +} + +/// Whether nothing more is poppable, once the backend has run. +fn nothing_more(rig: &mut Rig) -> bool { + rig.run(); + rig.core.pop().is_none() +} + +/// The ops a failure suspects, by sequence number. +fn suspects(failed: &Failed) -> Vec { + failed.suspect.writes().iter().map(|w| w.op.seq).collect() +} + +/// Resolve, reporting each heal to the oracle. +fn resolve(rig: &mut Rig, items: Vec<(FailureToken, Resolution)>) { + let healed: Vec = items + .iter() + .filter(|(_, resolution)| *resolution == Resolution::Heal) + .map(|(token, _)| token.id()) + .collect(); + let actions = rig.core.resolve(items).expect("resolve"); + for id in healed { + rig.oracle.healed(id); + } + rig.act(actions); +} + +fn state(rig: &Rig, id: u64) -> EpochState { + rig.core + .epoch_state(rig.epoch(id)) + .expect("the default lineage is live") +} + +fn finish(rig: Rig) { + rig.oracle.finish().expect("every operation completed"); +} + +/// Two writes tagged 1 to `GIVEN`, completed, then sealed through 1 with its flush failing. +fn failed_seal(rig: &mut Rig) -> Failed { + rig.write_to(GIVEN, 1, 0, b"first").expect("accepted"); + rig.write_to(GIVEN, 1, 8, b"second").expect("accepted"); + next_op(rig); + next_op(rig); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + next_failed(rig) +} + +/// From `win-durable-ioring`'s `dioring/tests/failures.rs`: its core half. The Windows half -- the +/// cause relayed from the ring, with its source and its `HRESULT` -- stays there. +#[test] +fn a_failed_flush_is_reported_with_its_cause_and_holds_the_mark() { + let mut rig = one(); + let failed = failed_seal(&mut rig); + match &failed.cause { + FailureCause::Commit { target: file, .. } => assert_eq!(*file, GIVEN), + other => panic!("expected a flush failure, got {other:?}"), + } + assert_eq!(suspects(&failed), [0, 1]); + assert_eq!( + rig.core.durable_through(rig.core.default_lineage()), + Ok(None) + ); + assert_eq!(state(&rig, 1), EpochState::Blocked(failed.id)); + assert_eq!( + rig.seal(1), + DurabilityRequest::AlreadySealed(EpochState::Blocked(failed.id)) + ); + let inventory = rig.core.failures(); + assert_eq!(inventory.len(), 1); + assert_eq!(inventory[0].id, failed.id); + assert!(inventory[0].token_live); + assert!(nothing_more(&mut rig), "one answer"); + drop(failed); + finish(rig); +} + +/// From `failures.rs`: CONTRACT.md's "Healing a failure", over the core. +#[test] +fn a_healed_failure_is_passed_at_the_first_seal_after_the_heal() { + let mut rig = one(); + let failed = failed_seal(&mut rig); + let refused = rig.write_to(GIVEN, 1, 0, &[1]).expect_err("1 is sealed"); + assert_eq!(refused.context, "write"); + + rig.write_to(GIVEN, 2, 0, b"FIRST").expect("accepted"); + rig.write_to(GIVEN, 2, 8, b"SECOND").expect("accepted"); + next_op(&mut rig); + next_op(&mut rig); + resolve(&mut rig, vec![(failed.token, Resolution::Heal)]); + assert!( + rig.core.take_token(failed.id).is_none(), + "the heal holds it" + ); + assert_eq!(rig.seal(2), DurabilityRequest::Submitted); + match next_entry(&mut rig) { + Entry::Healed { + failure, + suspect, + markings, + } => { + assert_eq!(failure, failed.id); + assert_eq!(suspect.writes(), failed.suspect.writes()); + assert!(markings.is_empty(), "nothing happened to 1's writes after"); + } + other => panic!("expected Healed before any Durable, got {other:?}"), + } + for id in [1, 2] { + match next_entry(&mut rig) { + Entry::Durable { through } => assert_eq!(through, rig.epoch(id)), + other => panic!("expected Durable through {id}, got {other:?}"), + } + } + assert!(rig.core.failures().is_empty()); + assert_eq!(state(&rig, 1), EpochState::Durable); + finish(rig); +} + +/// From `failures.rs`. +#[test] +fn without_the_heal_a_later_seal_is_answered_blocked() { + let mut rig = one(); + let failed = failed_seal(&mut rig); + rig.write_to(GIVEN, 2, 0, b"again").expect("accepted"); + next_op(&mut rig); + assert_eq!(rig.seal(2), DurabilityRequest::Submitted); + match next_entry(&mut rig) { + Entry::Blocked { through, by } => { + assert_eq!((through, by), (rig.epoch(2), failed.id)); + } + other => panic!("expected Blocked, got {other:?}"), + } + assert_eq!(state(&rig, 2), EpochState::Blocked(failed.id)); + resolve(&mut rig, vec![(failed.token, Resolution::Abandon)]); + match next_entry(&mut rig) { + Entry::Abandoned { + failure, suspect, .. + } => { + assert_eq!(failure, failed.id); + assert_eq!(suspect.writes().len(), 2); + } + other => panic!("expected Abandoned, got {other:?}"), + } + let mut durable = Vec::new(); + for _ in 0..2 { + match next_entry(&mut rig) { + Entry::Durable { through } => durable.push(through), + other => panic!("expected Durable, got {other:?}"), + } + } + assert_eq!(durable, [rig.epoch(1), rig.epoch(2)]); + assert_eq!(state(&rig, 1), EpochState::Abandoned); + assert_eq!(state(&rig, 2), EpochState::Durable); + finish(rig); +} + +/// From `failures.rs`. +#[test] +fn a_failed_flush_reaches_only_files_sharing_a_declared_domain() { + let mut rig = rig_with( + ADDED, + Vec::new(), + &[(GIVEN, &["disk-1"]), (ADDED, &["disk-2"])], + ); + rig.write_to(GIVEN, 1, 0, b"log").expect("accepted"); + rig.write_to(ADDED, 1, 0, b"data").expect("accepted"); + next_op(&mut rig); + next_op(&mut rig); + rig.seal(1); + let failed = next_failed(&mut rig); + assert_eq!(suspects(&failed), [1], "the log's disk is not the data's"); + drop(failed); + finish(rig); +} + +/// From `failures.rs`. +#[test] +fn a_resolution_naming_another_instances_failure_is_refused_whole() { + let mut rig = one(); + let ours = failed_seal(&mut rig); + let mut other = Rig::new(); + let (_, actions) = other + .core + .import_failure(crate::types::ImportScope::All) + .expect("import"); + other.act(actions); + let theirs = match other.core.pop() { + Some(Entry::Failed(failed)) => failed, + other => panic!("expected Failed, got {other:?}"), + }; + let error = rig + .core + .resolve(vec![ + (ours.token, Resolution::Abandon), + (theirs.token, Resolution::Abandon), + ]) + .expect_err("a foreign token"); + assert!(matches!(error.reason, ResolveRefusal::Foreign(id) if id == theirs.id)); + assert_eq!(error.returned.len(), 2); + assert!(nothing_more(&mut rig), "nothing was abandoned"); + assert_eq!(rig.core.failures().len(), 1); + finish(rig); +} + +/// From `failures.rs`. +#[test] +fn a_closed_failure_stays_and_its_token_can_be_taken_again() { + let mut rig = one(); + let failed = failed_seal(&mut rig); + assert!(rig.core.take_token(failed.id).is_none(), "live"); + failed.token.close(); + assert!(!rig.core.failures()[0].token_live); + let token = rig.core.take_token(failed.id).expect("closed"); + resolve(&mut rig, vec![(token, Resolution::Abandon)]); + assert!(matches!(next_entry(&mut rig), Entry::Abandoned { .. })); + assert!(matches!(next_entry(&mut rig), Entry::Durable { .. })); + assert!( + rig.core.take_token(failed.id).is_none(), + "resolved, and leaves no memory" + ); + finish(rig); +} + +/// From `failures.rs`. +#[test] +fn a_write_into_an_abandoned_epoch_is_refused_with_what_it_took() { + let mut rig = rig_with(GIVEN, vec![vec![0; 16]], &[(GIVEN, &[])]); + rig.write_to(GIVEN, 1, 0, b"sealed").expect("accepted"); + rig.write_to(GIVEN, 3, 8, b"open").expect("accepted"); + next_op(&mut rig); + next_op(&mut rig); + rig.seal(1); + let failed = next_failed(&mut rig); + assert_eq!(suspects(&failed), [0, 1], "sealed 1 and open 3"); + resolve(&mut rig, vec![(failed.token, Resolution::Abandon)]); + assert!(matches!(next_entry(&mut rig), Entry::Abandoned { .. })); + assert!(matches!(next_entry(&mut rig), Entry::Durable { .. })); + + let three = rig.epoch(3); + let error = rig + .write_to(GIVEN, 3, 0, &[7]) + .expect_err("3 was abandoned, though it is open"); + assert!(matches!(error.reason, PushRefusal::EpochAbandoned { epoch } if epoch == three)); + assert_eq!((error.buffer, error.context), (Some(vec![7]), "write")); + let region = RegisteredRegion { + index: 0, + offset: 0, + length: 4, + }; + let error = rig + .core + .write( + GIVEN, + 0, + SubmissionData::Registered(region), + three, + "span", + WriteOptions::new(), + &mut rig.backend, + ) + .expect_err("a registered write to 3"); + assert!(matches!(error.reason, PushRefusal::EpochAbandoned { .. })); + assert_eq!((error.buffer, error.context), (None, "span")); + let error = rig + .write_to(GIVEN, 1, 0, &[7]) + .expect_err("1 is sealed and abandoned"); + assert!( + matches!(error.reason, PushRefusal::Sealed { .. }), + "the seal is checked first: {:?}", + error.reason + ); + assert!(nothing_more(&mut rig), "a refused write completes nothing"); + + rig.write_to(GIVEN, 4, 0, b"later").expect("accepted"); + next_op(&mut rig); + finish(rig); +} + +/// From `dioring/tests/ending.rs`. +#[test] +fn a_failure_suspecting_a_write_holds_its_file_until_it_is_resolved() { + let mut rig = one(); + rig.write_to(GIVEN, 1, 0, b"one").expect("accepted"); + next_op(&mut rig); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + let failed = next_failed(&mut rig); + + let refused = match rig.core.remove_target(GIVEN) { + Err(RemoveTargetError::Busy(busy)) => busy, + Err(other) => panic!("expected Busy, got {other:?}"), + Ok(_) => panic!("expected Busy, and the file was removed"), + }; + assert_eq!(refused.failures, [failed.id]); + assert_eq!( + refused.uncovered, 0, + "its suspect write is reported by the failure" + ); + assert!(refused.to_string().contains("heal or abandon"), "{refused}"); + + resolve(&mut rig, vec![(failed.token, Resolution::Abandon)]); + assert!(matches!(next_entry(&mut rig), Entry::Abandoned { .. })); + rig.core.remove_target(GIVEN).expect("resolved"); + finish(rig); +} + +/// From `dioring/tests/ending.rs`: what the core's `close` hands back, which the driver's hands on. +#[test] +fn closing_hands_back_the_unresolved_failures() { + let mut rig = one(); + rig.write_to(GIVEN, 1, 0, b"one").expect("accepted"); + next_op(&mut rig); + rig.seal(1); + let failed = next_failed(&mut rig); + let left = rig.core.close(); + let ids: Vec<_> = left.failures.iter().map(|f| f.id).collect(); + assert_eq!(ids, [failed.id]); + assert_eq!(left.lineages[0].durable_through, None); +} + +/// From `dioring/tests/lineages.rs`: a failure suspecting a held write of each lineage holds both, +/// and a heal takes effect in each at its own next seal (DI-D-41). +#[test] +fn a_failure_spanning_two_lineages_heals_in_each_at_its_own_seal() { + let mut rig = rig_with(GIVEN, Vec::new(), &[(GIVEN, &[]), (ADDED, &[])]); + let minted = rig.core.mint_lineage(Some("log".to_owned())); + let own = rig.write_to(GIVEN, 1, 0, b"own").expect("accepted"); + let theirs = rig + .write_with( + ADDED, + Epoch::new(minted, 1), + 0, + b"theirs", + WriteOptions::new(), + ) + .expect("accepted"); + next_op(&mut rig); + next_op(&mut rig); + + rig.seal(1); + let failed = next_failed(&mut rig); + let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); + assert_eq!(suspects, [own, theirs], "a held write of each lineage"); + resolve(&mut rig, vec![(failed.token, Resolution::Heal)]); + + assert_eq!(rig.seal(2), DurabilityRequest::Submitted); + for id in [1, 2] { + match next_entry(&mut rig) { + Entry::Durable { through } => assert_eq!(through, rig.epoch(id)), + other => panic!("expected Durable through {id}, got {other:?}"), + } + } + assert_eq!(rig.core.failures().len(), 1, "it still holds the other"); + assert_eq!( + rig.core.epoch_state(Epoch::new(minted, 1)), + Ok(EpochState::Open) + ); + + assert_eq!(rig.seal_in(minted, 1), DurabilityRequest::Submitted); + loop { + match next_entry(&mut rig) { + Entry::Marked { .. } => continue, + Entry::Healed { failure, .. } => { + assert_eq!(failure, failed.id); + break; + } + other => panic!("expected Healed, got {other:?}"), + } + } + match next_entry(&mut rig) { + Entry::Durable { through } => assert_eq!(through, Epoch::new(minted, 1)), + other => panic!("expected Durable through the minted lineage's 1, got {other:?}"), + } + assert!(rig.core.failures().is_empty()); + finish(rig); +} + +/// From `dioring/tests/markings.rs`. +#[test] +fn a_heal_reports_its_record_with_the_markings_its_failure_gained() { + let mut rig = one(); + let sealed = rig.write_to(GIVEN, 1, 0, b"first").expect("accepted"); + let later = rig.write_to(GIVEN, 2, 8, b"second").expect("accepted"); + next_op(&mut rig); + next_op(&mut rig); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + let failed = next_failed(&mut rig); + let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); + assert_eq!(suspects, [sealed, later]); + + resolve(&mut rig, vec![(failed.token, Resolution::Heal)]); + assert_eq!(rig.seal(2), DurabilityRequest::Submitted); + let covered = match next_entry(&mut rig) { + Entry::Marked { marking, .. } => marking, + other => panic!("expected Marked, got {other:?}"), + }; + assert_eq!( + (covered.write, covered.kind.clone()), + ( + later, + MarkingKind::Covered { + flushed: true, + provider: false + } + ), + "a healing failure goes on gaining markings" + ); + match next_entry(&mut rig) { + Entry::Healed { + failure, markings, .. + } => { + assert_eq!(failure, failed.id); + assert_eq!(markings, [covered]); + } + other => panic!("expected Healed, got {other:?}"), + } + for id in [1, 2] { + match next_entry(&mut rig) { + Entry::Durable { through } => assert_eq!(through, rig.epoch(id)), + other => panic!("expected Durable through {id}, got {other:?}"), + } + } + finish(rig); +} + +/// A core over a clock the test moves, with its backend and the oracle: the rig's part played by +/// hand, since the rig's core counts its own readings. +struct Clocked { + core: DurableIoCore, &'static str, ManualClock>, + backend: MemoryBackend, + oracle: ConformanceOracle, +} + +impl Clocked { + fn act(&mut self, actions: CoreActions>) { + let mut pending = std::collections::VecDeque::from([actions]); + while let Some(actions) = pending.pop_front() { + for submission in actions.submit { + if let Err(refused) = self.backend.push(submission) { + pending.push_back(self.core.refused(refused)); + } + } + } + } + + fn next_entry(&mut self) -> RigEntry { + loop { + self.backend + .submit() + .expect("the memory backend's submit never fails"); + let completions: Vec<_> = + std::iter::from_fn(|| self.backend.pop().expect("never fails")).collect(); + for completion in completions { + let actions = self.core.complete(completion); + self.act(actions); + } + if let Some(entry) = self.core.pop() { + self.oracle + .observe(&entry) + .expect("the oracle accepts the entry"); + return entry; + } + assert!(self.backend.queued() > 0, "no entry, and nothing to run"); + } + } +} + +/// From `dioring/tests/stamps.rs`. +#[test] +fn an_instance_given_a_clock_stamps_a_failed_flush_with_its_reading_when_observed() { + let clock = ManualClock::at(7_000); + let mut rig = Clocked { + core: DurableIoCore::new(clock.clone(), Arc::new(NoDiagnostics)), + backend: MemoryBackend::new(nz(8), nz(16)).with_script(first_flush_fails(GIVEN)), + oracle: ConformanceOracle::new(), + }; + let target = MemoryTarget::new(); + rig.core + .add_target(GIVEN, target.describe()) + .expect("a new key"); + rig.backend.attach(GIVEN, target).expect("a new key"); + let epoch = Epoch::new(rig.core.default_lineage(), 1); + let id = rig + .core + .write( + GIVEN, + 0, + SubmissionData::Owned(b"stamped".to_vec()), + epoch, + "write", + WriteOptions::new(), + &mut rig.backend, + ) + .expect("accepted"); + rig.oracle + .pushed(id, OpKind::Write { epoch }, 7, "write") + .expect("the oracle accepts the push"); + assert!(matches!(rig.next_entry(), Entry::Op(_))); + clock.advance(3_000); + let (answer, actions) = rig + .core + .make_durable_through(epoch) + .expect("a live lineage"); + assert_eq!(answer, DurabilityRequest::Submitted); + rig.oracle.sealed(epoch); + rig.act(actions); + let failed = match rig.next_entry() { + Entry::Failed(failed) => failed, + other => panic!("expected Failed, got {other:?}"), + }; + assert_eq!(failed.observed, 10_000); + let inventory = rig.core.failures(); + assert_eq!(inventory.len(), 1); + assert_eq!(inventory[0].observed, failed.observed); + rig.oracle.finish().expect("every operation completed"); +} diff --git a/crates/durable-ioring/src/io_core/tests/pairs.rs b/crates/durable-ioring/src/io_core/tests/pairs.rs new file mode 100644 index 000000000..ceb51ccf0 --- /dev/null +++ b/crates/durable-ioring/src/io_core/tests/pairs.rs @@ -0,0 +1,505 @@ +// Copyright (c) 2026 Mike Grier +//! The seal and flush pairs (DI-19.2.2, DI-D-87): each begins as the core builds it and ends on +//! every path it can leave by, held to the pairing rule by `PairingChecker` and not by a copy of +//! it here. + +use std::sync::{Arc, Mutex}; + +use super::{Core, KEY, Rig, nz}; +use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink}; +use crate::memory::{ + Fault, FaultScript, MemoryBackend, MemoryTarget, ScriptOp, When, scripted_fault, +}; +use crate::oracle::{PairingChecker, PairingViolation}; +use crate::provider::DomainOutcome; +use crate::test_support::Ticks; +use crate::types::{DurabilityRequest, Epoch, FlushDomain, Resolution}; + +/// A sink that wants the kinds it is given, checks every event it is handed against the pairing +/// rule, and keeps the pairs' lines. It records under a mutex it holds only for that. +struct Checked { + wants: Vec, + state: Mutex, +} + +#[derive(Default)] +struct State { + checker: PairingChecker, + lines: Vec, + violation: Option, +} + +impl Checked { + fn wanting(wants: &[DiagnosticKind]) -> Arc { + Arc::new(Self { + wants: wants.to_vec(), + state: Mutex::new(State::default()), + }) + } + + fn pairs() -> Arc { + Self::wanting(&[DiagnosticKind::Seal, DiagnosticKind::Flush]) + } + + fn lines(&self) -> Vec { + let state = self.state.lock().expect("the record"); + assert_eq!(state.violation, None, "{:?}", state.lines); + state.lines.clone() + } + + /// The stream so far keeps the rule, with nothing left open. + fn finished(&self) { + let mut state = self.state.lock().expect("the record"); + assert_eq!(state.violation, None, "{:?}", state.lines); + let checker = std::mem::take(&mut state.checker); + checker.finish(&[]).expect("every pair ended"); + } +} + +impl DiagnosticSink for Checked { + fn wants(&self, kind: DiagnosticKind) -> bool { + self.wants.contains(&kind) + } + + fn report(&self, event: &Diagnostic<'_>) { + let mut state = self.state.lock().expect("the record"); + if event.pairing().is_some() { + state.lines.push(event.to_string()); + } + if state.violation.is_none() + && let Err(violation) = state.checker.observe(event) + { + state.violation = Some(violation); + } + } +} + +fn domain(name: &str) -> FlushDomain { + FlushDomain::new(name.as_bytes()) +} + +/// A rig reporting to `sink`, over a backend doing what `script` says, with `KEY` declared in +/// `declared`, and a provider serving `served` if any. +fn rig(sink: &Arc, script: FaultScript, served: &[&str], declared: &[&str]) -> Rig { + let sink: Arc = Arc::clone(sink) as Arc; + let core = if served.is_empty() { + Core::new(Ticks::default(), sink) + } else { + Core::with_provider( + Ticks::default(), + sink, + served.iter().map(|name| domain(name)).collect(), + ) + .expect("no repeats") + }; + let mut rig = Rig::over(core); + rig.backend = MemoryBackend::new(nz(8), nz(16)).with_script(script); + rig.attach( + KEY, + MemoryTarget::new().with_domains(declared.iter().map(|name| domain(name))), + ); + rig +} + +fn plain(sink: &Arc) -> Rig { + rig(sink, FaultScript::new(), &[], &[]) +} + +/// Which lines say what: the `n`th line contains `text`. +fn says(lines: &[String], expected: &[&str]) { + assert_eq!(lines.len(), expected.len(), "{lines:#?}"); + for (line, text) in lines.iter().zip(expected) { + assert!( + line.contains(text), + "{line:?} should say {text:?}; all: {lines:#?}" + ); + } +} + +#[test] +fn a_durable_seal_is_a_seal_pair_around_one_flush_pair() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + rig.run(); + rig.entries(); + says( + &sink.lines(), + &[ + "seal through 1 (pair", + "flush of file 1 (pair", + "succeeded", + "): durable", + ], + ); + sink.finished(); +} + +#[test] +fn a_seal_with_nothing_to_flush_begins_and_ends_with_no_flush_pair() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + rig.run(); + rig.entries(); + says(&sink.lines(), &["began", "durable"]); + sink.finished(); +} + +#[test] +fn a_seal_waiting_for_its_write_begins_before_its_flush_does() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + rig.write(1, 0, b"x").expect("accepted"); + assert_eq!(rig.seal(1), DurabilityRequest::Submitted); + says(&sink.lines(), &["seal through 1 (pair"]); + rig.run(); + rig.entries(); + says( + &sink.lines(), + &["seal through 1", "flush of file 1", "succeeded", "durable"], + ); + sink.finished(); +} + +#[test] +fn each_file_a_seal_flushes_is_its_own_flush_pair() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + rig.attach(super::OTHER, MemoryTarget::new()); + rig.write_to(KEY, 1, 0, b"a").expect("accepted"); + rig.write_to(super::OTHER, 1, 0, b"b").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert_eq!( + lines + .iter() + .filter(|l| l.contains("flush of file") && l.ends_with("began")) + .count(), + 2 + ); + assert_eq!(lines.iter().filter(|l| l.ends_with("succeeded")).count(), 2); + sink.finished(); +} + +#[test] +fn a_seal_sealed_again_begins_no_second_pair() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + assert!(matches!(rig.seal(1), DurabilityRequest::AlreadySealed(_))); + rig.run(); + rig.entries(); + assert_eq!( + sink.lines().iter().filter(|l| l.ends_with("began")).count(), + 2, + "one seal, one flush" + ); + sink.finished(); +} + +#[test] +fn a_failed_flush_ends_its_pair_failed_and_its_seals_failed_and_a_later_seal_ends_blocked() { + let sink = Checked::pairs(); + let script = FaultScript::new().on( + When::any().op(ScriptOp::Commit).nth(1), + Fault::Fail(scripted_fault("the flush failed")), + ); + let mut rig = rig(&sink, script, &[], &[]); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + let token = rig + .entries() + .into_iter() + .find_map(|entry| match entry { + crate::types::Entry::Failed(failed) => Some(failed.token), + _ => None, + }) + .expect("a failure"); + says( + &sink.lines(), + &[ + "seal through 1", + "flush of file 1", + "failed", + "seal through 1 (pair", + ], + ); + assert!(sink.lines()[3].ends_with("failed"), "{:?}", sink.lines()); + + rig.write(2, 0, b"y").expect("accepted"); + rig.run(); + rig.seal(2); + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert!( + lines + .last() + .is_some_and(|l| l.contains("seal through 2") && l.ends_with("blocked")), + "{lines:#?}" + ); + + let actions = rig + .core + .resolve(vec![(token, Resolution::Abandon)]) + .expect("ours"); + rig.act(actions); + rig.run(); + rig.entries(); + assert_eq!( + sink.lines().len(), + lines.len(), + "the seals answered already end no more" + ); + sink.finished(); +} + +#[test] +fn a_provider_domain_is_a_flush_pair_that_ends_as_the_provider_answers() { + for (outcome, ends) in [ + (DomainOutcome::Succeeded, "succeeded"), + (DomainOutcome::Failed(std::io::Error::other("no")), "failed"), + (DomainOutcome::Abandoned, "abandoned"), + ] { + let sink = Checked::pairs(); + let mut rig = rig(&sink, FaultScript::new(), &["log"], &["log"]); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + let request = rig.requests.pop().expect("a request"); + let actions = rig + .core + .provided(request.lineage, request.seal, &domain("log"), outcome); + rig.act(actions); + rig.run(); + rig.entries(); + let lines = sink.lines(); + let flush: Vec<&String> = lines + .iter() + .filter(|l| l.contains("flush of domain \"log\"")) + .collect(); + assert_eq!(flush.len(), 2, "{lines:#?}"); + assert!(flush[1].ends_with(ends), "{lines:#?}"); + let seal_ends = if ends == "succeeded" { + "durable" + } else { + "failed" + }; + assert!( + lines.last().is_some_and(|l| l.ends_with(seal_ends)), + "{lines:#?}" + ); + sink.finished(); + } +} + +#[test] +fn ending_a_lineage_ends_its_open_seal_unanswered_and_its_open_flush_abandoned() { + let sink = Checked::pairs(); + let script = FaultScript::new().on(When::any().op(ScriptOp::Commit), Fault::Hold); + let mut rig = rig(&sink, script, &[], &[]); + let minted = rig.core.mint_lineage(None); + rig.write_with(KEY, Epoch::new(minted, 1), 0, b"x", Default::default()) + .expect("accepted"); + rig.run(); + rig.seal_in(minted, 1); + rig.run(); + says(&sink.lines(), &["seal through 1", "flush of file 1"]); + let actions = rig.core.end_lineage(minted).expect("live, not the default"); + rig.act(actions); + let lines = sink.lines(); + assert!(lines.iter().any(|l| l.ends_with("abandoned")), "{lines:#?}"); + assert!( + lines.iter().any(|l| l.ends_with("ended unanswered")), + "{lines:#?}" + ); + rig.backend.release_all(); + rig.run(); + rig.entries(); + assert_eq!( + sink.lines().len(), + lines.len(), + "the late flush ends nothing" + ); + sink.finished(); +} + +#[test] +fn closing_the_core_ends_every_pair_still_open() { + let sink = Checked::pairs(); + let script = FaultScript::new().on(When::any().op(ScriptOp::Commit), Fault::Hold); + let mut rig = rig(&sink, script, &[], &[]); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + rig.write(2, 0, b"y").expect("accepted"); + rig.seal(2); + assert_eq!( + sink.lines().iter().filter(|l| l.ends_with("began")).count(), + 3 + ); + let Rig { core, .. } = rig; + drop(core.close()); + let lines = sink.lines(); + assert_eq!( + lines + .iter() + .filter(|l| l.ends_with("ended unanswered")) + .count(), + 2, + "{lines:#?}" + ); + assert_eq!( + lines.iter().filter(|l| l.ends_with("abandoned")).count(), + 1, + "{lines:#?}" + ); + sink.finished(); +} + +#[test] +fn dropping_the_core_ends_every_pair_still_open() { + let sink = Checked::pairs(); + let script = FaultScript::new().on(When::any().op(ScriptOp::Commit), Fault::Hold); + let mut rig = rig(&sink, script, &[], &[]); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + drop(rig); + let lines = sink.lines(); + assert!( + lines.iter().any(|l| l.ends_with("ended unanswered")), + "{lines:#?}" + ); + assert!(lines.iter().any(|l| l.ends_with("abandoned")), "{lines:#?}"); + sink.finished(); +} + +#[test] +fn a_sink_that_wants_neither_pair_is_told_of_none() { + let sink = Checked::wanting(&[]); + let mut rig = plain(&sink); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + rig.entries(); + assert!(sink.lines().is_empty()); + sink.finished(); +} + +#[test] +fn a_sink_that_wants_one_pair_is_told_of_that_one_only() { + for (wants, about) in [ + (DiagnosticKind::Flush, "flush of"), + (DiagnosticKind::Seal, "(pair"), + ] { + let sink = Checked::wanting(&[wants]); + let mut rig = plain(&sink); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert_eq!(lines.len(), 2, "{lines:#?}"); + assert!(lines.iter().all(|l| l.contains(about)), "{lines:#?}"); + if wants == DiagnosticKind::Seal { + assert!(lines.iter().all(|l| !l.contains("flush of")), "{lines:#?}"); + } + sink.finished(); + } +} + +#[test] +fn many_seals_each_end_once() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + for epoch in 1..=20 { + rig.write(epoch, 0, b"x").expect("accepted"); + rig.seal(epoch); + rig.run(); + } + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert_eq!( + lines.iter().filter(|l| l.ends_with("durable")).count(), + 20, + "{lines:#?}" + ); + sink.finished(); +} + +#[test] +fn a_seal_with_a_file_and_a_domain_is_a_flush_pair_for_each_part() { + let sink = Checked::pairs(); + let mut rig = rig(&sink, FaultScript::new(), &["log"], &["log", "disk"]); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + rig.seal(1); + rig.run(); + let request = rig.requests.pop().expect("a request"); + let actions = rig.core.provided( + request.lineage, + request.seal, + &domain("log"), + DomainOutcome::Succeeded, + ); + rig.act(actions); + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert!( + lines + .iter() + .any(|l| l.contains("flush of file 1") && l.ends_with("succeeded")), + "{lines:#?}" + ); + assert!( + lines + .iter() + .any(|l| l.contains("flush of domain \"log\"") && l.ends_with("succeeded")), + "{lines:#?}" + ); + assert!( + lines.last().is_some_and(|l| l.ends_with("durable")), + "{lines:#?}" + ); + sink.finished(); +} + +#[test] +fn a_minted_lineages_pairs_name_its_lineage() { + let sink = Checked::pairs(); + let mut rig = plain(&sink); + let minted = rig.core.mint_lineage(None); + rig.write_with(KEY, Epoch::new(minted, 3), 0, b"x", Default::default()) + .expect("accepted"); + rig.run(); + rig.seal_in(minted, 3); + rig.run(); + rig.entries(); + let lines = sink.lines(); + assert!(!lines.is_empty()); + assert!( + lines + .iter() + .all(|l| l.contains(&format!("lineage {} seal through 3", minted.seq))), + "{lines:#?}" + ); + sink.finished(); +} diff --git a/crates/durable-ioring/src/io_core/tests/refusals.rs b/crates/durable-ioring/src/io_core/tests/refusals.rs index 5f283a311..996c234a7 100644 --- a/crates/durable-ioring/src/io_core/tests/refusals.rs +++ b/crates/durable-ioring/src/io_core/tests/refusals.rs @@ -107,6 +107,41 @@ fn a_gated_operation_its_target_does_not_admit_is_refused_and_one_it_admits_is_h assert_eq!(rig.backend.queued(), 0, "held, not pushed"); } +#[test] +fn a_gated_operation_its_targets_access_does_not_cover_is_refused_at_push() { + // DI-D-66: the target states what it can do, and the backend refuses what that does not cover, + // for a gated operation at the call that gave it and not when its gate releases. + let mut rig = Rig::new(); + rig.attach( + OTHER, + MemoryTarget::new().with_access(crate::access::Access::ReadOnly), + ); + let gate = rig.epoch(1); + let epoch = rig.epoch(2); + let refused = rig + .write_with(OTHER, epoch, 0, &[7; 4], gated(gate)) + .expect_err("a write to a read-only target"); + assert!(matches!( + refused.reason, + PushRefusal::Backend(RefusalCause::AccessDenied) + )); + assert_eq!(refused.buffer, Some(vec![7; 4]), "handed back"); + let refused = rig + .write_with(OTHER, epoch, 0, &[7; 4], WriteOptions::new()) + .expect_err("ungated, the push refuses it"); + assert!(matches!( + refused.reason, + PushRefusal::Backend(RefusalCause::AccessDenied) + )); + + // Nothing was held and no id was spent: the next accepted operation takes the first. + let first = rig + .read_with(0, 1, ReadOptions::new().gate(gate)) + .expect("a read of the read-write target is held"); + assert_eq!(first.seq, 0); + assert_eq!(rig.core.close().held.len(), 1); +} + #[test] fn the_cores_own_checks_come_before_the_backends() { let mut rig = Rig::new(); diff --git a/crates/durable-ioring/src/io_core/tests/scripted.rs b/crates/durable-ioring/src/io_core/tests/scripted.rs new file mode 100644 index 000000000..7e1edad02 --- /dev/null +++ b/crates/durable-ioring/src/io_core/tests/scripted.rs @@ -0,0 +1,107 @@ +// Copyright (c) 2026 Mike Grier +//! The core driven by a fault script (DI-11.1): the failures it must handle, produced on demand by +//! the in-memory backend and not by detaching a target underneath it. + +use super::{KEY, Rig, shown}; +use crate::backend::RefusalCause; +use crate::memory::{Fault, FaultScript, MEMORY, ScriptOp, When, scripted_fault}; +use crate::types::{Entry, EpochState, FailureCause, PushRefusal}; + +const WRITES: When = When::any().op(ScriptOp::Write); +const COMMITS: When = When::any().op(ScriptOp::Commit); + +fn scripted(when: When, fault: Fault) -> Rig { + Rig::scripted(FaultScript::new().on(when, fault)) +} + +#[test] +fn a_scripted_commit_failure_blocks_the_epoch_and_names_its_target() { + let mut rig = scripted(COMMITS, Fault::Fail(scripted_fault("the flush failed"))); + rig.write(1, 0, b"lost").expect("accepted"); + rig.seal(1); + rig.run(); + let entries = rig.entries(); + assert!(matches!(entries[0], Entry::Op(_))); + let Entry::Failed(failed) = &entries[1] else { + panic!("a failure: {entries:?}"); + }; + let FailureCause::Commit { target, fault } = &failed.cause else { + panic!("a commit's failure: {:?}", failed.cause); + }; + assert_eq!(*target, KEY); + assert_eq!(fault.source, MEMORY); + assert_eq!(failed.suspect.writes().len(), 1); + assert!( + matches!( + rig.core.epoch_state(rig.epoch(1)), + Ok(EpochState::Blocked(_)) + ), + "the failure blocks the epoch" + ); + assert!( + rig.target().durable_contents().is_empty(), + "the target was never detached, and nothing it holds is durable" + ); + assert_eq!(rig.backend.fired().len(), 1); +} + +#[test] +fn a_held_write_completion_keeps_the_seal_from_committing_until_it_is_released() { + let mut rig = scripted(WRITES.nth(1), Fault::Hold); + rig.write(1, 0, b"x").expect("accepted"); + rig.seal(1); + rig.run(); + assert!(rig.entries().is_empty(), "the write has not completed"); + assert!(rig.target().durable_contents().is_empty()); + assert_eq!(rig.backend.held().len(), 1); + + rig.backend.release_all(); + rig.run(); + assert_eq!(shown(&mut rig), ["op0 write@1 1 write", "durable 1"]); + assert_eq!(rig.target().durable_contents(), b"x"); +} + +#[test] +fn completions_out_of_order_are_reported_and_the_seal_still_waits_for_every_write() { + let mut rig = scripted(WRITES.nth(1), Fault::Late(1)); + rig.write(1, 0, b"a").expect("accepted"); + rig.write(2, 1, b"b").expect("accepted"); + rig.seal(2); + rig.run(); + let shown = shown(&mut rig); + assert_eq!( + shown.last().map(String::as_str), + Some("durable 2"), + "{shown:?}" + ); + let mut ops: Vec<&str> = shown[..shown.len() - 1] + .iter() + .map(String::as_str) + .collect(); + ops.sort_unstable(); + assert_eq!(ops, ["op0 write@1 1 write", "op1 write@2 1 write"]); + assert_eq!(rig.target().durable_contents(), b"ab"); +} + +#[test] +fn a_scripted_refusal_comes_back_with_its_cause_and_the_next_push_is_accepted() { + let mut rig = scripted(WRITES.nth(1), Fault::Refuse(RefusalCause::Busy)); + let refused = rig.write(1, 0, b"kept").expect_err("scripted busy"); + assert!(matches!( + refused.reason, + PushRefusal::Backend(RefusalCause::Busy) + )); + assert_eq!(refused.buffer.as_deref(), Some(b"kept".as_slice())); + rig.write(1, 0, b"kept").expect("only the first is refused"); + rig.run(); + assert_eq!(rig.target().contents(), b"kept"); +} + +#[test] +fn a_write_that_fails_by_script_never_reaches_the_target() { + let mut rig = scripted(WRITES, Fault::Fail(scripted_fault("the disk is full"))); + rig.write(1, 0, b"x").expect("accepted"); + rig.run(); + assert_eq!(shown(&mut rig), ["op0 write@1 failed by memory write"]); + assert!(rig.target().contents().is_empty()); +} diff --git a/crates/durable-ioring/src/io_target.rs b/crates/durable-ioring/src/io_target.rs index 245daafb7..6dd97f07e 100644 --- a/crates/durable-ioring/src/io_target.rs +++ b/crates/durable-ioring/src/io_target.rs @@ -2,12 +2,13 @@ //! What a target states about itself (DI-D-48, DI-D-50): the [`IoTarget`] trait. //! //! The obligations a target meets are the contract's ("What this contract requires of a target"); -//! this module carries what a target *reports* -- its flush domains, warranty class, geometry and -//! capabilities. The driver hands the target itself to the backend, which runs its I/O, and its -//! [`IoTargetDescription`] to the core, which decides with it (DI-D-52). +//! this module carries what a target *reports* -- its flush domains, warranty class, geometry, +//! capabilities and data access. The driver hands the target itself to the backend, which runs its +//! I/O, and its [`IoTargetDescription`] to the core, which decides with it (DI-D-52). use std::fmt; +use crate::access::Access; use crate::types::FlushDomain; #[cfg(test)] @@ -167,6 +168,8 @@ pub struct IoTargetDescription { pub geometry: Geometry, /// What it offers beyond its obligations. pub capabilities: Capabilities, + /// What it can do with its data: an operation this does not cover is refused (DI-D-66). + pub access: Access, } /// A target: what a write or a read addresses and a commit makes durable, defined by the @@ -188,6 +191,12 @@ pub trait IoTarget: Send { /// What it offers beyond its obligations. fn capabilities(&self) -> Capabilities; + /// What it can do with its data: read it, write it, or both. A backend refuses a write to a target + /// that cannot write and a read from one that cannot read, with + /// [`RefusalCause::AccessDenied`](crate::backend::RefusalCause::AccessDenied). There is no + /// default: a target that has not said what it can do has not said it can do everything. + fn access(&self) -> Access; + /// Everything it states, as the core takes it. fn describe(&self) -> IoTargetDescription { IoTargetDescription { @@ -195,6 +204,7 @@ pub trait IoTarget: Send { warranty: self.warranty(), geometry: self.geometry(), capabilities: self.capabilities(), + access: self.access(), } } } diff --git a/crates/durable-ioring/src/io_target/tests.rs b/crates/durable-ioring/src/io_target/tests.rs index 0938626e7..3dbfcdca6 100644 --- a/crates/durable-ioring/src/io_target/tests.rs +++ b/crates/durable-ioring/src/io_target/tests.rs @@ -2,6 +2,7 @@ //! A target's geometry and description. use super::{Capabilities, Geometry, GeometryError, IoTarget, IoTargetDescription, WarrantyClass}; +use crate::access::Access; use crate::memory::MemoryTarget; use crate::types::FlushDomain; @@ -102,7 +103,8 @@ fn a_description_is_everything_the_target_states() { .with_domains(domains.clone()) .with_warranty(WarrantyClass::SharedDevice) .with_geometry(geometry) - .with_capabilities(Capabilities::NONE.with_write_through()); + .with_capabilities(Capabilities::NONE.with_write_through()) + .with_access(Access::ReadOnly); assert_eq!( target.describe(), IoTargetDescription { @@ -110,6 +112,7 @@ fn a_description_is_everything_the_target_states() { warranty: WarrantyClass::SharedDevice, geometry, capabilities: Capabilities::NONE.with_write_through(), + access: Access::ReadOnly, } ); } @@ -121,4 +124,14 @@ fn a_target_with_no_domains_describes_none() { assert_eq!(description.warranty, WarrantyClass::Complete); assert_eq!(description.geometry, Geometry::UNALIGNED); assert_eq!(description.capabilities, Capabilities::NONE); + assert_eq!(description.access, Access::ReadWrite); +} + +#[test] +fn a_description_carries_the_access_a_target_states_and_nothing_else_does() { + for access in [Access::ReadOnly, Access::WriteOnly, Access::ReadWrite] { + let target = MemoryTarget::new().with_access(access); + assert_eq!(target.access(), access); + assert_eq!(target.describe().access, access); + } } diff --git a/crates/durable-ioring/src/key.rs b/crates/durable-ioring/src/key.rs new file mode 100644 index 000000000..01f3132ba --- /dev/null +++ b/crates/durable-ioring/src/key.rs @@ -0,0 +1,166 @@ +// Copyright (c) 2026 Mike Grier +//! A target's key, typed by the access its holder declares for it (DI-13.10, DI-D-66). +//! +//! A ring addresses a target by [`IoTargetKey`], a number the consumer picks, so the key alone says +//! nothing of what the target can do. [`Key`] is the same number with a declaration beside it: that +//! the target can do what the [`HasData`] marker `A` says. The ring's submit methods take a +//! [`WriteKey`] or a [`ReadKey`], and a `Key` is one only where `A` can write or can read, so a +//! write through a `Key` does not compile. An untyped `IoTargetKey` is both, and is judged +//! when it is pushed ([DI-13.9](crate::backend::RefusalCause::AccessDenied)); so is any typed key that +//! declared more than its target has, unless registration refused it first. +//! +//! The declaration is the consumer's, and the ring cannot see it: the target is erased behind a +//! number. Registration is where it is compared with the target's own stated access, so it is the +//! only runtime step a typed key adds. A key that declares less than its target can do is accepted: +//! it is a narrower view of the same target. + +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; + +use crate::access::{Access, CanRead, CanWrite, HasData}; +use crate::types::{AddTargetCause, IoTargetKey}; + +mod sealed { + pub trait Sealed {} +} + +/// A key a target can be registered under: [`IoTargetKey`], which declares nothing, or a [`Key`], +/// which declares the access `A` names. Sealed, so that no other type can stand for a key that has +/// not been checked against what it claims. +pub trait TargetKey: sealed::Sealed { + /// The number the ring addresses the target by. + fn untyped(&self) -> IoTargetKey; + + /// The access this key declares for its target, or `None` for an untyped key. + fn declared(&self) -> Option; + + /// Whether a target that states `actual` may be registered under this key: a declaration the + /// target covers, or none. What it refuses is the cause to report. + /// + /// # Errors + /// + /// [`AddTargetCause::AccessNotCovered`] when the key declares more than `actual` can do. + fn admitted_by(&self, actual: Access) -> Result<(), AddTargetCause> { + match self.declared() { + Some(declared) if !actual.covers(declared) => { + Err(AddTargetCause::AccessNotCovered { declared, actual }) + } + _ => Ok(()), + } + } +} + +/// A key a write may be pushed through: an [`IoTargetKey`], or a [`Key`] where `A` can write. +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{Key, ReadOnly, WriteKey}; +/// +/// fn needs_write(_: K) {} +/// needs_write(Key::::new(durable_ioring::IoTargetKey(1))); +/// ``` +pub trait WriteKey: TargetKey {} + +/// A key a read may be pushed through: an [`IoTargetKey`], or a [`Key`] where `A` can read. +/// +/// ```compile_fail,E0277 +/// use durable_ioring::{Key, ReadKey, WriteOnly}; +/// +/// fn needs_read(_: K) {} +/// needs_read(Key::::new(durable_ioring::IoTargetKey(1))); +/// ``` +pub trait ReadKey: TargetKey {} + +/// An [`IoTargetKey`] with the access its holder declares for the target under it, as the type `A`. +/// The declaration is checked against the target's stated access when the target is registered +/// under this key, and not afterwards. +pub struct Key { + key: IoTargetKey, + access: PhantomData A>, +} + +impl Key { + /// `key`, declared to address a target that can do what `A` says. + #[must_use] + pub const fn new(key: IoTargetKey) -> Self { + Self { + key, + access: PhantomData, + } + } + + /// The number the ring addresses the target by. + #[must_use] + pub const fn untyped(self) -> IoTargetKey { + self.key + } +} + +// By hand: a derive would require `A` to implement each trait, and the markers implement few. +impl Clone for Key { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Key {} + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for Key {} + +impl Hash for Key { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key") + .field("key", &self.key) + .field("declared", &A::ACCESS) + .finish() + } +} + +impl From> for IoTargetKey { + fn from(key: Key) -> Self { + key.key + } +} + +impl sealed::Sealed for IoTargetKey {} +impl sealed::Sealed for Key {} + +impl TargetKey for IoTargetKey { + fn untyped(&self) -> IoTargetKey { + *self + } + + fn declared(&self) -> Option { + None + } +} + +impl TargetKey for Key { + fn untyped(&self) -> IoTargetKey { + self.key + } + + fn declared(&self) -> Option { + Some(A::ACCESS) + } +} + +impl WriteKey for IoTargetKey {} +impl ReadKey for IoTargetKey {} +impl WriteKey for Key {} +impl ReadKey for Key {} + +#[cfg(test)] +mod tests; diff --git a/crates/durable-ioring/src/key/tests.rs b/crates/durable-ioring/src/key/tests.rs new file mode 100644 index 000000000..726c330b1 --- /dev/null +++ b/crates/durable-ioring/src/key/tests.rs @@ -0,0 +1,155 @@ +// Copyright (c) 2026 Mike Grier + +use std::collections::HashSet; + +use super::{Key, ReadKey, TargetKey, WriteKey}; +use crate::access::{Access, HasData, ReadOnly, ReadWrite, WriteOnly}; +use crate::types::{AddTargetCause, IoTargetKey}; + +const ALL: [Access; 3] = [Access::ReadOnly, Access::WriteOnly, Access::ReadWrite]; + +fn pushes_a_write(key: K) -> IoTargetKey { + key.untyped() +} + +fn pushes_a_read(key: K) -> IoTargetKey { + key.untyped() +} + +#[test] +fn an_untyped_key_declares_nothing_and_is_the_number_it_holds() { + for number in [0, 1, 7, u64::MAX] { + let key = IoTargetKey(number); + assert_eq!(TargetKey::untyped(&key), key); + assert_eq!(key.declared(), None); + } +} + +#[test] +fn an_untyped_key_may_write_and_read_so_the_core_judges_it_when_it_is_pushed() { + let key = IoTargetKey(3); + assert_eq!(pushes_a_write(key), key); + assert_eq!(pushes_a_read(key), key); +} + +#[test] +fn a_typed_key_declares_the_access_its_marker_names() { + let number = IoTargetKey(9); + assert_eq!( + Key::::new(number).declared(), + Some(Access::ReadOnly) + ); + assert_eq!( + Key::::new(number).declared(), + Some(Access::WriteOnly) + ); + assert_eq!( + Key::::new(number).declared(), + Some(Access::ReadWrite) + ); +} + +#[test] +fn a_typed_key_keeps_the_number_through_every_way_of_taking_it_out() { + let number = IoTargetKey(41); + let key = Key::::new(number); + assert_eq!(key.untyped(), number); + assert_eq!(TargetKey::untyped(&key), number); + assert_eq!(IoTargetKey::from(key), number); +} + +#[test] +fn a_key_that_can_write_is_a_write_key_and_one_that_can_read_is_a_read_key() { + let number = IoTargetKey(5); + assert_eq!(pushes_a_write(Key::::new(number)), number); + assert_eq!(pushes_a_write(Key::::new(number)), number); + assert_eq!(pushes_a_read(Key::::new(number)), number); + assert_eq!(pushes_a_read(Key::::new(number)), number); +} + +#[test] +fn a_key_that_cannot_write_or_cannot_read_is_refused_to_the_compiler() { + // `Key` as a `WriteKey` and `Key` as a `ReadKey` do not compile; the + // doctests on the traits hold that. What can be held here is that each is still a registrable + // key, so the refusal is of the push and not of the key's existence. + fn registrable(_: K) {} + registrable(Key::::new(IoTargetKey(1))); + registrable(Key::::new(IoTargetKey(1))); +} + +#[test] +fn typed_keys_compare_and_hash_by_their_number_alone() { + let a = Key::::new(IoTargetKey(1)); + let same = Key::::new(IoTargetKey(1)); + let other = Key::::new(IoTargetKey(2)); + assert_eq!(a, same); + assert_ne!(a, other); + assert_eq!(a, a.clone()); + let set: HashSet> = [a, same, other].into_iter().collect(); + assert_eq!(set.len(), 2); +} + +#[test] +fn a_typed_keys_debug_shows_its_number_and_what_it_declares() { + let text = format!("{:?}", Key::::new(IoTargetKey(12))); + assert!(text.contains("12"), "{text}"); + assert!(text.contains("ReadOnly"), "{text}"); +} + +#[test] +fn a_target_is_admitted_under_a_key_whose_declaration_it_covers() { + // Every pair of declared and stated access, in one table: the target must be able to do all + // that the key declares, and a key that declares less than it can do is a narrower view. + let expected = [ + // (declared, actual, admitted) + (Access::ReadOnly, Access::ReadOnly, true), + (Access::ReadOnly, Access::WriteOnly, false), + (Access::ReadOnly, Access::ReadWrite, true), + (Access::WriteOnly, Access::ReadOnly, false), + (Access::WriteOnly, Access::WriteOnly, true), + (Access::WriteOnly, Access::ReadWrite, true), + (Access::ReadWrite, Access::ReadOnly, false), + (Access::ReadWrite, Access::WriteOnly, false), + (Access::ReadWrite, Access::ReadWrite, true), + ]; + for (declared, actual, admitted) in expected { + let key = match declared { + Access::ReadOnly => Key::::new(IoTargetKey(1)).admitted_by(actual), + Access::WriteOnly => Key::::new(IoTargetKey(1)).admitted_by(actual), + Access::ReadWrite => Key::::new(IoTargetKey(1)).admitted_by(actual), + }; + let wanted = if admitted { + Ok(()) + } else { + Err(AddTargetCause::AccessNotCovered { declared, actual }) + }; + assert_eq!(key, wanted, "declared {declared}, stated {actual}"); + } +} + +#[test] +fn an_untyped_key_is_admitted_by_every_target() { + for actual in ALL { + assert_eq!(IoTargetKey(1).admitted_by(actual), Ok(()), "{actual}"); + } +} + +#[test] +fn a_refusal_names_what_was_declared_and_what_the_target_states() { + let cause = Key::::new(IoTargetKey(1)) + .admitted_by(Access::ReadOnly) + .expect_err("a read-only target does not cover read and write"); + let text = cause.to_string(); + assert!(text.contains("read and write"), "{text}"); + assert!(text.contains("read only"), "{text}"); +} + +#[test] +fn the_marker_a_key_is_made_with_decides_what_it_declares_and_nothing_else_does() { + fn declared() -> Option { + Key::::new(IoTargetKey(0)).declared() + } + assert_eq!(declared::(), Some(ReadOnly::ACCESS)); + assert_eq!(declared::(), Some(WriteOnly::ACCESS)); + assert_eq!(declared::(), Some(ReadWrite::ACCESS)); +} diff --git a/crates/durable-ioring/src/lib.rs b/crates/durable-ioring/src/lib.rs index 51385c9ff..d1c34be27 100644 --- a/crates/durable-ioring/src/lib.rs +++ b/crates/durable-ioring/src/lib.rs @@ -11,9 +11,11 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] +mod access; pub mod backend; mod clock; mod contract; +mod correlation; mod diagnostics; mod domains; pub mod driver; @@ -23,21 +25,29 @@ mod error_code; mod ids; mod io_core; mod io_target; +mod key; +mod lock_watch; pub mod memory; pub mod oracle; mod provider; mod pump; +mod router; #[cfg(test)] mod test_support; mod types; +pub use access::{ + Access, CanRead, CanWrite, DataAccess, HasData, NoData, ReadOnly, ReadWrite, WriteOnly, +}; pub use clock::{Instant, StampClock}; pub use contract::{ DurableRing, EntryOf, EpochId, EpochIdOf, FailureIdOf, HandleOf, Identities, Lin, OpIdOf, PushResult, RegisteredBufferRing, TokenOf, }; +pub use correlation::CorrelationId; pub use diagnostics::{ - Diagnostic, DiagnosticKind, DiagnosticSink, FanOutDiagnostics, NoDiagnostics, StderrDiagnostics, + Diagnostic, DiagnosticKind, DiagnosticSink, Edge, FanOutDiagnostics, FlushOutcome, FlushPart, + LINE_PREFIX, NoDiagnostics, Pairing, SealOutcome, StderrDiagnostics, }; pub use error_code::ErrorCode; pub use ids::{DioringIds, FailureId, FailureToken, Lineage, LineageHandle, OpId}; @@ -45,14 +55,16 @@ pub use io_core::{CoreActions, DuplicateDomain, DurableIoCore}; pub use io_target::{ Capabilities, Geometry, GeometryError, IoTarget, IoTargetDescription, WarrantyClass, }; +pub use key::{Key, ReadKey, TargetKey, WriteKey}; pub use provider::{ DomainCompletion, DomainOutcome, DomainRequest, DurabilityProvider, FlushRequest, IoTargetWrites, ProviderPart, ProviderRequest, }; pub use pump::Waitable; +pub use router::{RoutedTwice, RouterBuilder, RoutingProvider}; pub use types::{ - AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, Entry, Epoch, - EpochState, Failed, FailureCause, FailureInfo, FlushDomain, HeldOperation, ImportScope, + AddTargetCause, AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, Entry, + Epoch, EpochState, Failed, FailureCause, FailureInfo, FlushDomain, HeldOperation, ImportScope, Imported, IoFault, IoTargetBusy, IoTargetKey, Leftovers, LineageBusy, LineageInfo, Marking, MarkingKind, OpCompletion, OpKind, Outcome, PushError, PushRefusal, ReadOptions, RemoveTargetError, Resolution, ResolveError, ResolveRefusal, RetireLineageError, diff --git a/crates/durable-ioring/src/lock_watch.rs b/crates/durable-ioring/src/lock_watch.rs new file mode 100644 index 000000000..4e55c150e --- /dev/null +++ b/crates/durable-ioring/src/lock_watch.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2026 Mike Grier +//! The waits for an instance's locks (DI-12.1.1, DI-D-26, DI-D-87): a lock taken through a +//! [`LockWatch`] reports a wait when -- and only when -- taking it had to, as a pair: its begin +//! when the immediate try fails, and its end as the lock is taken, with the lock held. +//! +//! The core's sink lives inside the engine, which is itself behind the lock being watched, so a wait +//! cannot be reported from inside it. The watch holds its own handle to the sink, and sits beside +//! the lock instead. +//! +//! An acquisition that is immediate costs a `try_lock` and nothing else: no id is minted and no +//! event is built. A sink is asked once a lock is found held, and only then. + +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError, TryLockError}; + +use crate::correlation::CorrelationId; +use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink}; + +/// What an instance calls the lock on its engine's slot in a [`LockWatch`] report. +pub const ENGINE_LOCK: &str = "engine"; + +/// Where an instance's lock waits are reported. +#[derive(Clone)] +pub struct LockWatch { + instance: u64, + sink: Arc, +} + +impl LockWatch { + /// A watch reporting `instance`'s lock waits to `sink`. + pub fn new(instance: u64, sink: Arc) -> Self { + Self { instance, sink } + } + + /// Lock `mutex`, which this instance calls `name`. A panic while it was held is an + /// inconsistency the core already reported and panicked on (DI-D-46): the state is left as + /// that panic found it, as [`crate::engine::lock`] does. + pub fn lock<'a, T>(&'a self, mutex: &'a Mutex, name: &'static str) -> WatchedGuard<'a, T> { + let guard = match mutex.try_lock() { + Ok(guard) => guard, + Err(TryLockError::Poisoned(poisoned)) => poisoned.into_inner(), + Err(TryLockError::WouldBlock) => { + let Some(id) = CorrelationId::for_pair(&*self.sink, DiagnosticKind::LockWait) + else { + return WatchedGuard::unwatched( + mutex.lock().unwrap_or_else(PoisonError::into_inner), + ); + }; + self.sink.report(&Diagnostic::LockWaitBegan { + instance: self.instance, + lock: name, + id, + }); + let taken = mutex.lock().unwrap_or_else(PoisonError::into_inner); + // Written with the lock held, as the engineer ruled (DI-D-80): the sink's own + // synchronization is a leaf beneath it, which `DiagnosticSink` requires. + self.sink.report(&Diagnostic::LockWaitEnded { + instance: self.instance, + lock: name, + id, + }); + taken + } + }; + WatchedGuard { guard } + } +} + +/// A locked mutex, taken through a [`LockWatch`] or without one. +pub struct WatchedGuard<'a, T> { + guard: MutexGuard<'a, T>, +} + +impl<'a, T> WatchedGuard<'a, T> { + /// A guard whose acquisition is not reported: a lock taken with no watch. + pub fn unwatched(guard: MutexGuard<'a, T>) -> Self { + Self { guard } + } +} + +impl Deref for WatchedGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + &self.guard + } +} + +impl DerefMut for WatchedGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.guard + } +} + +#[cfg(test)] +mod tests; diff --git a/crates/durable-ioring/src/lock_watch/tests.rs b/crates/durable-ioring/src/lock_watch/tests.rs new file mode 100644 index 000000000..003853a75 --- /dev/null +++ b/crates/durable-ioring/src/lock_watch/tests.rs @@ -0,0 +1,252 @@ +// Copyright (c) 2026 Mike Grier +//! The lock watch (DI-12.1.1, DI-19.2.3): an immediate acquisition is free and silent; a waited one +//! is a pair, its begin before the wait and its end with the lock held, held to the pairing rule by +//! `PairingChecker`. + +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::mpsc::{Sender, channel}; +use std::sync::{Arc, Mutex}; +use std::thread; + +use super::{LockWatch, WatchedGuard}; +use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink}; +use crate::oracle::PairingChecker; + +type Hook = Box; + +/// A sink keeping each lock wait's pair, checked against the pairing rule, and wanting lock waits +/// or not as it was built. It records under a mutex it holds only for that. +struct Waits { + wanted: bool, + asked: AtomicU32, + pairs: Mutex<(Vec, PairingChecker)>, + /// Told as a begin is reported: the waiting thread is about to block. + began: Mutex>>, + /// Run as an end is reported, for a test that looks at the world as the end finds it. + on_end: Mutex>, +} + +impl Waits { + fn new(wanted: bool) -> Arc { + Arc::new(Self { + wanted, + asked: AtomicU32::new(0), + pairs: Mutex::new((Vec::new(), PairingChecker::new())), + began: Mutex::new(None), + on_end: Mutex::new(None), + }) + } + + fn lines(&self) -> Vec { + self.pairs.lock().expect("the pairs").0.clone() + } + + /// Every pair begun has ended, and the stream kept the rule. + fn finished(&self) { + let checker = std::mem::take(&mut self.pairs.lock().expect("the pairs").1); + checker.finish(&[]).expect("every wait's pair ended"); + } +} + +impl DiagnosticSink for Waits { + fn wants(&self, kind: DiagnosticKind) -> bool { + self.asked.fetch_add(1, Ordering::Relaxed); + self.wanted && kind == DiagnosticKind::LockWait + } + + fn report(&self, event: &Diagnostic<'_>) { + assert!(event.pairing().is_some(), "a lock watch reported {event}"); + if matches!(event, Diagnostic::LockWaitEnded { .. }) + && let Some(look) = &*self.on_end.lock().expect("the hook") + { + look(); + } + { + let mut pairs = self.pairs.lock().expect("the pairs"); + pairs.0.push(event.to_string()); + pairs.1.observe(event).expect("the pairing rule"); + } + if matches!(event, Diagnostic::LockWaitBegan { .. }) + && let Some(began) = &*self.began.lock().expect("the signal") + { + began.send(()).expect("the test is listening"); + } + } +} + +fn watch(instance: u64, sink: &Arc) -> LockWatch { + LockWatch::new(instance, sink.clone()) +} + +/// Take `name` through `watch` on another thread while this one holds `mutex`, and let go of it +/// once the other has reported its wait's begin; `meanwhile` runs while it waits. +fn waited_for( + watch: &LockWatch, + sink: &Waits, + mutex: &Mutex, + name: &'static str, + meanwhile: impl FnOnce(), +) { + let (signal, began) = channel(); + *sink.began.lock().expect("the signal") = Some(signal); + let held = mutex.lock().expect("not poisoned"); + thread::scope(|scope| { + let other = scope.spawn(|| drop(watch.lock(mutex, name))); + began.recv().expect("the other thread began its wait"); + meanwhile(); + drop(held); + other.join().expect("the other thread finished"); + }); +} + +#[test] +fn an_immediate_acquisition_reports_nothing_and_asks_nothing() { + let sink = Waits::new(true); + let mutex = Mutex::new(5); + let watch = watch(1, &sink); + assert_eq!(*watch.lock(&mutex, "engine"), 5); + drop(watch.lock(&mutex, "engine")); + assert!(sink.lines().is_empty()); + assert_eq!( + sink.asked.load(Ordering::Relaxed), + 0, + "the sink was asked though no wait happened" + ); + sink.finished(); +} + +#[test] +fn a_waited_acquisition_is_one_pair_naming_its_instance_and_its_lock() { + let sink = Waits::new(true); + let mutex = Mutex::new(()); + let watch = watch(9, &sink); + waited_for(&watch, &sink, &mutex, "engine", || {}); + let lines = sink.lines(); + assert_eq!(lines.len(), 2, "{lines:?}"); + assert!( + lines[0].starts_with("instance 9 (pair ") + && lines[0].ends_with("waits for its engine lock"), + "{lines:?}" + ); + assert!( + lines[1].starts_with("instance 9 (pair ") && lines[1].ends_with("took its engine lock"), + "{lines:?}" + ); + assert_eq!(sink.asked.load(Ordering::Relaxed), 1, "asked once"); + sink.finished(); +} + +#[test] +fn a_pairs_begin_is_written_before_the_wait_and_its_end_after_it() { + let sink = Waits::new(true); + let mutex = Mutex::new(()); + let watch = watch(1, &sink); + waited_for(&watch, &sink, &mutex, "engine", || { + assert_eq!( + sink.lines().len(), + 1, + "the begin, and only the begin, before the wait" + ); + }); + assert_eq!(sink.lines().len(), 2); + sink.finished(); +} + +#[test] +fn a_pairs_end_is_written_with_the_lock_held_and_the_sinks_own_lock_is_a_leaf_beneath_it() { + let sink = Waits::new(true); + let mutex = Arc::new(Mutex::new(())); + let looked = Arc::new(AtomicU32::new(0)); + { + let (mutex, looked) = (mutex.clone(), looked.clone()); + *sink.on_end.lock().expect("the hook") = Some(Box::new(move || { + assert!( + mutex.try_lock().is_err(), + "the end was written with the lock let go" + ); + looked.fetch_add(1, Ordering::Relaxed); + })); + } + let watch = watch(1, &sink); + waited_for(&watch, &sink, &mutex, "engine", || {}); + assert_eq!(looked.load(Ordering::Relaxed), 1); + // The sink took its own mutex to record the end while the watched lock was held, and no + // thread holds the sink's mutex while it takes the watched one: the order is a leaf beneath. + sink.finished(); +} + +#[test] +fn each_waited_acquisition_is_its_own_pair() { + let sink = Waits::new(true); + let mutex = Mutex::new(()); + let watch = watch(2, &sink); + for name in ["engine", "hub", "engine", "slot"] { + waited_for(&watch, &sink, &mutex, name, || {}); + } + assert_eq!(sink.lines().len(), 8); + sink.finished(); +} + +#[test] +fn a_waited_acquisition_by_a_sink_that_does_not_want_it_is_no_pair_and_asks_once() { + let sink = Waits::new(false); + let mutex = Mutex::new(()); + let watch = watch(1, &sink); + let (signal, started) = channel(); + let held = mutex.lock().expect("not poisoned"); + thread::scope(|scope| { + let other = scope.spawn(|| { + signal.send(()).expect("the test is listening"); + drop(watch.lock(&mutex, "engine")); + }); + started.recv().expect("the other thread started"); + thread::sleep(std::time::Duration::from_millis(20)); + drop(held); + other.join().expect("the other thread finished"); + }); + assert!(sink.lines().is_empty()); + assert!( + sink.asked.load(Ordering::Relaxed) <= 1, + "asked once at most" + ); + sink.finished(); +} + +#[test] +fn a_poisoned_lock_is_taken_as_the_panic_left_it_and_reports_nothing() { + let sink = Waits::new(true); + let mutex = Mutex::new(7); + let poisoned = thread::scope(|scope| { + scope + .spawn(|| { + let _held = mutex.lock().expect("not yet poisoned"); + panic!("poisoning the lock on purpose"); + }) + .join() + }); + assert!(poisoned.is_err()); + assert!(mutex.is_poisoned()); + let watch = watch(1, &sink); + assert_eq!(*watch.lock(&mutex, "engine"), 7); + assert!(sink.lines().is_empty()); + sink.finished(); +} + +#[test] +fn a_change_made_through_the_guard_is_there_when_it_is_let_go() { + let sink = Waits::new(true); + let mutex = Mutex::new(vec![1]); + let watch = watch(1, &sink); + watch.lock(&mutex, "engine").push(2); + assert_eq!(*mutex.lock().expect("not poisoned"), [1, 2]); +} + +#[test] +fn an_unwatched_guard_derefs_and_reports_nothing() { + let mutex = Mutex::new(1); + let mut guard = WatchedGuard::unwatched(mutex.lock().expect("not poisoned")); + *guard += 1; + assert_eq!(*guard, 2); + drop(guard); + assert_eq!(*mutex.lock().expect("not poisoned"), 2); +} diff --git a/crates/durable-ioring/src/memory.rs b/crates/durable-ioring/src/memory.rs index fe020091f..08540e899 100644 --- a/crates/durable-ioring/src/memory.rs +++ b/crates/durable-ioring/src/memory.rs @@ -2,9 +2,11 @@ //! An in-memory backend and target (DI-D-50 P13, DI-D-52): a deterministic backend, holding each //! target's bytes in memory, for testing the core and driving it without a device. //! -//! It faults on nothing of its own accord; `DI-11.1` adds the fault script. A submission is executed -//! when it is submitted, in push order, and its completion is ready at once. A commit makes the -//! target's current bytes its durable bytes, so a test can read what a commit warranted. +//! It faults on nothing of its own accord. A [`FaultScript`] (DI-11.1, DI-D-74) says what it and its +//! targets do wrong and when: an operation failed, held, completed late or out of order or short, a +//! push refused, the doorbell or a pop failed. A submission is executed when it is submitted, in push +//! order, and its completion is ready at once, unless a script holds it. A commit makes the target's +//! current bytes its durable bytes, so a test can read what a commit warranted. //! //! **Refusals.** A push is refused with [`RefusalCause::SubmitNeeded`] when the submission queue is //! full, and with [`RefusalCause::DrainNeeded`] when as many submissions are queued or completed and @@ -17,6 +19,7 @@ use std::num::NonZeroU32; use std::sync::{Arc, Condvar, Mutex, PoisonError}; use std::time::Duration; +use crate::access::Access; use crate::backend::{ Completion, DuplicateTarget, IoBackend, RefusalCause, Refused, RegisteredRegion, Submission, SubmissionData, SubmissionOp, SubmissionToken, @@ -25,9 +28,13 @@ use crate::io_target::{Capabilities, Geometry, IoTarget, WarrantyClass}; use crate::pump::Waitable; use crate::types::{FlushDomain, IoFault, IoTargetKey}; +mod script; #[cfg(test)] mod tests; +pub use script::{Fault, FaultScript, Fired, ScriptOp, When}; +use script::{Point, Probe, ScriptState}; + /// The source every fault the in-memory backend reports names. pub const MEMORY: &str = "memory"; @@ -39,9 +46,17 @@ fn memory_fault(message: &str) -> IoFault { } } +/// A fault from the in-memory backend with this message and no code, for a [`FaultScript`] to fail +/// an operation with: the same kind of fault the backend reports for its own failures, whose source +/// is [`MEMORY`]. +#[must_use] +pub fn scripted_fault(message: &str) -> IoFault { + memory_fault(message) +} + /// A target whose bytes are held in memory: what was written, and what the last successful commit /// warranted. It states what it is built with: no flush domains, [`WarrantyClass::Complete`], -/// [`Geometry::UNALIGNED`] and [`Capabilities::NONE`] unless set. +/// [`Geometry::UNALIGNED`], [`Capabilities::NONE`] and [`Access::ReadWrite`] unless set. #[derive(Clone, Debug)] pub struct MemoryTarget { contents: Vec, @@ -50,6 +65,7 @@ pub struct MemoryTarget { warranty: WarrantyClass, geometry: Geometry, capabilities: Capabilities, + access: Access, } impl Default for MemoryTarget { @@ -69,6 +85,7 @@ impl MemoryTarget { warranty: WarrantyClass::Complete, geometry: Geometry::UNALIGNED, capabilities: Capabilities::NONE, + access: Access::ReadWrite, } } @@ -100,6 +117,14 @@ impl MemoryTarget { self } + /// State what it can do with its data, which the backend enforces: a write to a target that + /// cannot write, or a read from one that cannot read, is refused. + #[must_use] + pub fn with_access(mut self, access: Access) -> Self { + self.access = access; + self + } + /// Its bytes as written: every byte up to the end of the furthest write, zero where nothing /// was written. #[must_use] @@ -154,6 +179,10 @@ impl IoTarget for MemoryTarget { fn capabilities(&self) -> Capabilities { self.capabilities } + + fn access(&self) -> Access { + self.access + } } /// The in-memory backend's notification: a count of its raisings, which a thread can wait to see @@ -226,6 +255,17 @@ pub struct MemoryBackend { submission_queue_size: usize, completion_queue_size: usize, notification: MemoryNotification, + script: ScriptState, + /// Completions a script is withholding, in the order they were withheld. + held: Vec, +} + +/// A completion a script is withholding. +#[derive(Debug)] +struct Held { + completion: Completion>, + /// How many more doorbells until the backend releases it; `None` until the test does. + late: Option, } impl MemoryBackend { @@ -241,6 +281,62 @@ impl MemoryBackend { submission_queue_size: size(submission_queue_size), completion_queue_size: size(completion_queue_size), notification: MemoryNotification::default(), + script: ScriptState::default(), + held: Vec::new(), + } + } + + /// Give it `script`, counted from the start, in place of any it had: what it and its targets + /// do wrong, and when. See [`FaultScript`]. + #[must_use] + pub fn with_script(mut self, script: FaultScript) -> Self { + self.script = ScriptState::new(script); + self + } + + /// The tokens of the completions a script is withholding, in the order they were withheld. + #[must_use] + pub fn held(&self) -> Vec { + self.held.iter().map(|held| held.completion.token).collect() + } + + /// Release the completion held under `token`: it is ready after every completion that is + /// ready now, and the notification is raised if none was. False if nothing is held under it. + pub fn release(&mut self, token: SubmissionToken) -> bool { + let Some(position) = self + .held + .iter() + .position(|held| held.completion.token == token) + else { + return false; + }; + let was_empty = self.completed.is_empty(); + let held = self.held.remove(position); + self.completed.push_back(held.completion); + self.raise_if_first(was_empty); + true + } + + /// Release every held completion, in the order they were held; how many. + pub fn release_all(&mut self) -> usize { + let was_empty = self.completed.is_empty(); + let count = self.held.len(); + self.completed + .extend(self.held.drain(..).map(|held| held.completion)); + self.raise_if_first(was_empty); + count + } + + /// What the script has fired, in the order it fired. + #[must_use] + pub fn fired(&self) -> &[Fired] { + self.script.fired() + } + + /// Raise the notification if the completions went from none to some. + fn raise_if_first(&self, was_empty: bool) { + if was_empty && !self.completed.is_empty() { + self.notification.raise(); } } @@ -255,7 +351,7 @@ impl MemoryBackend { #[must_use] pub fn with_registered(mut self, buffers: Vec>) -> Self { assert!( - self.queued.is_empty() && self.completed.is_empty(), + self.queued.is_empty() && self.completed.is_empty() && self.held.is_empty(), "registered buffers are set before anything is pushed: an operation outstanding may name one" ); self.registered = buffers; @@ -300,7 +396,8 @@ impl MemoryBackend { return Some(cause); } let submit = self.queued.len() >= self.submission_queue_size; - let drain = self.queued.len() + self.completed.len() >= self.completion_queue_size; + let outstanding = self.queued.len() + self.completed.len() + self.held.len(); + let drain = outstanding >= self.completion_queue_size; match (submit, drain) { (true, true) => Some(RefusalCause::SubmitAndDrainNeeded), (true, false) => Some(RefusalCause::SubmitNeeded), @@ -314,6 +411,14 @@ impl MemoryBackend { let Some(target) = self.targets.get(&target) else { return Err(RefusalCause::UnknownTarget); }; + let covered = match op { + SubmissionOp::Write { .. } => target.access.can_write(), + SubmissionOp::Read { .. } => target.access.can_read(), + SubmissionOp::Commit => true, + }; + if !covered { + return Err(RefusalCause::AccessDenied); + } let shape = match op { SubmissionOp::Write { offset, data, .. } | SubmissionOp::Read { offset, data } => { Some((*offset, data)) @@ -350,6 +455,59 @@ impl MemoryBackend { Ok(()) } + /// Execute `submission` as the script says and put its completion where the script says. + fn run(&mut self, submission: Submission>) { + let probe = Probe::of(submission.token, submission.target, &submission.op); + let fault = self.script.consult(Point::Execute, probe); + let (completion, late) = match fault { + None => (self.execute(submission, None), None), + Some(Fault::Fail(fault)) => ( + Completion { + token: submission.token, + result: Err(fault), + buffer: owned_buffer(submission.op), + }, + None, + ), + Some(Fault::FailAfterApplying(fault)) => { + let mut completion = self.execute(submission, None); + if completion.result.is_ok() { + completion.result = Err(fault); + } + (completion, None) + } + Some(Fault::Short(count)) => (self.execute(submission, Some(count)), None), + Some(Fault::Hold) => (self.execute(submission, None), Some(None)), + Some(Fault::Late(0)) => (self.execute(submission, None), None), + Some(Fault::Late(doorbells)) => (self.execute(submission, None), Some(Some(doorbells))), + Some(Fault::Refuse(_) | Fault::RefuseDoorbell(_) | Fault::FailPop(_)) => { + unreachable!("the script returns only the faults of the point it is asked at") + } + }; + match late { + None => self.completed.push_back(completion), + Some(late) => self.held.push(Held { completion, late }), + } + } + + /// Age every completion held for a number of doorbells by one, and take those it ripens. + fn ripen(&mut self) -> Vec>> { + let mut ripe = Vec::new(); + let mut kept = Vec::with_capacity(self.held.len()); + for mut held in std::mem::take(&mut self.held) { + if let Some(left) = held.late.as_mut() { + *left -= 1; + if *left == 0 { + ripe.push(held.completion); + continue; + } + } + kept.push(held); + } + self.held = kept; + ripe + } + /// The bytes `region` names, if it lies within a registered buffer. fn region(&self, region: RegisteredRegion) -> Option<&[u8]> { let start = position(region.offset); @@ -357,25 +515,19 @@ impl MemoryBackend { self.registered.get(position(region.index))?.get(start..end) } - /// Execute `submission` against its target. - fn execute(&mut self, submission: Submission>) -> Completion> { + /// Execute `submission` against its target, transferring at most `short` bytes of a write or + /// a read if that is given. + fn execute( + &mut self, + submission: Submission>, + short: Option, + ) -> Completion> { let Submission { token, target, op } = submission; let Some(target) = self.targets.get_mut(&target) else { - let buffer = match op { - SubmissionOp::Write { - data: SubmissionData::Owned(buffer), - .. - } - | SubmissionOp::Read { - data: SubmissionData::Owned(buffer), - .. - } => Some(buffer), - _ => None, - }; return Completion { token, result: Err(memory_fault("the target was detached")), - buffer, + buffer: owned_buffer(op), }; }; let (result, buffer) = match op { @@ -383,19 +535,21 @@ impl MemoryBackend { let offset = position_u64(offset); match data { SubmissionData::Owned(buffer) => { - if target.write(offset, &buffer).is_err() { + let count = limit(buffer.len(), short); + if target.write(offset, &buffer[..count]).is_err() { return could_not_grow(token, Some(buffer)); } - (buffer.len(), Some(buffer)) + (count, Some(buffer)) } SubmissionData::Registered(region) => { let start = position(region.offset); let bytes = &self.registered[position(region.index)] [start..start + position(region.length)]; - if target.write(offset, bytes).is_err() { + let count = limit(bytes.len(), short); + if target.write(offset, &bytes[..count]).is_err() { return could_not_grow(token, None); } - (bytes.len(), None) + (count, None) } } } @@ -403,13 +557,15 @@ impl MemoryBackend { let offset = position_u64(offset); match data { SubmissionData::Owned(mut buffer) => { - (target.read(offset, &mut buffer), Some(buffer)) + let count = limit(buffer.len(), short); + (target.read(offset, &mut buffer[..count]), Some(buffer)) } SubmissionData::Registered(region) => { let start = position(region.offset); let bytes = &mut self.registered[position(region.index)] [start..start + position(region.length)]; - (target.read(offset, bytes), None) + let count = limit(bytes.len(), short); + (target.read(offset, &mut bytes[..count]), None) } } } @@ -435,6 +591,27 @@ fn could_not_grow(token: SubmissionToken, buffer: Option>) -> Completion } } +/// The submission's owned buffer, which comes back with its completion; `None` for a registered +/// region or a commit. +fn owned_buffer(op: SubmissionOp>) -> Option> { + match op { + SubmissionOp::Write { + data: SubmissionData::Owned(buffer), + .. + } + | SubmissionOp::Read { + data: SubmissionData::Owned(buffer), + .. + } => Some(buffer), + _ => None, + } +} + +/// How many of `length` bytes transfer when at most `short` do, if a script says so. +fn limit(length: usize, short: Option) -> usize { + short.map_or(length, |count| length.min(position(count))) +} + /// A queue size as a count. fn size(size: NonZeroU32) -> usize { position(size.get()) @@ -483,23 +660,37 @@ impl IoBackend for MemoryBackend { if let Some(cause) = self.refusal(&submission) { return Err(Refused { cause, submission }); } + let probe = Probe::of(submission.token, submission.target, &submission.op); + if let Some(Fault::Refuse(cause)) = self.script.consult(Point::Push, probe) { + return Err(Refused { cause, submission }); + } self.queued.push_back(submission); Ok(()) } fn submit(&mut self) -> Result<(), RefusalCause> { + if let Some(Fault::RefuseDoorbell(cause)) = + self.script.consult(Point::Doorbell, Probe::bare()) + { + return Err(cause); + } let was_empty = self.completed.is_empty(); + // A completion held for a number of doorbells is ready, once they have passed, after what + // this doorbell executes, which is what makes it late. Aged first, so that what this + // doorbell holds is not aged by it. + let ripe = self.ripen(); while let Some(submission) = self.queued.pop_front() { - let completion = self.execute(submission); - self.completed.push_back(completion); - } - if was_empty && !self.completed.is_empty() { - self.notification.raise(); + self.run(submission); } + self.completed.extend(ripe); + self.raise_if_first(was_empty); Ok(()) } fn pop(&mut self) -> Result>>, IoFault> { + if let Some(Fault::FailPop(fault)) = self.script.consult(Point::Pop, Probe::bare()) { + return Err(fault); + } Ok(self.completed.pop_front()) } diff --git a/crates/durable-ioring/src/memory/script.rs b/crates/durable-ioring/src/memory/script.rs new file mode 100644 index 000000000..1adc2bc72 --- /dev/null +++ b/crates/durable-ioring/src/memory/script.rs @@ -0,0 +1,374 @@ +// Copyright (c) 2026 Mike Grier +//! The in-memory backend's fault script (DI-11.1, DI-D-50 P13, DI-D-74): a plan, written ahead of +//! a run, of what the backend and its targets do wrong, and when. +//! +//! A failing flush cannot be produced on a healthy machine, and a consumer's failure paths have +//! nothing to run against until one can be. A [`FaultScript`] is a list of steps, each a [`When`] and +//! a [`Fault`]: the nth write to a target fails, a commit fails, a completion is held until the test +//! releases it, a push is refused as though a queue were full. Nothing is random and nothing +//! depends on timing, so a run with a script is the same run every time. +//! +//! **Where a step applies.** A fault belongs to one point in the backend, which is how its [`When`] +//! counts: a refusal counts the pushes the backend would otherwise have admitted; a failure, a short +//! count, a hold or a late completion counts the submissions the doorbell executes; a refused doorbell +//! counts doorbells; a failed pop counts pops. Every step that matches a point counts it, whether or +//! not an earlier step fired for it, so a step's count does not depend on the steps before it; when +//! more than one fires for the same submission, the earliest in the script is the one applied. +//! +//! **The script does not make a refusal the backend would have made.** A push the backend refuses +//! for a real reason -- an unknown target, an access or geometry the target does not admit, a full +//! queue -- is refused for that reason, before the script is consulted, and is not counted. + +use crate::backend::{RefusalCause, SubmissionOp, SubmissionToken}; +use crate::types::{IoFault, IoTargetKey}; + +#[cfg(test)] +mod tests; + +/// Which kind of operation a step applies to. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ScriptOp { + /// A write. + Write, + /// A read. + Read, + /// A commit. + Commit, +} + +impl ScriptOp { + /// The kind of `op`. + pub(super) fn of(op: &SubmissionOp) -> Self { + match op { + SubmissionOp::Write { .. } => Self::Write, + SubmissionOp::Read { .. } => Self::Read, + SubmissionOp::Commit => Self::Commit, + } + } +} + +/// Which of the occurrences a step matches it fires on. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Occurrence { + /// Every one. + Every, + /// Only the nth, counting from one. + Nth(u32), + /// The nth and every one after it. + From(u32), +} + +/// When a step fires: which operations it matches, and which of those it fires on. +/// +/// Built from [`any`](Self::any) and narrowed: by the kind of operation, by the target, and by +/// occurrence, counting from one. With no occurrence given, a step fires on every match. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct When { + op: Option, + target: Option, + occurrence: Occurrence, +} + +impl When { + /// Every operation on every target, every time. + #[must_use] + pub const fn any() -> Self { + Self { + op: None, + target: None, + occurrence: Occurrence::Every, + } + } + + /// Only operations of this kind. + #[must_use] + pub const fn op(mut self, op: ScriptOp) -> Self { + self.op = Some(op); + self + } + + /// Only operations on this target. + #[must_use] + pub const fn target(mut self, target: IoTargetKey) -> Self { + self.target = Some(target); + self + } + + /// Only the `n`th match, counting from one, and no other. + /// + /// # Panics + /// + /// If `n` is zero: occurrences are counted from one. + #[must_use] + pub const fn nth(mut self, n: u32) -> Self { + assert!(n > 0, "occurrences are counted from one"); + self.occurrence = Occurrence::Nth(n); + self + } + + /// The `n`th match, counting from one, and every match after it: a device that fails from + /// then on. + /// + /// # Panics + /// + /// If `n` is zero: occurrences are counted from one. + #[must_use] + pub const fn from(mut self, n: u32) -> Self { + assert!(n > 0, "occurrences are counted from one"); + self.occurrence = Occurrence::From(n); + self + } + + /// Whether an operation of this kind on this target is one the step matches. `None` for + /// either is a point with no operation, which a step naming one does not match. + fn matches(&self, op: Option, target: Option) -> bool { + self.op.is_none_or(|wanted| op == Some(wanted)) + && self.target.is_none_or(|wanted| target == Some(wanted)) + } + + /// Whether the step fires on its `seen`th match, counting from one. + fn fires(&self, seen: u32) -> bool { + match self.occurrence { + Occurrence::Every => true, + Occurrence::Nth(n) => seen == n, + Occurrence::From(n) => seen >= n, + } + } + + /// Whether the step names an operation or a target. + fn is_narrowed(&self) -> bool { + self.op.is_some() || self.target.is_some() + } +} + +/// What goes wrong when a step fires. +/// +/// What each does to the operation, the target and the completion is stated on the variant. A +/// fault a step cannot apply to the kind of operation it matched is stated there too. +#[derive(Clone, Debug)] +pub enum Fault { + /// The push is refused for this cause, and the submission comes back unchanged. Refusing as + /// [`SubmitNeeded`](RefusalCause::SubmitNeeded), [`DrainNeeded`](RefusalCause::DrainNeeded) or + /// [`SubmitAndDrainNeeded`](RefusalCause::SubmitAndDrainNeeded) is a full queue that is not one. + Refuse(RefusalCause), + /// The operation does not reach its target and completes with this fault. A write leaves the + /// target's bytes as they were and a commit leaves its durable bytes as they were, which is a + /// flush that failed; the submission's owned buffer comes back with the completion. + Fail(IoFault), + /// The operation reaches its target, and completes with this fault anyway: a write whose bytes + /// are in the target but are reported failed, a commit that made the bytes durable and is + /// reported failed. A read is a read that failed, as for [`Fail`](Self::Fail). + FailAfterApplying(IoFault), + /// The operation transfers at most this many bytes and completes successfully with the count it + /// transferred. A write that completes short breaks the obligation that a write completes in + /// full or with a fault (DI-D-50 P2), which is what makes it a source for testing what handles + /// a short count, and what the conformance oracle refuses; a short read is an ordinary one. + /// A commit has no count: a step naming commits cannot be given this, and a step naming no + /// operation does not apply it to one. + Short(u32), + /// The operation executes as it would, and its completion is withheld until the test releases + /// it with [`MemoryBackend::release`](super::MemoryBackend::release): a completion that is + /// late for as long as the test wants, and out of order against the ones released before it. + Hold, + /// As [`Hold`](Self::Hold), released by the backend itself at the end of the doorbell this many + /// doorbells after the one that executed it. Zero is an ordinary completion. + Late(u32), + /// The doorbell fails with this cause and issues nothing: what was queued stays queued. A step + /// naming this names no operation or target. + RefuseDoorbell(RefusalCause), + /// The pop fails with this fault, and no completion is taken: what was ready stays ready. A step + /// naming this names no operation or target. + FailPop(IoFault), +} + +/// The place in the backend a fault applies at. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) enum Point { + /// A push the backend would otherwise admit. + Push, + /// A submission the doorbell executes. + Execute, + /// A doorbell. + Doorbell, + /// A pop. + Pop, +} + +impl Fault { + /// Where it applies. + pub(super) fn point(&self) -> Point { + match self { + Self::Refuse(_) => Point::Push, + Self::Fail(_) + | Self::FailAfterApplying(_) + | Self::Short(_) + | Self::Hold + | Self::Late(_) => Point::Execute, + Self::RefuseDoorbell(_) => Point::Doorbell, + Self::FailPop(_) => Point::Pop, + } + } +} + +/// One step of a script. +#[derive(Clone, Debug)] +struct Step { + when: When, + fault: Fault, +} + +/// A script of faults for the in-memory backend, given to it with +/// [`MemoryBackend::with_script`](super::MemoryBackend::with_script). +/// +/// A script is plain data and carries no count: the counts belong to the backend it is given to, so +/// one script can drive many backends, each from the start. +/// +/// # Example +/// +/// ``` +/// use durable_ioring::memory::{scripted_fault, Fault, FaultScript, ScriptOp, When}; +/// +/// // The second commit on any target fails, and every write after the third is held. +/// let script = FaultScript::new() +/// .on( +/// When::any().op(ScriptOp::Commit).nth(2), +/// Fault::Fail(scripted_fault("the device was removed")), +/// ) +/// .on(When::any().op(ScriptOp::Write).from(4), Fault::Hold); +/// assert_eq!(script.len(), 2); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct FaultScript { + steps: Vec, +} + +impl FaultScript { + /// A script with no steps, which does nothing. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Add a step: `fault`, when `when` says. Steps are consulted in the order they were added. + /// + /// # Panics + /// + /// For a step that cannot mean anything: [`Fault::Short`] on commits, which have no count, and + /// [`Fault::RefuseDoorbell`] or [`Fault::FailPop`] with an operation or a target, neither of + /// which a doorbell or a pop has. These are a script's author's mistakes, found where the + /// script is written and not when a run silently ignores them. + #[must_use] + pub fn on(mut self, when: When, fault: Fault) -> Self { + assert!( + !(matches!(fault, Fault::Short(_)) && when.op == Some(ScriptOp::Commit)), + "a commit has no count, so it cannot complete short" + ); + assert!( + !(matches!(fault.point(), Point::Doorbell | Point::Pop) && when.is_narrowed()), + "a doorbell or a pop has no operation or target to name" + ); + self.steps.push(Step { when, fault }); + self + } + + /// How many steps it has. + #[must_use] + pub fn len(&self) -> usize { + self.steps.len() + } + + /// Whether it has no steps. + #[must_use] + pub fn is_empty(&self) -> bool { + self.steps.is_empty() + } +} + +/// What a step fired for, recorded in the order the steps fired. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Fired { + /// The step's position in the script, counting from zero. + pub step: usize, + /// The submission it fired for; `None` for a doorbell or a pop. + pub token: Option, + /// The kind of that submission's operation. + pub op: Option, + /// That submission's target. + pub target: Option, +} + +/// What a point was reached with. +#[derive(Clone, Copy, Debug)] +pub(super) struct Probe { + pub(super) token: Option, + pub(super) op: Option, + pub(super) target: Option, +} + +impl Probe { + /// A doorbell or a pop, which has no submission. + pub(super) fn bare() -> Self { + Self { + token: None, + op: None, + target: None, + } + } + + /// A submission. + pub(super) fn of(token: SubmissionToken, target: IoTargetKey, op: &SubmissionOp) -> Self { + Self { + token: Some(token), + op: Some(ScriptOp::of(op)), + target: Some(target), + } + } +} + +/// A script and the counts a backend has made against it. +#[derive(Debug, Default)] +pub(super) struct ScriptState { + script: FaultScript, + /// How many times each step has matched, by step. + seen: Vec, + fired: Vec, +} + +impl ScriptState { + /// `script`, counted from the start. + pub(super) fn new(script: FaultScript) -> Self { + Self { + seen: vec![0; script.steps.len()], + script, + fired: Vec::new(), + } + } + + /// What `point`, reached with `probe`, does: every step at that point that matches counts it, + /// and the earliest that fires is the fault, if any. + pub(super) fn consult(&mut self, point: Point, probe: Probe) -> Option { + let mut firing = None; + for (index, step) in self.script.steps.iter().enumerate() { + if step.fault.point() != point || !step.when.matches(probe.op, probe.target) { + continue; + } + self.seen[index] = self.seen[index].saturating_add(1); + if firing.is_none() && step.when.fires(self.seen[index]) { + firing = Some(index); + } + } + let index = firing?; + self.fired.push(Fired { + step: index, + token: probe.token, + op: probe.op, + target: probe.target, + }); + Some(self.script.steps[index].fault.clone()) + } + + /// What has fired, in order. + pub(super) fn fired(&self) -> &[Fired] { + &self.fired + } +} diff --git a/crates/durable-ioring/src/memory/script/tests.rs b/crates/durable-ioring/src/memory/script/tests.rs new file mode 100644 index 000000000..cc676ab4b --- /dev/null +++ b/crates/durable-ioring/src/memory/script/tests.rs @@ -0,0 +1,763 @@ +// Copyright (c) 2026 Mike Grier +//! The fault script (DI-11.1): what each fault does to the operation, the target and the completion, +//! when a step fires, and what a script refuses to mean. + +use std::num::NonZeroU32; + +use super::{Fault, FaultScript, Fired, ScriptOp, When}; +use crate::backend::{ + Completion, IoBackend, RefusalCause, RegisteredRegion, Submission, SubmissionData, + SubmissionOp, SubmissionToken, +}; +use crate::memory::{MEMORY, MemoryBackend, MemoryTarget, scripted_fault}; +use crate::types::{IoFault, IoTargetKey, WriteCaching}; + +const KEY: IoTargetKey = IoTargetKey(7); +const OTHER: IoTargetKey = IoTargetKey(8); + +fn scripted(script: FaultScript) -> MemoryBackend { + scripted_queues(script, 8, 16) +} + +fn scripted_queues(script: FaultScript, submissions: u32, completions: u32) -> MemoryBackend { + let mut backend = MemoryBackend::new( + NonZeroU32::new(submissions).expect("non-zero"), + NonZeroU32::new(completions).expect("non-zero"), + ) + .with_script(script); + backend.attach(KEY, MemoryTarget::new()).expect("a new key"); + backend + .attach(OTHER, MemoryTarget::new()) + .expect("a new key"); + backend +} + +fn write_to(target: IoTargetKey, token: u64, offset: u64, bytes: &[u8]) -> Submission> { + Submission { + token: SubmissionToken(token), + target, + op: SubmissionOp::Write { + offset, + data: SubmissionData::Owned(bytes.to_vec()), + caching: WriteCaching::Cached, + }, + } +} + +fn write(token: u64, offset: u64, bytes: &[u8]) -> Submission> { + write_to(KEY, token, offset, bytes) +} + +fn read(token: u64, offset: u64, length: usize) -> Submission> { + Submission { + token: SubmissionToken(token), + target: KEY, + op: SubmissionOp::Read { + offset, + data: SubmissionData::Owned(vec![0xEE; length]), + }, + } +} + +fn commit_to(target: IoTargetKey, token: u64) -> Submission> { + Submission { + token: SubmissionToken(token), + target, + op: SubmissionOp::Commit, + } +} + +fn commit(token: u64) -> Submission> { + commit_to(KEY, token) +} + +fn push(backend: &mut MemoryBackend, submissions: Vec>>) { + for submission in submissions { + backend.push(submission).expect("room"); + } +} + +fn drain(backend: &mut MemoryBackend) -> Vec>> { + std::iter::from_fn(|| backend.pop().expect("no script fails the pop here")).collect() +} + +/// Push each, ring the doorbell, and pop everything ready. +fn run( + backend: &mut MemoryBackend, + submissions: Vec>>, +) -> Vec>> { + push(backend, submissions); + backend.submit().expect("the doorbell is not scripted"); + drain(backend) +} + +fn tokens(completions: &[Completion>]) -> Vec { + completions.iter().map(|c| c.token.0).collect() +} + +fn contents(backend: &MemoryBackend) -> &[u8] { + backend.target(KEY).expect("attached").contents() +} + +fn durable(backend: &MemoryBackend) -> &[u8] { + backend.target(KEY).expect("attached").durable_contents() +} + +fn disk_full() -> IoFault { + scripted_fault("the disk is full") +} + +/// The fault `completion` ended with, which must be one. +fn failed(completion: &Completion>) -> &IoFault { + completion + .result + .as_ref() + .expect_err("the completion failed") +} + +fn step(when: When, fault: Fault) -> FaultScript { + FaultScript::new().on(when, fault) +} + +const WRITES: When = When::any().op(ScriptOp::Write); +const COMMITS: When = When::any().op(ScriptOp::Commit); + +#[test] +fn a_script_with_no_steps_changes_nothing() { + let mut backend = scripted(FaultScript::new()); + assert!(FaultScript::new().is_empty()); + let done = run( + &mut backend, + vec![write(1, 0, b"ab"), commit(2), read(3, 0, 2)], + ); + assert_eq!(tokens(&done), [1, 2, 3]); + assert!(done.iter().all(|c| c.result.is_ok())); + assert_eq!(durable(&backend), b"ab"); + assert!(backend.fired().is_empty()); +} + +#[test] +fn a_failed_write_leaves_the_target_as_it_was_and_hands_its_buffer_back() { + let fault = disk_full(); + let mut backend = scripted(step(WRITES.nth(2), Fault::Fail(fault.clone()))); + let done = run( + &mut backend, + vec![write(1, 0, b"aa"), write(2, 2, b"bb"), write(3, 4, b"cc")], + ); + assert_eq!(done[0].result, Ok(2)); + assert_eq!(failed(&done[1]), &fault); + assert_eq!(failed(&done[1]).source, MEMORY); + assert_eq!(done[1].buffer.as_deref(), Some(b"bb".as_slice())); + assert_eq!(done[2].result, Ok(2)); + assert_eq!( + contents(&backend), + b"aa\0\0cc", + "the second write never arrived" + ); +} + +#[test] +fn a_failed_commit_makes_nothing_durable() { + let mut backend = scripted(step(COMMITS, Fault::Fail(disk_full()))); + let done = run(&mut backend, vec![write(1, 0, b"lost"), commit(2)]); + assert_eq!(done[0].result, Ok(4)); + assert!(done[1].result.is_err()); + assert!(done[1].buffer.is_none()); + assert_eq!(contents(&backend), b"lost"); + assert!(durable(&backend).is_empty(), "the flush failed"); +} + +#[test] +fn a_later_commit_after_a_failed_one_is_not_made_to_fail_by_it() { + let mut backend = scripted(step(COMMITS.nth(1), Fault::Fail(disk_full()))); + let done = run(&mut backend, vec![write(1, 0, b"x"), commit(2), commit(3)]); + assert!(done[1].result.is_err()); + assert_eq!(done[2].result, Ok(0)); + assert_eq!(durable(&backend), b"x", "the second commit succeeded"); +} + +#[test] +fn a_write_failed_after_applying_is_in_the_target_and_reported_failed() { + let mut backend = scripted(step(WRITES, Fault::FailAfterApplying(disk_full()))); + let done = run(&mut backend, vec![write(1, 0, b"here")]); + assert!(done[0].result.is_err()); + assert_eq!(done[0].buffer.as_deref(), Some(b"here".as_slice())); + assert_eq!(contents(&backend), b"here"); +} + +#[test] +fn a_commit_failed_after_applying_is_durable_and_reported_failed() { + let mut backend = scripted(step(COMMITS, Fault::FailAfterApplying(disk_full()))); + let done = run(&mut backend, vec![write(1, 0, b"kept"), commit(2)]); + assert!(done[1].result.is_err()); + assert_eq!(durable(&backend), b"kept"); +} + +#[test] +fn a_read_failed_either_way_is_a_failed_read() { + for fault in [ + Fault::Fail(disk_full()), + Fault::FailAfterApplying(disk_full()), + ] { + let mut backend = scripted(step(When::any().op(ScriptOp::Read), fault)); + let done = run(&mut backend, vec![write(1, 0, b"abc"), read(2, 0, 3)]); + assert_eq!(done[0].result, Ok(3)); + assert!(done[1].result.is_err()); + assert_eq!(done[1].buffer.as_ref().map(Vec::len), Some(3)); + } +} + +#[test] +fn a_short_write_applies_only_the_count_it_reports() { + let mut backend = scripted(step(WRITES, Fault::Short(2))); + let done = run(&mut backend, vec![write(1, 0, b"abcd")]); + assert_eq!(done[0].result, Ok(2)); + assert_eq!(done[0].buffer.as_deref(), Some(b"abcd".as_slice())); + assert_eq!(contents(&backend), b"ab"); +} + +#[test] +fn a_short_read_transfers_only_the_count_it_reports() { + let mut backend = scripted(step(When::any().op(ScriptOp::Read), Fault::Short(2))); + let done = run(&mut backend, vec![write(1, 0, b"abcd"), read(2, 0, 4)]); + assert_eq!(done[1].result, Ok(2)); + assert_eq!( + done[1].buffer.as_deref(), + Some([b'a', b'b', 0xEE, 0xEE].as_slice()), + "the rest of the buffer is untouched" + ); +} + +#[test] +fn a_short_count_at_or_beyond_the_length_is_the_whole_transfer() { + for count in [4, 5, u32::MAX] { + let mut backend = scripted(step(When::any(), Fault::Short(count))); + let done = run(&mut backend, vec![write(1, 0, b"abcd"), read(2, 0, 4)]); + assert_eq!(done[0].result, Ok(4), "count {count}"); + assert_eq!(done[1].result, Ok(4), "count {count}"); + assert_eq!(contents(&backend), b"abcd"); + } +} + +#[test] +fn a_short_count_of_zero_transfers_nothing_and_succeeds() { + let mut backend = scripted(step(WRITES, Fault::Short(0))); + let done = run(&mut backend, vec![write(1, 0, b"abcd")]); + assert_eq!(done[0].result, Ok(0)); + assert!(contents(&backend).is_empty()); +} + +#[test] +fn a_short_count_leaves_commits_whole() { + // A step naming no operation applies the count where there is one. + let mut backend = scripted(step(When::any(), Fault::Short(1))); + let done = run(&mut backend, vec![write(1, 0, b"ab"), commit(2)]); + assert_eq!(done[0].result, Ok(1)); + assert_eq!(done[1].result, Ok(0)); + assert_eq!(durable(&backend), b"a"); +} + +#[test] +fn a_short_registered_write_applies_only_the_count_it_reports() { + let mut backend = + scripted(step(WRITES, Fault::Short(3))).with_registered(vec![b"wxyz".to_vec()]); + let region = RegisteredRegion { + index: 0, + offset: 0, + length: 4, + }; + let submission = Submission { + token: SubmissionToken(1), + target: KEY, + op: SubmissionOp::Write { + offset: 0, + data: SubmissionData::Registered(region), + caching: WriteCaching::Cached, + }, + }; + let done = run(&mut backend, vec![submission]); + assert_eq!(done[0].result, Ok(3)); + assert_eq!(contents(&backend), b"wxy"); +} + +#[test] +fn a_held_completion_is_withheld_until_released_though_its_operation_ran() { + let mut backend = scripted(step(WRITES.nth(1), Fault::Hold)); + let done = run(&mut backend, vec![write(1, 0, b"ab"), write(2, 2, b"cd")]); + assert_eq!(tokens(&done), [2]); + assert_eq!(backend.held(), [SubmissionToken(1)]); + assert_eq!( + contents(&backend), + b"abcd", + "it ran; only its completion waits" + ); + assert!(backend.release(SubmissionToken(1))); + let late = drain(&mut backend); + assert_eq!(tokens(&late), [1]); + assert_eq!(late[0].result, Ok(2)); + assert!(backend.held().is_empty()); +} + +#[test] +fn a_held_completion_is_released_once() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + run(&mut backend, vec![write(1, 0, b"a")]); + assert!( + !backend.release(SubmissionToken(99)), + "nothing held under it" + ); + assert!(backend.release(SubmissionToken(1))); + assert!(!backend.release(SubmissionToken(1)), "it is no longer held"); + assert_eq!(tokens(&drain(&mut backend)), [1]); +} + +#[test] +fn completions_released_in_a_chosen_order_come_out_in_it() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + run( + &mut backend, + vec![write(1, 0, b"a"), write(2, 1, b"b"), write(3, 2, b"c")], + ); + assert_eq!( + backend.held(), + [SubmissionToken(1), SubmissionToken(2), SubmissionToken(3)] + ); + for token in [3, 1, 2] { + assert!(backend.release(SubmissionToken(token))); + } + assert_eq!(tokens(&drain(&mut backend)), [3, 1, 2], "out of order"); +} + +#[test] +fn releasing_everything_releases_in_the_order_held() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + run(&mut backend, vec![write(5, 0, b"a"), write(4, 1, b"b")]); + assert_eq!(backend.release_all(), 2); + assert_eq!(backend.release_all(), 0, "nothing left to release"); + assert_eq!(tokens(&drain(&mut backend)), [5, 4]); +} + +#[test] +fn a_released_completion_follows_those_already_ready() { + let mut backend = scripted(step(WRITES.nth(1), Fault::Hold)); + push(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + backend.submit().expect("doorbell"); + assert!(backend.release(SubmissionToken(1))); + assert_eq!(tokens(&drain(&mut backend)), [2, 1]); +} + +#[test] +fn a_held_commit_has_made_its_bytes_durable_before_it_completes() { + let mut backend = scripted(step(COMMITS, Fault::Hold)); + let done = run(&mut backend, vec![write(1, 0, b"kept"), commit(2)]); + assert_eq!(tokens(&done), [1]); + assert_eq!( + durable(&backend), + b"kept", + "it ran; only its completion waits" + ); + backend.release_all(); + assert_eq!(drain(&mut backend)[0].result, Ok(0)); +} + +#[test] +fn a_late_completion_is_released_by_the_doorbell_that_many_doorbells_after() { + let mut backend = scripted(step(WRITES.nth(1), Fault::Late(2))); + push(&mut backend, vec![write(1, 0, b"a")]); + backend.submit().expect("doorbell 1"); + assert!(drain(&mut backend).is_empty(), "executed, but late"); + backend.submit().expect("doorbell 2"); + assert!(drain(&mut backend).is_empty(), "still one doorbell short"); + backend.submit().expect("doorbell 3"); + assert_eq!(tokens(&drain(&mut backend)), [1]); + assert!(backend.held().is_empty()); +} + +#[test] +fn a_late_completion_arrives_after_the_ones_executed_behind_it() { + let mut backend = scripted(step(WRITES.nth(1), Fault::Late(1))); + push(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + backend.submit().expect("doorbell 1"); + assert_eq!(tokens(&drain(&mut backend)), [2]); + backend.submit().expect("doorbell 2"); + assert_eq!(tokens(&drain(&mut backend)), [1], "late, so out of order"); +} + +#[test] +fn a_late_completion_released_by_the_test_first_is_not_released_again() { + let mut backend = scripted(step(WRITES, Fault::Late(2))); + run(&mut backend, vec![write(1, 0, b"a")]); + assert!( + backend.release(SubmissionToken(1)), + "a test may release it early" + ); + assert_eq!(tokens(&drain(&mut backend)), [1]); + backend.submit().expect("doorbell"); + backend.submit().expect("doorbell"); + assert!(drain(&mut backend).is_empty()); +} + +#[test] +fn a_late_of_zero_is_an_ordinary_completion() { + let mut backend = scripted(step(WRITES, Fault::Late(0))); + let done = run(&mut backend, vec![write(1, 0, b"a")]); + assert_eq!(tokens(&done), [1]); + assert!(backend.held().is_empty()); +} + +#[test] +fn a_scripted_refusal_hands_the_submission_back_and_queues_nothing() { + let mut backend = scripted(step( + When::any().nth(1), + Fault::Refuse(RefusalCause::SubmitNeeded), + )); + let refused = backend.push(write(1, 0, b"kept")).expect_err("refused"); + assert_eq!(refused.cause, RefusalCause::SubmitNeeded); + assert_eq!(refused.submission.token, SubmissionToken(1)); + assert!(matches!( + refused.submission.op, + SubmissionOp::Write { data: SubmissionData::Owned(ref bytes), .. } if bytes == b"kept" + )); + assert_eq!(backend.queued(), 0); + backend + .push(refused.submission) + .expect("only the first is refused"); + assert_eq!(backend.queued(), 1); +} + +#[test] +fn a_scripted_refusal_may_say_any_cause() { + for cause in [ + RefusalCause::DrainNeeded, + RefusalCause::SubmitAndDrainNeeded, + RefusalCause::Busy, + RefusalCause::Unsupported, + RefusalCause::Unusable(disk_full()), + RefusalCause::Unknown(disk_full()), + ] { + let mut backend = scripted(step(When::any(), Fault::Refuse(cause.clone()))); + let refused = backend.push(commit(1)).expect_err("refused"); + assert_eq!(refused.cause, cause); + } +} + +#[test] +fn a_real_refusal_is_not_masked_and_is_not_counted() { + let mut backend = scripted(step(When::any().nth(1), Fault::Refuse(RefusalCause::Busy))); + let unknown = Submission { + token: SubmissionToken(1), + target: IoTargetKey(99), + op: SubmissionOp::Commit, + }; + assert_eq!( + backend.push(unknown).expect_err("refused").cause, + RefusalCause::UnknownTarget, + "the real reason, not the script's" + ); + assert_eq!( + backend + .push(commit(2)) + .expect_err("the first admitted push") + .cause, + RefusalCause::Busy, + "the refused-for-real push did not use the script's one" + ); + backend.push(commit(3)).expect("the second admitted push"); +} + +#[test] +fn a_scripted_full_queue_is_refused_though_the_queue_is_not_full() { + let mut backend = scripted(step( + WRITES.from(2), + Fault::Refuse(RefusalCause::DrainNeeded), + )); + backend + .push(write(1, 0, b"a")) + .expect("the first is admitted"); + let refused = backend.push(write(2, 1, b"b")).expect_err("scripted full"); + assert_eq!(refused.cause, RefusalCause::DrainNeeded); + assert!(refused.cause.needs_drain() && !refused.cause.needs_submit()); + backend + .push(commit(3)) + .expect("a commit is not matched by a step naming writes"); +} + +#[test] +fn a_refused_doorbell_issues_nothing_and_leaves_the_queue_as_it_was() { + let mut backend = scripted(step(When::any(), Fault::RefuseDoorbell(RefusalCause::Busy))); + push(&mut backend, vec![write(1, 0, b"a")]); + assert_eq!(backend.submit(), Err(RefusalCause::Busy)); + assert_eq!(backend.queued(), 1); + assert!(contents(&backend).is_empty(), "nothing was issued"); +} + +#[test] +fn a_refused_doorbell_on_one_occasion_clears_on_the_next() { + let mut backend = scripted(step( + When::any().nth(1), + Fault::RefuseDoorbell(RefusalCause::Busy), + )); + push(&mut backend, vec![write(1, 0, b"a")]); + assert!(backend.submit().is_err()); + backend + .submit() + .expect("the second doorbell is not scripted"); + assert_eq!(tokens(&drain(&mut backend)), [1]); + assert_eq!(contents(&backend), b"a"); +} + +#[test] +fn a_failed_pop_takes_nothing_and_the_completion_is_still_ready() { + let fault = disk_full(); + let mut backend = scripted(step(When::any().nth(1), Fault::FailPop(fault.clone()))); + push(&mut backend, vec![write(1, 0, b"a")]); + backend.submit().expect("doorbell"); + assert_eq!(backend.pop().expect_err("scripted"), fault); + assert_eq!(backend.completed(), 1, "it was not taken"); + assert_eq!(tokens(&drain(&mut backend)), [1]); +} + +#[test] +fn a_held_completion_still_holds_its_place_in_the_completion_queue() { + let mut backend = scripted_queues(step(WRITES.nth(1), Fault::Hold), 8, 2); + backend.push(write(1, 0, b"a")).expect("room"); + backend.submit().expect("doorbell"); + backend + .push(write(2, 1, b"b")) + .expect("room: one held, one queued"); + assert_eq!( + backend.push(write(3, 2, b"c")).expect_err("no room").cause, + RefusalCause::DrainNeeded, + "the held completion is outstanding, so a completion would not fit" + ); +} + +#[test] +fn registered_buffers_cannot_be_replaced_while_a_completion_is_held() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + run(&mut backend, vec![write(1, 0, b"a")]); + let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + backend.with_registered(vec![vec![0; 4]]) + })); + assert!(outcome.is_err(), "a held operation may name a region"); +} + +#[test] +fn a_step_matches_by_kind_and_by_target() { + let mut backend = scripted(step( + When::any().op(ScriptOp::Write).target(OTHER), + Fault::Fail(disk_full()), + )); + let done = run( + &mut backend, + vec![ + write_to(KEY, 1, 0, b"a"), + write_to(OTHER, 2, 0, b"b"), + commit_to(OTHER, 3), + ], + ); + assert!(done[0].result.is_ok(), "another target"); + assert!(done[1].result.is_err(), "the target and the kind"); + assert!(done[2].result.is_ok(), "the target, but a commit"); +} + +#[test] +fn a_steps_count_is_of_its_own_matches() { + // The second write to OTHER, though it is the third write overall. + let mut backend = scripted(step(WRITES.target(OTHER).nth(2), Fault::Fail(disk_full()))); + let done = run( + &mut backend, + vec![ + write_to(OTHER, 1, 0, b"a"), + write_to(KEY, 2, 0, b"b"), + write_to(OTHER, 3, 1, b"c"), + ], + ); + assert_eq!( + done.iter().map(|c| c.result.is_ok()).collect::>(), + [true, true, false] + ); +} + +#[test] +fn from_fires_on_the_nth_and_every_one_after() { + let mut backend = scripted(step(WRITES.from(2), Fault::Fail(disk_full()))); + let done = run( + &mut backend, + vec![write(1, 0, b"a"), write(2, 1, b"b"), write(3, 2, b"c")], + ); + assert_eq!( + done.iter().map(|c| c.result.is_ok()).collect::>(), + [true, false, false] + ); +} + +#[test] +fn with_no_occurrence_a_step_fires_every_time() { + let mut backend = scripted(step(COMMITS, Fault::Fail(disk_full()))); + let done = run(&mut backend, vec![commit(1), commit(2), commit(3)]); + assert!(done.iter().all(|c| c.result.is_err())); +} + +#[test] +fn every_matching_step_counts_but_only_the_earliest_that_fires_applies() { + let first = scripted_fault("first"); + let second = scripted_fault("second"); + let script = FaultScript::new() + .on(WRITES.nth(2), Fault::Fail(first.clone())) + .on(WRITES, Fault::Fail(second.clone())); + let mut backend = scripted(script); + let done = run( + &mut backend, + vec![write(1, 0, b"a"), write(2, 1, b"b"), write(3, 2, b"c")], + ); + assert_eq!( + failed(&done[0]), + &second, + "only the second step fires for the first write" + ); + assert_eq!( + failed(&done[1]), + &first, + "both fire for the second, and the earlier wins" + ); + assert_eq!(failed(&done[2]), &second); +} + +#[test] +fn a_step_that_lost_to_an_earlier_one_still_counted_the_occurrence() { + let script = FaultScript::new() + .on(WRITES.nth(1), Fault::Hold) + .on(WRITES.nth(2), Fault::Fail(disk_full())); + let mut backend = scripted(script); + let done = run(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + assert_eq!(tokens(&done), [2]); + assert!( + done[0].result.is_err(), + "the second step counted the first write too" + ); +} + +#[test] +fn what_fired_is_recorded_in_order_with_what_it_fired_for() { + let script = FaultScript::new() + .on(COMMITS, Fault::Fail(disk_full())) + .on(WRITES.target(OTHER), Fault::Hold); + let mut backend = scripted(script); + run( + &mut backend, + vec![write(1, 0, b"a"), write_to(OTHER, 2, 0, b"b"), commit(3)], + ); + assert_eq!( + backend.fired(), + [ + Fired { + step: 1, + token: Some(SubmissionToken(2)), + op: Some(ScriptOp::Write), + target: Some(OTHER), + }, + Fired { + step: 0, + token: Some(SubmissionToken(3)), + op: Some(ScriptOp::Commit), + target: Some(KEY), + }, + ] + ); +} + +#[test] +fn a_doorbell_or_a_pop_that_fired_is_recorded_without_a_submission() { + let script = FaultScript::new().on(When::any().nth(1), Fault::FailPop(disk_full())); + let mut backend = scripted(script); + assert!(backend.pop().is_err()); + assert_eq!( + backend.fired(), + [Fired { + step: 0, + token: None, + op: None, + target: None, + }] + ); +} + +#[test] +fn a_script_is_plain_data_and_each_backend_counts_it_from_the_start() { + let script = step(WRITES.nth(1), Fault::Fail(disk_full())); + assert_eq!(script.len(), 1); + for _ in 0..2 { + let mut backend = scripted(script.clone()); + let done = run(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + assert!(done[0].result.is_err()); + assert!(done[1].result.is_ok()); + } +} + +#[test] +fn releasing_into_an_empty_queue_raises_the_notification_once() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + let notification = backend.notification().expect("a notification"); + run(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + assert_eq!(notification.raised(), 0, "nothing was ready"); + backend.release_all(); + assert_eq!(notification.raised(), 1, "none to some, once for both"); + backend.release(SubmissionToken(1)); + assert_eq!(notification.raised(), 1, "nothing was held to release"); +} + +#[test] +fn a_single_release_into_an_empty_queue_raises_the_notification() { + let mut backend = scripted(step(WRITES, Fault::Hold)); + let notification = backend.notification().expect("a notification"); + run(&mut backend, vec![write(1, 0, b"a"), write(2, 1, b"b")]); + backend.release(SubmissionToken(2)); + assert_eq!(notification.raised(), 1); + backend.release(SubmissionToken(1)); + assert_eq!(notification.raised(), 1, "the queue was not empty"); +} + +#[test] +fn a_late_release_into_an_empty_queue_raises_the_notification() { + let mut backend = scripted(step(WRITES, Fault::Late(1))); + let notification = backend.notification().expect("a notification"); + push(&mut backend, vec![write(1, 0, b"a")]); + backend.submit().expect("doorbell 1"); + assert_eq!(notification.raised(), 0); + backend.submit().expect("doorbell 2"); + assert_eq!(notification.raised(), 1); +} + +#[test] +#[should_panic(expected = "a commit has no count")] +fn a_short_count_on_commits_is_a_scripts_mistake() { + let _ = step(COMMITS, Fault::Short(1)); +} + +#[test] +#[should_panic(expected = "no operation or target to name")] +fn a_refused_doorbell_naming_an_operation_is_a_scripts_mistake() { + let _ = step( + When::any().op(ScriptOp::Write), + Fault::RefuseDoorbell(RefusalCause::Busy), + ); +} + +#[test] +#[should_panic(expected = "no operation or target to name")] +fn a_failed_pop_naming_a_target_is_a_scripts_mistake() { + let _ = step(When::any().target(KEY), Fault::FailPop(disk_full())); +} + +#[test] +#[should_panic(expected = "counted from one")] +fn the_zeroth_occurrence_is_a_scripts_mistake() { + let _ = When::any().nth(0); +} + +#[test] +#[should_panic(expected = "counted from one")] +fn from_the_zeroth_occurrence_is_a_scripts_mistake() { + let _ = When::any().from(0); +} diff --git a/crates/durable-ioring/src/memory/tests.rs b/crates/durable-ioring/src/memory/tests.rs index 9018ce0ba..a4ae7ab0c 100644 --- a/crates/durable-ioring/src/memory/tests.rs +++ b/crates/durable-ioring/src/memory/tests.rs @@ -6,6 +6,7 @@ use std::thread; use std::time::Duration; use super::{MEMORY, MemoryBackend, MemoryTarget}; +use crate::access::Access; use crate::backend::{ Completion, IoBackend, RefusalCause, RegisteredRegion, Submission, SubmissionData, SubmissionOp, SubmissionToken, @@ -399,6 +400,108 @@ fn the_targets_geometry_is_enforced() { backend.push(commit(5)).expect("a commit has no geometry"); } +/// A backend with `KEY` attached as a target of `access`. +fn with_access(access: Access) -> MemoryBackend { + let mut backend = queues(8, 16); + backend + .attach(KEY, MemoryTarget::new().with_access(access)) + .expect("a new key"); + backend +} + +#[test] +fn a_target_that_cannot_write_refuses_a_write_and_takes_a_read() { + let mut backend = with_access(Access::ReadOnly); + let refused = backend + .push(write(1, 0, b"no")) + .expect_err("a write to a read-only target"); + assert_eq!(refused.cause, RefusalCause::AccessDenied); + assert_eq!( + refused.submission.token, + SubmissionToken(1), + "the submission comes back" + ); + assert!(contents(&backend).is_empty(), "nothing was written"); + let done = run(&mut backend, vec![read(2, 0, 4)]); + assert_eq!(done[0].result, Ok(0), "a read of an empty target"); +} + +#[test] +fn a_target_that_cannot_read_refuses_a_read_and_takes_a_write() { + let mut backend = with_access(Access::WriteOnly); + let refused = backend + .push(read(1, 0, 4)) + .expect_err("a read from a write-only target"); + assert_eq!(refused.cause, RefusalCause::AccessDenied); + let done = run(&mut backend, vec![write(2, 0, b"yes")]); + assert_eq!(done[0].result, Ok(3)); + assert_eq!(contents(&backend), b"yes"); +} + +#[test] +fn a_target_that_can_do_both_takes_both_and_a_default_target_can() { + let mut backend = with_access(Access::ReadWrite); + let done = run(&mut backend, vec![write(1, 0, b"both"), read(2, 0, 4)]); + assert_eq!( + (done[0].result.clone(), done[1].result.clone()), + (Ok(4), Ok(4)) + ); + assert_eq!(MemoryTarget::new().access(), Access::ReadWrite); +} + +#[test] +fn a_commit_is_not_refused_on_account_of_the_targets_access() { + // A commit is issued only to a target with writes, so no access refuses it. + for access in [Access::ReadOnly, Access::WriteOnly, Access::ReadWrite] { + let mut backend = with_access(access); + let done = run(&mut backend, vec![commit(1)]); + assert_eq!(done[0].result, Ok(0), "{access:?}"); + } +} + +#[test] +fn an_operation_the_access_does_not_cover_is_named_after_an_unknown_target_and_before_the_geometry() +{ + let geometry = Geometry::new(512, 512, 1).expect("powers of two"); + let mut backend = queues(8, 16); + backend + .attach( + KEY, + MemoryTarget::new() + .with_geometry(geometry) + .with_access(Access::ReadOnly), + ) + .expect("a new key"); + // Off the sector as well as a write to a read-only target: the access is named. + assert_eq!( + refusal(&mut backend, write(1, 1, &[0; 3])), + RefusalCause::AccessDenied + ); + // An unattached key has no access to speak of. + let mut unattached = write(2, 0, b"x"); + unattached.target = IoTargetKey(99); + assert_eq!( + refusal(&mut backend, unattached), + RefusalCause::UnknownTarget + ); +} + +#[test] +fn check_refuses_for_the_access_as_a_push_does() { + let backend = with_access(Access::ReadOnly); + assert_eq!( + backend.check(KEY, &write(1, 0, b"x").op), + Err(RefusalCause::AccessDenied) + ); + assert_eq!(backend.check(KEY, &read(2, 0, 1).op), Ok(())); + let backend = with_access(Access::WriteOnly); + assert_eq!( + backend.check(KEY, &read(3, 0, 1).op), + Err(RefusalCause::AccessDenied) + ); + assert_eq!(backend.check(KEY, &write(4, 0, b"x").op), Ok(())); +} + #[test] fn a_buffers_address_is_checked_against_the_geometry() { let mut backend = queues(8, 16).with_registered(vec![vec![0; 8]]); diff --git a/crates/durable-ioring/src/oracle.rs b/crates/durable-ioring/src/oracle.rs index 1895ef068..3446417ac 100644 --- a/crates/durable-ioring/src/oracle.rs +++ b/crates/durable-ioring/src/oracle.rs @@ -114,9 +114,12 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use crate::contract::Identities; use crate::types::{Entry, Epoch, IoFault, Marking, MarkingKind, OpKind, Outcome, SuspectWrite}; +mod pairing; #[cfg(test)] mod tests; +pub use pairing::{PairingChecker, PairingViolation}; + /// What a push gave the instance, kept until its completion. struct Pushed { kind: OpKind, diff --git a/crates/durable-ioring/src/oracle/pairing.rs b/crates/durable-ioring/src/oracle/pairing.rs new file mode 100644 index 000000000..a6cd9f6a9 --- /dev/null +++ b/crates/durable-ioring/src/oracle/pairing.rs @@ -0,0 +1,263 @@ +// Copyright (c) 2026 Mike Grier +//! The pairing rule (DI-D-80, DI-D-87), as one executable definition: what a sink's stream of +//! begin and end events must satisfy. Every sink's tests bind to it -- the core's, the driver's, +//! and those of a sink outside this crate -- rather than restating the rule. +//! +//! Which events are the ends of which pairs is [`Diagnostic::pairing`]'s; this checks only how +//! they follow one another. An event that is no end of a pair is passed over. +//! +//! # Checked +//! +//! - **Every end follows a begin** of the same id, and of the same kind. +//! - **An end repeats its begin's identity**: the instance, and the lineage and epoch, the part, or +//! the lock, as the pair has them. +//! - **No id is begun twice**, and **nothing of a pair follows its end**. +//! - **No begin is left open** when the stream is declared finished, except those the caller names, +//! and each of those must be open. + +use std::collections::{HashMap, HashSet}; +use std::fmt; + +use crate::correlation::CorrelationId; +use crate::diagnostics::{Diagnostic, DiagnosticKind, Edge}; + +#[cfg(test)] +mod tests; + +/// The pairing rule over a stream of events, fed one at a time in the order a sink received them. +#[derive(Debug, Default)] +pub struct PairingChecker { + open: HashMap, + ended: HashSet, +} + +/// An open pair: its kind, and the identity its end must repeat. +#[derive(Debug)] +struct Begun { + kind: DiagnosticKind, + subject: String, +} + +impl PairingChecker { + /// A checker that has seen nothing. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Check `event`, the stream's next, against what came before it. + /// + /// # Errors + /// + /// The [`PairingViolation`] it is, if it is one. + pub fn observe(&mut self, event: &Diagnostic<'_>) -> Result<(), PairingViolation> { + let Some(pairing) = event.pairing() else { + return Ok(()); + }; + let id = pairing.id; + if self.ended.contains(&id) { + return Err(PairingViolation::AfterEnd { + id, + edge: pairing.edge, + }); + } + let subject = subject(event); + match pairing.edge { + Edge::Begin => { + if self.open.contains_key(&id) { + return Err(PairingViolation::BegunTwice { id }); + } + self.open.insert( + id, + Begun { + kind: pairing.kind, + subject, + }, + ); + } + Edge::End => { + let Some(begun) = self.open.remove(&id) else { + return Err(PairingViolation::EndWithoutBegin { + id, + kind: pairing.kind, + }); + }; + if begun.kind != pairing.kind { + return Err(PairingViolation::KindMismatch { + id, + began: begun.kind, + ended: pairing.kind, + }); + } + if begun.subject != subject { + return Err(PairingViolation::SubjectMismatch { + id, + began: begun.subject, + ended: subject, + }); + } + self.ended.insert(id); + } + } + Ok(()) + } + + /// The pairs begun and not yet ended, by id, in order. + #[must_use] + pub fn open(&self) -> Vec { + let mut open: Vec = self.open.keys().copied().collect(); + open.sort_unstable(); + open + } + + /// Declare the stream finished, with exactly the pairs in `left_open` still open. + /// + /// # Errors + /// + /// [`PairingViolation::LeftOpen`] for the pairs open and not named, or + /// [`PairingViolation::NamedNotOpen`] for a pair named and not open. + pub fn finish(self, left_open: &[CorrelationId]) -> Result<(), PairingViolation> { + let named: HashSet = left_open.iter().copied().collect(); + let mut unnamed: Vec = self + .open + .keys() + .filter(|id| !named.contains(id)) + .copied() + .collect(); + if !unnamed.is_empty() { + unnamed.sort_unstable(); + return Err(PairingViolation::LeftOpen { ids: unnamed }); + } + let mut not_open: Vec = named + .into_iter() + .filter(|id| !self.open.contains_key(id)) + .collect(); + not_open.sort_unstable(); + match not_open.first() { + Some(&id) => Err(PairingViolation::NamedNotOpen { id }), + None => Ok(()), + } + } +} + +/// The identity an end repeats from its begin, as text: the instance, and what the pair is about. +fn subject(event: &Diagnostic<'_>) -> String { + match event { + Diagnostic::SealBegan { + instance, + lineage, + through, + .. + } + | Diagnostic::SealEnded { + instance, + lineage, + through, + .. + } => format!("instance {instance} lineage {lineage} seal through {through}"), + Diagnostic::FlushBegan { + instance, + lineage, + through, + part, + .. + } + | Diagnostic::FlushEnded { + instance, + lineage, + through, + part, + .. + } => format!("instance {instance} lineage {lineage} seal through {through}, {part}"), + Diagnostic::LockWaitBegan { instance, lock, .. } + | Diagnostic::LockWaitEnded { instance, lock, .. } => { + format!("instance {instance} lock {lock}") + } + _ => String::new(), + } +} + +/// How a stream broke the pairing rule. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum PairingViolation { + /// An end whose id was never begun. + EndWithoutBegin { + /// The id. + id: CorrelationId, + /// The end's kind. + kind: DiagnosticKind, + }, + /// A begin whose id is already open. + BegunTwice { + /// The id. + id: CorrelationId, + }, + /// An end of another kind than its begin. + KindMismatch { + /// The id. + id: CorrelationId, + /// The begin's kind. + began: DiagnosticKind, + /// The end's kind. + ended: DiagnosticKind, + }, + /// An end that does not repeat its begin's identity. + SubjectMismatch { + /// The id. + id: CorrelationId, + /// The begin's identity. + began: String, + /// The end's identity. + ended: String, + }, + /// An event of a pair that has already ended. + AfterEnd { + /// The id. + id: CorrelationId, + /// Which end the event is. + edge: Edge, + }, + /// Pairs open when the stream was declared finished, and not named as left open. + LeftOpen { + /// Their ids, in order. + ids: Vec, + }, + /// A pair named as left open that is not open: never begun, or ended. + NamedNotOpen { + /// The id. + id: CorrelationId, + }, +} + +impl fmt::Display for PairingViolation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EndWithoutBegin { id, kind } => { + write!(f, "pair {id} ({kind:?}) ended without having begun") + } + Self::BegunTwice { id } => write!(f, "pair {id} began while already open"), + Self::KindMismatch { id, began, ended } => { + write!(f, "pair {id} began as {began:?} and ended as {ended:?}") + } + Self::SubjectMismatch { id, began, ended } => { + write!(f, "pair {id} began about {began} and ended about {ended}") + } + Self::AfterEnd { id, edge } => { + write!( + f, + "pair {id} had ended, and an event of it ({edge:?}) followed" + ) + } + Self::LeftOpen { ids } => { + let ids: Vec = ids.iter().map(ToString::to_string).collect(); + write!(f, "pairs left open: {}", ids.join(", ")) + } + Self::NamedNotOpen { id } => { + write!(f, "pair {id} was named as left open, and is not open") + } + } + } +} + +impl std::error::Error for PairingViolation {} diff --git a/crates/durable-ioring/src/oracle/pairing/tests.rs b/crates/durable-ioring/src/oracle/pairing/tests.rs new file mode 100644 index 000000000..afd6e8c66 --- /dev/null +++ b/crates/durable-ioring/src/oracle/pairing/tests.rs @@ -0,0 +1,485 @@ +// Copyright (c) 2026 Mike Grier +//! The pairing rule (DI-19.2.1): each rule shown to accept a stream that keeps it and to refuse +//! one that breaks it. + +use super::{PairingChecker, PairingViolation}; +use crate::correlation::CorrelationId; +use crate::diagnostics::{Diagnostic, DiagnosticKind, Edge, FlushOutcome, FlushPart, SealOutcome}; +use crate::types::{FlushDomain, IoTargetKey}; + +fn id(n: u64) -> CorrelationId { + CorrelationId::chosen(n) +} + +/// Feed one event, built in place: `fmt::Arguments` borrows temporaries the statement holds. +macro_rules! feed { + ($checker:expr, $event:expr) => { + $checker.observe(&$event) + }; +} + +fn seal_began( + checker: &mut PairingChecker, + n: u64, + lineage: u64, + through: u64, +) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::SealBegan { + instance: 1, + lineage, + through: format_args!("{through}"), + id: id(n) + } + ) +} + +fn seal_ended( + checker: &mut PairingChecker, + n: u64, + lineage: u64, + through: u64, +) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::SealEnded { + instance: 1, + lineage, + through: format_args!("{through}"), + id: id(n), + outcome: SealOutcome::Durable + } + ) +} + +fn flush_began(checker: &mut PairingChecker, n: u64, file: u64) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("3"), + part: FlushPart::File(IoTargetKey(file)), + id: id(n) + } + ) +} + +fn flush_ended(checker: &mut PairingChecker, n: u64, file: u64) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("3"), + part: FlushPart::File(IoTargetKey(file)), + id: id(n), + outcome: FlushOutcome::Succeeded + } + ) +} + +fn lock_began( + checker: &mut PairingChecker, + n: u64, + lock: &'static str, +) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::LockWaitBegan { + instance: 1, + lock, + id: id(n) + } + ) +} + +fn lock_ended( + checker: &mut PairingChecker, + n: u64, + lock: &'static str, +) -> Result<(), PairingViolation> { + feed!( + checker, + Diagnostic::LockWaitEnded { + instance: 1, + lock, + id: id(n) + } + ) +} + +#[test] +fn a_begin_then_its_end_is_accepted_for_every_kind() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 1, 0, 5).expect("a begin"); + seal_ended(&mut checker, 1, 0, 5).expect("its end"); + flush_began(&mut checker, 2, 7).expect("a begin"); + flush_ended(&mut checker, 2, 7).expect("its end"); + lock_began(&mut checker, 3, "engine").expect("a begin"); + lock_ended(&mut checker, 3, "engine").expect("its end"); + checker.finish(&[]).expect("nothing open"); +} + +#[test] +fn every_outcome_ends_a_pair() { + let mut checker = PairingChecker::new(); + for (n, outcome) in [ + SealOutcome::Durable, + SealOutcome::Failed, + SealOutcome::Blocked, + SealOutcome::Ended, + ] + .into_iter() + .enumerate() + { + let n = n as u64 + 1; + seal_began(&mut checker, n, 0, n).expect("a begin"); + feed!( + checker, + Diagnostic::SealEnded { + instance: 1, + lineage: 0, + through: format_args!("{n}"), + id: id(n), + outcome + } + ) + .expect("its end"); + } + let domain = FlushDomain::new(b"log".as_slice()); + for (n, outcome) in [ + FlushOutcome::Succeeded, + FlushOutcome::Failed, + FlushOutcome::Abandoned, + ] + .into_iter() + .enumerate() + { + let n = n as u64 + 10; + feed!( + checker, + Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id: id(n) + } + ) + .expect("a begin"); + feed!( + checker, + Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id: id(n), + outcome + } + ) + .expect("its end"); + } + checker.finish(&[]).expect("nothing open"); +} + +#[test] +fn pairs_that_interleave_or_nest_are_accepted() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 1, 0, 1).expect("A begins"); + flush_began(&mut checker, 2, 1).expect("B begins"); + flush_began(&mut checker, 3, 2).expect("C begins"); + flush_ended(&mut checker, 2, 1).expect("B ends"); + lock_began(&mut checker, 4, "engine").expect("D begins"); + seal_ended(&mut checker, 1, 0, 1).expect("A ends before C"); + lock_ended(&mut checker, 4, "engine").expect("D ends"); + flush_ended(&mut checker, 3, 2).expect("C ends last"); + checker.finish(&[]).expect("nothing open"); +} + +#[test] +fn many_pairs_ended_in_reverse_are_accepted() { + let mut checker = PairingChecker::new(); + for n in 1..=50 { + flush_began(&mut checker, n, n).expect("a begin"); + } + assert_eq!(checker.open().len(), 50); + for n in (1..=50).rev() { + flush_ended(&mut checker, n, n).expect("its end"); + } + assert!(checker.open().is_empty()); + checker.finish(&[]).expect("nothing open"); +} + +#[test] +fn an_event_that_is_no_pair_is_passed_over() { + let mut checker = PairingChecker::new(); + feed!( + checker, + Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("y") + } + ) + .expect("no pair"); + seal_began(&mut checker, 1, 0, 1).expect("a begin"); + feed!( + checker, + Diagnostic::Inconsistency { + instance: 2, + what: "z", + detail: format_args!("between") + } + ) + .expect("no pair"); + seal_ended(&mut checker, 1, 0, 1).expect("its end"); + checker.finish(&[]).expect("nothing open"); +} + +#[test] +fn an_end_with_no_begin_is_refused() { + let mut checker = PairingChecker::new(); + assert_eq!( + flush_ended(&mut checker, 9, 1), + Err(PairingViolation::EndWithoutBegin { + id: id(9), + kind: DiagnosticKind::Flush + }) + ); +} + +#[test] +fn an_id_begun_twice_is_refused() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 4, 0, 1).expect("a begin"); + assert_eq!( + seal_began(&mut checker, 4, 0, 1), + Err(PairingViolation::BegunTwice { id: id(4) }) + ); +} + +#[test] +fn the_same_id_begun_again_by_another_kind_is_refused_too() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 4, 0, 1).expect("a begin"); + assert_eq!( + lock_began(&mut checker, 4, "engine"), + Err(PairingViolation::BegunTwice { id: id(4) }) + ); +} + +#[test] +fn an_end_of_another_kind_is_refused() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 5, 0, 1).expect("a begin"); + assert_eq!( + lock_ended(&mut checker, 5, "engine"), + Err(PairingViolation::KindMismatch { + id: id(5), + began: DiagnosticKind::Seal, + ended: DiagnosticKind::LockWait + }) + ); +} + +#[test] +fn an_end_that_does_not_repeat_its_begins_identity_is_refused() { + for (began, ended) in [((0, 5), (1, 5)), ((0, 5), (0, 6))] { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 1, began.0, began.1).expect("a begin"); + assert!(matches!( + seal_ended(&mut checker, 1, ended.0, ended.1), + Err(PairingViolation::SubjectMismatch { .. }) + )); + } + let mut checker = PairingChecker::new(); + flush_began(&mut checker, 2, 7).expect("a begin"); + assert!( + matches!( + flush_ended(&mut checker, 2, 8), + Err(PairingViolation::SubjectMismatch { .. }) + ), + "another file" + ); + let mut checker = PairingChecker::new(); + lock_began(&mut checker, 3, "engine").expect("a begin"); + assert!( + matches!( + lock_ended(&mut checker, 3, "slot"), + Err(PairingViolation::SubjectMismatch { .. }) + ), + "another lock" + ); + let mut checker = PairingChecker::new(); + feed!( + checker, + Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id: id(4) + } + ) + .expect("a begin"); + assert!( + matches!( + feed!( + checker, + Diagnostic::LockWaitEnded { + instance: 2, + lock: "engine", + id: id(4) + } + ), + Err(PairingViolation::SubjectMismatch { .. }) + ), + "another instance" + ); +} + +#[test] +fn an_end_that_repeats_its_begins_identity_by_other_means_is_accepted() { + let mut checker = PairingChecker::new(); + let five = 5_u64; + feed!( + checker, + Diagnostic::SealBegan { + instance: 1, + lineage: 0, + through: format_args!("{five}"), + id: id(1) + } + ) + .expect("a begin"); + feed!( + checker, + Diagnostic::SealEnded { + instance: 1, + lineage: 0, + through: format_args!("5"), + id: id(1), + outcome: SealOutcome::Failed + } + ) + .expect("the same text"); +} + +#[test] +fn a_second_end_is_refused() { + let mut checker = PairingChecker::new(); + lock_began(&mut checker, 6, "engine").expect("a begin"); + lock_ended(&mut checker, 6, "engine").expect("its end"); + assert_eq!( + lock_ended(&mut checker, 6, "engine"), + Err(PairingViolation::AfterEnd { + id: id(6), + edge: Edge::End + }) + ); +} + +#[test] +fn a_begin_after_its_pair_ended_is_refused() { + let mut checker = PairingChecker::new(); + lock_began(&mut checker, 6, "engine").expect("a begin"); + lock_ended(&mut checker, 6, "engine").expect("its end"); + assert_eq!( + lock_began(&mut checker, 6, "engine"), + Err(PairingViolation::AfterEnd { + id: id(6), + edge: Edge::Begin + }) + ); +} + +#[test] +fn a_pair_left_open_is_refused_at_the_finish() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 2, 0, 1).expect("a begin"); + flush_began(&mut checker, 1, 1).expect("a begin"); + flush_began(&mut checker, 3, 2).expect("a begin"); + flush_ended(&mut checker, 3, 2).expect("an end"); + assert_eq!(checker.open(), [id(1), id(2)]); + assert_eq!( + checker.finish(&[]), + Err(PairingViolation::LeftOpen { + ids: vec![id(1), id(2)] + }) + ); +} + +#[test] +fn a_pair_named_as_left_open_is_accepted_at_the_finish() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 2, 0, 1).expect("a begin"); + flush_began(&mut checker, 1, 1).expect("a begin"); + checker.finish(&[id(1), id(2)]).expect("both named"); +} + +#[test] +fn a_pair_named_but_not_all_that_are_open_is_refused() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 2, 0, 1).expect("a begin"); + flush_began(&mut checker, 1, 1).expect("a begin"); + assert_eq!( + checker.finish(&[id(1)]), + Err(PairingViolation::LeftOpen { ids: vec![id(2)] }) + ); +} + +#[test] +fn naming_a_pair_that_is_not_open_is_refused() { + let mut checker = PairingChecker::new(); + seal_began(&mut checker, 1, 0, 1).expect("a begin"); + seal_ended(&mut checker, 1, 0, 1).expect("its end"); + assert_eq!( + PairingChecker::new().finish(&[id(7)]), + Err(PairingViolation::NamedNotOpen { id: id(7) }), + "never begun" + ); + assert_eq!( + checker.finish(&[id(1)]), + Err(PairingViolation::NamedNotOpen { id: id(1) }), + "ended" + ); +} + +#[test] +fn an_empty_stream_finishes() { + PairingChecker::new().finish(&[]).expect("nothing open"); +} + +#[test] +fn each_violation_says_which_pair() { + let shown = [ + PairingViolation::EndWithoutBegin { + id: id(11), + kind: DiagnosticKind::Flush, + }, + PairingViolation::BegunTwice { id: id(11) }, + PairingViolation::KindMismatch { + id: id(11), + began: DiagnosticKind::Seal, + ended: DiagnosticKind::Flush, + }, + PairingViolation::SubjectMismatch { + id: id(11), + began: "a".to_owned(), + ended: "b".to_owned(), + }, + PairingViolation::AfterEnd { + id: id(11), + edge: Edge::End, + }, + PairingViolation::LeftOpen { + ids: vec![id(11), id(12)], + }, + PairingViolation::NamedNotOpen { id: id(11) }, + ]; + for violation in shown { + let text = violation.to_string(); + assert!(text.contains("11"), "{text}"); + } +} diff --git a/crates/durable-ioring/src/provider.rs b/crates/durable-ioring/src/provider.rs index 58ecfcc85..841a9d38f 100644 --- a/crates/durable-ioring/src/provider.rs +++ b/crates/durable-ioring/src/provider.rs @@ -11,8 +11,8 @@ use crate::types::{FailureCause, FlushDomain, IoTargetKey}; /// A consumer-supplied durability provider. It makes writes the instance has seen complete /// durable, by any means, and answers per flush domain. An instance holds at most one; several -/// are composed by a router that is itself a provider. Every other domain, and every file declared -/// with none, is the built-in default's. +/// are composed by [the routing provider](crate::RoutingProvider), itself a provider. Every other +/// domain, and every file declared with none, is the built-in default's. pub trait DurabilityProvider: Send + Debug { /// The flush domains this provider serves, read once when the instance is built. fn domains(&self) -> Vec; diff --git a/crates/durable-ioring/src/pump.rs b/crates/durable-ioring/src/pump.rs index c18478c5c..db4dd38f4 100644 --- a/crates/durable-ioring/src/pump.rs +++ b/crates/durable-ioring/src/pump.rs @@ -19,7 +19,8 @@ use std::time::Duration; use crate::backend::IoBackend; use crate::clock::StampClock; use crate::contract::EpochId; -use crate::engine::{Engine, EngineGuard, Slot, lock, present}; +use crate::engine::{Engine, EngineGuard, Slot, lock, present_watched}; +use crate::lock_watch::{ENGINE_LOCK, LockWatch, WatchedGuard}; use crate::types::IoFault; #[cfg(test)] @@ -66,6 +67,8 @@ where /// Waited on with no lock held; the engine's slot is not touched until the wait is over. notification: Bk::Notification, engine: Slot, + /// Times the waits for `engine`'s lock, if the driver asked. + watch: Option, } impl PumpedDelivery @@ -93,9 +96,19 @@ where Self { notification, engine: Arc::new(Mutex::new(Some(engine))), + watch: None, } } + /// Report the waits for the engine's lock this delivery makes through `watch` (DI-D-26). A + /// path of the driver's own that reaches the slot through [`shared`](Self::shared) is not + /// reported unless it takes the lock through the same watch. + #[must_use] + pub fn with_lock_watch(mut self, watch: LockWatch) -> Self { + self.watch = Some(watch); + self + } + /// Drain the backend to empty; if that delivered entries, return. Otherwise wait up to /// `timeout` for the notification and drain again. A wait that times out drains too: a drain of /// nothing is a no-op, and a zero timeout is the pump that does not wait. @@ -109,14 +122,14 @@ where /// completions an earlier drain took, or by the setup signal the ring crate raises once /// ([D-20](../../windows-ioring-sys/DESIGN-NOTES.md#d-20)). pub fn pump(&mut self, timeout: Option) -> Pumped { - if lock(&self.engine).as_mut().is_some_and(Engine::drain) { + if self.locked().as_mut().is_some_and(Engine::drain) { return Pumped { ready: true, waited: Ok(false), }; } let waited = self.notification.wait(timeout); - let ready = lock(&self.engine).as_mut().is_some_and(Engine::drain); + let ready = self.locked().as_mut().is_some_and(Engine::drain); Pumped { ready, waited } } } @@ -129,7 +142,14 @@ where { /// The engine, locked: what a consumer call acts through. pub fn engine(&self) -> EngineGuard<'_, Engine> { - present(lock(&self.engine)) + present_watched(self.locked()) + } + + fn locked(&self) -> WatchedGuard<'_, Option>> { + match &self.watch { + Some(watch) => watch.lock(&self.engine, ENGINE_LOCK), + None => WatchedGuard::unwatched(lock(&self.engine)), + } } /// The engine's slot, for a path of the driver's own that reaches it on another thread. diff --git a/crates/durable-ioring/src/pump/tests.rs b/crates/durable-ioring/src/pump/tests.rs index c5ff7a685..6a1ce1834 100644 --- a/crates/durable-ioring/src/pump/tests.rs +++ b/crates/durable-ioring/src/pump/tests.rs @@ -17,11 +17,13 @@ use crate::backend::{ Completion, DuplicateTarget, IoBackend, RefusalCause, Refused, Submission, SubmissionData, SubmissionOp, }; -use crate::diagnostics::NoDiagnostics; +use crate::diagnostics::{Diagnostic, DiagnosticKind, DiagnosticSink, NoDiagnostics}; use crate::engine::{Engine, Slot, lock}; use crate::io_core::DurableIoCore; use crate::io_target::IoTarget; +use crate::lock_watch::LockWatch; use crate::memory::{MemoryBackend, MemoryNotification, MemoryTarget}; +use crate::oracle::PairingChecker; use crate::test_support::Ticks; use crate::types::{Entry, Epoch, FlushDomain, IoFault, IoTargetKey, Outcome, WriteOptions}; @@ -501,3 +503,101 @@ fn a_wait_that_fails_still_drains_what_completed_during_it_and_reports_why() { assert!(error.to_string().contains("the wait failed"), "{error}"); assert_eq!(written(&mut pump.engine()), vec![15]); } + +/// Keeps the lock waits it is given, by their begins, holds them to the pairing rule, and says when +/// a wait is about to block: its begin is reported just before. +struct Waits { + seen: Mutex>, + checker: Mutex, + about_to_block: std::sync::mpsc::Sender<()>, +} + +impl Waits { + fn new(about_to_block: std::sync::mpsc::Sender<()>) -> Arc { + Arc::new(Self { + seen: Mutex::default(), + checker: Mutex::default(), + about_to_block, + }) + } + + /// Every wait's pair ended, and the stream kept the rule. + fn finished(&self) { + let checker = std::mem::take(&mut *self.checker.lock().expect("the checker")); + checker.finish(&[]).expect("every wait's pair ended"); + } +} + +impl DiagnosticSink for Waits { + fn wants(&self, kind: DiagnosticKind) -> bool { + kind == DiagnosticKind::LockWait + } + + fn report(&self, event: &Diagnostic<'_>) { + self.checker + .lock() + .expect("the checker") + .observe(event) + .expect("the pairing rule"); + match event { + Diagnostic::LockWaitBegan { instance, lock, .. } => { + self.seen + .lock() + .expect("the record") + .push((*instance, (*lock).to_owned())); + self.about_to_block.send(()).expect("the test is listening"); + } + Diagnostic::LockWaitEnded { .. } => {} + other => panic!("reported {other}"), + } + } +} + +#[test] +fn a_consumer_call_that_waits_for_the_engine_reports_the_wait_and_one_that_does_not_reports_none() { + let (signal, about_to_block) = std::sync::mpsc::channel(); + let waits = Waits::new(signal); + let watch = LockWatch::new(11, waits.clone()); + let pump = pump().with_lock_watch(watch); + + drop(pump.engine()); + assert!(waits.seen.lock().expect("the record").is_empty()); + + let slot = pump.shared(); + let held = lock(&slot); + std::thread::scope(|scope| { + let other = scope.spawn(|| drop(pump.engine())); + about_to_block + .recv() + .expect("the other call reached its wait"); + drop(held); + other.join().expect("the other call finished"); + }); + assert_eq!( + *waits.seen.lock().expect("the record"), + [(11, "engine".to_owned())] + ); + waits.finished(); +} + +#[test] +fn a_pumps_drain_that_waits_for_the_engine_reports_the_wait() { + let (signal, about_to_block) = std::sync::mpsc::channel(); + let waits = Waits::new(signal); + let watch = LockWatch::new(12, waits.clone()); + let mut pump = pump().with_lock_watch(watch); + + let slot = pump.shared(); + let held = lock(&slot); + std::thread::scope(|scope| { + let other = scope.spawn(|| pump.pump(Some(Duration::ZERO))); + about_to_block.recv().expect("the drain reached its wait"); + drop(held); + other.join().expect("the pump finished"); + }); + assert_eq!( + *waits.seen.lock().expect("the record"), + [(12, "engine".to_owned())] + ); + waits.finished(); +} diff --git a/crates/durable-ioring/src/router.rs b/crates/durable-ioring/src/router.rs new file mode 100644 index 000000000..de89a9623 --- /dev/null +++ b/crates/durable-ioring/src/router.rs @@ -0,0 +1,211 @@ +// Copyright (c) 2026 Mike Grier +//! The routing provider (DI-2.12 point 1, DI-D-81): one provider composed of others, so an +//! instance, which holds at most one consumer provider, can have several. +//! +//! Each provider registered with the router serves exactly the flush domains its own +//! [`domains`](DurabilityProvider::domains) returns, read once when the router is built. A domain +//! claimed twice -- by two registrations, or twice by one -- refuses the build. +//! +//! A seal's request is split by the provider that serves each domain, and each provider with a part +//! is called once, in registration order. Each domain's completion is handed to its provider as it +//! came, so its answer goes straight back to the instance; the router answers nothing itself. A +//! domain no provider serves is a caller breaking the protocol, and is panicked on (DI-D-46). + +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::fmt; + +use crate::contract::Identities; +use crate::provider::{DomainRequest, DurabilityProvider, FlushRequest}; +use crate::types::FlushDomain; + +/// Builds a [`RoutingProvider`] from providers registered with it in turn. Registrations are +/// numbered from 0 in the order made, and [`RoutedTwice`] names them by that number. +pub struct RouterBuilder { + providers: Vec>>, +} + +impl RouterBuilder { + /// A builder with no provider registered. + #[must_use] + pub fn new() -> Self { + Self { + providers: Vec::new(), + } + } + + /// Register `provider`, to serve the domains it declares. Its domains are read by + /// [`build`](Self::build), not here. + #[must_use] + pub fn register(mut self, provider: impl DurabilityProvider + 'static) -> Self { + self.providers.push(Box::new(provider)); + self + } + + /// The router: each registered provider's domains read once, in registration order. + /// + /// # Errors + /// + /// [`RoutedTwice`] for the first domain met a second time, reading the registrations in order + /// and each one's domains in the order it gave them. The providers already read are dropped + /// with the builder, and those after it are not asked for their domains. + pub fn build(self) -> Result, RoutedTwice> { + let mut owners: HashMap = HashMap::new(); + let mut routes = Vec::with_capacity(self.providers.len()); + for (registration, provider) in self.providers.into_iter().enumerate() { + let domains = provider.domains(); + for domain in &domains { + match owners.entry(domain.clone()) { + Entry::Occupied(owner) => { + return Err(RoutedTwice { + domain: domain.clone(), + first: *owner.get(), + second: registration, + }); + } + Entry::Vacant(vacant) => { + vacant.insert(registration); + } + } + } + routes.push(Route { provider, domains }); + } + Ok(RoutingProvider { routes, owners }) + } +} + +impl Default for RouterBuilder { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for RouterBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RouterBuilder") + .field("providers", &self.providers) + .finish() + } +} + +/// A provider that serves the domains of the providers registered with it, each its own, and +/// itself no other. Built by [`RouterBuilder`]. +/// +/// Its [`domains`](DurabilityProvider::domains) are the registrations in order, and each one's +/// domains in the order it gave them when the router was built; no domain appears twice, which the +/// build refused. [`make_durable`](DurabilityProvider::make_durable) calls each provider that has a +/// part of the request, once, in registration order, with its parts in the request's order and each +/// part's completion untouched. A panic in one provider propagates, as the trait says, and the +/// parts not yet handed on are dropped with it, so their completions are abandoned, which fails +/// their domains. +/// +/// # Panics +/// +/// If the request names a domain no registered provider serves, before any provider is called, +/// with a message naming the domain; every part is dropped with the request, so every domain in it +/// is abandoned. The instance sends a provider only the domains it read from it at build, so this +/// is a driver, or a caller outside an instance, breaking the protocol (DI-D-46, DI-D-83), and the +/// panic is the router's only report of it. +pub struct RoutingProvider { + routes: Vec>, + /// Each domain's registration. + owners: HashMap, +} + +/// One registration: the provider and the domains it declared when the router was built. +struct Route { + provider: Box>, + domains: Vec, +} + +impl RoutingProvider { + /// A builder with no provider registered. + #[must_use] + pub fn builder() -> RouterBuilder { + RouterBuilder::new() + } +} + +impl DurabilityProvider for RoutingProvider { + fn domains(&self) -> Vec { + self.routes + .iter() + .flat_map(|route| route.domains.iter().cloned()) + .collect() + } + + fn make_durable(&mut self, request: FlushRequest) { + let mut parts: Vec>> = + self.routes.iter().map(|_| Vec::new()).collect(); + for part in request.domains { + let Some(®istration) = self.owners.get(&part.domain) else { + panic!( + "the routing provider was asked to make the flush domain {:?} durable, which no \ + provider registered with it serves", + part.domain + ); + }; + parts[registration].push(part); + } + // A panic here unwinds through `parts`, dropping the parts of the providers not yet called, + // whose completions then answer as abandoned. + for (route, domains) in self.routes.iter_mut().zip(parts) { + if !domains.is_empty() { + route.provider.make_durable(FlushRequest { domains }); + } + } + } +} + +impl fmt::Debug for RoutingProvider { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RoutingProvider") + .field("routes", &self.routes) + .finish_non_exhaustive() + } +} + +impl fmt::Debug for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Route") + .field("provider", &self.provider) + .field("domains", &self.domains) + .finish() + } +} + +/// A router's build met a flush domain claimed twice: the domain, and the registrations that +/// claimed it, numbered from 0 in registration order. `first` and `second` are equal when one +/// provider declared the domain twice. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoutedTwice { + /// The domain. + pub domain: FlushDomain, + /// The registration that claimed it first. + pub first: usize, + /// The registration that claimed it again. + pub second: usize, +} + +impl fmt::Display for RoutedTwice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.first == self.second { + write!( + f, + "registration {} declared the flush domain {:?} twice", + self.first, self.domain + ) + } else { + write!( + f, + "the flush domain {:?} is claimed by registrations {} and {}", + self.domain, self.first, self.second + ) + } + } +} + +impl std::error::Error for RoutedTwice {} + +#[cfg(test)] +mod tests; diff --git a/crates/durable-ioring/src/router/tests.rs b/crates/durable-ioring/src/router/tests.rs new file mode 100644 index 000000000..d601bec17 --- /dev/null +++ b/crates/durable-ioring/src/router/tests.rs @@ -0,0 +1,277 @@ +// Copyright (c) 2026 Mike Grier +//! The router's build (DI-12.2.1, DI-D-81): the union of its providers' domains, and a domain +//! claimed twice refused. Its dispatch is in `dispatch`. + +mod dispatch; + +use std::error::Error; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use super::{RoutedTwice, RouterBuilder, RoutingProvider}; +use crate::ids::DioringIds; +use crate::provider::{DurabilityProvider, FlushRequest}; +use crate::types::FlushDomain; + +type V = DioringIds; + +fn domain(name: &str) -> FlushDomain { + FlushDomain::new(name.as_bytes()) +} + +fn domains(names: &[&str]) -> Vec { + names.iter().map(|name| domain(name)).collect() +} + +/// A provider that declares `domains` and counts how often it is asked for them. +#[derive(Debug)] +struct Serves { + domains: Vec, + asked: Arc, +} + +impl DurabilityProvider for Serves { + fn domains(&self) -> Vec { + self.asked.fetch_add(1, Ordering::Relaxed); + self.domains.clone() + } + + fn make_durable(&mut self, _: FlushRequest) { + panic!("a router's build makes no request"); + } +} + +fn serves(names: &[&str]) -> Serves { + counted(names, &Arc::new(AtomicUsize::new(0))) +} + +fn counted(names: &[&str], asked: &Arc) -> Serves { + Serves { + domains: domains(names), + asked: Arc::clone(asked), + } +} + +fn built(providers: Vec) -> Result, RoutedTwice> { + providers + .into_iter() + .fold(RoutingProvider::builder(), RouterBuilder::register) + .build() +} + +#[test] +fn one_provider_serves_its_own_domains() { + let router = built(vec![serves(&["log", "data"])]).expect("no domain twice"); + assert_eq!(router.domains(), domains(&["log", "data"])); +} + +#[test] +fn two_providers_serve_the_union_in_registration_order() { + let router = + built(vec![serves(&["log"]), serves(&["disk-a", "disk-b"])]).expect("no domain twice"); + assert_eq!(router.domains(), domains(&["log", "disk-a", "disk-b"])); +} + +#[test] +fn the_union_keeps_each_providers_own_order() { + let router = + built(vec![serves(&["z", "a", "m"]), serves(&["c", "b"])]).expect("no domain twice"); + assert_eq!(router.domains(), domains(&["z", "a", "m", "c", "b"])); +} + +#[test] +fn many_providers_serve_every_domain_once() { + let names: Vec = (0..64).map(|n| format!("d{n}")).collect(); + let providers = names + .chunks(4) + .map(|chunk| serves(&chunk.iter().map(String::as_str).collect::>())) + .collect(); + let router = built(providers).expect("no domain twice"); + let expected: Vec<_> = names.iter().map(|name| domain(name)).collect(); + assert_eq!(router.domains(), expected); +} + +#[test] +fn a_provider_that_serves_no_domain_adds_none() { + let router = + built(vec![serves(&["log"]), serves(&[]), serves(&["data"])]).expect("no domain twice"); + assert_eq!(router.domains(), domains(&["log", "data"])); +} + +#[test] +fn a_router_with_no_provider_serves_nothing() { + let router = RoutingProvider::::builder() + .build() + .expect("nothing to repeat"); + assert!(router.domains().is_empty()); + let defaulted = RouterBuilder::::default() + .build() + .expect("nothing to repeat"); + assert!(defaulted.domains().is_empty()); +} + +#[test] +fn providers_that_serve_nothing_make_a_router_that_serves_nothing() { + let router = built(vec![serves(&[]), serves(&[])]).expect("nothing to repeat"); + assert!(router.domains().is_empty()); +} + +#[test] +fn a_domain_claimed_by_adjacent_registrations_is_refused_naming_both() { + let refused = built(vec![serves(&["log"]), serves(&["data", "log"])]).expect_err("log twice"); + assert_eq!( + refused, + RoutedTwice { + domain: domain("log"), + first: 0, + second: 1, + } + ); + let shown = refused.to_string(); + assert!(shown.contains("registrations 0 and 1"), "{shown}"); +} + +#[test] +fn a_domain_claimed_by_distant_registrations_is_refused_naming_both() { + let refused = built(vec![ + serves(&["a"]), + serves(&["b"]), + serves(&[]), + serves(&["c"]), + serves(&["d"]), + serves(&["e", "a"]), + ]) + .expect_err("a twice"); + assert_eq!( + refused, + RoutedTwice { + domain: domain("a"), + first: 0, + second: 5, + } + ); +} + +#[test] +fn a_provider_declaring_a_domain_twice_is_refused_naming_it_once() { + let refused = + built(vec![serves(&["x"]), serves(&["log", "data", "log"])]).expect_err("log twice"); + assert_eq!( + refused, + RoutedTwice { + domain: domain("log"), + first: 1, + second: 1, + } + ); + let shown = refused.to_string(); + assert!(shown.contains("registration 1 declared"), "{shown}"); + assert!(shown.contains("twice"), "{shown}"); +} + +#[test] +fn the_first_repeat_met_in_reading_order_is_the_one_refused() { + // `b` is claimed again by registration 2 and `a` by registration 3; registration 2 is read + // first, so `b` is the one named, though `a` was registered first. + let refused = built(vec![ + serves(&["a", "b"]), + serves(&["c"]), + serves(&["b"]), + serves(&["a"]), + ]) + .expect_err("a repeat"); + assert_eq!( + refused, + RoutedTwice { + domain: domain("b"), + first: 0, + second: 2, + } + ); +} + +#[test] +fn domains_are_one_when_their_bytes_are_equal() { + let shared: Arc<[u8]> = Arc::from(&b"volume"[..]); + let apart: Arc<[u8]> = Arc::from(&b"volume"[..]); + let first = Serves { + domains: vec![FlushDomain::new(shared)], + asked: Arc::new(AtomicUsize::new(0)), + }; + let second = Serves { + domains: vec![FlushDomain::new(apart)], + asked: Arc::new(AtomicUsize::new(0)), + }; + let refused = built(vec![first, second]).expect_err("the same bytes twice"); + assert_eq!(refused.domain, domain("volume")); +} + +#[test] +fn domains_that_differ_in_any_byte_are_distinct() { + let router = built(vec![ + serves(&["log"]), + serves(&["log2"]), + serves(&["Log"]), + serves(&[""]), + ]) + .expect("all distinct"); + assert_eq!(router.domains(), domains(&["log", "log2", "Log", ""])); +} + +#[test] +fn each_providers_domains_are_read_once_at_build() { + let asked = Arc::new(AtomicUsize::new(0)); + let builder = RoutingProvider::builder() + .register(counted(&["a"], &asked)) + .register(counted(&["b"], &asked)); + assert_eq!( + asked.load(Ordering::Relaxed), + 0, + "registering reads nothing" + ); + let router = builder.build().expect("no domain twice"); + assert_eq!(asked.load(Ordering::Relaxed), 2, "one read per provider"); + let _ = router.domains(); + let _ = router.domains(); + assert_eq!( + asked.load(Ordering::Relaxed), + 2, + "the router answers from what it read" + ); +} + +#[test] +fn a_refused_build_does_not_ask_the_providers_after_the_repeat() { + let before = Arc::new(AtomicUsize::new(0)); + let after = Arc::new(AtomicUsize::new(0)); + let refused = RoutingProvider::builder() + .register(counted(&["a"], &before)) + .register(counted(&["a"], &before)) + .register(counted(&["b"], &after)) + .build(); + assert!(refused.is_err()); + assert_eq!(before.load(Ordering::Relaxed), 2); + assert_eq!(after.load(Ordering::Relaxed), 0); +} + +#[test] +fn the_refusal_is_an_error() { + let refused: Box = Box::new(RoutedTwice { + domain: domain("log"), + first: 0, + second: 3, + }); + let shown = refused.to_string(); + assert!(shown.contains("registrations 0 and 3"), "{shown}"); + assert!(refused.source().is_none()); +} + +#[test] +fn a_router_shows_its_routes() { + let router = built(vec![serves(&["log"])]).expect("no domain twice"); + let shown = format!("{router:?}"); + assert!(shown.starts_with("RoutingProvider"), "{shown}"); + assert!(shown.contains("Serves"), "{shown}"); + let builder = RoutingProvider::::builder().register(serves(&["log"])); + assert!(format!("{builder:?}").contains("Serves")); +} diff --git a/crates/durable-ioring/src/router/tests/dispatch.rs b/crates/durable-ioring/src/router/tests/dispatch.rs new file mode 100644 index 000000000..ff6bb861f --- /dev/null +++ b/crates/durable-ioring/src/router/tests/dispatch.rs @@ -0,0 +1,473 @@ +// Copyright (c) 2026 Mike Grier +//! The router's dispatch (DI-12.2.2): one call per provider with a part, in registration order, +//! each completion handed on untouched, a panic propagating, and an unrouted domain panicked on. + +use std::io; +use std::panic::{self, AssertUnwindSafe}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, Weak, mpsc}; +use std::thread; + +use super::{V, domain, domains}; +use crate::driver::{Answer, DomainCall}; +use crate::ids::{InstanceId, Lineage, OpId}; +use crate::provider::{ + DomainCompletion, DomainOutcome, DomainRequest, DurabilityProvider, FlushRequest, + IoTargetWrites, +}; +use crate::router::RoutingProvider; +use crate::types::{FlushDomain, IoTargetKey}; + +/// How a domain was answered, as the instance would record it. +#[derive(Clone, Debug, PartialEq, Eq)] +enum Seen { + Succeeded, + Failed(io::ErrorKind, String), + Abandoned, +} + +/// Where completions answer: a stand-in for the instance. +#[derive(Default)] +struct Home { + issued: AtomicUsize, + answers: Mutex>, +} + +impl Answer for Home { + fn issued(&self) { + self.issued.fetch_add(1, Ordering::Relaxed); + } + + fn answer(&self, call: &DomainCall, outcome: DomainOutcome) { + let seen = match outcome { + DomainOutcome::Succeeded => Seen::Succeeded, + DomainOutcome::Failed(error) => Seen::Failed(error.kind(), error.to_string()), + DomainOutcome::Abandoned => Seen::Abandoned, + }; + self.answers + .lock() + .expect("not poisoned") + .push((call.name.clone(), seen)); + } +} + +impl Home { + fn answers(&self) -> Vec<(FlushDomain, Seen)> { + self.answers.lock().expect("not poisoned").clone() + } + + fn answer_for(&self, name: &str) -> Option { + let wanted = domain(name); + let found: Vec<_> = self + .answers() + .into_iter() + .filter(|(domain, _)| *domain == wanted) + .map(|(_, seen)| seen) + .collect(); + assert!(found.len() <= 1, "{name} answered {} times", found.len()); + found.into_iter().next() + } + + /// Every completion made has been answered, once. + fn all_answered(&self) -> bool { + self.answers().len() == self.issued.load(Ordering::Relaxed) + } +} + +/// A request with one part per name, in order: each part's one file is keyed by its place in the +/// request, with one write numbered the same. +fn request(home: &Arc, names: &[&str]) -> FlushRequest { + let instance = InstanceId::next(); + let lineage = Lineage { instance, seq: 0 }; + let weak: Weak = Arc::downgrade(home) as Weak; + FlushRequest { + domains: names + .iter() + .enumerate() + .map(|(place, name)| { + let seq = u64::try_from(place).expect("small"); + DomainRequest { + domain: domain(name), + files: vec![IoTargetWrites { + key: IoTargetKey(seq), + writes: vec![OpId { instance, seq }], + }], + completion: DomainCompletion::new( + weak.clone(), + DomainCall { + lineage, + seal: 7, + name: domain(name), + }, + ), + } + }) + .collect(), + } +} + +/// What a provider does with the parts it is given. +#[derive(Debug)] +enum Act { + Succeed, + Fail, + Drop, + Keep, + Hand(mpsc::Sender), + Panic, +} + +/// One call a provider received: its name, and each part's domain and file keys, in order. +type Call = (&'static str, Vec<(FlushDomain, Vec)>); + +#[derive(Debug)] +struct Fake { + name: &'static str, + domains: Vec, + act: Act, + calls: Arc>>, + kept: Vec, +} + +impl DurabilityProvider for Fake { + fn domains(&self) -> Vec { + self.domains.clone() + } + + fn make_durable(&mut self, request: FlushRequest) { + let call = request + .domains + .iter() + .map(|part| { + let keys = part.files.iter().map(|file| file.key.0).collect(); + (part.domain.clone(), keys) + }) + .collect(); + self.calls + .lock() + .expect("not poisoned") + .push((self.name, call)); + for part in request.domains { + match &self.act { + Act::Succeed => part.completion.succeed(), + Act::Fail => part + .completion + .fail(io::Error::new(io::ErrorKind::TimedOut, self.name)), + Act::Drop => drop(part), + Act::Keep => self.kept.push(part.completion), + Act::Hand(sender) => sender.send(part.completion).expect("the test listens"), + Act::Panic => panic!("{} panicked", self.name), + } + } + } +} + +/// A rig: the calls every provider received, and a router built over the providers given. +struct Rig { + calls: Arc>>, + router: RoutingProvider, +} + +impl Rig { + fn new(providers: Vec<(&'static str, &[&str], Act)>) -> Self { + let calls = Arc::new(Mutex::new(Vec::new())); + let router = providers + .into_iter() + .fold(RoutingProvider::builder(), |builder, (name, names, act)| { + builder.register(Fake { + name, + domains: domains(names), + act, + calls: Arc::clone(&calls), + kept: Vec::new(), + }) + }) + .build() + .expect("no domain twice"); + Self { calls, router } + } + + fn calls(&self) -> Vec { + self.calls.lock().expect("not poisoned").clone() + } + + fn called(&self) -> Vec<&'static str> { + self.calls().into_iter().map(|(name, _)| name).collect() + } +} + +fn part(name: &str, key: u64) -> (FlushDomain, Vec) { + (domain(name), vec![key]) +} + +#[test] +fn one_provider_is_given_the_whole_request_as_it_came() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("only", &["log", "data"], Act::Succeed)]); + rig.router.make_durable(request(&home, &["data", "log"])); + assert_eq!( + rig.calls(), + vec![("only", vec![part("data", 0), part("log", 1)])] + ); + assert_eq!(home.answer_for("data"), Some(Seen::Succeeded)); + assert_eq!(home.answer_for("log"), Some(Seen::Succeeded)); + assert!(home.all_answered()); +} + +#[test] +fn each_provider_is_given_only_its_own_domains() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("logs", &["log"], Act::Succeed), + ("disks", &["disk-a", "disk-b"], Act::Succeed), + ]); + rig.router + .make_durable(request(&home, &["disk-b", "log", "disk-a"])); + assert_eq!( + rig.calls(), + vec![ + ("logs", vec![part("log", 1)]), + ("disks", vec![part("disk-b", 0), part("disk-a", 2)]), + ] + ); + assert!(home.all_answered()); +} + +#[test] +fn providers_are_called_in_registration_order_whatever_the_request_order() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("first", &["a"], Act::Succeed), + ("second", &["b"], Act::Succeed), + ("third", &["c"], Act::Succeed), + ]); + rig.router.make_durable(request(&home, &["c", "b", "a"])); + assert_eq!(rig.called(), vec!["first", "second", "third"]); +} + +#[test] +fn a_provider_with_no_part_is_not_called() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("idle", &["a"], Act::Panic), + ("busy", &["b"], Act::Succeed), + ("empty", &[], Act::Panic), + ]); + rig.router.make_durable(request(&home, &["b"])); + assert_eq!(rig.called(), vec!["busy"]); + assert!(home.all_answered()); +} + +#[test] +fn an_empty_request_calls_no_provider() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("a", &["a"], Act::Panic), ("b", &["b"], Act::Panic)]); + rig.router.make_durable(request(&home, &[])); + assert!(rig.calls().is_empty()); + assert!(home.answers().is_empty()); +} + +#[test] +fn several_domains_of_one_provider_are_one_call() { + let home = Arc::new(Home::default()); + let names = ["d0", "d1", "d2", "d3", "d4", "d5"]; + let mut rig = Rig::new(vec![("many", &names, Act::Succeed)]); + rig.router.make_durable(request(&home, &names)); + let calls = rig.calls(); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].1.len(), names.len()); + assert!(home.all_answered()); +} + +#[test] +fn an_answer_inside_the_call_reaches_the_instance_during_it() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("ok", &["a"], Act::Succeed), + ("bad", &["b"], Act::Fail), + ]); + rig.router.make_durable(request(&home, &["a", "b"])); + assert_eq!(home.answer_for("a"), Some(Seen::Succeeded)); + assert_eq!( + home.answer_for("b"), + Some(Seen::Failed(io::ErrorKind::TimedOut, "bad".to_owned())) + ); +} + +#[test] +fn a_late_answer_from_another_thread_reaches_the_instance_as_it_arrives() { + let home = Arc::new(Home::default()); + let (sender, handed) = mpsc::channel(); + let mut rig = Rig::new(vec![("later", &["a", "b"], Act::Hand(sender))]); + rig.router.make_durable(request(&home, &["a", "b"])); + assert!(home.answers().is_empty(), "nothing answered yet"); + let first = handed.recv().expect("a"); + let second = handed.recv().expect("b"); + thread::spawn(move || second.succeed()) + .join() + .expect("answered"); + assert_eq!(home.answers(), vec![(domain("b"), Seen::Succeeded)]); + thread::spawn(move || first.fail(io::Error::other("late"))) + .join() + .expect("answered"); + assert_eq!( + home.answer_for("a"), + Some(Seen::Failed(io::ErrorKind::Other, "late".to_owned())) + ); + assert!(home.all_answered()); +} + +#[test] +fn completions_a_provider_drops_are_abandoned_and_only_its_own() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("drops", &["a"], Act::Drop), + ("answers", &["b"], Act::Succeed), + ]); + rig.router.make_durable(request(&home, &["a", "b"])); + assert_eq!(home.answer_for("a"), Some(Seen::Abandoned)); + assert_eq!(home.answer_for("b"), Some(Seen::Succeeded)); +} + +#[test] +fn completions_a_provider_keeps_stay_unanswered_until_it_is_dropped() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("keeps", &["a"], Act::Keep)]); + rig.router.make_durable(request(&home, &["a"])); + assert_eq!(home.answer_for("a"), None); + assert!(!home.all_answered()); + drop(rig); + assert_eq!(home.answer_for("a"), Some(Seen::Abandoned)); +} + +#[test] +fn a_panic_propagates_and_abandons_the_parts_not_yet_handed_on() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("before", &["a"], Act::Succeed), + ("panics", &["b", "c"], Act::Panic), + ("after", &["d"], Act::Succeed), + ]); + let request = request(&home, &["d", "c", "b", "a"]); + let outcome = panic::catch_unwind(AssertUnwindSafe(|| rig.router.make_durable(request))); + let payload = outcome.expect_err("the panic propagates"); + let message = payload.downcast_ref::().expect("a formatted panic"); + assert_eq!(message, "panics panicked"); + assert_eq!(rig.called(), vec!["before", "panics"]); + assert_eq!(home.answer_for("a"), Some(Seen::Succeeded)); + assert_eq!(home.answer_for("b"), Some(Seen::Abandoned)); + assert_eq!(home.answer_for("c"), Some(Seen::Abandoned)); + assert_eq!(home.answer_for("d"), Some(Seen::Abandoned)); + assert!(home.all_answered()); +} + +/// The message of the panic `request` raises in `router`, which must panic. +fn panic_of(router: &mut RoutingProvider, request: FlushRequest) -> String { + let outcome = panic::catch_unwind(AssertUnwindSafe(|| router.make_durable(request))); + let payload = outcome.expect_err("an unrouted domain panics"); + payload + .downcast_ref::() + .expect("a formatted panic") + .clone() +} + +#[test] +fn a_domain_no_provider_serves_is_panicked_on_naming_it() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("known", &["a"], Act::Succeed)]); + let message = panic_of(&mut rig.router, request(&home, &["a", "stray"])); + assert!( + message.contains(&format!("{:?}", domain("stray"))), + "{message}" + ); + assert!(message.contains("no provider registered"), "{message}"); +} + +#[test] +fn an_unrouted_domain_is_panicked_on_before_any_provider_is_called() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("first", &["a"], Act::Succeed), + ("second", &["b"], Act::Succeed), + ]); + let _ = panic_of(&mut rig.router, request(&home, &["a", "b", "stray"])); + assert!(rig.calls().is_empty(), "no provider called"); +} + +#[test] +fn an_unrouted_domain_abandons_every_domain_of_the_request() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("known", &["a", "b"], Act::Succeed)]); + let _ = panic_of(&mut rig.router, request(&home, &["a", "stray", "b"])); + assert_eq!(home.answer_for("a"), Some(Seen::Abandoned)); + assert_eq!(home.answer_for("stray"), Some(Seen::Abandoned)); + assert_eq!(home.answer_for("b"), Some(Seen::Abandoned)); + assert!(home.all_answered()); +} + +#[test] +fn a_router_with_no_provider_panics_on_any_domain() { + let home = Arc::new(Home::default()); + let mut router = RoutingProvider::::builder().build().expect("empty"); + let message = panic_of(&mut router, request(&home, &["a"])); + assert!(message.contains(&format!("{:?}", domain("a"))), "{message}"); + assert_eq!(home.answer_for("a"), Some(Seen::Abandoned)); +} + +#[test] +fn the_router_is_usable_after_an_unrouted_request_panicked() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![("known", &["a"], Act::Succeed)]); + let _ = panic_of(&mut rig.router, request(&home, &["stray"])); + rig.router.make_durable(request(&home, &["a"])); + assert_eq!(rig.called(), vec!["known"]); + assert_eq!(home.answer_for("a"), Some(Seen::Succeeded)); +} + +#[test] +fn each_seal_is_dispatched_on_its_own() { + let home = Arc::new(Home::default()); + let mut rig = Rig::new(vec![ + ("x", &["a"], Act::Succeed), + ("y", &["b"], Act::Succeed), + ]); + rig.router.make_durable(request(&home, &["a"])); + rig.router.make_durable(request(&home, &["b"])); + rig.router.make_durable(request(&home, &["a", "b"])); + assert_eq!(rig.called(), vec!["x", "y", "x", "y"]); + assert_eq!(home.answers().len(), 4); + assert!(home.all_answered()); +} + +#[test] +fn a_router_can_be_registered_with_a_router() { + let home = Arc::new(Home::default()); + let calls = Arc::new(Mutex::new(Vec::new())); + let fake = |name, names: &[&str]| Fake { + name, + domains: domains(names), + act: Act::Succeed, + calls: Arc::clone(&calls), + kept: Vec::new(), + }; + let inner = RoutingProvider::builder() + .register(fake("inner-a", &["a"])) + .register(fake("inner-b", &["b"])) + .build() + .expect("no domain twice"); + let mut outer = RoutingProvider::builder() + .register(inner) + .register(fake("outer-c", &["c"])) + .build() + .expect("no domain twice"); + assert_eq!(outer.domains(), domains(&["a", "b", "c"])); + outer.make_durable(request(&home, &["c", "b", "a"])); + let called: Vec<_> = calls + .lock() + .expect("not poisoned") + .iter() + .map(|(name, _)| *name) + .collect(); + assert_eq!(called, vec!["inner-a", "inner-b", "outer-c"]); + assert!(home.all_answered()); +} diff --git a/crates/durable-ioring/src/types.rs b/crates/durable-ioring/src/types.rs index b6cd80d0d..d94d43485 100644 --- a/crates/durable-ioring/src/types.rs +++ b/crates/durable-ioring/src/types.rs @@ -7,6 +7,7 @@ use std::fmt; use std::io; use std::sync::Arc; +use crate::access::Access; use crate::backend::RefusalCause; use crate::contract::Identities; use crate::error_code::ErrorCode; @@ -705,14 +706,44 @@ pub struct Leftovers { pub lineages: Vec>, } -/// A refused `add_target`: a target is already present under `key`. The target, of the -/// implementation's type, is handed back. +/// Why an `add_target` was refused (DI-13.10). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum AddTargetCause { + /// A target is already present under the key. + KeyInUse, + /// The key declared more access than the target states: a `Key` for a target that + /// can only be read, for one. A key that declares less is accepted. + AccessNotCovered { + /// What the key declared. + declared: Access, + /// What the target states. + actual: Access, + }, +} + +impl fmt::Display for AddTargetCause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::KeyInUse => f.write_str("a target is already present under the key"), + Self::AccessNotCovered { declared, actual } => write!( + f, + "the key declares {declared} access and the target states {actual}" + ), + } + } +} + +/// A refused `add_target`, with its `cause`. The target, of the implementation's type, is handed +/// back. #[derive(Debug)] pub struct AddTargetError { - /// The key already in use. + /// The key the target was offered under. pub key: IoTargetKey, /// The consumer's target. pub target: T, + /// Why it was refused. + pub cause: AddTargetCause, } /// Everything still holding a file that `remove_target` refused, and what clears each hold. Every diff --git a/crates/io-buffers/COMPLETED-CHECKLIST.md b/crates/io-buffers/COMPLETED-CHECKLIST.md index 03ea08c03..61052740a 100644 --- a/crates/io-buffers/COMPLETED-CHECKLIST.md +++ b/crates/io-buffers/COMPLETED-CHECKLIST.md @@ -269,7 +269,7 @@ The assistant's choices in it are queued in `durable-ioring`'s `DI-9.7`; the dec The item as it stood at completion: -- [ ] **IB-2+.4** -- **Whether `windows-namespace-request-sys`' `AlignedBuffer` moves onto +- [x] **IB-2+.4** -- **Whether `windows-namespace-request-sys`' `AlignedBuffer` moves onto `AlignedBuf`, and by which route.** The engineer's decision, because the type is public API of a published crate (0.2.3): it appears in public return types (`Outcome`, the query and security captures) and is re-exported. Its differences from `AlignedBuf` are diff --git a/crates/win-debugging-sys/Cargo.toml b/crates/win-debugging-sys/Cargo.toml new file mode 100644 index 000000000..7fcd1e9e6 --- /dev/null +++ b/crates/win-debugging-sys/Cargo.toml @@ -0,0 +1,53 @@ +# Copyright (c) 2026 Mike Grier + +[package] +name = "win-debugging-sys" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +readme = "README.md" +description = "Memory-safe Rust over the Win32 debugging API, starting with OutputDebugStringW: send a string to the debugger. Thin over Win32, adding no policy." +keywords = ["windows", "debugging", "debugger", "ffi"] +categories = ["api-bindings", "os::windows-apis", "development-tools::debugging"] + +# Named by the engineer, 2026-10-20 ("how about win-debugging-sys?"), under the workspace's `win-` prefix +# decision (DESIGN-NOTES.md at the repository root), and placed as a safe layer more or less directly over +# a Win32 API area, the one Microsoft Learn files the function under: Diagnostics > Debugging and Error +# Handling > Basic Debugging > Debugging Reference > Debugging Functions (WD-D-1). + +[dependencies] +# `default-features = false` matches every other crate in the workspace. +# +# `Win32_System_Diagnostics_Debug` is `OutputDebugStringW`. +[dependencies.windows-sys] +version = "0.61.2" +default-features = false +features = ["Win32_System_Diagnostics_Debug"] + +[features] +# A debugger that runs a program and keeps the strings it sends (WD-D-3), for a test to see that what is sent +# reaches a debugger. Off by default: it is a test facility and not a wrapper of the debugging functions. +# +# `Win32_Foundation` is `CloseHandle`, the debug status codes and the handle types. +# `Win32_Security` is the security-attributes type `CreateProcessW` takes. +# `Win32_System_Threading` is `CreateProcessW`, `TerminateProcess` and the debug creation flags. +# The debug functions and event structures are `Win32_System_Diagnostics_Debug`, which the crate has already. +test-util = [ + "windows-sys/Win32_Foundation", + "windows-sys/Win32_Security", + "windows-sys/Win32_System_Threading", +] + +[dev-dependencies] +# The crate's own tests use its `test-util` feature, as `win-durable-ioring` does for `fault-injection`. +win-debugging-sys = { path = ".", features = ["test-util"] } + +[package.metadata.docs.rs] +# The crate is Windows-only, so docs.rs must build on a Windows target or it +# would render an almost-empty crate. +default-target = "x86_64-pc-windows-msvc" +targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"] diff --git a/crates/win-debugging-sys/DESIGN-NOTES.md b/crates/win-debugging-sys/DESIGN-NOTES.md new file mode 100644 index 000000000..54927a22c --- /dev/null +++ b/crates/win-debugging-sys/DESIGN-NOTES.md @@ -0,0 +1,100 @@ +# Design notes: win-debugging-sys + +## WD-D-1: the crate is named for the debugging section, and `OutputDebugStringW` is the first function filed in it + +**The name is the engineer's, 2026-10-20.** The assistant proposed `win-debug-sys` for the crate that holds the +`OutputDebugStringW` call the debugger sink of `win-durable-ioring` needs, and the engineer answered: "how about +win-debugging-sys?" The workspace's `win-` prefix rule applies, as the crate is a safe layer more or less directly over +Win32 ([DESIGN-NOTES.md](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix)). The decision that led to it is +[DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76). + +**The section.** By [the placement rule](../../DESIGN-NOTES.md#safe-wrappers-are-placed-by-the-learn-section-they-are-documented-under), +the snapshot [WIN32-LEARN-MAP-DATA.txt](../../WIN32-LEARN-MAP-DATA.txt) (2026-10-17) files `OutputDebugString` once, +under *Desktop App Technologies > Diagnostics > Debugging and Error Handling > Basic Debugging > Debugging Reference > +Debugging Functions*. That is this crate's section. The same section lists the debugger's side of the exchange -- +`WaitForDebugEventEx`, `ContinueDebugEvent`, `DebugActiveProcess`, `ReadProcessMemory` and the rest -- so those are this +crate's by the rule, and none is wrapped yet ([WD-D-2](#wd-d-2) says what the tests use in their place). Neither +Microsoft's section title nor its header appears in the name or in an identifier, as the rule requires. + +**What a `-sys` crate here is.** It is memory-safe over the call and adds no policy: it passes the arguments as given, +reports what the call reports, and refuses ahead of the call only what the call's page makes impossible to pass. + +**Scheduled work.** None for this crate. The first caller is the debugger sink in `win-durable-ioring` +([DI-12.1.3](../durable-ioring/CHECKLIST.md)). Wrapping the debugger's side is not scheduled: no step needs it, and +whether the crate should grow it is the engineer's to say. + +## WD-D-2: `output_debug_string` takes UTF-16 units and `output_debug_str` a `str`, a NUL is the crate's one refusal, and the call is observed by a debugger + +Read 2026-10-20 from the pages for `OutputDebugStringW` (`ms.date` 2024-02-02, `updated_at` 2025-08-27), +`OUTPUT_DEBUG_STRING_INFO` (`ms.date` 2018-12-05, `updated_at` 2024-02-22) and `WaitForDebugEventEx` (`ms.date` +2018-12-05, `updated_at` 2024-02-22). + +**The call cannot fail.** Its one parameter is "The null-terminated string to be displayed", its return value is "None", +and the remarks say that with no debugger and no active system debugger the call "does nothing". So the functions return +`Result<(), OutputDebugStringError>` only for the crate's own refusal, and a call that is made has no failure to report +and no way to say whether anything was shown. + +**The shape.** `output_debug_string(&[u16])` is the call, with the units as given: an unpaired surrogate is a unit, as it +is in a Windows string, and the call may show it however the debugger does. `output_debug_str(&str)` is the same over the +units a `str` encodes to, for the caller that has text and not units, which a diagnostic sink does. Both take a copy of +the units with a terminator added, because a slice is not NUL-terminated and the call reads to a NUL. The copy costs an +allocation, and is what makes the read stop where the slice does. + +**The one refusal, and what it rests on.** A string with a NUL is refused as `OutputDebugStringError::EmbeddedNul`, naming +the first NUL's place in units, and the call is not made. The citation is the page's "null-terminated string": the call +would take the string to end at that NUL and show only what came before it, with nothing to tell the caller so. The page +does not call such a string invalid, so this is not a prediction of the platform's rejection and is not framed as one: it +is the crate's own narrowing, in an error of its own that no call's failure shares. The alternatives were to cut the +string at the NUL, or to leave the NULs out; each is the crate deciding what the caller meant, and the refusal leaves +that to the caller. The index is in units and not bytes or characters, since units are what both functions take, and a +`str`'s is therefore not its byte offset. + +**The call is observed by a debugger, over the documented route.** `tests/debugger.rs` starts the test binary as a process +being debugged (`CreateProcessW` with `DEBUG_ONLY_THIS_PROCESS`), runs a test in it that sends strings, and receives each +with `WaitForDebugEventEx` and `ReadProcessMemory`. The route is documented end to end: the page for the call says a +debugger must wait with `WaitForDebugEventEx` to be sent Unicode, and the page for the structure says the string is read +out of the process with `ReadProcessMemory`. The other way to read debug output, a program that reads what every process +sends through a shared section, is not described by the pages for these functions, and is not used +([DESIGN-NOTES.md](../../DESIGN-NOTES.md#no-undocumented-apis)). + +**What it observed, which is evidence and not contract.** A debugger that waited with `WaitForDebugEventEx` was sent each +string as Unicode, unit for unit, an unpaired surrogate and a character outside the basic plane included, and the empty +string arrived as an event whose string is empty. A string refused for a NUL produced no event, not even for what came +before the NUL. A string of 100,000 units arrived cut to the length the page for the structure gives: "the lower 16 bits of +the length of the string in bytes", with the terminator counted. The +tests state each of these as what happened on the machine they ran on, and the crate's documentation does not promise +any of it: the page says the string is shown by the debugger, not how it arrives. + +**What the debugger side uses.** The debugger's functions are called by the `test-util` facility of +[WD-D-3](#wd-d-3), which this test uses. + +**Scheduled work.** None: the crate's one function is done, and its first caller is +[DI-12.1.3](../durable-ioring/CHECKLIST.md). + +## WD-D-3: a `test-util` feature holds a debugger that runs a program and keeps what it sends, and is not a wrapper of the debugging functions + +**Why it is here and not in each test.** The first debugger written for the observation of [WD-D-2](#wd-d-2) was a test +of this crate, in about a hundred lines of `unsafe`. `win-durable-ioring` forbids unsafe code and says a Win32 call +no lower crate wraps safely is added to that crate and never written there (DI-D-4 in +[durable-ioring's notes](../durable-ioring/DESIGN-NOTES.md)), and its debugger sink needs the same observation. A second +copy in that crate would be the thing the rule forbids, and two copies of a harness are two places for it to drift. So +there is one, here, in the crate that owns the debugging functions, and both crates' tests use it. + +**What it is.** `run_debugged(program, arguments, bound)` starts a process with `CreateProcessW` and +`DEBUG_ONLY_THIS_PROCESS`, waits for each event with `WaitForDebugEventEx`, reads each string out of the process with +`ReadProcessMemory`, continues the event, and returns a `Captured`: the strings, each with whether it arrived as Unicode, +and the exit code. A process that has not exited by the bound is ended and has no exit code, and a process that could not +be started, or a string that could not be read, is an `io::Error` after the process has been ended and its handles +closed. The process runs without a console, so what its test harness prints reaches no one. + +**What it is not.** It is not the safe wrapper of the debugger's functions the placement rule would give this crate, and it +is not offered as one: it runs a process, and carries a test's policy, a bound, ending the process, and closing handles. It +is behind the off-by-default `test-util` feature for that reason, as `win-durable-ioring` keeps `fault-injection` and the +file watcher keeps `test-util`, and this crate's own tests take the feature through a dev-dependency on itself, as +`win-durable-ioring`'s do. Whether the debugger's functions should be wrapped one by one is +[WD-D-1](#wd-d-1)'s open question, and this does not answer it. + +**Its own tests** are the integration test of [WD-D-2](#wd-d-2): a process that sends strings and the strings it is +sent, a program that is not there, and a process that outlives its bound. + +**Scheduled work.** None beyond [DI-12.1.3](../durable-ioring/CHECKLIST.md), which is its second user. diff --git a/crates/win-debugging-sys/PLANS.md b/crates/win-debugging-sys/PLANS.md new file mode 100644 index 000000000..b59156cee --- /dev/null +++ b/crates/win-debugging-sys/PLANS.md @@ -0,0 +1,9 @@ +# Plans: win-debugging-sys + +The crate has no checklist of its own. Its one function is `DI-12.1.2` in the durable-ioring +[COMPLETED-CHECKLIST.md](../durable-ioring/COMPLETED-CHECKLIST.md#di-1212), and its first caller is `DI-12.1.3` in that +component's [CHECKLIST.md](../durable-ioring/CHECKLIST.md). Add a row here when new work is planned against a +`CHECKLIST.md` in this directory. + +| Path to CHECKLIST.md | Status | Brief description | Design Notes | +|---|---|---|---| diff --git a/crates/win-debugging-sys/README.md b/crates/win-debugging-sys/README.md new file mode 100644 index 000000000..f90e8d095 --- /dev/null +++ b/crates/win-debugging-sys/README.md @@ -0,0 +1,34 @@ +# win-debugging-sys + +Memory-safe Rust over the Win32 debugging API, the area Microsoft Learn files `OutputDebugString` under: +*Diagnostics > Debugging and Error Handling > Basic Debugging > Debugging Reference > Debugging Functions*. Thin over +Win32, adding no policy. + +- `output_debug_string` is `OutputDebugStringW`: it sends a slice of UTF-16 code units to the debugger. The call takes + a NUL-terminated string, so the units are copied with the terminator added, and nothing past the end of the slice is + read. `output_debug_str` is the same for a `str`. +- The call returns nothing and reports no error, so a call that is made cannot fail. A string with a NUL in it is + refused as `OutputDebugStringError::EmbeddedNul`, naming the unit, and the call is not made: the call would take the + string to end at the NUL and show only what came before. That is this crate's refusal and not the call's. See + [WD-D-2](DESIGN-NOTES.md#wd-d-2). +- Whether anything is shown depends on a debugger being attached, or on the system debugger being active and its filter + allowing the string. With neither, the page says the call "does nothing", and it gives no way to tell. + +```rust +use win_debugging_sys::{OutputDebugStringError, output_debug_str}; + +output_debug_str("a line for the debugger")?; + +assert_eq!( + output_debug_str("cut\0short"), + Err(OutputDebugStringError::EmbeddedNul { index: 3 }) +); +# Ok::<(), OutputDebugStringError>(()) +``` + +With the off-by-default `test-util` feature the crate also holds `run_debugged`, a debugger that runs a program and +returns the strings it sent, for a test to see that what a crate sends reaches a debugger. It is a test facility and not a +wrapper of the debugging functions. See [WD-D-3](DESIGN-NOTES.md#wd-d-3). + +The crate was made so that the diagnostic sink of `win-durable-ioring` that writes to the debugger has a safe call to +make. See [WD-D-1](DESIGN-NOTES.md#wd-d-1) for the name and where the function is filed. diff --git a/crates/win-debugging-sys/sabotage.json b/crates/win-debugging-sys/sabotage.json new file mode 100644 index 000000000..6a65fb800 --- /dev/null +++ b/crates/win-debugging-sys/sabotage.json @@ -0,0 +1,79 @@ +{ + "package": "win-debugging-sys", + "description": "Sabotages for win-debugging-sys. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", + "notCoveredHere": "A string sent to the call with its terminator missing, or with a NUL before it, is not injected: the call would read past the buffer, and the sweep would be measuring a crash and not a test. The refusal and the terminator are each checked on the built string, in memory, and the call is checked by a debugger receiving what it sent (WD-D-2).", + "sabotages": [ + { + "name": "WD-D-2: the terminator is added twice", + "file": "src/output.rs", + "expect": "caught", + "why": "The built string is the units and one terminator. A second changes what is sent and is seen by every test of the built string; a missing one is the sabotage that is not injected, see notCoveredHere.", + "find": [" units.push(NUL);\n"], + "replace": [" units.extend([NUL, NUL]);\n"] + }, + { + "name": "WD-D-2: a NUL in the string is not looked for", + "file": "src/output.rs", + "expect": "caught", + "why": "A NUL in the units would end the string the call reads at that unit, and show only what came before. The refusal is what stops a string being cut without the caller being told.", + "find": [" if let Some(index) = units.iter().position(|&unit| unit == NUL) {\n"], + "replace": [" if let Some(index) = units.iter().position(|&unit| unit == NUL).filter(|_| false) {\n"] + }, + { + "name": "WD-D-2: the refusal names the unit after the NUL", + "file": "src/output.rs", + "expect": "caught", + "why": "The index is where the first NUL is, in units, so a caller can find it; one more points at the wrong unit.", + "find": [" return Err(OutputDebugStringError::EmbeddedNul { index });\n"], + "replace": [" return Err(OutputDebugStringError::EmbeddedNul { index: index + 1 });\n"] + }, + { + "name": "WD-D-2: the refusal names the last NUL and not the first", + "file": "src/output.rs", + "expect": "caught", + "why": "The first NUL is where the call would end the string, and the one a caller meets first.", + "find": [" if let Some(index) = units.iter().position(|&unit| unit == NUL) {\n"], + "replace": [" if let Some(index) = units.iter().rposition(|&unit| unit == NUL) {\n"] + }, + { + "name": "WD-D-2: a str is sent as its first unit only", + "file": "src/output.rs", + "expect": "caught", + "why": "A str is every unit it encodes to, so the NUL in one is found wherever it is.", + "find": [" units.extend(text.encode_utf16());\n"], + "replace": [" units.extend(text.encode_utf16().take(1));\n"] + }, + { + "name": "WD-D-2: the call is not made", + "file": "src/output.rs", + "expect": "caught", + "why": "Only a debugger can tell that the call was made, which is why the test of it starts one: nothing in the process sees the difference.", + "find": [" unsafe { OutputDebugStringW(units.as_ptr()) };\n"], + "replace": [" let _ = units.as_ptr();\n"] + }, + { + "name": "WD-D-3: every string is taken as ANSI", + "file": "src/capture.rs", + "expect": "caught", + "why": "Whether a string arrived as Unicode is what the opt-in of WaitForDebugEventEx changes, and the observation of it is the point of the facility.", + "find": [" let unicode = info.fUnicode != 0;\n"], + "replace": [" let unicode = false;\n"] + }, + { + "name": "WD-D-3: the terminator is kept in a received string", + "file": "src/capture.rs", + "expect": "caught", + "why": "The length an event carries counts the terminator the call sends; a string is what comes before it, so an empty one is empty.", + "find": [" units.truncate(end);\n"], + "replace": [" units.truncate(end + 1);\n"] + }, + { + "name": "WD-D-2 control: the NUL is looked for by its literal value", + "file": "src/output.rs", + "expect": "survives", + "why": "A control: the same test by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" if let Some(index) = units.iter().position(|&unit| unit == NUL) {\n"], + "replace": [" if let Some(index) = units.iter().position(|&unit| unit == 0) {\n"] + } + ] +} diff --git a/crates/win-debugging-sys/src/capture.rs b/crates/win-debugging-sys/src/capture.rs new file mode 100644 index 000000000..f1ca65ce1 --- /dev/null +++ b/crates/win-debugging-sys/src/capture.rs @@ -0,0 +1,224 @@ +// Copyright (c) 2026 Mike Grier +//! A debugger that runs a program and keeps the strings it sends (WD-D-3), so that a test, here or in +//! another crate, can see that what a crate sends with [`output_debug_string`](crate::output_debug_string) +//! reaches a debugger by the documented route and not by a protocol no page describes. +//! +//! This is a test facility and not a wrapper of the debugging functions: it is behind the `test-util` feature, +//! it starts a process, and the policy it carries (a bound on the wait, the process ended if it is reached, the +//! handles closed) is a test's. It uses `CreateProcessW` with `DEBUG_ONLY_THIS_PROCESS`, and for each event +//! `WaitForDebugEventEx`, `ReadProcessMemory` and `ContinueDebugEvent`. A debugger that waits with +//! `WaitForDebugEventEx` opts into Unicode strings, which the page for `OutputDebugStringW` says a debugger must do +//! to be sent them. + +use std::ffi::c_void; +use std::io; +use std::mem::{size_of, zeroed}; +use std::path::Path; +use std::ptr::null; +use std::time::{Duration, Instant}; + +use windows_sys::Win32::Foundation::{ + CloseHandle, DBG_CONTINUE, DBG_EXCEPTION_NOT_HANDLED, HANDLE, +}; +use windows_sys::Win32::System::Diagnostics::Debug::{ + CREATE_PROCESS_DEBUG_EVENT, ContinueDebugEvent, DEBUG_EVENT, EXCEPTION_DEBUG_EVENT, + EXIT_PROCESS_DEBUG_EVENT, LOAD_DLL_DEBUG_EVENT, OUTPUT_DEBUG_STRING_EVENT, + OUTPUT_DEBUG_STRING_INFO, ReadProcessMemory, WaitForDebugEventEx, +}; +use windows_sys::Win32::System::Threading::{ + CreateProcessW, DEBUG_ONLY_THIS_PROCESS, DETACHED_PROCESS, PROCESS_INFORMATION, STARTUPINFOW, + TerminateProcess, +}; + +/// One string a debugged process sent. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DebugString { + /// Whether it arrived as Unicode, which is `OUTPUT_DEBUG_STRING_INFO`'s `fUnicode` being nonzero, and not as + /// ANSI. + pub unicode: bool, + /// Its units, as UTF-16 whichever way it arrived, up to its first NUL. + /// + /// For a string the event could not describe whole this is what the event described: the page for the structure + /// says its length field holds "the lower 16 bits of the length of the string in bytes". + pub units: Vec, +} + +impl DebugString { + /// The units as text, with any unit that is not valid UTF-16 replaced. + #[must_use] + pub fn text(&self) -> String { + String::from_utf16_lossy(&self.units) + } +} + +/// What a debugger saw of a process it ran. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Captured { + /// Each string the process sent, in the order it was received. + pub strings: Vec, + /// The code the process exited with, or `None` if it had not exited when the bound was reached, in which case it + /// was ended. + pub exit_code: Option, +} + +/// Runs `program` with `arguments` as a process being debugged, and returns what it sent to the debugger. +/// +/// The command line is `program` in quotes, then each argument after a space as given: an argument that needs +/// quoting is quoted by the caller. The process runs without a console, so what it prints reaches no one. It is +/// waited for up to `bound`; one that has not exited by then is ended, and [`Captured::exit_code`] is `None`. +/// +/// # Errors +/// +/// The operating system's, if the process could not be created, a debug event could not be continued, or a string +/// could not be read out of the process. A process that was started is ended and its handles closed first. +pub fn run_debugged(program: &Path, arguments: &[&str], bound: Duration) -> io::Result { + let mut command_line = format!("\"{}\"", program.display()); + for argument in arguments { + command_line.push(' '); + command_line.push_str(argument); + } + let mut command: Vec = command_line.encode_utf16().chain([0]).collect(); + + // SAFETY: both structures are plain data that the call reads or fills in, and all-zero is a valid value of each. + let mut startup: STARTUPINFOW = unsafe { zeroed() }; + startup.cb = size_of::() as u32; + // SAFETY: as above. + let mut process: PROCESS_INFORMATION = unsafe { zeroed() }; + // SAFETY: `command` is a writable, NUL-terminated wide string that outlives the call, as the page requires of + // the command line; the other pointers are null, which the page allows, or point at the structures above. + let created = unsafe { + CreateProcessW( + null(), + command.as_mut_ptr(), + null(), + null(), + 0, + DEBUG_ONLY_THIS_PROCESS | DETACHED_PROCESS, + null(), + null(), + &startup, + &mut process, + ) + }; + if created == 0 { + return Err(io::Error::last_os_error()); + } + + let captured = collect(&process, bound); + + // SAFETY: the handles are the ones the call returned and are closed once. A process still running, because the + // bound was reached or a call failed, is ended first. + unsafe { + if !matches!( + captured, + Ok(Captured { + exit_code: Some(_), + .. + }) + ) { + TerminateProcess(process.hProcess, 1); + } + CloseHandle(process.hThread); + CloseHandle(process.hProcess); + } + captured +} + +/// `EXCEPTION_BREAKPOINT`, the loader's first event in a debugged process. +const BREAKPOINT: i32 = 0x8000_0003_u32 as i32; + +/// The debugger's main loop: waits for each event, keeps the strings, and continues the process. +fn collect(process: &PROCESS_INFORMATION, bound: Duration) -> io::Result { + let started = Instant::now(); + let mut captured = Captured { + strings: Vec::new(), + exit_code: None, + }; + while started.elapsed() < bound && captured.exit_code.is_none() { + // SAFETY: all-zero is a valid `DEBUG_EVENT`, which the call fills in. + let mut event: DEBUG_EVENT = unsafe { zeroed() }; + // SAFETY: `event` is valid for the call to write. + if unsafe { WaitForDebugEventEx(&mut event, 1_000) } == 0 { + continue; + } + let mut status = DBG_CONTINUE; + // SAFETY: each union member read is the one the event code says the system filled in. + unsafe { + match event.dwDebugEventCode { + OUTPUT_DEBUG_STRING_EVENT => { + captured + .strings + .push(read_string(process.hProcess, &event.u.DebugString)?); + } + CREATE_PROCESS_DEBUG_EVENT => close_image(event.u.CreateProcessInfo.hFile), + LOAD_DLL_DEBUG_EVENT => close_image(event.u.LoadDll.hFile), + EXIT_PROCESS_DEBUG_EVENT => { + captured.exit_code = Some(event.u.ExitProcess.dwExitCode); + } + // The loader's breakpoint is continued; any other exception is the process's to handle. + EXCEPTION_DEBUG_EVENT + if event.u.Exception.ExceptionRecord.ExceptionCode != BREAKPOINT => + { + status = DBG_EXCEPTION_NOT_HANDLED; + } + _ => {} + } + } + // SAFETY: the ids are those of the event just received. + if unsafe { ContinueDebugEvent(event.dwProcessId, event.dwThreadId, status) } == 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(captured) +} + +/// Closes the image or DLL handle an event hands the debugger, as the page for the wait asks. +/// +/// # Safety +/// +/// `file` is the handle from the event, or null. +unsafe fn close_image(file: HANDLE) { + if !file.is_null() { + // SAFETY: the caller's: the debugger owns this handle and it is closed once. + unsafe { CloseHandle(file) }; + } +} + +/// Reads the string an output event describes out of the process, by `ReadProcessMemory`, which the page for the +/// structure says is how. +fn read_string(process: HANDLE, info: &OUTPUT_DEBUG_STRING_INFO) -> io::Result { + let unicode = info.fUnicode != 0; + // The field holds "the lower 16 bits of the length of the string in bytes". + let bytes = usize::from(info.nDebugStringLength); + let mut buffer = vec![0_u8; bytes]; + let mut read = 0_usize; + // SAFETY: `buffer` is valid for `bytes` writes, and `read` for one. + let ok = unsafe { + ReadProcessMemory( + process, + info.lpDebugStringData as *const c_void, + buffer.as_mut_ptr().cast(), + bytes, + &mut read, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + buffer.truncate(read); + let mut units: Vec = if unicode { + buffer + .as_chunks::<2>() + .0 + .iter() + .map(|pair| u16::from_le_bytes(*pair)) + .collect() + } else { + buffer.iter().map(|&byte| u16::from(byte)).collect() + }; + // The length counts the terminator the call sends; what is kept is the string before it. + if let Some(end) = units.iter().position(|&unit| unit == 0) { + units.truncate(end); + } + Ok(DebugString { unicode, units }) +} diff --git a/crates/win-debugging-sys/src/lib.rs b/crates/win-debugging-sys/src/lib.rs new file mode 100644 index 000000000..6e711f27d --- /dev/null +++ b/crates/win-debugging-sys/src/lib.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Mike Grier +#![doc = include_str!("../README.md")] +#![cfg(windows)] +#![deny(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] + +#[cfg(feature = "test-util")] +mod capture; +mod output; + +#[cfg(feature = "test-util")] +pub use capture::{Captured, DebugString, run_debugged}; +pub use output::{OutputDebugStringError, output_debug_str, output_debug_string}; diff --git a/crates/win-debugging-sys/src/output.rs b/crates/win-debugging-sys/src/output.rs new file mode 100644 index 000000000..e43d02671 --- /dev/null +++ b/crates/win-debugging-sys/src/output.rs @@ -0,0 +1,104 @@ +// Copyright (c) 2026 Mike Grier +//! `OutputDebugStringW`: send a string to the debugger. +//! +//! Read 2026-10-20 from the call's page (`ms.date` 2024-02-02, `updated_at` 2025-08-27). The call takes one +//! argument, "The null-terminated string to be displayed", returns nothing, and has no error to report: the +//! page's return value is "None", and its remarks say that with no debugger and no active system debugger the +//! call "does nothing". So the call cannot fail, and this crate's one error is its own refusal, made before the +//! call ([`OutputDebugStringError::EmbeddedNul`]). + +use std::error::Error; +use std::fmt; + +use windows_sys::Win32::System::Diagnostics::Debug::OutputDebugStringW; + +#[cfg(test)] +mod tests; + +/// Why a string was not sent to the debugger. +/// +/// The call was not made. This is the crate's refusal and not the call's: the call reports no failure. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum OutputDebugStringError { + /// The string holds a NUL, and the call's string is "null-terminated", so the call would take the string + /// to end there and display only what comes before it. + /// + /// Sending the part before the NUL, or leaving the rest out, would be this crate choosing what a caller + /// meant, so the string is not sent. + EmbeddedNul { + /// The place of the first NUL, in UTF-16 code units from the start of the string, which for text + /// given as a `str` is not its byte or character offset. + index: usize, + }, +} + +impl fmt::Display for OutputDebugStringError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EmbeddedNul { index } => write!( + f, + "the string has a NUL at unit {index}, and OutputDebugStringW's string ends at its first NUL" + ), + } + } +} + +impl Error for OutputDebugStringError {} + +/// Sends a string of UTF-16 code units to the debugger, by `OutputDebugStringW`. +/// +/// The units are sent as they are: an empty slice sends the empty string, and an unpaired surrogate is a unit +/// like any other, which the call may display in whatever way the debugger does. The call takes a +/// NUL-terminated string, so a copy of the units with the terminator added is what it is given, and a slice +/// with a NUL in it is refused. +/// +/// The call converts the string "based on the current system locale information and passes it to +/// **OutputDebugStringA** to be displayed", so by the page "some Unicode characters may not be displayed +/// correctly". Whether anything is displayed depends on a debugger being attached, or on the system debugger +/// being active and its filter allowing the string, and the call gives no way to tell. +/// +/// # Errors +/// +/// [`OutputDebugStringError::EmbeddedNul`] if `text` holds a NUL. The call is not made. +pub fn output_debug_string(text: &[u16]) -> Result<(), OutputDebugStringError> { + let mut units = Vec::with_capacity(text.len() + 1); + units.extend_from_slice(text); + send(units) +} + +/// Sends a `str` to the debugger, by `OutputDebugStringW`, as the UTF-16 units it encodes to. +/// +/// As [`output_debug_string`], for text that is valid Unicode. A `str` cannot hold an unpaired surrogate, so +/// a caller that has one in a UTF-16 string uses that function. +/// +/// # Errors +/// +/// [`OutputDebugStringError::EmbeddedNul`] if `text` holds a NUL, with the index in UTF-16 code units. The +/// call is not made. +pub fn output_debug_str(text: &str) -> Result<(), OutputDebugStringError> { + let mut units: Vec = Vec::with_capacity(text.len() + 1); + units.extend(text.encode_utf16()); + send(units) +} + +/// Refuses units with a NUL, and sends the rest with the terminator the call needs. +fn send(mut units: Vec) -> Result<(), OutputDebugStringError> { + terminate(&mut units)?; + // SAFETY: `units` is a buffer of `u16` that ends in a NUL and has none before it, so the pointer is valid + // for reads up to and including that NUL, which is as far as the call reads. It is not used after the call. + unsafe { OutputDebugStringW(units.as_ptr()) }; + Ok(()) +} + +/// Adds the terminator the call needs to `units`, or refuses units that have a NUL already. +fn terminate(units: &mut Vec) -> Result<(), OutputDebugStringError> { + if let Some(index) = units.iter().position(|&unit| unit == NUL) { + return Err(OutputDebugStringError::EmbeddedNul { index }); + } + units.push(NUL); + Ok(()) +} + +/// The unit that ends the string the call takes. +const NUL: u16 = 0; diff --git a/crates/win-debugging-sys/src/output/tests.rs b/crates/win-debugging-sys/src/output/tests.rs new file mode 100644 index 000000000..156efdf3d --- /dev/null +++ b/crates/win-debugging-sys/src/output/tests.rs @@ -0,0 +1,173 @@ +// Copyright (c) 2026 Mike Grier +//! What is built from a string before the call, apart from the call (WD-D-2), and that each function +//! reaches it. What a debugger receives is `tests/debugger.rs`. + +use super::{NUL, OutputDebugStringError, output_debug_str, output_debug_string, terminate}; + +fn units(text: &str) -> Vec { + text.encode_utf16().collect() +} + +fn terminated(mut input: Vec) -> Result, OutputDebugStringError> { + terminate(&mut input)?; + Ok(input) +} + +#[test] +fn an_empty_string_is_just_the_terminator() { + assert_eq!(terminated(Vec::new()), Ok(vec![NUL])); +} + +#[test] +fn an_ordinary_string_keeps_its_units_and_gains_the_terminator() { + let mut expected = units("hello"); + expected.push(NUL); + assert_eq!(terminated(units("hello")), Ok(expected)); +} + +#[test] +fn a_one_unit_string_is_two_units() { + assert_eq!(terminated(vec![0x61]), Ok(vec![0x61, NUL])); +} + +#[test] +fn spaces_punctuation_and_line_breaks_are_not_terminators() { + let text = "a b,c;d\r\ne\tf\u{7f}"; + let mut expected = units(text); + expected.push(NUL); + assert_eq!(terminated(units(text)), Ok(expected)); +} + +#[test] +fn a_string_outside_the_basic_plane_is_its_surrogate_pairs() { + let mut expected = units("a\u{1f600}b"); + assert_eq!(expected.len(), 4); + expected.push(NUL); + assert_eq!(terminated(units("a\u{1f600}b")), Ok(expected)); +} + +#[test] +fn an_unpaired_surrogate_is_a_unit_like_any_other() { + assert_eq!( + terminated(vec![0xd800, 0x61, 0xdc00]), + Ok(vec![0xd800, 0x61, 0xdc00, NUL]) + ); +} + +#[test] +fn a_unit_with_only_the_low_byte_clear_is_not_a_terminator() { + assert_eq!(terminated(vec![0x0100]), Ok(vec![0x0100, NUL])); +} + +#[test] +fn a_unit_with_only_the_high_byte_clear_is_not_a_terminator() { + assert_eq!(terminated(vec![0x0001]), Ok(vec![0x0001, NUL])); +} + +#[test] +fn a_nul_alone_is_refused_at_unit_zero() { + assert_eq!( + terminated(vec![NUL]), + Err(OutputDebugStringError::EmbeddedNul { index: 0 }) + ); +} + +#[test] +fn a_nul_in_the_middle_is_refused_at_its_unit() { + assert_eq!( + terminated(vec![0x61, 0x62, NUL, 0x63]), + Err(OutputDebugStringError::EmbeddedNul { index: 2 }) + ); +} + +#[test] +fn a_nul_at_the_end_is_refused_though_it_would_have_been_a_terminator() { + assert_eq!( + terminated(vec![0x61, NUL]), + Err(OutputDebugStringError::EmbeddedNul { index: 1 }) + ); +} + +#[test] +fn the_first_of_several_nuls_is_the_one_named() { + assert_eq!( + terminated(vec![0x61, NUL, 0x62, NUL, NUL]), + Err(OutputDebugStringError::EmbeddedNul { index: 1 }) + ); +} + +#[test] +fn a_refused_string_is_left_as_it_was_given() { + let mut input = vec![0x61, NUL, 0x62]; + assert!(terminate(&mut input).is_err()); + assert_eq!(input, [0x61, NUL, 0x62]); +} + +#[test] +fn a_long_string_is_every_unit_it_was_given_and_one_more() { + let input: Vec = (0..1_048_576_u32) + .map(|n| (n % 0xfffe) as u16 + 1) + .collect(); + let out = terminated(input.clone()).expect("no unit is a NUL"); + assert_eq!(out.len(), input.len() + 1); + assert_eq!(&out[..input.len()], &input[..]); + assert_eq!(out[input.len()], NUL); +} + +#[test] +fn a_nul_at_the_very_end_of_a_long_string_is_found() { + let mut input = vec![0x61_u16; 1_048_576]; + input.push(NUL); + assert_eq!( + terminated(input), + Err(OutputDebugStringError::EmbeddedNul { index: 1_048_576 }) + ); +} + +#[test] +fn a_str_with_a_nul_is_refused_at_the_unit_not_the_byte() { + // The first character is two units, and three bytes in UTF-8, before the NUL. + assert_eq!( + output_debug_str("\u{1f600}\u{e9}\0z"), + Err(OutputDebugStringError::EmbeddedNul { index: 3 }) + ); +} + +#[test] +fn a_slice_with_a_nul_is_refused_and_the_call_is_not_made() { + assert_eq!( + output_debug_string(&[0x61, NUL]), + Err(OutputDebugStringError::EmbeddedNul { index: 1 }) + ); +} + +#[test] +fn the_empty_string_and_ordinary_text_are_sent_with_no_failure() { + // With no debugger the call "does nothing", so this shows the call is made and returns, and that + // neither function refuses what it should not. + assert_eq!(output_debug_string(&[]), Ok(())); + assert_eq!(output_debug_str(""), Ok(())); + assert_eq!(output_debug_str("win-debugging-sys: a unit test"), Ok(())); + assert_eq!(output_debug_string(&units("\u{3042}\u{1f600}")), Ok(())); + assert_eq!(output_debug_string(&[0xd800]), Ok(())); +} + +#[test] +fn a_very_long_string_is_sent_with_no_failure() { + assert_eq!(output_debug_str(&"x".repeat(300_000)), Ok(())); +} + +#[test] +fn the_refusal_reads_as_a_sentence_naming_the_unit() { + let error = OutputDebugStringError::EmbeddedNul { index: 7 }; + assert_eq!( + error.to_string(), + "the string has a NUL at unit 7, and OutputDebugStringW's string ends at its first NUL" + ); +} + +#[test] +fn the_refusal_is_an_error_with_no_source() { + let error = OutputDebugStringError::EmbeddedNul { index: 0 }; + assert!(std::error::Error::source(&error).is_none()); +} diff --git a/crates/win-debugging-sys/tests/debugger.rs b/crates/win-debugging-sys/tests/debugger.rs new file mode 100644 index 000000000..268b8ebe9 --- /dev/null +++ b/crates/win-debugging-sys/tests/debugger.rs @@ -0,0 +1,120 @@ +// Copyright (c) 2026 Mike Grier +//! What a debugger receives (WD-D-2): this test binary starts itself as a process being debugged, runs +//! `a_process_that_sends_strings` in it, and keeps each string the process sends, through the crate's `test-util` +//! debugger (WD-D-3), which uses only documented debugger functions. +//! +//! This is evidence of what was observed, written as a test so that it is observed again: the crate's +//! contract is the call's page, which says the string is displayed "by the debugger", not how it arrives. + +#![cfg(windows)] + +use std::time::Duration; + +use win_debugging_sys::{Captured, output_debug_str, output_debug_string, run_debugged}; + +/// The name of the test the process being debugged runs. +const SENDER: &str = "a_process_that_sends_strings"; + +/// How long the whole exchange may take. Generous: the process starts, sends a handful of strings, and exits. +const BOUND: Duration = Duration::from_secs(30); + +/// The length of the string that is too long for the debugger's event to hold whole. +const LONG: usize = 100_000; + +/// What the process sends, in order, before the long string. +fn cases() -> Vec> { + let units = |text: &str| text.encode_utf16().collect::>(); + vec![ + units("win-debugging-sys: one"), + Vec::new(), + units("two words, and punctuation; \u{e9}"), + units("\u{3042}\u{1f600}"), + vec![0xd800, 0x61], + ] +} + +/// The string the process sends last, too long for the event to describe whole. +fn long() -> Vec { + vec![u16::from(b'L'); LONG] +} + +/// Run in the process being debugged: sends the strings. Run on its own, with no debugger, the call does +/// nothing, so this is harmless in an ordinary run of the tests. +#[test] +fn a_process_that_sends_strings() { + for case in cases() { + output_debug_string(&case).expect("no NUL"); + } + // Refused, so nothing of it is sent, not even what comes before the NUL. + assert!(output_debug_str("cut\0short").is_err()); + assert!(output_debug_string(&[0x61, 0, 0x62]).is_err()); + output_debug_string(&long()).expect("no NUL"); +} + +/// Starts this test binary running `SENDER` as a process being debugged, and keeps what it sends. +fn debug_the_sender() -> Captured { + let exe = std::env::current_exe().expect("the test binary's path"); + run_debugged(&exe, &["--exact", SENDER, "--test-threads=1"], BOUND) + .expect("the process was debugged") +} + +#[test] +fn a_debugger_receives_each_string_the_process_sends_and_none_that_was_refused() { + let observed = debug_the_sender(); + assert_eq!(observed.exit_code, Some(0), "the sender did not pass"); + let expected = cases(); + assert_eq!( + observed.strings.len(), + expected.len() + 1, + "one event for each string sent, and none for the two refused" + ); + // Observed: a debugger that waits with `WaitForDebugEventEx` is sent each string as Unicode, as the page + // says it will be, with every unit as sent, an unpaired surrogate included. + for (received, sent) in observed.strings.iter().zip(&expected) { + assert!(received.unicode, "{sent:?} arrived as ANSI"); + assert_eq!(&received.units, sent); + } +} + +#[test] +fn a_string_too_long_for_the_event_arrives_cut_at_the_length_the_page_says() { + let observed = debug_the_sender(); + let received = observed.strings.last().expect("the long string's event"); + assert!(received.unicode); + // The page for the structure: the length field holds "the lower 16 bits of the length of the string in + // bytes", and the string's length counts the terminator. + let sent_bytes = (long().len() + 1) * size_of::(); + let kept_bytes = sent_bytes % (1 << 16); + assert_eq!(received.units.len() * size_of::(), kept_bytes); + assert!(received.units.iter().all(|&unit| unit == u16::from(b'L'))); +} + +#[test] +fn a_program_that_is_not_there_is_an_error_and_not_a_capture() { + let missing = std::path::Path::new("Q:\\no\\such\\program.exe"); + assert!(run_debugged(missing, &[], Duration::from_secs(1)).is_err()); +} + +#[test] +fn a_process_that_outlives_the_bound_is_ended_and_has_no_exit_code() { + let exe = std::env::current_exe().expect("the test binary's path"); + let observed = run_debugged( + &exe, + &[ + "--exact", + "a_process_that_waits", + "--ignored", + "--test-threads=1", + ], + Duration::from_millis(1_500), + ) + .expect("the process was debugged"); + assert_eq!(observed.exit_code, None); +} + +/// Run in the process being debugged, only by the test above: it outlives that test's bound. +#[test] +#[ignore = "run by a_process_that_outlives_the_bound_is_ended_and_has_no_exit_code, in a process of its own"] +fn a_process_that_waits() { + std::thread::sleep(Duration::from_secs(60)); +} diff --git a/crates/win-durable-ioring/Cargo.toml b/crates/win-durable-ioring/Cargo.toml index b447d7697..e544078fc 100644 --- a/crates/win-durable-ioring/Cargo.toml +++ b/crates/win-durable-ioring/Cargo.toml @@ -40,14 +40,34 @@ win-volume-sys = { version = "0.1.1", path = "../win-volume-sys" } windows-namespace-request-sys = { version = "0.5.0", path = "../windows-namespace-request-sys" } # The path type that crate takes, and the flags and access constants an open is made from. wtf-string = { version = "0.1.0", path = "../wtf-string" } +# The security descriptor a created file is given (DI-14.3), taken from the crate Microsoft Learn +# files it under, and read back from an open handle by the tests (DI-14.8.5). It was the request +# crate's until DI-14.8.1 moved it. +win-authorization-sys = { version = "0.1.0", path = "../win-authorization-sys" } +# `DuplicateHandle`, which the readiness event's wait-only copy is made with (DI-7.5), taken from +# the crate Microsoft Learn files it under, and the flags of a handle, which the tests read back +# (DI-14.8.5). It was `win-sync-sys`'s until DI-14.8.3 moved it. +win-handles-sys = { version = "0.1.0", path = "../win-handles-sys" } # The rule that wraps a Win32 code as an `HRESULT` and reads one back out (DI-9.7.11): this crate # owns the numbering of the codes it reports, and that crate owns the rule. win-error-codes = { version = "0.0.2", path = "../win-error-codes" } +# The debugger sink's call (DI-12.1.3): `OutputDebugStringW`, made safe. The crate forbids `unsafe`, so it cannot +# make the call itself. +win-debugging-sys = { version = "0.0.1", path = "../win-debugging-sys" } + +# The default diagnostic sink (DI-12.1.6): the ETW provider for dioring's begin and end events, which an instance built with no sink +# of its own writes to. A crate of its own, so that this one needs no unsafe code to register a provider. +durable-ioring-events = { version = "0.0.1", path = "../durable-ioring-events" } + [dependencies.windows-sys] version = "0.61.2" default-features = false -features = ["Win32_Foundation", "Win32_Storage_FileSystem"] +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_SystemServices", +] [features] # The fault seam (DI-3.2.4): `Dioring::fail_next_flush`, failures on demand for tests. Off by @@ -57,6 +77,13 @@ features = ["Win32_Foundation", "Win32_Storage_FileSystem"] fault-injection = ["windows-ioring-sys/fault-injection"] [dev-dependencies] +# A trace session, for the test that shows the default sink's events reach ETW (DI-12.1.6). It needs privilege, so that test is +# ignored by default. A dev-dependency's features reach test builds only. +win-etw-sys = { path = "../win-etw-sys", features = ["test-util"] } +# A debugger that runs a program and keeps the strings it sends, which the debugger sink's tests observe it with +# (DI-12.1.3). The debugging crate's own `test-util` feature: this crate forbids `unsafe`, so the debugger is +# written there. A dev-dependency's features reach test builds only. +win-debugging-sys = { path = "../win-debugging-sys", features = ["test-util"] } # This crate's own tests, and the worked examples in README.md -- compiled as doctests -- drive # failures through the seam, so every test build enables it: a feature gate a plain `cargo test` # skips would leave those examples unrun. @@ -69,6 +96,11 @@ windows-ioring-sys = { path = "../windows-ioring-sys", features = ["kernel-seam" # Aligned buffers, for the tests that write to an unbuffered file, whose buffers must start on a # sector (DI-D-60). io-buffers = { version = "0.1.0", path = "../io-buffers" } +# A directory listing's identities, which the open by identifier is tested with (DI-15.2): the record +# `FileIdExtdDirectoryInfo` fills, read by the crate that enumerates with it. Path-only, with no `version`: Cargo checks a pinned +# version against the path at every build, so a release of that crate would break this one, and a +# versionless dev-dependency is left out of the published manifest. +windows-file-enumeration-sys = { path = "../windows-file-enumeration-sys" } # A blocking push, built on the public API as a helper to use or adapt (DI-7.4, DI-D-56). Its own # tests run with the crate's: without `test = true` cargo builds an example and never runs them. diff --git a/crates/win-durable-ioring/README.md b/crates/win-durable-ioring/README.md index 1705c4694..38d320bc7 100644 --- a/crates/win-durable-ioring/README.md +++ b/crates/win-durable-ioring/README.md @@ -30,11 +30,85 @@ A blocking push is not an API of either crate: a refused push names its cause, a waits it out is a maintained example, [examples/blocking_push/](examples/blocking_push/main.rs), to use as it stands or adapt ([DI-D-56](../durable-ioring/DESIGN-NOTES.md#di-d-56)). +## Diagnostics + +An instance reports what it waits on as pairs of events, a begin and an end sharing an id -- a seal, from its being asked +for to its answer; each flush of a file or a domain; and a wait for one of its locks -- and its inconsistencies, to a +`DiagnosticSink` given in `Setup::diagnostics`. How long each took is the gap between its begin and its end, which the reader +measures; the instance times nothing. **Given none, it writes them to ETW**, to the provider +`durable-ioring-events` ([durable-ioring-events](../durable-ioring-events/README.md)), through `EtwDiagnostics`. That costs a +test of whether a session is listening when none is, and no event is built; the first such instance registers the provider +for the process, and the last gives it back. Any other sink is given in its place: `NoDiagnostics` for none, `StderrDiagnostics` +or `DebugOutputDiagnostics` for text, or `FanOutDiagnostics` to send events to several. A reader of the trace needs the +provider's manifest to see names and fields; the instance does not. + `DeliveryMode::Pool`, `DeliveryMode::Signalled` and `EntryDelivery::new` take `windows-threadpool-sys`'s `CallbackEnviron`, so that crate's types are part of this crate's public API. A minor release of `windows-threadpool-sys` that changes those types is a minor release of this crate too, in the 0.x series: 0.3 of this crate pairs with 0.3 of `windows-threadpool-sys`. +## Several durability providers + +An instance holds the built-in provider, which flushes each file through its own ring, and at most one more, given in +`Setup::provider`: a `DurabilityProvider` serving the flush domains it declares, which answers for those domains of each +seal. **Several are composed by `RoutingProvider`**, itself a provider. Each provider registered with it serves exactly the +domains its own `domains()` returns, read once when the router is built; a domain claimed twice refuses the build, naming +both registrations. A seal's work is split by domain, and each provider with a part is called once, in registration order; +its answers go straight back to the instance. A file declared with domains of two providers is in both calls and is durable +when both have succeeded; a domain no provider serves stays the built-in provider's. The rules are in +[API.md](../durable-ioring/API.md) section 15 and [DI-D-81](../durable-ioring/DESIGN-NOTES.md#di-d-81) to +[DI-D-83](../durable-ioring/DESIGN-NOTES.md#di-d-83). + +```rust +use win_durable_ioring::*; + +/// A provider for one device, serving the domains it was built with. A real one flushes the +/// files each part names, by its own means, before it answers. +#[derive(Debug)] +struct Device(Vec); + +impl DurabilityProvider> for Device { + fn domains(&self) -> Vec { + self.0.clone() + } + + fn make_durable(&mut self, request: FlushRequest>) { + for part in request.domains { + part.completion.succeed(); + } + } +} + +let log = FlushDomain::new(b"log-disk".to_vec()); +let data = FlushDomain::new(b"data-disk".to_vec()); +let router = RoutingProvider::builder() + .register(Device(vec![log.clone()])) + .register(Device(vec![data.clone()])) + .build()?; +assert_eq!(router.domains(), [log.clone(), data]); + +let (ring, _default) = Dioring::>::new(Setup { + submission_queue_size: 16, + completion_queue_size: 16, + files: Vec::new(), + buffers: Vec::new(), + provider: Some(Box::new(router)), + diagnostics: None, + delivery: DeliveryMode::Pool(None), +}) +.expect("an instance"); +drop(ring); + +// The same domain from two providers refuses the router, naming both registrations. +let refused = RoutingProvider::>::builder() + .register(Device(vec![log.clone()])) + .register(Device(vec![log.clone()])) + .build() + .expect_err("the log twice"); +assert_eq!((refused.domain, refused.first, refused.second), (log, 0, 1)); +# Ok::<(), Box>(()) +``` + ## Opening a file A file target is made from a file **this crate opened**, and from nothing else @@ -48,27 +122,178 @@ write-through. A consumer that needs the stronger warranty asks for `OpenedFile< own signature, and a cached file cannot be passed. A handle opened elsewhere cannot be given to a target in this version. +The data access of a handle is its type. `FileOptions::new()` asks for none, `read()` and `write()` add +the right to read and to write, and the file an open makes is an `OpenedFile` whose `A` is +`NoData`, `ReadOnly`, `WriteOnly` or `ReadWrite`. A target needs a data right, so a file opened with +`NoData` cannot be made into one, and the program does not compile. `OpenedDisk` and `OpenedVolume` carry +the same parameter, so either can be opened for reading only. The other rights an open can ask for, the +attribute, delete and security rights, are a `FileRights` set given to `FileOptions::rights`; they gate +calls a consumer makes on the handle it is lent and not anything this crate does. There is no +constructor from a raw mask and no right for appending only, which would land every write at the end +of the file whatever offset the core names, so it cannot be asked for. What the file's security +allows is the kernel's to say. + +`FileOptions::share` takes a `ShareMode`, a set of `READ`, `WRITE` and `DELETE` naming what other opens of +the file may request while this one is open; a file is opened sharing all three unless it is told +otherwise, and `ShareMode::NONE` is an exclusive open. An open that the sharing of either side does not +allow fails as a `SharingViolation`, an `io::ErrorKind::ResourceBusy` error whose cause carries what this +open asked for. `CreateFileW` reports both sides' conflicts as one error and no documented call reports +what the other open holds, so it cannot say which side conflicted. The kernel's own error is its source. + +`FileOptions::random_access` and `FileOptions::sequential_scan` are the two cache hints +(`FILE_FLAG_RANDOM_ACCESS` and `FILE_FLAG_SEQUENTIAL_SCAN`). They are passed to an unbuffered file as +well as a cached one, never refused, and change nothing a target claims: the class, the alignment and +what it offers follow from the buffering mode alone. What the `CreateFileW` documentation says about them +on an unbuffered file can be read two ways, and the methods' own documentation says what was measured. + +`FileOptions::overlapped` sets `FILE_FLAG_OVERLAPPED`. The ring takes such a handle and gives the result +for each operation that it gives without the flag; what the flag changes is when a completion is there and, +for an unbuffered file, the order a batch's completions come in. The handle the open lends is the same +overlapped handle, so I/O a caller makes on it directly, without an offset, is I/O on a handle for which +`CreateFileW` says the file pointer is not maintained. + +The path is a `Wtf16String`, handed to Win32 as the caller gave it: nothing lengthens, shortens or +refuses it, so a path past `MAX_PATH` without a `\\?\` prefix, and an empty one, are what `CreateFileW` +makes of them. Convert a `Path` with `Wtf16String::from_os_str`, which loses nothing. + +`FileOptions::disposition` takes a `Disposition`: `CreateNew`, `OpenExisting` (where the options start) or +`OpenAlways`, named as `CreateFileW` names them. `CREATE_ALWAYS` and `TRUNCATE_EXISTING`, the two that +empty a file that exists, are not offered in this version; a caller empties a file through the handle the +open lends, with `File::set_len(0)`. `FileOptions::open_reparse_point` sets `FILE_FLAG_OPEN_REPARSE_POINT`, +which no disposition implies: without it a link is followed, including by `CreateNew`. + +`OpenedFile::open` blocks for as long as `CreateFileW` does. `OpenedFile::request` builds an `OpenRequest` +and opens nothing, fixing a relative path to the current directory as it is then, and +`OpenRequest::perform` makes the call on whatever thread the caller chooses. A request is `Send`, and +performing it again opens the file again. The refusal of an unbuffered file on a remote device is made in +`perform`, on the opened handle from its final path, so a file the open created is still there when it is +refused. + +A file the open creates is given `FileOptions::attributes`, a `FileAttributes` set of `ARCHIVE`, `HIDDEN`, +`OFFLINE`, `READONLY`, `SYSTEM` and `TEMPORARY` (`FILE_ATTRIBUTE_NORMAL`, the absence of attributes, is the +empty set, and `FILE_ATTRIBUTE_ENCRYPTED` is not offered), and `OpenRequest::with_security_descriptor`, a +`SecurityDescriptor` built from SDDL, which the request owns and frees. The descriptor is on the request and +not in the options because it owns memory and cannot be copied, as the options can. Neither applies to a file +that already exists. Handles are never inheritable, and there is no option to make one so. + ```rust use win_durable_ioring::windows_backend::{ - Cached, FileOptions, OpenedFile, Unbuffered, Win32FileTarget, + Cached, Disposition, FileAttributes, FileOptions, OpenedFile, SecurityDescriptor, }; -use win_durable_ioring::{IoTarget, WarrantyClass}; +use win_durable_ioring::Wtf16String; + +let path = std::env::temp_dir().join(format!("durable-ioring-create-{}", std::process::id())); +let options = FileOptions::new() + .read() + .write() + .disposition(Disposition::CreateNew) + .attributes(FileAttributes::HIDDEN); +let descriptor = SecurityDescriptor::from_sddl(&Wtf16String::from("D:P(A;;GA;;;WD)"))?; + +let created = OpenedFile::::request(Wtf16String::from_os_str(path.as_os_str()), options)? + .with_security_descriptor(descriptor) + .perform()?; +drop(created); +# std::fs::remove_file(&path)?; +# Ok::<(), Box>(()) +``` + +```rust +use win_durable_ioring::windows_backend::{ + Cached, Disposition, FileOptions, OpenedFile, Unbuffered, Win32FileTarget, +}; +use win_durable_ioring::{IoTarget, WarrantyClass, Wtf16String}; let path = std::env::temp_dir().join(format!("durable-ioring-open-{}", std::process::id())); std::fs::write(&path, [0u8; 8192])?; -let options = FileOptions::new().read(true).write(true); +let options = FileOptions::new().read().write(); +// The open takes the path as the caller gave it, a `Wtf16String`: converting loses nothing. +let name = || Wtf16String::from_os_str(path.as_os_str()); -let cached = Win32FileTarget::new(OpenedFile::::open(&path, options)?); +let cached = Win32FileTarget::new(OpenedFile::::open(name(), options)?); assert_eq!(cached.warranty(), WarrantyClass::BestEffort); -let unbuffered = Win32FileTarget::new(OpenedFile::::open(&path, options)?); +let unbuffered = Win32FileTarget::new(OpenedFile::::open(name(), options)?); assert_eq!(unbuffered.warranty(), WarrantyClass::SharedDevice); assert!(unbuffered.capabilities().write_through()); + +// Built here, and made on the thread that may block. +let request = OpenedFile::::request(name(), options.disposition(Disposition::OpenAlways))?; +let opened = std::thread::spawn(move || request.perform()).join().expect("the thread")?; +drop(opened); # std::fs::remove_file(&path)?; # Ok::<(), std::io::Error>(()) ``` -An unbuffered file is refused on a remote device, where the claim cannot hold. +An unbuffered file is refused on a remote device, where the claim cannot hold. The handle is asked where it +is after the open, so a link is judged by the handle it opens: followed, the file it leads to; opened with +`open_reparse_point`, the link itself. + +### By identity + +A file has an identity, `FileId`: its volume's serial number and a 128-bit identifier, read from any handle to +it. `OpenedFile::open_by_id` opens it again by that, or by a 64-bit file ID or an object ID, on the volume of a +hint, which is any handle on that volume ([DI-D-70](../durable-ioring/DESIGN-NOTES.md#di-d-70)). Its options are +`ByIdOptions`, since `OpenFileById` takes no disposition and no attributes. The call never sees a `FileId`'s +volume, so the file it opens is asked its identity, and one from another volume is refused as an +`IdentityMismatch`. An identifier can name a directory, which opens, and a target made from it faults every read +and write ([DI-D-72](../durable-ioring/DESIGN-NOTES.md#di-d-72)). + +```rust +use win_durable_ioring::windows_backend::{ByIdOptions, Cached, FileOptions, OpenedFile}; +use win_durable_ioring::Wtf16String; + +let path = std::env::temp_dir().join(format!("durable-ioring-by-id-{}", std::process::id())); +std::fs::write(&path, b"by identity")?; +let file = OpenedFile::::open( + Wtf16String::from_os_str(path.as_os_str()), + FileOptions::new().read(), +)?; +let id = file.file_id()?; + +// Any handle on the volume is a hint: here, another file's, opened by std. +let neighbour = path.with_extension("neighbour"); +std::fs::write(&neighbour, b"")?; +let hint = std::fs::File::open(&neighbour)?; +let again = OpenedFile::::open_by_id(hint, id, ByIdOptions::new().read())?; +assert_eq!(again.file_id()?, id); +# drop((file, again)); +# std::fs::remove_file(&path)?; +# std::fs::remove_file(&neighbour)?; +# Ok::<(), std::io::Error>(()) +``` + +### Reopening + +`OpenedFile::reopen` makes a new handle to what a handle already names, through `ReOpenFile`, in the buffering +mode and with the access its type asks for ([DI-D-71](../durable-ioring/DESIGN-NOTES.md#di-d-71)): cached to +unbuffered and back, or a read-only handle for a scrubber beside the writer. The original is any handle, this +crate's or one opened elsewhere, and the result is judged by the same checks as an open by path. Its options are +`ReopenOptions`. The reopen's sharing is judged against every handle still open to the file, the original among +them, so a reopen of a file its original opened without sharing is refused as a `SharingViolation`. + +```rust +use win_durable_ioring::windows_backend::{ + Cached, FileOptions, OpenedFile, ReadOnly, ReopenOptions, Unbuffered, +}; +use win_durable_ioring::Wtf16String; + +let path = std::env::temp_dir().join(format!("durable-ioring-reopen-{}", std::process::id())); +std::fs::write(&path, [0u8; 4096])?; +let writer = OpenedFile::::open( + Wtf16String::from_os_str(path.as_os_str()), + FileOptions::new().read().write(), +)?; + +let scrubber = OpenedFile::::reopen( + writer.handle().clone(), + ReopenOptions::new().read(), +)?; +assert_eq!(scrubber.file_id()?, writer.file_id()?); +# drop((writer, scrubber)); +# std::fs::remove_file(&path)?; +# Ok::<(), std::io::Error>(()) +``` ### A raw volume @@ -86,11 +311,13 @@ file system on it needs and which fails if anything else has the volume open. ```rust,no_run use win_durable_ioring::windows_backend::{ - OpenedVolume, Unbuffered, VolumeOptions, VolumePath, Win32FileTarget, + OpenedVolume, ReadWrite, Unbuffered, VolumeOptions, VolumePath, Win32FileTarget, }; let path = VolumePath::drive('E')?; -let volume = OpenedVolume::::open(&path, VolumeOptions::new().lock(true))?; +// The data access is named in the type: a volume opened for reading only is `ReadOnly`. +let volume = + OpenedVolume::::open(&path, VolumeOptions::new().lock(true))?; let target = Win32FileTarget::from_volume(volume); # Ok::<(), Box>(()) ``` @@ -103,12 +330,18 @@ let target = Win32FileTarget::from_volume(volume); which a device control reports. It claims `SharedDevice` when opened unbuffered and `BestEffort` when opened cached, never `Complete`, for the reason a volume does not. Windows documents that it blocks a direct write to a sector a mounted volume covers, so a disk with volumes on it is written through them, or has them locked or is taken offline first; the -tests here use a disk with no partition. Opening one for writing needs an administrator. +tests here use a disk with no partition. Opening one for writing needs an administrator. A disk and a +volume are opened sharing reading, writing and deletion unless `DiskOptions::share` or +`VolumeOptions::share` takes a `ShareMode` otherwise, and a conflict is the same `SharingViolation` a file +reports. ```rust,no_run -use win_durable_ioring::windows_backend::{DiskPath, OpenedDisk, Unbuffered, Win32FileTarget}; +use win_durable_ioring::windows_backend::{ + DiskOptions, DiskPath, OpenedDisk, ReadOnly, Unbuffered, Win32FileTarget, +}; -let disk = OpenedDisk::::open(DiskPath::number(3))?; +// The data access is named in the type: this one is opened for reading only. +let disk = OpenedDisk::::open(DiskPath::number(3), DiskOptions::new())?; let target = Win32FileTarget::from_disk(disk); # Ok::<(), std::io::Error>(()) ``` @@ -147,16 +380,16 @@ flush of a file report the error given whatever the device answered. Setting up # let path = std::env::temp_dir().join(format!("durable-ioring-heal-{}", std::process::id())); # std::fs::write(&path, [0u8; 64])?; # let file = windows_backend::OpenedFile::::open( -# &path, -# windows_backend::FileOptions::new().read(true).write(true), +# win_durable_ioring::Wtf16String::from_os_str(path.as_os_str()), +# windows_backend::FileOptions::new().read().write(), # )?; # let (mut ring, lineage) = Ring::new(Setup { # submission_queue_size: 64, # completion_queue_size: 64, -# files: vec![TargetSetup { -# key: FILE, -# target: windows_backend::Win32FileTarget::new(file), -# }], +# files: vec![TargetSetup::new( +# FILE, +# windows_backend::Win32FileTarget::new(file), +# )], # buffers: Vec::new(), # provider: None, # diagnostics: None, @@ -231,16 +464,16 @@ pushed before the failure is observed: the seal's commit is issued as soon as `A # let path = std::env::temp_dir().join(format!("durable-ioring-many-{}", std::process::id())); # std::fs::write(&path, [0u8; 64])?; # let file = windows_backend::OpenedFile::::open( -# &path, -# windows_backend::FileOptions::new().read(true).write(true), +# win_durable_ioring::Wtf16String::from_os_str(path.as_os_str()), +# windows_backend::FileOptions::new().read().write(), # )?; # let (mut ring, lineage) = Ring::new(Setup { # submission_queue_size: 64, # completion_queue_size: 64, -# files: vec![TargetSetup { -# key: FILE, -# target: windows_backend::Win32FileTarget::new(file), -# }], +# files: vec![TargetSetup::new( +# FILE, +# windows_backend::Win32FileTarget::new(file), +# )], # buffers: Vec::new(), # provider: None, # diagnostics: None, @@ -320,16 +553,16 @@ fails as never issued. # let path = std::env::temp_dir().join(format!("durable-ioring-abandon-{}", std::process::id())); # std::fs::write(&path, [0u8; 64])?; # let file = windows_backend::OpenedFile::::open( -# &path, -# windows_backend::FileOptions::new().read(true).write(true), +# win_durable_ioring::Wtf16String::from_os_str(path.as_os_str()), +# windows_backend::FileOptions::new().read().write(), # )?; # let (mut ring, lineage) = Ring::new(Setup { # submission_queue_size: 64, # completion_queue_size: 64, -# files: vec![TargetSetup { -# key: FILE, -# target: windows_backend::Win32FileTarget::new(file), -# }], +# files: vec![TargetSetup::new( +# FILE, +# windows_backend::Win32FileTarget::new(file), +# )], # buffers: Vec::new(), # provider: None, # diagnostics: None, diff --git a/crates/win-durable-ioring/examples/access_rights_measurements.rs b/crates/win-durable-ioring/examples/access_rights_measurements.rs new file mode 100644 index 000000000..c8e9d8986 --- /dev/null +++ b/crates/win-durable-ioring/examples/access_rights_measurements.rs @@ -0,0 +1,149 @@ +// Copyright (c) 2026 Mike Grier + +//! Measures what the kernel allows on a handle opened with each data access and other rights, +//! through the calls a file target and the consumer that owns it make on the same handle: the ring's +//! read, write and commit (a commit is `FlushFileBuffers`, which the documentation says needs +//! `GENERIC_WRITE`), the size query, and the length, time and flush calls a database makes. It prints +//! a table and asserts nothing; the measurements are what `DI-D-65` rests on. +//! +//! Every call is a safe `std` call on a duplicate of the handle the open produced, so the handle is +//! judged as the kernel enforces it. Run: `cargo build --example access_rights_measurements`, then the +//! executable under `target/debug/examples`. + +use std::fs::{self, File, FileTimes}; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::os::windows::io::AsHandle; +use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; + +use io_buffers::AlignedBuf; +use win_durable_ioring::windows_backend::{ + Buffering, Cached, DataAccess, FileOptions, FileRights, OpenedFile, Unbuffered, +}; + +/// One sector of the size an unbuffered transfer needs, and the length the calls below extend to. +const SECTOR: usize = 4096; +const EXTENDED: u64 = 3 * SECTOR as u64; + +/// Opens the scratch file with one row's options and gives a duplicate of the handle. +type Opener = Box io::Result>; + +fn opener(options: FileOptions) -> Opener { + Box::new(move |path| { + let opened = OpenedFile::::open( + win_durable_ioring::Wtf16String::from_os_str(path.as_ref()), + options, + )?; + Ok(File::from(opened.as_handle().try_clone_to_owned()?)) + }) +} + +/// The rows: a label and what opens the file for it, in buffering mode `B`. +fn rows() -> Vec<(&'static str, Opener)> { + let attributes = FileRights::READ_ATTRIBUTES; + let write_attributes = FileRights::WRITE_ATTRIBUTES; + vec![ + ("NoData", opener::(FileOptions::new())), + ("ReadOnly", opener::(FileOptions::new().read())), + ("WriteOnly", opener::(FileOptions::new().write())), + ( + "ReadWrite", + opener::(FileOptions::new().read().write()), + ), + ( + "ReadOnly + READ_ATTRIBUTES", + opener::(FileOptions::new().read().rights(attributes)), + ), + ( + "WriteOnly + READ_ATTRIBUTES", + opener::(FileOptions::new().write().rights(attributes)), + ), + ( + "WriteOnly + WRITE_ATTRIBUTES", + opener::(FileOptions::new().write().rights(write_attributes)), + ), + ( + "ReadWrite + WRITE_ATTRIBUTES", + opener::(FileOptions::new().read().write().rights(write_attributes)), + ), + ] +} + +fn outcome(result: io::Result) -> String { + match result { + Ok(_) => "ok".to_owned(), + Err(error) => error + .raw_os_error() + .map_or_else(|| format!("{:?}", error.kind()), |code| format!("e{code}")), + } +} + +fn read_sector(file: &mut File) -> io::Result<()> { + let mut buffer = AlignedBuf::zeroed(SECTOR, SECTOR).expect("an aligned buffer"); + file.seek(SeekFrom::Start(0))?; + file.read_exact(buffer.as_mut_slice()) +} + +fn write_sector(file: &mut File) -> io::Result<()> { + let buffer = AlignedBuf::from_bytes(&[0x5a; SECTOR], SECTOR).expect("an aligned buffer"); + file.seek(SeekFrom::Start(0))?; + file.write_all(buffer.as_slice()) +} + +/// The calls, in the order a row reports them. Each takes the handle as the open produced it. +fn calls(file: &mut File) -> Vec<(&'static str, String)> { + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000_000); + vec![ + ("read", outcome(read_sector(file))), + ("write", outcome(write_sector(file))), + ( + "size", + outcome(file.metadata().map(|metadata| metadata.len())), + ), + ("extend", outcome(file.set_len(EXTENDED))), + ("sync_all", outcome(file.sync_all())), + ("sync_data", outcome(file.sync_data())), + ( + "set_times", + outcome(file.set_times(FileTimes::new().set_modified(modified))), + ), + ] +} + +fn scratch(tag: &str, serial: u32) -> PathBuf { + std::env::temp_dir().join(format!( + "access-measure-{}-{tag}-{serial}", + std::process::id() + )) +} + +/// The one place anything is printed. +fn emit(out: &mut impl Write, line: &str) { + writeln!(out, "{line}").expect("write to the output"); +} + +fn measure(out: &mut impl Write, mode: &str, serial: &mut u32) { + emit(out, &format!("== {mode}")); + for (label, open) in rows::() { + *serial += 1; + let path = scratch(mode, *serial); + fs::write(&path, vec![0u8; 2 * SECTOR]).expect("fill the scratch file"); + let line = match open(&path) { + Ok(mut file) => calls(&mut file) + .into_iter() + .map(|(name, result)| format!("{name}={result}")) + .collect::>() + .join(" "), + Err(error) => format!("open refused: {}", outcome(Err::<(), _>(error))), + }; + emit(out, &format!("{label}: {line}")); + fs::remove_file(&path).expect("remove the scratch file"); + } +} + +fn main() { + let mut out = io::stdout().lock(); + let mut serial = 0; + measure::(&mut out, "cached", &mut serial); + measure::(&mut out, "unbuffered", &mut serial); +} diff --git a/crates/win-durable-ioring/examples/blocking_push/main.rs b/crates/win-durable-ioring/examples/blocking_push/main.rs index 952331a7e..577f69fc7 100644 --- a/crates/win-durable-ioring/examples/blocking_push/main.rs +++ b/crates/win-durable-ioring/examples/blocking_push/main.rs @@ -178,12 +178,10 @@ impl Scratch { } fn target(&self) -> TargetSetup { - let file = OpenedFile::::open(&self.0, FileOptions::new().read(true).write(true)) + let name = win_durable_ioring::Wtf16String::from_os_str(self.0.as_os_str()); + let file = OpenedFile::::open(name, FileOptions::new().read().write()) .expect("open the scratch file"); - TargetSetup { - key: FILE, - target: Win32FileTarget::new(file), - } + TargetSetup::new(FILE, Win32FileTarget::new(file)) } } diff --git a/crates/win-durable-ioring/examples/disposition_measurements.rs b/crates/win-durable-ioring/examples/disposition_measurements.rs new file mode 100644 index 000000000..55c2fe4be --- /dev/null +++ b/crates/win-durable-ioring/examples/disposition_measurements.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2026 Mike Grier + +//! Measures which access masks the kernel accepts with each creation disposition that overwrites, +//! through the request crate's `OpenFile`, which is `CreateFileW` with nothing added. `DI-13.7` has to +//! offer the five dispositions, and `TRUNCATE_EXISTING` is documented as needing `GENERIC_WRITE`, which +//! the typed write access does not ask for; this finds out what it does need. It prints a table and +//! asserts nothing. +//! +//! Run: `cargo build --example disposition_measurements`, then the executable under +//! `target/debug/examples`. + +use std::fs; +use std::path::Path; + +use windows_namespace_request_sys::open::OpenFile; +use windows_sys::Win32::Foundation::GENERIC_WRITE; +use windows_sys::Win32::Storage::FileSystem::{ + CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_APPEND_DATA, FILE_FLAG_NO_BUFFERING, FILE_READ_DATA, + FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, + FILE_WRITE_EA, OPEN_ALWAYS, OPEN_EXISTING, TRUNCATE_EXISTING, +}; +use wtf_string::Wtf16String; + +const CONTENT: &[u8] = b"not empty"; + +/// The access masks tried, as the typed accesses and the documented generic right would ask. +fn masks() -> Vec<(&'static str, u32)> { + vec![ + ("FILE_WRITE_DATA (WriteOnly)", FILE_WRITE_DATA), + ( + "FILE_READ_DATA | FILE_WRITE_DATA (ReadWrite)", + FILE_READ_DATA | FILE_WRITE_DATA, + ), + ( + "FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES", + FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, + ), + ( + "FILE_WRITE_DATA | FILE_WRITE_EA", + FILE_WRITE_DATA | FILE_WRITE_EA, + ), + ( + "FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA", + FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA, + ), + ("FILE_WRITE_DATA | DELETE", FILE_WRITE_DATA | DELETE), + ( + "FILE_WRITE_DATA | FILE_APPEND_DATA", + FILE_WRITE_DATA | FILE_APPEND_DATA, + ), + ("GENERIC_WRITE", GENERIC_WRITE), + ("FILE_READ_DATA (ReadOnly)", FILE_READ_DATA), + ("0 (NoData)", 0), + ] +} + +const DISPOSITIONS: [(&str, u32); 5] = [ + ("CREATE_NEW", CREATE_NEW), + ("CREATE_ALWAYS", CREATE_ALWAYS), + ("OPEN_EXISTING", OPEN_EXISTING), + ("OPEN_ALWAYS", OPEN_ALWAYS), + ("TRUNCATE_EXISTING", TRUNCATE_EXISTING), +]; + +/// What one open does: the Win32 code it fails with, or whether it left the file empty. +fn attempt(path: &Path, access: u32, disposition: u32, flags: u32) -> String { + let request = OpenFile::new(Wtf16String::from_os_str(path.as_os_str())) + .expect("a temporary path") + .with_desired_access(access) + .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_creation_disposition(disposition) + .with_flags_and_attributes(flags); + match request.perform() { + Ok(handle) => { + drop(handle); + let length = fs::metadata(path).map_or(u64::MAX, |metadata| metadata.len()); + if length == 0 { + "ok, emptied".to_owned() + } else { + "ok".to_owned() + } + } + Err(error) => format!("error {}", error.code()), + } +} + +fn run(title: &str, flags: u32) { + println!("\n{title}\n"); + print!("{:<58}", "access"); + for (name, _) in DISPOSITIONS { + print!(" | {name:<17}"); + } + println!(); + for (label, access) in masks() { + print!("{label:<58}"); + for (_, disposition) in DISPOSITIONS { + let path = std::env::temp_dir().join(format!("disposition-{}.tmp", std::process::id())); + // CREATE_NEW needs the file to be absent; every other row needs it present. + let _ = fs::remove_file(&path); + if disposition != CREATE_NEW { + fs::write(&path, CONTENT).expect("fill the scratch file"); + } + let outcome = attempt(&path, access, disposition, flags); + print!(" | {outcome:<17}"); + let _ = fs::remove_file(&path); + } + println!(); + } +} + +fn main() { + run("cached", 0); + run( + "unbuffered (FILE_FLAG_NO_BUFFERING)", + FILE_FLAG_NO_BUFFERING, + ); +} diff --git a/crates/win-durable-ioring/sabotage.json b/crates/win-durable-ioring/sabotage.json index a1d1f3750..8ac1bd895 100644 --- a/crates/win-durable-ioring/sabotage.json +++ b/crates/win-durable-ioring/sabotage.json @@ -3,7 +3,7 @@ "testArgs": ["-p", "win-durable-ioring", "--no-fail-fast"], "whyTestArgs": "--no-fail-fast, so the doctests run even when the unit tests have already failed. README.md's worked examples are compiled as doctests and claim to break the build when a change invalidates them (DI-3.2.4); without this flag cargo stops at the first failing target, the doctests never run under any sabotage the unit tests catch, and nothing shows that claim is true.", "description": "Sabotages for win-durable-ioring, the Windows driver (DI-8.2); the portable core's are in ../durable-ioring/sabotage.json. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", - "notCoveredHere": "A ring without the registration op is absent as a cause of a construction failure: its path is the refused build's, which tests reach (DI-7.2), and the capability probe is outside the ring crate's seam (its D-60), so nothing can make the probe refuse. Also absent -- no test can produce the failure: creating the readiness event and pool delivery at construction. What would cover these creation failures, here and below, is a fault seam in win-sync-sys and windows-threadpool-sys of the kind the ring crate has (its kernel-seam), which neither has: the failure is the process running out of handles or memory, which a test cannot cause without breaking its own harness. EntryDelivery::new's failures after the readiness signal is duplicated -- creating the thread-pool wait, and setting the signal -- and a delivery ended by a failed pop, are absent for the same reason; delivery.rs says so. The Windows path's turning of a short write count into a fault (windows_backend.rs's transfer) is reached only by its unit tests: the I/O ring does not complete a file or volume write short (DI-D-50). Also absent from the Windows backend (windows_backend.rs), as no test can produce them: a registration left in flight when the ring is sealed (only a raw push makes one, which is unsafe, which this crate forbids; a completion the ring held nothing for is not a case at all, since the ring is an owned one, the ring crate's D-91); a notification that cannot be duplicated, which also ends a pumped instance's construction (the delivery's handing the engine back is reached over the in-memory backend, in durable-ioring's pump/tests.rs); and an owned buffer the ring refuses, which needs one longer than a u32. The Windows notification's wait ending as abandoned (windows_backend/wait.rs) is absent: only a mutex is abandoned, and making one needs unsafe, which this crate forbids; what would cover it is a safe Mutex in win-sync-sys (its WS-1+.3). Its survives entry is named DI-7.3: blind spot. The notification's wait failing, and Dioring::pump reporting that error, are covered, by a duplicate of an event with no SYNCHRONIZE access, which win-sync-sys makes without unsafe. SignalledDelivery::new's failures (making or duplicating its event, and the ring crate's wake, as the pool's) are absent for the same reason as the pool's. Which pool a delivery's callback runs on is covered, for the pool's delivery and the signalled one's, by holding the named pool's only thread (held_pool.rs).", + "notCoveredHere": "A ring without the registration op is absent as a cause of a construction failure: its path is the refused build's, which tests reach (DI-7.2), and the capability probe is outside the ring crate's seam (its D-60), so nothing can make the probe refuse. Also absent -- no test can produce the failure: creating the readiness event and pool delivery at construction. What would cover these creation failures, here and below, is a fault seam in win-sync-sys and windows-threadpool-sys of the kind the ring crate has (its kernel-seam), which neither has: the failure is the process running out of handles or memory, which a test cannot cause without breaking its own harness. EntryDelivery::new's failures after the readiness signal is duplicated -- creating the thread-pool wait, and setting the signal -- and a delivery ended by a failed pop, are absent for the same reason; delivery.rs says so. The Windows path's turning of a short write count into a fault (windows_backend.rs's transfer) is reached only by its unit tests: the I/O ring does not complete a file or volume write short (DI-D-50). Also absent from the Windows backend (windows_backend.rs), as no test can produce them: a registration left in flight when the ring is sealed (only a raw push makes one, which is unsafe, which this crate forbids; a completion the ring held nothing for is not a case at all, since the ring is an owned one, the ring crate's D-91); a notification that cannot be duplicated, which also ends a pumped instance's construction (the delivery's handing the engine back is reached over the in-memory backend, in durable-ioring's pump/tests.rs); and an owned buffer the ring refuses, which needs one longer than a u32. The Windows notification's wait ending as abandoned (windows_backend/wait.rs) is absent: only a mutex is abandoned, and making one needs unsafe, which this crate forbids; what would cover it is a safe Mutex in win-sync-sys (its WS-1+.3). Its survives entry is named DI-7.3: blind spot. The notification's wait failing, and Dioring::pump reporting that error, are covered, by a duplicate of an event with no SYNCHRONIZE access, which win-sync-sys makes without unsafe. SignalledDelivery::new's failures (making or duplicating its event, and the ring crate's wake, as the pool's) are absent for the same reason as the pool's. Which pool a delivery's callback runs on is covered, for the pool's delivery and the signalled one's, by holding the named pool's only thread (held_pool.rs). The data access of an OpenedDisk and an OpenedVolume, which their open passes to the kernel as the marker's rights (DI-13.8), is reached only by the raw_disk and raw_volume tests, which need an administrator and a scratch virtual disk and so run in CI and not here; no entry here sabotages it, since none could be caught locally. The constant that makes an unbuffered file refuse a remote device (LOCAL_DEVICE_ONLY, DI-10.1) is held by a const assertion in opened_file/tests.rs, so changing it fails the build and no entry can name it; the open's use of it is reached only by the test that opens a file over the administrative share, which is ignored by default and runs in CI. That OpenedVolume::open and OpenedDisk::open pass their options' share mode (DI-13.11) to raw_open::open is reached only by the exclusive-volume and exclusive-disk tests, which need an administrator and run in CI; the two survives entries name them, and an elevated run on 2026-10-16 showed each mutation fails its test, and raw_open itself is tried on a file.", "sabotages": [ { "name": "DI-3.2.1: a duplicate file key is not refused", @@ -74,8 +74,8 @@ "file": "src/dioring.rs", "expect": "caught", "why": "Two files under one key would make every report naming it ambiguous; the refusal hands the file back.", - "find": [" if engine.core.target(key).is_some() || engine.backend.target(key).is_some() {\n"], - "replace": [" if false {\n"] + "find": [" let cause = if engine.core.target(key).is_some() || engine.backend.target(key).is_some() {\n"], + "replace": [" let cause = if false {\n"] }, { "name": "DI-3.2.2.2: the handover does not set the signal", @@ -587,8 +587,8 @@ "file": "src/dioring.rs", "expect": "caught", "why": "What the backend hands back, the instance hands back: the buffers while the kernel was not given them.", - "find": [" files: setups(targets),", " buffers,", " provider,"], - "replace": [" files: setups(targets),", " buffers: None,", " provider,"] + "find": [" files: setups(targets, &declarations),", " buffers,", " provider,"], + "replace": [" files: setups(targets, &declarations),", " buffers: None,", " provider,"] }, { "name": "DI-7.2: the files' registration waits the default bound", @@ -793,15 +793,15 @@ "file": "src/dioring.rs", "expect": "caught", "why": "The delivery chosen at construction is the one the instance has: a pool drains an instance nothing is meant to drain.", - "find": [" DeliveryMode::Pumped => PumpedDelivery::new(engine).map(Delivery::Pumped),"], - "replace": [" DeliveryMode::Pumped => PoolDelivery::new(engine, on_ready, None).map(Delivery::Pool),"] + "find": [" DeliveryMode::Pumped => PumpedDelivery::new(engine)\n .map(|pumped| pumped.with_lock_watch(watch.clone()))\n .map(Delivery::Pumped),"], + "replace": [" DeliveryMode::Pumped => PoolDelivery::new(engine, on_ready, None, watch.clone()).map(Delivery::Pool),"] }, { "name": "DI-7.3: an instance set up with a pool is built for pumping", "file": "src/dioring.rs", "expect": "caught", "why": "The other direction: an instance given a pool that nothing drains delivers nothing, to every consumer that waits for its readiness.", - "find": [" PoolDelivery::new(engine, on_ready, environment).map(Delivery::Pool)"], + "find": [" PoolDelivery::new(engine, on_ready, environment, watch.clone()).map(Delivery::Pool)"], "replace": [" PumpedDelivery::new(engine).map(Delivery::Pumped)"] }, { @@ -816,8 +816,8 @@ "name": "DI-7.5: each call for the signal makes a new event", "file": "src/windows_backend/signalled.rs", "expect": "caught", - "why": "Every duplicate is a handle to the one event the pool sets; a fresh event is never set, and the consumer waits on it for ever.", - "find": [" self.signal.try_clone()"], + "why": "Every clone shares the one event the pool sets; a fresh event is never set, and the consumer waits on it for ever.", + "find": [" Ok(self.signal.clone())"], "replace": [" Event::new(ResetMode::Auto, false)"] }, { @@ -833,7 +833,7 @@ "file": "src/dioring.rs", "expect": "caught", "why": "The delivery chosen at construction is the one the instance has: a pumped instance has no pool wake, so nothing sets a signal for the consumer.", - "find": [" SignalledDelivery::new(engine, environment).map(Delivery::Signalled)"], + "find": [" SignalledDelivery::new(engine, environment, watch.clone()).map(Delivery::Signalled)"], "replace": [" PumpedDelivery::new(engine).map(Delivery::Pumped)"] }, { @@ -966,12 +966,45 @@ "replace": [" if matches!(prefix.kind(), Prefix::UNC(..))"] }, { - "name": "DI-10.1: a new file's remote check is skipped by looking nowhere", + "name": "DI-14.5: the location of a handle is not the handle's final path", "file": "src/windows_backend/opened_file.rs", "expect": "caught", - "why": "A path that does not exist yet is judged by the directory it would be created in; without that, creating an unbuffered file fails or goes unchecked.", - "find": [" Err(error) if error.kind() == io::ErrorKind::NotFound => {"], - "replace": [" Err(error) if error.kind() == io::ErrorKind::OutOfMemory => {"] + "why": "The remote check is made on what the opened handle is. A location that was a constant, or the path the caller gave, would judge every handle local.", + "find": [" Ok(PathBuf::from(path.to_os_string()))"], + "replace": [" Ok(PathBuf::from(r\"C:\\\"))"] + }, + { + "name": "DI-14.5: a handle whose final path cannot be read loses the call's error", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "A handle whose device is not known to be local is refused with the call's own code kept as the source of the refusal, not with an invented one that no caller can act on.", + "find": [" io::Error::from_raw_os_error(open_request::raw_code(code.code()))"], + "replace": [" io::Error::other(code)"] + }, + { + "name": "DI-14.6.2: a handle with no final path is accepted unbuffered", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "An unbuffered file claims that its writes reach a local device. A handle whose location cannot be read, NUL for one, must not be given that claim.", + "find": [" let location = final_location(&handle).map_err(|cause| {\n io::Error::new(io::ErrorKind::Unsupported, UnknownLocation { cause })\n })?;"], + "replace": [" let location = final_location(&handle).unwrap_or_default();"] + }, + { + "name": "DI-14.6.2: the refusal of a handle with no final path is the call's error and not this crate's own", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The refusal is this crate's narrowing, named as its own so a caller can tell it from a failure of the open; the call's code is kept as its source.", + "find": [" io::Error::new(io::ErrorKind::Unsupported, UnknownLocation { cause })"], + "replace": [" cause"] + }, + { + "name": "DI-14.6.2: a cached open is refused for a handle with no final path", + "file": "src/windows_backend/opened_file.rs", + "expect": "refused-by-build", + "buildError": "assertion failed: !Cached::LOCAL_DEVICE_ONLY", + "why": "Only the warranty of an unbuffered file needs a local file. A cached open claims best effort, and where a name leads is the caller's (DI-D-67). A const assertion on the flag refuses the build before the test that opens NUL cached would run.", + "find": [" const LOCAL_DEVICE_ONLY: bool = false;"], + "replace": [" const LOCAL_DEVICE_ONLY: bool = true;"] }, { "name": "DI-10.1: a target takes its class from a declaration instead of its file", @@ -1142,44 +1175,1054 @@ "replace": [" Ok(0) => Ok(0),\n"] }, { - "name": "DI-13.2: a created-and-truncated file is not emptied", + "name": "DI-13.7.1: the path is handed to the request through a lossy conversion", + "file": "src/windows_backend/opened_file/open_request.rs", + "expect": "caught", + "why": "The path is the caller's, a Wtf16String, and reaches the request as given; a conversion through UTF-8 replaces a lone surrogate, so a file with one in its name no longer opens. Caught by the test that opens such a name.", + "find": [" Ok(OpenFile::new(path)\n"], + "replace": [" Ok(OpenFile::new(Wtf16String::from(path.to_os_string().to_string_lossy().as_ref()))\n"] + }, + { + "name": "DI-13.7.1: control, the path is cloned on its way to the request", + "file": "src/windows_backend/opened_file/open_request.rs", + "expect": "survives", + "why": "A copy of the path is the same path, so this changes nothing the open can observe.", + "find": [" Ok(OpenFile::new(path)\n"], + "replace": [" Ok(OpenFile::new(path.clone())\n"] + }, + { + "name": "DI-14.1: the random-access hint sets nothing", "file": "src/windows_backend/opened_file.rs", "expect": "caught", - "why": "With CREATE_ALWAYS avoided, OPEN_ALWAYS plus an emptying call is the whole of create-and-truncate; without the call an existing file keeps its content.", - "find": [" (true, true, false) => (OPEN_ALWAYS, true),\n"], - "replace": [" (true, true, false) => (OPEN_ALWAYS, false),\n"] + "why": "The hint is the flag it names, so a request for it that reaches CreateFileW with no flag is a hint that was dropped without a word. Caught by the test of the flags the options produce.", + "find": [" hints |= FILE_FLAG_RANDOM_ACCESS;\n"], + "replace": [" hints |= FILE_FLAG_RANDOM_ACCESS & 0;\n"] }, { - "name": "DI-13.2: a new file is created through a link that is already there", + "name": "DI-14.1: the sequential-scan hint is dropped when both are asked for", "file": "src/windows_backend/opened_file.rs", "expect": "caught", - "why": "std added FILE_FLAG_OPEN_REPARSE_POINT for a new file, so CREATE_NEW does not follow a link; the open moved off std and keeps that.", - "find": [" let reparse = if self.create_new {\n FILE_FLAG_OPEN_REPARSE_POINT\n"], - "replace": [" let reparse = if self.create_new {\n 0\n"] + "why": "Both together is the documentation's self-defeating pair and is passed on, not refused and not narrowed to one of them.", + "find": [" if self.sequential_scan {\n"], + "replace": [" if self.sequential_scan && !self.random_access {\n"] }, { - "name": "DI-13.2: the emptying step is skipped", - "file": "src/windows_backend/opened_file/open_request.rs", + "name": "DI-14.1: a hint replaces the flags the buffering mode opens with", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "A hint is added to FILE_FLAG_NO_BUFFERING and to a new file's reparse flag. In place of them, an unbuffered file with a hint would be a cached one that still claims SharedDevice.", + "find": [" flags: flags | reparse | named | hints | self.attributes.bits(),\n"], + "replace": [" flags: if hints == 0 { flags | reparse | named | self.attributes.bits() } else { hints | reparse | named | self.attributes.bits() },\n"] + }, + { + "name": "DI-14.1: a hint is refused on an unbuffered file", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The engineer ruled (DI-13.1 (f)) that a hint is passed to an unbuffered file and documented there, not refused. Caught by the tests that open every pair against the kernel and the ring.", + "find": [" let (rights, share) = (options.rights, options.share);\n let request = open_request::request(path, options.parameters(B::FLAGS))?;\n"], + "replace": [" let (rights, share) = (options.rights, options.share);\n if B::FLAGS != 0 && (options.random_access || options.sequential_scan) {\n return Err(io::Error::new(io::ErrorKind::InvalidInput, \"a hint is not offered to an unbuffered file\"));\n }\n let request = open_request::request(path, options.parameters(B::FLAGS))?;\n"] + }, + { + "name": "DI-14.1 control: the hint flags start from a typed zero", + "file": "src/windows_backend/opened_file.rs", + "expect": "survives", + "why": "A control: the same value with a stated type, which no test can tell apart from the original, so a harness that reports it caught is reporting something other than the tests.", + "find": [" let mut hints = 0;\n"], + "replace": [" let mut hints = 0u32;\n"] + }, + { + "name": "DI-13.8: a right is the value of another right", + "file": "src/windows_backend/file_rights.rs", + "expect": "caught", + "why": "Each right is the constant it is named for, so a member holding a neighbour's value asks the kernel for something else under a name that says otherwise. Caught by the test that checks every right against the constant it is named for.", + "find": [" WRITE_ATTRIBUTES = FILE_WRITE_ATTRIBUTES;\n"], + "replace": [" WRITE_ATTRIBUTES = FILE_READ_ATTRIBUTES;\n"] + }, + { + "name": "DI-13.8: containing a set only needs one of its rights", + "file": "src/windows_backend/file_rights.rs", + "expect": "caught", + "why": "A set contains another when it holds every right in it, so a test that asks for any of them would call a read-only set a read-write one.", + "find": [" self.0 & other.0 == other.0\n"], + "replace": [" self.0 & other.0 != 0 || other.0 == 0\n"] + }, + { + "name": "DI-13.8: read-only access asks for the right to write", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "A marker is the data rights it names and no more, so a read-only handle that asked for write would be granted one the type says it lacks. Caught by the tests that have the kernel judge each access.", + "find": [" (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })\n"], + "replace": [" (if A::READ { FILE_READ_DATA | FILE_WRITE_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })\n"] + }, + { + "name": "DI-13.8: write-only access asks for the right to read", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same, the other way round: a write-only handle that asked for read would read when its type says it cannot.", + "find": [" (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })\n"], + "replace": [" (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_READ_DATA | FILE_WRITE_DATA } else { 0 })\n"] + }, + { + "name": "DI-13.8: the other rights are left out of the open", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "What an open asks for is the data access and the other rights joined, so an open that dropped the others would ask for less than the caller said. Caught by the test of what the options ask the kernel for and by the one that sets a time through a handle opened with WRITE_ATTRIBUTES.", + "find": [" access: data_rights::() | self.rights.bits(),\n"], + "replace": [" access: data_rights::(),\n"] + }, + { + "name": "DI-13.8: changing the data access forgets the other rights", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read() and write() move the type and carry everything else, so one that reset the other rights would lose what an earlier call asked for.", + "find": [" rights: self.rights,\n share: self.share,\n"], + "replace": [" rights: FileRights::NONE,\n share: self.share,\n"] + }, + { + "name": "DI-13.7.3: a disposition is sent to the call as another one", + "file": "src/windows_backend/disposition.rs", + "expect": "caught", + "why": "Each variant is the value it is named for; one that asked for OPEN_EXISTING in OpenAlways's place would fail to create where it says it does. Caught by the mapping test and by the test that opens each against the kernel.", + "find": [" Self::OpenAlways => OPEN_ALWAYS,\n"], + "replace": [" Self::OpenAlways => OPEN_EXISTING,\n"] + }, + { + "name": "DI-13.7.3: a new-file open is made to succeed on a file that is there", + "file": "src/windows_backend/disposition.rs", + "expect": "caught", + "why": "CreateNew is the disposition that fails when the file exists; as OPEN_ALWAYS it would open it. Caught by the kernel-facing test, not only by the mapping.", + "find": [" Self::CreateNew => CREATE_NEW,\n"], + "replace": [" Self::CreateNew => OPEN_ALWAYS,\n"] + }, + { + "name": "DI-13.7.3: the options forget their disposition", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The options reach CreateFileW as OPEN_EXISTING whatever was asked, so the disposition has no effect.", + "find": [" disposition: self.disposition.bits(),\n"], + "replace": [" disposition: Disposition::OpenExisting.bits(),\n"] + }, + { + "name": "DI-13.7.3: changing the data access forgets the disposition", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read() and write() move the type and carry everything else, so one that reset the disposition would lose what an earlier call asked for.", + "find": [" disposition: self.disposition,\n"], + "replace": [" disposition: Disposition::OpenExisting,\n"] + }, + { + "name": "DI-13.7.3: changing the data access forgets the reparse option", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same for the option that opens a link itself.", + "find": [" open_reparse_point: self.open_reparse_point,\n"], + "replace": [" open_reparse_point: false,\n"] + }, + { + "name": "DI-13.7.3: the reparse option sets no flag", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "Without the flag a link is followed, so a new file is created through one that is already there. Caught by the test of the flags and by the test that makes the link.", + "find": [" let reparse = if self.open_reparse_point {\n FILE_FLAG_OPEN_REPARSE_POINT\n"], + "replace": [" let reparse = if self.open_reparse_point {\n 0\n"] + }, + { + "name": "DI-13.7.3: a new-file disposition sets the reparse flag by itself", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The flag is an option and no disposition implies it; std's open did, and this one reflects Win32.", + "find": [" let reparse = if self.open_reparse_point {\n"], + "replace": [" let reparse = if self.open_reparse_point || self.disposition == Disposition::CreateNew {\n"] + }, + { + "name": "DI-13.7.4: a request forgets the share mode its sharing violation reports", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The request is built on one thread and performed on another, so what the violation says this open allowed must be carried in it and not read from the options again; a request that reset it would report an open that shared everything.", + "find": [" rights,\n share,\n mode: PhantomData,\n"], + "replace": [" rights,\n share: ShareMode::ALL,\n mode: PhantomData,\n"] + }, + { + "name": "DI-13.7.4: a request forgets the rights its sharing violation reports", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same for the rights asked for besides the data access.", + "find": [" rights,\n share,\n mode: PhantomData,\n"], + "replace": [" rights: FileRights::NONE,\n share,\n mode: PhantomData,\n"] + }, + { + "name": "DI-13.7.4 control: open runs its build and its perform through a binding", + "file": "src/windows_backend/opened_file.rs", + "expect": "survives", + "why": "A control: the same two steps with the request named between them, which no test can tell apart from open.", + "find": [" Self::request(path, options)?.perform()\n"], + "replace": [" let request = Self::request(path, options)?;\n request.perform()\n"] + }, + { + "name": "DI-13.7.3 control: the flags of an open joined in another order", + "file": "src/windows_backend/opened_file.rs", + "expect": "survives", + "why": "A control: the same mask with its parts the other way round, which no test can tell apart.", + "find": [" flags: flags | reparse | named | hints | self.attributes.bits(),\n"], + "replace": [" flags: self.attributes.bits() | hints | named | reparse | flags,\n"] + }, + { + "name": "DI-14.3: the attributes are left out of the call", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The options record the attributes and the call must carry them; a created file that is not hidden when it was asked to be is the visible result.", + "find": [" flags: flags | reparse | named | hints | self.attributes.bits(),\n"], + "replace": [" flags: flags | reparse | named | hints,\n"] + }, + { + "name": "DI-14.3: changing the data access forgets the attributes", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read() and write() move the type and carry everything else.", + "find": [" attributes: self.attributes,\n"], + "replace": [" attributes: FileAttributes::NONE,\n"] + }, + { + "name": "DI-14.3: a created file's handle is asked to be inheritable", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "Handles are never inheritable in v1 (the engineer's ruling, DI-13.1): an inheritable handle reaches child processes that can write through it. The request is where the choice is made. Caught twice over: by the test that pins the choice on the request, and, since DI-14.8.5, by the test that reads the opened handle's flags back through GetHandleInformation and sees the handle inheritable.", + "find": [" .with_security(Some(SecurityAttributes::new(Some(descriptor), false))),\n"], + "replace": [" .with_security(Some(SecurityAttributes::new(Some(descriptor), true))),\n"] + }, + { + "name": "DI-14.3: the descriptor is not given to the call", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "A descriptor that never reaches CreateFileW leaves the file with the parent's defaults and no error. Caught by the test that a deny-read descriptor refuses a later open, and, since DI-14.8.5, by the test that reads the created file's DACL back through GetSecurityInfo.", + "find": [" .with_security(Some(SecurityAttributes::new(Some(descriptor), false))),\n"], + "replace": [" .with_security(Some(SecurityAttributes::new(None, false))),\n"] + }, + { + "name": "DI-14.3: an attribute is another attribute", + "file": "src/windows_backend/file_attributes.rs", + "expect": "caught", + "why": "Each member is the attribute it is named for; HIDDEN as SYSTEM would hide nothing and mark every file it touched as part of the operating system.", + "find": [" HIDDEN = FILE_ATTRIBUTE_HIDDEN;\n"], + "replace": [" HIDDEN = FILE_ATTRIBUTE_SYSTEM;\n"] + }, + { + "name": "DI-14.2: delete-on-close sets another flag", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "Each option is the flag it is named for; one that set another would delete nothing, or something else.", + "find": [" named |= FILE_FLAG_DELETE_ON_CLOSE;\n"], + "replace": [" named |= FILE_FLAG_POSIX_SEMANTICS;\n"] + }, + { + "name": "DI-14.2: no-recall sets nothing", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "An option that reaches CreateFileW as no flag is a request that was dropped without a word.", + "find": [" named |= FILE_FLAG_OPEN_NO_RECALL;\n"], + "replace": [" named |= 0;\n"] + }, + { + "name": "DI-14.2: POSIX semantics sets nothing", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same for the third flag.", + "find": [" named |= FILE_FLAG_POSIX_SEMANTICS;\n"], + "replace": [" named |= 0;\n"] + }, + { + "name": "DI-14.2: the three flags are left out of the call", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The options record the flags and the call must carry them; a delete-on-close file that is not deleted is the visible result.", + "find": [" flags: flags | reparse | named | hints | self.attributes.bits(),\n"], + "replace": [" flags: flags | reparse | hints | self.attributes.bits(),\n"] + }, + { + "name": "DI-14.2: changing the data access forgets delete-on-close", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read() and write() move the type and carry everything else.", + "find": [" delete_on_close: self.delete_on_close,\n"], + "replace": [" delete_on_close: false,\n"] + }, + { + "name": "DI-14.2: changing the data access forgets no-recall", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same for the second flag.", + "find": [" open_no_recall: self.open_no_recall,\n"], + "replace": [" open_no_recall: false,\n"] + }, + { + "name": "DI-14.2: changing the data access forgets POSIX semantics", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The same for the third flag.", + "find": [" posix_semantics: self.posix_semantics,\n"], + "replace": [" posix_semantics: false,\n"] + }, + { + "name": "DI-14.9: overlapped sets nothing", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "An option that reaches CreateFileW as no flag is a request dropped without a word; the handle would be a synchronous one.", + "find": [" named |= FILE_FLAG_OVERLAPPED;\n"], + "replace": [" named |= 0;\n"] + }, + { + "name": "DI-14.9: overlapped is set whether asked for or not", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "Every open would become an overlapped one, which changes when a ring operation's completion is there.", + "find": [" if self.overlapped {\n"], + "replace": [" if self.overlapped || true {\n"] + }, + { + "name": "DI-14.9: changing the data access forgets overlapped", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read() and write() move the type and carry everything else.", + "find": [" overlapped: self.overlapped,\n"], + "replace": [" overlapped: false,\n"] + }, + { + "name": "DI-13.8 control: the same test of containment with its operands the other way round", + "file": "src/windows_backend/file_rights.rs", + "expect": "survives", + "why": "A control: the same predicate with its operands swapped, which no test can tell apart, so a harness that reports it caught is reporting something other than the tests.", + "find": [" self.0 & other.0 == other.0\n"], + "replace": [" other.0 & self.0 == other.0\n"] + }, + { + "name": "DI-13.8 control: the two data rights of a marker joined in the other order", + "file": "src/windows_backend/opened_file.rs", + "expect": "survives", + "why": "A control: the same mask with its two halves the other way round, which no test can tell apart.", + "find": [" (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })\n"], + "replace": [" (if A::WRITE { FILE_WRITE_DATA } else { 0 }) | (if A::READ { FILE_READ_DATA } else { 0 })\n"] + }, + { + "name": "DI-13.9: a file's target states more access than the file was opened with", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "A target states the access its handle was opened with, so one that always said read and write would be believed by the core and refused by the kernel. Caught by the test that reads the stated access off a target made from each kind of file.", + "find": [" access: A::ACCESS,\n }\n }\n\n /// The target `volume` makes"], + "replace": [" access: Access::ReadWrite,\n }\n }\n\n /// The target `volume` makes"] + }, + { + "name": "DI-13.9: a volume's target states more access than the volume was opened with", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "The same for a raw volume, whose open is judged by the kernel only where there is an administrator, so the stated access is pinned by the test that assembles one.", + "find": [" access: A::ACCESS,\n }\n }\n\n /// The target `disk` makes"], + "replace": [" access: Access::ReadWrite,\n }\n }\n\n /// The target `disk` makes"] + }, + { + "name": "DI-13.9: a disk's target states more access than the disk was opened with", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "The same for a whole disk.", + "find": [" access: A::ACCESS,\n }\n }\n\n /// The target `file` makes, with the consumer's attestation"], + "replace": [" access: Access::ReadWrite,\n }\n }\n\n /// The target `file` makes, with the consumer's attestation"] + }, + { + "name": "DI-13.9: the backend lets a write through to a target that cannot write", + "file": "src/windows_backend.rs", + "expect": "caught", + "why": "A write is covered by a target that can write. Asking whether it can read instead lets a write to a read-only target reach the kernel, which is what the test of the refusal at the push shows.", + "find": [" SubmissionOp::Write { .. } => access.can_write(),\n"], + "replace": [" SubmissionOp::Write { .. } => access.can_read(),\n"] + }, + { + "name": "DI-13.9: the backend lets a read through from a target that cannot read", + "file": "src/windows_backend.rs", + "expect": "caught", + "why": "The same for a read from a write-only target.", + "find": [" SubmissionOp::Read { .. } => access.can_read(),\n"], + "replace": [" SubmissionOp::Read { .. } => access.can_write(),\n"] + }, + { + "name": "DI-13.9: the backend does not refuse what the access does not cover", + "file": "src/windows_backend.rs", + "expect": "caught", + "why": "An operation the access does not cover is refused as AccessDenied and never reaches the ring.", + "find": [" if !covered {\n return Err(RefusalCause::AccessDenied);\n"], + "replace": [" if false && !covered {\n return Err(RefusalCause::AccessDenied);\n"] + }, + { + "name": "DI-13.9 control: the access asked of a write is stated as a binding first", + "file": "src/windows_backend.rs", + "expect": "survives", + "why": "A control: the same call made through a local, which no test can tell apart.", + "find": [" SubmissionOp::Write { .. } => access.can_write(),\n"], + "replace": [" SubmissionOp::Write { .. } => { let writes = access.can_write(); writes }\n"] + }, + { + "name": "DI-13.10: registration does not judge a key's declaration", + "file": "src/dioring.rs", + "expect": "caught", + "why": "add_target refuses a key that declares more than its target states, and hands the target back with the cause. Admitting every key leaves a declaration nothing compares.", + "find": [" declared.admitted_by(target.access()).err()\n"], + "replace": [" None\n"] + }, + { + "name": "DI-13.10: construction admits a key that declares more than its target states", + "file": "src/dioring.rs", + "expect": "caught", + "why": "Construction refuses a setup whose key declared what its target cannot do, and hands every file back.", + "find": [" (!actual.covers(declared)).then_some(SetupRefusal::AccessNotCovered {\n"], + "replace": [" (actual.covers(declared)).then_some(SetupRefusal::AccessNotCovered {\n"] + }, + { + "name": "DI-13.10: a setup made with a typed key does not keep its declaration", + "file": "src/dioring.rs", + "expect": "caught", + "why": "TargetSetup::new reads the declaration off the key, so a setup that dropped it would never be judged.", + "find": [" declared: key.declared(),\n"], + "replace": [" declared: None,\n"] + }, + { + "name": "DI-13.10: a failed construction hands a file back with another declaration", + "file": "src/dioring.rs", + "expect": "caught", + "why": "Files come back as they were given, which includes what their keys declared; a hand-back that dropped it would let a second attempt skip the check.", + "find": [" .find_map(|&(given, access)| (given == key).then_some(access))\n"], + "replace": [" .find_map(|&(given, _)| (given == key).then_some(Access::ReadWrite))\n"] + }, + { + "name": "DI-13.10 control: the registered key's number is taken through a binding", + "file": "src/dioring.rs", + "expect": "survives", + "why": "A control: the same number taken through a local, which no test can tell apart.", + "find": [" let key = declared.untyped();\n"], + "replace": [" let key = { let number = declared.untyped(); number };\n"] + }, + { + "name": "DI-13.4: every share mode is said to share less than it does", + "file": "src/windows_backend/share_mode.rs", + "expect": "caught", + "why": "ShareMode::ALL is what every file was opened with before the mode was an option, so one that left out a member would refuse a deleter that the default admitted.", + "find": [" pub const ALL: Self = Self(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);"], + "replace": [" pub const ALL: Self = Self(FILE_SHARE_READ | FILE_SHARE_WRITE);"] + }, + { + "name": "DI-13.4: a member is the value of another", + "file": "src/windows_backend/share_mode.rs", + "expect": "caught", + "why": "Each member is the flag it is named for; a swapped one shares the wrong access.", + "find": [" READ = FILE_SHARE_READ;"], + "replace": [" READ = FILE_SHARE_WRITE;"] + }, + { + "name": "DI-13.4: containing a set only needs one of its members", + "file": "src/windows_backend/share_mode.rs", + "expect": "caught", + "why": "A set contains another when every member of the other is in it.", + "find": [" self.0 & other.0 == other.0"], + "replace": [" self.0 & other.0 != 0"] + }, + { + "name": "DI-13.4: a file is opened sharing nothing unless told otherwise", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The default is every member, as std opens a file; an exclusive default would refuse every second open.", + "find": [" share: ShareMode::ALL,"], + "replace": [" share: ShareMode::NONE,"] + }, + { + "name": "DI-13.4: the options forget their share mode when the data access changes", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "read and write rebuild the options for a new access type, and a field not carried across is silently reset.", + "find": [" share: self.share,"], + "replace": [" share: ShareMode::ALL,"] + }, + { + "name": "DI-13.4: the request is made with every member whatever was asked", + "file": "src/windows_backend/opened_file.rs", "expect": "caught", - "why": "The emptying happens after the open, so it is tried on the call itself and not only on the parameters that ask for it.", - "find": [" if !parameters.truncate_after {\n"], - "replace": [" if true {\n"] + "why": "The share mode an open asks for is the one it is made with.", + "find": [" share: self.share.bits(),"], + "replace": [" share: ShareMode::ALL.bits(),"] }, { - "name": "DI-13.2: a path longer than an ordinary one is handed over as it was", + "name": "DI-13.4: the request is made sharing nothing whatever was asked", "file": "src/windows_backend/opened_file/open_request.rs", "expect": "caught", - "why": "The request crate refuses a path past MAX_PATH by design, and std did not, so the verbatim form is what keeps a long path opening.", - "find": [" Ok(verbatim(&absolute))\n"], - "replace": [" Ok(path.to_path_buf())\n"] + "why": "The open passes the parameters' share mode to the call and decides none itself.", + "find": [" .with_share_mode(parameters.share)"], + "replace": [" .with_share_mode(0)"] }, { - "name": "DI-13.2: a file is no longer shared for deletion", + "name": "DI-13.4: another error is taken for a sharing violation", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "Only the kernel's sharing violation becomes a SharingViolation; any other failure is its own error as it was.", + "find": [" if error.raw_os_error() != Some(ERROR_SHARING_VIOLATION.cast_signed()) {"], + "replace": [" if error.raw_os_error() == Some(ERROR_SHARING_VIOLATION.cast_signed()) {"] + }, + { + "name": "DI-13.4: a sharing violation reports some other kind of error", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The error's kind is how a consumer who does not downcast tells a sharing conflict from the rest.", + "find": [" io::ErrorKind::ResourceBusy,"], + "replace": [" io::ErrorKind::Other,"] + }, + { + "name": "DI-13.4: a sharing violation drops the kernel's error", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "The kernel's error, which carries the Win32 code, is the violation's source.", + "find": [" Some(&self.os_error)"], + "replace": [" None"] + }, + { + "name": "DI-13.4: a read-only open is reported as read and write", + "file": "src/windows_backend/opened_file.rs", + "expect": "caught", + "why": "A violation says what this open asked for, so the data access it names is the marker's.", + "find": [" (true, false) => Some(Access::ReadOnly),"], + "replace": [" (true, false) => Some(Access::ReadWrite),"] + }, + { + "name": "DI-13.4 control: the share mode is passed to the call through a binding", "file": "src/windows_backend/opened_file/open_request.rs", + "expect": "survives", + "why": "A control: the same mode passed through a local, which no test can tell apart.", + "find": [" .with_share_mode(parameters.share)"], + "replace": [" .with_share_mode({ let share = parameters.share; share })"] + }, + { + "name": "DI-13.11: a device is opened asking for no data right", + "file": "src/windows_backend/raw_open.rs", + "expect": "caught", + "why": "The access a raw volume or disk is opened with is its marker's data rights; the open is the one place that decides it.", + "find": [" access: data_rights::(),"], + "replace": [" access: 0,"] + }, + { + "name": "DI-13.11: a device is opened without its buffering flags", + "file": "src/windows_backend/raw_open.rs", + "expect": "caught", + "why": "The buffering mode is what an unbuffered device's warranty rests on.", + "find": [" flags: B::FLAGS,"], + "replace": [" flags: 0,"] + }, + { + "name": "DI-13.11: a device is opened with every member whatever was asked", + "file": "src/windows_backend/raw_open.rs", + "expect": "caught", + "why": "The share mode a device open asks for is the one it is made with.", + "find": [" share: share.bits(),"], + "replace": [" share: ShareMode::ALL.bits(),"] + }, + { + "name": "DI-13.11: a device is opened sharing nothing whatever was asked", + "file": "src/windows_backend/raw_open.rs", + "expect": "caught", + "why": "The call is made with what the open asked for, which tries the same open on a file.", + "find": [" .share_mode(asked.share)"], + "replace": [" .share_mode(0)"] + }, + { + "name": "DI-13.11: a device's sharing violation names no data access", + "file": "src/windows_backend/raw_open.rs", + "expect": "caught", + "why": "A violation says what this open asked for, as a file's does.", + "find": [" refine_sharing(error, &given, data_access::(), FileRights::NONE, share)\n"], + "replace": [" refine_sharing(error, &given, None, FileRights::NONE, share)\n"] + }, + { + "name": "DI-13.11: a volume is opened sharing nothing unless told otherwise", + "file": "src/windows_backend/opened_volume.rs", + "expect": "caught", + "why": "The default is every member, as std opens one.", + "find": [" share: ShareMode::ALL,"], + "replace": [" share: ShareMode::NONE,"] + }, + { + "name": "DI-13.11: a volume ignores the share mode it is given", + "file": "src/windows_backend/opened_volume.rs", + "expect": "caught", + "why": "VolumeOptions::share replaces the mode.", + "find": [" self.share = share;"], + "replace": [" self.share = ShareMode::ALL;"] + }, + { + "name": "DI-13.11: a disk is opened sharing nothing unless told otherwise", + "file": "src/windows_backend/opened_disk.rs", + "expect": "caught", + "why": "The default is every member, as std opens one.", + "find": [" share: ShareMode::ALL,"], + "replace": [" share: ShareMode::NONE,"] + }, + { + "name": "DI-13.11: a disk ignores the share mode it is given", + "file": "src/windows_backend/opened_disk.rs", + "expect": "caught", + "why": "DiskOptions::share replaces the mode.", + "find": [" self.share = share;"], + "replace": [" self.share = ShareMode::ALL;"] + }, + { + "name": "DI-13.11: a volume's open is made sharing every member whatever its options say", + "file": "src/windows_backend/opened_volume.rs", + "expect": "survives", + "why": "A blind spot, named: that OpenedVolume::open passes its options' share mode to the open is reached only by the exclusive-volume test in raw_volume, which needs an administrator and a scratch virtual disk and runs in CI, and the sweep runs no ignored test so it survives here. Applied by hand and run elevated on 2026-10-16, the mutation fails that test. The open itself is tried on a file by raw_open's tests.", + "find": [" let file = raw_open::open::(path.as_str(), options.share)?;"], + "replace": [" let file = raw_open::open::(path.as_str(), ShareMode::ALL)?;"] + }, + { + "name": "DI-13.11: a disk's open is made sharing every member whatever its options say", + "file": "src/windows_backend/opened_disk.rs", + "expect": "survives", + "why": "A blind spot, named: that OpenedDisk::open passes its options' share mode to the open is reached only by the exclusive-disk test in raw_disk, which needs an administrator and a scratch virtual disk and runs in CI, and the sweep runs no ignored test so it survives here. Applied by hand and run elevated on 2026-10-16, the mutation fails that test.", + "find": [" let file = raw_open::open::(path.path(), options.share)?;"], + "replace": [" let file = raw_open::open::(path.path(), ShareMode::ALL)?;"] + }, + { + "name": "DI-13.11 control: a device's flags are taken through a binding", + "file": "src/windows_backend/raw_open.rs", + "expect": "survives", + "why": "A control: the same flags taken through a local, which no test can tell apart.", + "find": [" flags: B::FLAGS,"], + "replace": [" flags: { let flags = B::FLAGS; flags },"] + }, + { + "name": "DI-10.5: an attested file's target does not claim Complete", + "file": "src/windows_backend/file_target.rs", "expect": "caught", - "why": "std shared every file for reading, writing and deletion, and the share mode is the open's own to state now that std no longer does.", - "find": [" .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)\n"], - "replace": [" .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)\n"] + "why": "The attestation is the whole point of the constructor, so one that leaves the class at SharedDevice does nothing.", + "find": [" Self::new(file).attested_exclusive()\n"], + "replace": [" Self::new(file)\n"] + }, + { + "name": "DI-10.5: an attested volume's target does not claim Complete", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "As above, for a volume.", + "find": [" Self::from_volume(volume).attested_exclusive()\n"], + "replace": [" Self::from_volume(volume)\n"] + }, + { + "name": "DI-10.5: an attested disk's target does not claim Complete", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "As above, for a disk.", + "find": [" Self::from_disk(disk).attested_exclusive()\n"], + "replace": [" Self::from_disk(disk)\n"] + }, + { + "name": "DI-10.5: the attestation drops the flush domains", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "Complete is a claim that the domains reported are all of them, so an attestation that discarded the found ones would claim it over none.", + "find": [" self.warranty = WarrantyClass::Complete;\n self\n"], + "replace": [" self.warranty = WarrantyClass::Complete;\n self.domains.clear();\n self\n"] + }, + { + "name": "DI-10.5: a cached file can be attested", + "file": "src/windows_backend/file_target.rs", + "expect": "caught", + "why": "Caught by the compile_fail doctest on the constructor and by nothing else: only the type refuses a cached file, so widening it to any buffering is a change no unit test can see and the doctest then compiles when it must not.", + "find": [" pub fn new_attested_exclusive(file: OpenedFile) -> Self {\n"], + "replace": [" pub fn new_attested_exclusive(file: OpenedFile) -> Self {\n"] + }, + { + "name": "DI-16.1: the final path is asked of a duplicate", + "file": "src/windows_backend/opened_file.rs", + "expect": "survives", + "why": "Declared, and not a gap a test can close: the request is made and dropped inside final_location, which returns only the path, so a duplicate would exist only for the call and nothing outside can tell it from the share. The kind query that once held a share was the one place a duplicate was observable, and was removed (DI-D-73 in durable-ioring's notes).", + "find": [" let path = QueryFinalPath::new(handle.clone())\n"], + "replace": [" let path = QueryFinalPath::new(SharedHandle::from(std::os::windows::io::AsHandle::as_handle(handle).try_clone_to_owned()?))\n"] + }, + { + "name": "DI-10.5 control: the attested claim is made through a binding", + "file": "src/windows_backend/file_target.rs", + "expect": "survives", + "why": "A control: the same target returned through a local, which no test can tell apart.", + "find": [" self.warranty = WarrantyClass::Complete;\n self\n"], + "replace": [" self.warranty = WarrantyClass::Complete;\n let this = self;\n this\n"] + }, + { + "name": "DI-15.1: the identifier is dropped from the identity", + "file": "src/windows_backend/file_id.rs", + "expect": "caught", + "why": "With the identifier zeroed, every file on a volume would report one identity, and an open by it would open the wrong file or none.", + "find": [" Self::new(info.volume_serial_number(), info.file_id())"], + "replace": [" Self::new(info.volume_serial_number(), [0; 16])"] + }, + { + "name": "DI-15.1: the volume serial is dropped from the identity", + "file": "src/windows_backend/file_id.rs", + "expect": "caught", + "why": "The FILE_ID_INFO page says the identifier and the serial together identify a file on one computer; without the serial two volumes' files could compare equal. No two-volume test exists, so this is caught by the agreement with the request crate's read of the same handle.", + "find": [" Self::new(info.volume_serial_number(), info.file_id())"], + "replace": [" Self::new(0, info.file_id())"] + }, + { + "name": "DI-15.1: a refusal loses the kernel's code", + "file": "src/windows_backend/file_id.rs", + "expect": "caught", + "why": "The error is documented as the code the call failed with, unaltered; a caller distinguishing a stale or foreign handle needs it.", + "find": [" .map_err(|error| error.to_io_error())"], + "replace": [" .map_err(|_| io::Error::from(io::ErrorKind::Other))"] + }, + { + "name": "DI-15.1 control: the refusal's closure is renamed", + "file": "src/windows_backend/file_id.rs", + "expect": "survives", + "why": "A control: the same conversion through a differently named binding, which no test can tell apart.", + "find": [" .map_err(|error| error.to_io_error())"], + "replace": [" .map_err(|failure| failure.to_io_error())"] + }, + { + "name": "DI-15.2: an open by FileId does not check the opened file's identity", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "The call takes the volume from the hint and not from the FileId, so without the check a hint on another volume opens another volume's file and reports it as the one asked for (DI-D-70).", + "find": [" if opened != asked {"], + "replace": [" if false && opened != asked {"] + }, + { + "name": "DI-15.2: the identity check compares the identifier and not the volume", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "The identifier is the same on every volume that happens to have it; the volume serial is the half of the identity the call never sees, and the only half the check exists for.", + "find": [" if opened != asked {"], + "replace": [" if opened.id() != asked.id() {"] + }, + { + "name": "DI-15.2: a sharing violation by identifier names the wrong identifier", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "The SharingViolation's OpenedBy is the caller's only way to say which open conflicted.", + "find": [" || OpenedBy::Identifier(self.identifier),"], + "replace": [" || OpenedBy::Identifier(FileIdentifier::FileId64(0)),"] + }, + { + "name": "DI-15.2: the buffering mode's flags are not passed", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "An OpenedFile opened cached would claim SharedDevice for data the system cache holds.", + "find": [" .with_flags_and_attributes(options.flags(B::FLAGS));"], + "replace": [" .with_flags_and_attributes(options.flags(0));"] + }, + { + "name": "DI-15.2: the share mode is not passed", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "An exclusive open by identifier would share everything.", + "find": [" .with_share_mode(options.share.bits())"], + "replace": [" .with_share_mode(ShareMode::ALL.bits())"] + }, + { + "name": "DI-15.2: the data access is not asked for", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "An OpenedFile<_, ReadWrite> whose handle cannot read or write would fail every operation a target made of it is given.", + "find": [" data_rights::() | self.rights.bits()"], + "replace": [" self.rights.bits()"] + }, + { + "name": "DI-15.2: the overlapped option sets another flag", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "caught", + "why": "Each option must set its own flag and no other.", + "find": [" flags |= FILE_FLAG_OVERLAPPED;"], + "replace": [" flags |= FILE_FLAG_RANDOM_ACCESS;"] + }, + { + "name": "DI-15.2: an object ID's first field is not turned around", + "file": "src/windows_backend/file_id.rs", + "expect": "caught", + "why": "The bytes are a GUID as Windows lays one out; read in the wrong order they name another object, or none.", + "find": [" for field in [guid_layout::DATA1, guid_layout::DATA2, guid_layout::DATA3] {"], + "replace": [" for field in [guid_layout::DATA2, guid_layout::DATA3] {"] + }, + { + "name": "DI-15.2: the open by identifier skips the location checks", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "survives", + "why": "Declared, and not a gap a test can close: the remote and unknown-location checks are the path open's, made by the same function and caught there, and no test reaches them by identifier, since the OpenFileById page lists SMB as not supported and the call opens only a file on a file system, whose final path is readable.", + "find": [" OpenedFile::admit(handle)\n"], + "replace": [" Ok(OpenedFile {\n handle,\n geometry: B::geometry(),\n mode: PhantomData,\n })\n"] + }, + { + "name": "DI-15.2 control: the open's error binding is renamed", + "file": "src/windows_backend/opened_file/by_id.rs", + "expect": "survives", + "why": "A control: the same refinement through a differently named binding, which no test can tell apart.", + "find": [" let handle = self.request.perform().map_err(|error| {\n refine_sharing_by(\n error.to_io_error(),"], + "replace": [" let handle = self.request.perform().map_err(|failure| {\n refine_sharing_by(\n failure.to_io_error(),"] + }, + { + "name": "DI-15.4: a reopen skips the checks on the reopened handle", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "A reopen from any handle can reach a device or a share, so an unbuffered reopen of NUL or of a file on a share would claim SharedDevice for a handle whose location is unknown or remote (DI-14.5, DI-14.6.2).", + "find": [" OpenedFile::admit(SharedHandle::from(handle))\n"], + "replace": [" Ok(OpenedFile {\n handle: SharedHandle::from(handle),\n geometry: B::geometry(),\n mode: PhantomData,\n })\n"] + }, + { + "name": "DI-15.4: a reopen's buffering flags are not passed", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "Cached to unbuffered is the reason to reopen; an OpenedFile reopened cached would claim SharedDevice for data the system cache holds.", + "find": [" .with_flags_and_attributes(options.flags(B::FLAGS));"], + "replace": [" .with_flags_and_attributes(options.flags(0));"] + }, + { + "name": "DI-15.4: a reopen's share mode is not passed", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "The documented rule judges the reopen's sharing against the original; a reopen that shared everything regardless would accept what the caller asked to refuse.", + "find": [" .with_share_mode(options.share.bits())"], + "replace": [" .with_share_mode(ShareMode::ALL.bits())"] + }, + { + "name": "DI-15.4: a reopen does not ask for its data access", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "A read-only scrubber that cannot read, or a widened handle that cannot write, would fail every operation a target made of it is given.", + "find": [" data_rights::() | self.rights.bits()"], + "replace": [" self.rights.bits()"] + }, + { + "name": "DI-15.4: delete-on-close sets another flag", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "Each option must set its own flag and no other; a reopen meant to delete on close would leave the file behind.", + "find": [" flags |= FILE_FLAG_DELETE_ON_CLOSE;"], + "replace": [" flags |= FILE_FLAG_POSIX_SEMANTICS;"] + }, + { + "name": "DI-15.4: POSIX semantics sets nothing", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "An option the caller set that reaches no call is a silent no.", + "find": [" flags |= FILE_FLAG_POSIX_SEMANTICS;"], + "replace": [" flags |= 0;"] + }, + { + "name": "DI-15.4: changing the data access forgets delete-on-close", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "read() and write() move the options to another type; a flag set before them must survive the move.", + "find": [" delete_on_close: self.delete_on_close,"], + "replace": [" delete_on_close: false,"] + }, + { + "name": "DI-15.4: a reopen's sharing violation names an open by path", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "caught", + "why": "OpenedBy is the caller's only way to say which open conflicted, and a reopen has no path.", + "find": [" || OpenedBy::Reopen,"], + "replace": [" || OpenedBy::Path(std::path::PathBuf::new()),"] + }, + { + "name": "DI-15.4 control: the reopen's error binding is renamed", + "file": "src/windows_backend/opened_file/reopen.rs", + "expect": "survives", + "why": "A control: the same refinement through a differently named binding, which no test can tell apart.", + "find": [" let handle = self.request.perform().map_err(|error| {\n refine_sharing_by(\n error.to_io_error(),"], + "replace": [" let handle = self.request.perform().map_err(|failure| {\n refine_sharing_by(\n failure.to_io_error(),"] + }, + { + "name": "DI-12.1.1: a pool delivery's consumer calls lock the engine unwatched", + "file": "src/windows_backend/pool.rs", + "expect": "caught", + "why": "The engine's lock is watched on every path that takes it, so a wait for it on a consumer's call is reported.", + "find": [" present_watched(self.watch.lock(&self.engine, ENGINE_LOCK))"], + "replace": [" present(lock(&self.engine))"] + }, + { + "name": "DI-12.1.1: the hub locks the engine unwatched", + "file": "src/dioring/hub.rs", + "expect": "caught", + "why": "A lineage handle's release, and a provider's answer, take the engine's lock through the hub, and a wait there is as much a delay as a consumer's.", + "find": [" let deliver = self\n .watch\n .lock(&self.engine, ENGINE_LOCK)\n .as_mut()\n .is_some_and(act);"], + "replace": [" let deliver = lock(&self.engine).as_mut().is_some_and(act);"] + }, + { + "name": "DI-12.1.1: a pumped delivery is given no lock watch", + "file": "src/dioring.rs", + "expect": "caught", + "why": "A pumped instance's consumer calls and its pump take the engine's lock through the delivery, which reports only if it was given the watch.", + "find": [" .map(|pumped| pumped.with_lock_watch(watch.clone()))"], + "replace": [" .map(|pumped| pumped)"] + }, + { + "name": "DI-12.1.3: the debugger sink wants nothing", + "file": "src/debug_output.rs", + "expect": "caught", + "why": "The sink states that it wants every kind; a consumer who chose it would otherwise see nothing and have no error to say so.", + "find": [" true\n"], + "replace": [" false\n"] + }, + { + "name": "DI-12.1.3: a NUL in an event's text is not written as backslash zero", + "file": "src/debug_output.rs", + "expect": "caught", + "why": "The call's string ends at a NUL, and the crate refuses a string that has one, so a line with a NUL must not reach it: the sink escapes the NUL and sends the line.", + "find": [" format!(\"{LINE_PREFIX}{event}\").replace('\\0', \"\\\\0\")\n"], + "replace": [" format!(\"{LINE_PREFIX}{event}\")\n"] + }, + { + "name": "DI-12.1.3: a line is written without the dioring prefix", + "file": "src/debug_output.rs", + "expect": "caught", + "why": "A line is the prefix and the event's text, as the standard-error sink's is.", + "find": [" format!(\"{LINE_PREFIX}{event}\").replace('\\0', \"\\\\0\")\n"], + "replace": [" format!(\"{event}\").replace('\\0', \"\\\\0\")\n"] + }, + { + "name": "DI-12.1.3: the line is not sent", + "file": "src/debug_output.rs", + "expect": "caught", + "why": "Only a debugger can tell that the call was made, which is why the test of it starts one.", + "find": [" let sent = output_debug_str(&line(event));\n"], + "replace": [" let sent = output_debug_str(&line(event)[..0]);\n"] + }, + { + "name": "DI-12.1.3 control: the debugger sink wants every kind by negation", + "file": "src/debug_output.rs", + "expect": "survives", + "why": "A control: the same answer by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" true\n"], + "replace": [" !false\n"] + }, + { + "name": "DI-12.1.6: the default sink is none", + "file": "src/dioring.rs", + "expect": "caught", + "why": "A consumer who chooses nothing is to get the events (DI-D-51 T4); a default that writes nowhere is the driver before the provider existed.", + "find": [" let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(EtwDiagnostics::new()));\n"], + "replace": [" let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(durable_ioring::NoDiagnostics));\n"] + }, + { + "name": "DI-12.1.6: a chosen sink is replaced by the default", + "file": "src/dioring.rs", + "expect": "caught", + "why": "Any sink can be given when the driver is built, and the default is only for an instance that gave none.", + "find": [" let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(EtwDiagnostics::new()));\n"], + "replace": [" let _ = diagnostics;\n let diagnostics: Arc = Arc::new(EtwDiagnostics::new());\n"] + }, + { + "name": "DI-12.1.6 control: the default sink is built by its Default", + "file": "src/dioring.rs", + "expect": "survives", + "why": "A control: the same sink by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(EtwDiagnostics::new()));\n"], + "replace": [" let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(EtwDiagnostics::default()));\n"] + }, + { + "name": "DI-12.2.4: the router sends every part to its first provider", + "file": "../durable-ioring/src/router.rs", + "expect": "caught", + "why": "Through a real instance, each routed provider is asked only for its own domains (DI-D-81): a part sent to another is flushed by something that never said it could, and the instance's tests here see it without the router's own.", + "find": [" parts[registration].push(part);"], + "replace": [" parts[0].push(part);"] + }, + { + "name": "DI-12.2.4: the router calls its providers in reverse registration order", + "file": "../durable-ioring/src/router.rs", + "expect": "caught", + "why": "Through a real instance, a panicking provider stops the ones registered after it, whose domains are abandoned without being called (DI-D-83); calling in reverse hands the later ones their parts first.", + "find": [" for (route, domains) in self.routes.iter_mut().zip(parts) {"], + "replace": [" for (route, domains) in self.routes.iter_mut().rev().zip(parts.into_iter().rev()) {"] + }, + { + "name": "DI-12.2.4 control: the router's per-provider parts are made by counting", + "file": "../durable-ioring/src/router.rs", + "expect": "survives", + "why": "A control: the same empty parts made by counting the routes instead of walking them, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" self.routes.iter().map(|_| Vec::new()).collect();"], + "replace": [" (0..self.routes.len()).map(|_| Vec::new()).collect();"] + }, + { + "name": "DI-19.2.4: a lock wait's end names a pair of its own, as the driver's tests see it", + "file": "../durable-ioring/src/lock_watch.rs", + "expect": "caught", + "why": "The driver's lock-wait tests bind to PairingChecker (DI-D-87), the one definition of the pairing rule; an end whose id is not its begin's is visible to the checker alone, so this is caught only if the binding is real.", + "find": [ + " self.sink.report(&Diagnostic::LockWaitEnded {", + " instance: self.instance,", + " lock: name,", + " id," + ], + "replace": [ + " self.sink.report(&Diagnostic::LockWaitEnded {", + " instance: self.instance,", + " lock: name,", + " id: CorrelationId::for_pair(&*self.sink, DiagnosticKind::LockWait).unwrap_or(id)," + ] + }, + { + "name": "DI-19.2.4: a lock wait's end is not reported, as the driver's tests see it", + "file": "../durable-ioring/src/lock_watch.rs", + "expect": "caught", + "why": "Each driver test that waits for the engine's lock expects its thread's begin and its end.", + "find": [ + " self.sink.report(&Diagnostic::LockWaitEnded {" + ], + "replace": [ + " let _ = &(Diagnostic::LockWaitEnded {" + ] + }, + { + "name": "DI-19.2.4 control: the taken guard is declared with its type spelled, as the driver's tests see it", + "file": "../durable-ioring/src/lock_watch.rs", + "expect": "survives", + "why": "A control: the same binding with its type written out, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " let taken = mutex.lock().unwrap_or_else(PoisonError::into_inner);" + ], + "replace": [ + " let taken: MutexGuard<'_, T> = mutex.lock().unwrap_or_else(PoisonError::into_inner);" + ] + }, + { + "name": "DI-19.2.5: the one definition calls a seal's end a begin, as the driver's tests see it", + "file": "../durable-ioring/src/diagnostics.rs", + "expect": "caught", + "why": "DI-19.2.5: Diagnostic::pairing is the one definition of which events pair (DI-D-88). Changing it must fail this suite through the code that binds to it; a suite that held a copy of the rule would pass. The driver's chosen-sink test passes every event to PairingChecker, which takes the edge from pairing.", + "find": [ + " | Self::LockWaitBegan { id, .. } => (*id, Edge::Begin),", + " Self::SealEnded { id, .. }", + " | Self::FlushEnded { id, .. }" + ], + "replace": [ + " | Self::LockWaitBegan { id, .. }", + " | Self::SealEnded { id, .. } => (*id, Edge::Begin),", + " Self::FlushEnded { id, .. }" + ] + }, + { + "name": "DI-19.2.5: the checker refuses an end of its begin's own kind, as the driver's tests see it", + "file": "../durable-ioring/src/oracle/pairing.rs", + "expect": "caught", + "why": "DI-19.2.5: the rule that an end is of its begin's kind lives in PairingChecker alone. Inverting it there must fail the driver's tests, which hold their streams to it and do not restate it.", + "find": [ + " if begun.kind != pairing.kind {" + ], + "replace": [ + " if begun.kind == pairing.kind {" + ] + }, + { + "name": "DI-19.2.5 control: the checker gathers its open pairs by cloning, as the driver's tests see it", + "file": "../durable-ioring/src/oracle/pairing.rs", + "expect": "survives", + "why": "A control: the same ids gathered by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " let mut open: Vec = self.open.keys().copied().collect();" + ], + "replace": [ + " let mut open: Vec = self.open.keys().cloned().collect();" + ] } ] } diff --git a/crates/win-durable-ioring/src/debug_output.rs b/crates/win-durable-ioring/src/debug_output.rs new file mode 100644 index 000000000..1e19833c7 --- /dev/null +++ b/crates/win-durable-ioring/src/debug_output.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2026 Mike Grier +//! A diagnostic sink that writes each event to the debugger (DI-12.1.3, DI-D-51). + +use durable_ioring::{Diagnostic, DiagnosticKind, DiagnosticSink, LINE_PREFIX}; +use win_debugging_sys::output_debug_str; + +#[cfg(test)] +mod tests; + +/// A sink that writes every event as a line to the debugger, by `OutputDebugStringW`, prefixed `dioring:` as +/// [`StderrDiagnostics`](durable_ioring::StderrDiagnostics)'s lines are. +/// +/// It wants every kind. A line is built for each event, whether or not a debugger is attached: with neither a +/// debugger nor an active system debugger the call "does nothing", and it gives no way to tell, so a consumer +/// that does not want the cost chooses another sink, or a [`FanOutDiagnostics`](durable_ioring::FanOutDiagnostics) +/// that leaves this one out. +/// +/// The call takes a NUL-terminated string, so a NUL in an event's text, which a domain's name or an inconsistency's +/// detail can carry, is written as the two characters `\0` and the line is sent. Nothing else in the text is +/// changed. The call converts the line to the system's code page, and "some Unicode characters may not be +/// displayed correctly". +#[derive(Clone, Copy, Debug, Default)] +pub struct DebugOutputDiagnostics; + +impl DiagnosticSink for DebugOutputDiagnostics { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, event: &Diagnostic<'_>) { + let sent = output_debug_str(&line(event)); + // The line has no NUL, which is the call's one refusal, so it is made. + debug_assert!(sent.is_ok(), "the line had a NUL: {sent:?}"); + } +} + +/// The line an event is written as: the prefix, the event's text, and no NUL. +fn line(event: &Diagnostic<'_>) -> String { + format!("{LINE_PREFIX}{event}").replace('\0', "\\0") +} diff --git a/crates/win-durable-ioring/src/debug_output/tests.rs b/crates/win-durable-ioring/src/debug_output/tests.rs new file mode 100644 index 000000000..6e4f1828f --- /dev/null +++ b/crates/win-durable-ioring/src/debug_output/tests.rs @@ -0,0 +1,220 @@ +// Copyright (c) 2026 Mike Grier +//! The debugger sink's formatting, apart from its emission (DI-12.1.3): the line each event becomes, what it wants, +//! and the one change made to a line. That a line reaches a debugger is `tests/debug_output.rs`. + +use durable_ioring::{ + CorrelationId, Diagnostic, DiagnosticKind, DiagnosticSink, FlushDomain, FlushOutcome, + FlushPart, IoTargetKey, SealOutcome, +}; + +use super::{DebugOutputDiagnostics, line}; + +const KINDS: [DiagnosticKind; 4] = [ + DiagnosticKind::Inconsistency, + DiagnosticKind::LockWait, + DiagnosticKind::Seal, + DiagnosticKind::Flush, +]; + +/// A pair id, minted as the core mints one: the debugger sink wants every kind. +fn minted() -> CorrelationId { + CorrelationId::for_pair(&DebugOutputDiagnostics, DiagnosticKind::Seal).expect("wanted") +} + +#[test] +fn it_wants_every_kind() { + for kind in KINDS { + assert!(DebugOutputDiagnostics.wants(kind), "{kind:?}"); + } +} + +#[test] +fn an_inconsistency_is_a_dioring_line() { + let event = Diagnostic::Inconsistency { + instance: 7, + what: "a write completed twice", + detail: format_args!("lineage 3"), + }; + assert_eq!( + line(&event), + "dioring: instance 7 found its own state inconsistent: a write completed twice (lineage 3)" + ); +} + +#[test] +fn a_lock_waits_pair_is_two_dioring_lines() { + let id = minted(); + assert_eq!( + line(&Diagnostic::LockWaitBegan { + instance: 4, + lock: "engine", + id + }), + format!("dioring: instance 4 (pair {id}): waits for its engine lock") + ); + assert_eq!( + line(&Diagnostic::LockWaitEnded { + instance: 4, + lock: "engine", + id + }), + format!("dioring: instance 4 (pair {id}): took its engine lock") + ); +} + +#[test] +fn a_seals_pair_is_two_dioring_lines() { + let id = minted(); + assert_eq!( + line(&Diagnostic::SealBegan { + instance: 2, + lineage: 0, + through: format_args!("5"), + id + }), + format!("dioring: instance 2 lineage 0 seal through 5 (pair {id}): began") + ); + assert_eq!( + line(&Diagnostic::SealEnded { + instance: 2, + lineage: 0, + through: format_args!("5"), + id, + outcome: SealOutcome::Blocked + }), + format!("dioring: instance 2 lineage 0 seal through 5 (pair {id}): blocked") + ); +} + +#[test] +fn a_file_flush_is_two_dioring_lines() { + let id = minted(); + let part = FlushPart::File(IoTargetKey(7)); + assert_eq!( + line(&Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part, + id + }), + format!("dioring: instance 2 lineage 1 seal through 9: flush of file 7 (pair {id}) began") + ); + assert_eq!( + line(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part, + id, + outcome: FlushOutcome::Failed + }), + format!("dioring: instance 2 lineage 1 seal through 9: flush of file 7 (pair {id}) failed") + ); +} + +#[test] +fn a_domain_flush_end_is_a_dioring_line() { + let id = minted(); + let domain = FlushDomain::new(b"ssd-4".to_vec()); + assert_eq!( + line(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id, + outcome: FlushOutcome::Succeeded + }), + format!( + "dioring: instance 2 lineage 1 seal through 9: flush of domain \"ssd-4\" (pair {id}) succeeded" + ) + ); +} + +#[test] +fn a_nul_in_a_detail_is_written_as_backslash_zero_and_the_rest_is_left() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("a\0b\0\0c"), + }; + let text = line(&event); + assert_eq!( + text, + "dioring: instance 1 found its own state inconsistent: x (a\\0b\\0\\0c)" + ); + assert!(!text.contains('\0')); +} + +#[test] +fn a_nul_in_a_domains_name_is_written_as_backslash_zero() { + let domain = FlushDomain::new(vec![b'a', 0, b'b']); + let event = Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&domain), + id: minted(), + }; + let text = line(&event); + assert!(text.contains("domain \"a\\0b\""), "{text}"); + assert!(!text.contains('\0')); +} + +#[test] +fn a_backslash_and_a_zero_already_in_the_text_are_left_as_they_are() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("a\\0b"), + }; + assert_eq!( + line(&event), + "dioring: instance 1 found its own state inconsistent: x (a\\0b)" + ); +} + +#[test] +fn a_line_with_text_outside_ascii_keeps_it() { + let event = Diagnostic::Inconsistency { + instance: 1, + what: "\u{3042}", + detail: format_args!("\u{1f600}"), + }; + assert_eq!( + line(&event), + "dioring: instance 1 found its own state inconsistent: \u{3042} (\u{1f600})" + ); +} + +#[test] +fn reporting_makes_the_call_and_does_not_fail_for_any_event() { + // With no debugger the call does nothing; this shows each report is made, with a NUL in the text too. + let sink = DebugOutputDiagnostics; + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("a\0b"), + }); + let id = minted(); + sink.report(&Diagnostic::LockWaitBegan { + instance: 1, + lock: "engine", + id, + }); + sink.report(&Diagnostic::LockWaitEnded { + instance: 1, + lock: "engine", + id, + }); +} + +#[test] +fn the_sink_is_copy_default_and_debug() { + let a = DebugOutputDiagnostics; + let b = a; + let c: DebugOutputDiagnostics = Default::default(); + let _ = (a, b, c); + assert_eq!(format!("{a:?}"), "DebugOutputDiagnostics"); +} diff --git a/crates/win-durable-ioring/src/delivery.rs b/crates/win-durable-ioring/src/delivery.rs index 5deaed745..92c9a2706 100644 --- a/crates/win-durable-ioring/src/delivery.rs +++ b/crates/win-durable-ioring/src/delivery.rs @@ -29,6 +29,7 @@ use durable_ioring::{ DurableRing, EntryOf, EpochIdOf, FailureIdOf, HandleOf, Lin, PushResult, RegisteredBufferRing, TokenOf, }; +use durable_ioring::{ReadKey, TargetKey, WriteKey}; #[cfg(test)] mod tests; @@ -107,9 +108,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`DurableRing::add_target`]. - pub fn add_target( + pub fn add_target( &self, - key: IoTargetKey, + key: K, target: D::Target, ) -> Result<(), AddTargetError> { self.with(|ring| ring.add_target(key, target)) @@ -162,9 +163,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`DurableRing::write`]. - pub fn write( + pub fn write( &self, - file: IoTargetKey, + file: K, offset: u64, buffer: D::Buffer, tag: Tag<'_, D::Ids>, @@ -181,9 +182,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`DurableRing::write_with`]. - pub fn write_with( + pub fn write_with( &self, - file: IoTargetKey, + file: K, offset: u64, buffer: D::Buffer, tag: Tag<'_, D::Ids>, @@ -201,9 +202,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`DurableRing::read`]. - pub fn read( + pub fn read( &self, - file: IoTargetKey, + file: K, offset: u64, buffer: D::Buffer, context: D::Context, @@ -219,9 +220,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`DurableRing::read_with`]. - pub fn read_with( + pub fn read_with( &self, - file: IoTargetKey, + file: K, offset: u64, buffer: D::Buffer, context: D::Context, @@ -344,9 +345,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`RegisteredBufferRing::write_registered`]. - pub fn write_registered( + pub fn write_registered( &self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, tag: Tag<'_, D::Ids>, @@ -360,9 +361,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`RegisteredBufferRing::write_registered_with`]. - pub fn write_registered_with( + pub fn write_registered_with( &self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, tag: Tag<'_, D::Ids>, @@ -377,9 +378,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`RegisteredBufferRing::read_registered`]. - pub fn read_registered( + pub fn read_registered( &self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, context: D::Context, @@ -392,9 +393,9 @@ impl DeliveryHandle { /// # Errors /// /// As [`RegisteredBufferRing::read_registered_with`]. - pub fn read_registered_with( + pub fn read_registered_with( &self, - file: IoTargetKey, + file: K, offset: u64, span: RegisteredSpan, context: D::Context, @@ -459,10 +460,10 @@ impl + Send + 'static> EntryDelivery { /// /// # Errors /// - /// A [`DeliverySetupError`] handing back the instance and the handler, when duplicating the - /// readiness signal, creating the thread-pool wait, or setting the signal fails. Only the first - /// is reached by a test: the others fail when the process cannot get another handle or the - /// thread pool cannot create a wait. + /// A [`DeliverySetupError`] handing back the instance and the handler, when taking the + /// readiness signal from the instance, creating the thread-pool wait, or setting the signal + /// fails. Only the first is reached by a test: the others fail when the thread pool cannot + /// create a wait. pub fn new( mut ring: D, on_entry: F, @@ -471,11 +472,9 @@ impl + Send + 'static> EntryDelivery { where F: FnMut(DeliveryEvent>, &DeliveryHandle) + Send + 'static, { - let duplicates = ring - .readiness() - .and_then(|signal| Ok((signal.try_clone()?, signal))); - let (signal, kick) = match duplicates { - Ok(duplicates) => duplicates, + let clones = ring.readiness().map(|signal| (signal.clone(), signal)); + let (signal, kick) = match clones { + Ok(clones) => clones, Err(error) => { return Err(DeliverySetupError { error, diff --git a/crates/win-durable-ioring/src/delivery/tests.rs b/crates/win-durable-ioring/src/delivery/tests.rs index 874d4e0ed..5bde11bf0 100644 --- a/crates/win-durable-ioring/src/delivery/tests.rs +++ b/crates/win-durable-ioring/src/delivery/tests.rs @@ -10,7 +10,7 @@ use std::time::{Duration, Instant}; use windows_ioring_sys::RegisteredSpan; use super::{DeliveryEvent, EntryDelivery}; -use crate::dioring::tests::TempFile; +use crate::dioring::tests::{TempFile, wtf}; use crate::dioring::{DeliveryMode, Dioring, Setup, TargetSetup}; use crate::fake::{Fake, Signals}; use crate::windows_backend::Win32FileTarget; @@ -38,10 +38,7 @@ fn instance(temp: &TempFile, buffers: Vec>) -> Ring { Ring::new(Setup { submission_queue_size: 128, completion_queue_size: 128, - files: vec![TargetSetup { - key: FILE, - target: Win32FileTarget::new(temp.open()), - }], + files: vec![TargetSetup::new(FILE, Win32FileTarget::new(temp.open()))], buffers, provider: None, diagnostics: None, @@ -487,6 +484,41 @@ fn a_file_is_removed_through_the_handle() { )); } +#[test] +fn a_typed_key_is_judged_at_registration_through_the_handle_too() { + let temp = temp(); + let other = TempFile::new("delivery-typed"); + let (delivery, _rx) = forwarding(instance(&temp, Vec::new())); + let read_only = crate::windows_backend::OpenedFile::< + crate::windows_backend::Cached, + crate::windows_backend::ReadOnly, + >::open( + wtf(&other.0), + crate::windows_backend::FileOptions::new().read(), + ) + .expect("open read-only"); + + let error = delivery + .add_target( + durable_ioring::Key::::new(IoTargetKey(2)), + Win32FileTarget::new(read_only), + ) + .expect_err("the key declares more than a read-only target states"); + assert_eq!( + error.cause, + durable_ioring::AddTargetCause::AccessNotCovered { + declared: durable_ioring::Access::ReadWrite, + actual: durable_ioring::Access::ReadOnly, + } + ); + delivery + .add_target( + durable_ioring::Key::::new(IoTargetKey(2)), + error.target, + ) + .expect("the target comes back and registers under a key it covers"); +} + #[test] fn a_failed_pop_is_reported_to_the_handler_once_and_delivery_goes_on() { // DI-D-58: the wake that took the readiness edge is the only thing that told the consumer, so diff --git a/crates/win-durable-ioring/src/dioring.rs b/crates/win-durable-ioring/src/dioring.rs index 1affe3a21..c92331ffa 100644 --- a/crates/win-durable-ioring/src/dioring.rs +++ b/crates/win-durable-ioring/src/dioring.rs @@ -43,28 +43,31 @@ use crate::windows_backend::{ Construction, Win32FileTarget, WindowsBackend, WindowsBackendError, WindowsBackendRefusal, WindowsBackendSetup, }; +use durable_ioring::DiagnosticSink; use durable_ioring::DurableIoCore; use durable_ioring::IoTarget; use durable_ioring::StampClock; use durable_ioring::backend::{IoBackend, RegisteredRegion, SubmissionData}; use durable_ioring::driver::Home; +use durable_ioring::driver::LockWatch; use durable_ioring::driver::PumpedDelivery; use durable_ioring::driver::first_repeat; use durable_ioring::driver::{Answer, DomainCall}; use durable_ioring::driver::{Engine, EngineGuard}; use durable_ioring::{ - AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, Epoch, EpochState, - FailureInfo, FlushDomain, ImportScope, Imported, IoTargetKey, Leftovers, LineageInfo, - ReadOptions, RemoveTargetError, Resolution, ResolveError, RetireLineageError, + Access, AddTargetCause, AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, + Epoch, EpochState, FailureInfo, FlushDomain, ImportScope, Imported, IoTargetKey, Leftovers, + LineageInfo, ReadOptions, RemoveTargetError, Resolution, ResolveError, RetireLineageError, RetireLineageRefusal, SealError, Tag, UnknownLineage, WriteOptions, }; -use durable_ioring::{DiagnosticSink, NoDiagnostics}; use durable_ioring::{DioringIds, FailureId, FailureToken, Lineage, LineageHandle}; use durable_ioring::{ DomainCompletion, DomainRequest, DurabilityProvider, FlushRequest, ProviderPart, ProviderRequest, }; use durable_ioring::{DurableRing, EntryOf, EpochId, PushResult, RegisteredBufferRing}; +use durable_ioring::{ReadKey, TargetKey, WriteKey}; +use durable_ioring_events::EtwDiagnostics; mod delivery; mod hub; @@ -142,8 +145,10 @@ pub struct Setup<'env, 'pool, E: EpochId + 'static, R> { /// The consumer's durability provider, if any. `None` leaves every domain to the built-in /// default. pub provider: Option>>>, - /// Where the instance's diagnostics go (DI-D-51). `None` is the driver's default, which is - /// [`NoDiagnostics`] until `DI-12.1` makes it ETW. + /// Where the instance's diagnostics go (DI-D-51). `None` is the driver's default, [`EtwDiagnostics`]: the instance's begin and + /// end events go to the ETW provider `durable-ioring-events`, for a session that is listening, and cost a test of whether one + /// is when none is. Any sink can be given in its place, [`NoDiagnostics`](durable_ioring::NoDiagnostics) among them. + /// Building an instance with the default registers the provider for the process, if no instance has. pub diagnostics: Option>, /// How the instance's completions are drained (DI-D-52, DI-D-55). pub delivery: DeliveryMode<'env, 'pool>, @@ -178,6 +183,34 @@ pub struct TargetSetup { pub key: IoTargetKey, /// The target: its file, and what it states, flush domains among it. pub target: Win32FileTarget, + /// The access the consumer's key declares for it, if the key was a typed one (DI-13.10). + /// Construction is refused with [`SetupRefusal::AccessNotCovered`] when the target states + /// less. + pub declared: Option, +} + +impl TargetSetup { + /// `target` under `key`, which may be a [`Key`](durable_ioring::Key) declaring what the + /// target can do, or an untyped [`IoTargetKey`], which declares nothing. + #[must_use] + pub fn new(key: K, target: Win32FileTarget) -> Self { + Self { + key: key.untyped(), + declared: key.declared(), + target, + } + } + + /// What construction refuses this target for, if it declared more than it states. + fn refusal(&self) -> Option { + let declared = self.declared?; + let actual = self.target.access(); + (!actual.covers(declared)).then_some(SetupRefusal::AccessNotCovered { + key: self.key, + declared, + actual, + }) + } } /// Why construction failed. @@ -185,6 +218,15 @@ pub struct TargetSetup { pub enum SetupRefusal { /// Two targets share a key. DuplicateKey(IoTargetKey), + /// A target's key declared more access than the target states (DI-13.10). + AccessNotCovered { + /// The key. + key: IoTargetKey, + /// What the key declared. + declared: Access, + /// What the target states. + actual: Access, + }, /// The provider named a domain twice. DuplicateProviderDomain(FlushDomain), /// Creating the ring, making a registration, or wiring the ring's delivery failed. @@ -312,7 +354,7 @@ where diagnostics, delivery, } = setup; - let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(NoDiagnostics)); + let diagnostics = diagnostics.unwrap_or_else(|| Arc::new(EtwDiagnostics::new())); if let Some(key) = first_repeat(files.iter().map(|f| f.key)) { return Err(SetupError { @@ -322,6 +364,14 @@ where provider, }); } + if let Some(reason) = files.iter().find_map(TargetSetup::refusal) { + return Err(SetupError { + reason, + files, + buffers: Some(buffers), + provider, + }); + } let provider_domains = provider.as_ref().map(|p| p.domains()).unwrap_or_default(); if let Some(domain) = first_repeat(provider_domains.iter().cloned()) { return Err(SetupError { @@ -332,8 +382,8 @@ where }); } - let signals = Event::new(ResetMode::Auto, false) - .and_then(|readiness| Ok((readiness.try_clone()?, readiness))); + let signals = + Event::new(ResetMode::Auto, false).map(|readiness| (readiness.clone(), readiness)); let (ready, readiness) = match signals { Ok(signals) => signals, Err(error) => { @@ -352,13 +402,18 @@ where DurableIoCore::new(clock, Arc::clone(&diagnostics)) }; let keys: Vec = files.iter().map(|f| f.key).collect(); + // Kept so that a failed construction hands the files back as they were given. + let declarations: Vec<(IoTargetKey, Access)> = files + .iter() + .filter_map(|f| f.declared.map(|declared| (f.key, declared))) + .collect(); for file in &files { core.add_target(file.key, file.target.describe()) .expect("the keys were checked unique above"); } let targets = files .into_iter() - .map(|TargetSetup { key, target }| (key, target)) + .map(|TargetSetup { key, target, .. }| (key, target)) .collect(); let backend = match WindowsBackend::construct( WindowsBackendSetup { @@ -381,7 +436,7 @@ where }; return Err(SetupError { reason, - files: setups(targets), + files: setups(targets, &declarations), buffers, provider, }); @@ -389,6 +444,7 @@ where }; let default_lineage = core.default_lineage(); + let watch = LockWatch::new(default_lineage.instance.number(), Arc::clone(&diagnostics)); let on_ready = move || { // Setting an event this instance created, with full access, does not fail. let set = ready.set(); @@ -397,13 +453,15 @@ where let engine = Engine::new(core, backend); let delivered = match delivery { DeliveryMode::Pool(environment) => { - PoolDelivery::new(engine, on_ready, environment).map(Delivery::Pool) + PoolDelivery::new(engine, on_ready, environment, watch.clone()).map(Delivery::Pool) } // Nothing sets the signal the pool's wake would: entries are made poppable by the // pump, which sets the readiness event itself, as every other path does. - DeliveryMode::Pumped => PumpedDelivery::new(engine).map(Delivery::Pumped), + DeliveryMode::Pumped => PumpedDelivery::new(engine) + .map(|pumped| pumped.with_lock_watch(watch.clone())) + .map(Delivery::Pumped), DeliveryMode::Signalled(environment) => { - SignalledDelivery::new(engine, environment).map(Delivery::Signalled) + SignalledDelivery::new(engine, environment, watch.clone()).map(Delivery::Signalled) } }; let delivery = match delivered { @@ -413,9 +471,11 @@ where let files = keys .into_iter() .filter_map(|key| { - backend - .detach(key) - .map(|target| TargetSetup { key, target }) + backend.detach(key).map(|target| TargetSetup { + key, + target, + declared: declared_for(&declarations, key), + }) }) .collect(); return Err(SetupError { @@ -431,6 +491,7 @@ where readiness, default_lineage.instance, diagnostics, + watch, )); let default = LineageHandle::mint(default_lineage, home(&hub)); let handle = default.clone(); @@ -447,13 +508,27 @@ where } /// Targets handed back as they were given. -fn setups(targets: Vec<(IoTargetKey, Win32FileTarget)>) -> Vec { +fn setups( + targets: Vec<(IoTargetKey, Win32FileTarget)>, + declarations: &[(IoTargetKey, Access)], +) -> Vec { targets .into_iter() - .map(|(key, target)| TargetSetup { key, target }) + .map(|(key, target)| TargetSetup { + key, + target, + declared: declared_for(declarations, key), + }) .collect() } +/// What the key declared at construction, if it was a typed key. +fn declared_for(declarations: &[(IoTargetKey, Access)], key: IoTargetKey) -> Option { + declarations + .iter() + .find_map(|&(given, access)| (given == key).then_some(access)) +} + /// Where an instance's lineage handles report their release. fn home(hub: &Arc) -> Weak { let home: Weak = Arc::downgrade(hub); @@ -484,14 +559,20 @@ where self.hub.readiness() } - fn add_target( + fn add_target( &mut self, - key: IoTargetKey, + declared: T, target: Win32FileTarget, ) -> Result<(), AddTargetError> { + let key = declared.untyped(); let mut engine = self.engine(); - if engine.core.target(key).is_some() || engine.backend.target(key).is_some() { - return Err(AddTargetError { key, target }); + let cause = if engine.core.target(key).is_some() || engine.backend.target(key).is_some() { + Some(AddTargetCause::KeyInUse) + } else { + declared.admitted_by(target.access()).err() + }; + if let Some(cause) = cause { + return Err(AddTargetError { key, target, cause }); } engine .core @@ -516,9 +597,9 @@ where .expect("a target the core decided for is attached")) } - fn write_with( + fn write_with( &mut self, - file: IoTargetKey, + file: T, offset: u64, buffer: B, tag: Tag<'_, Self::Ids>, @@ -531,7 +612,7 @@ where let (pushed, attention) = { let mut engine = self.engine(); let pushed = engine.write( - file, + file.untyped(), offset, SubmissionData::Owned(buffer), tag.epoch(), @@ -544,9 +625,9 @@ where pushed } - fn read_with( + fn read_with( &mut self, - file: IoTargetKey, + file: T, offset: u64, buffer: B, context: C, @@ -561,7 +642,7 @@ where // (DI-D-17). engine.backend.enable_owned_reads(); let pushed = engine.read( - file, + file.untyped(), offset, SubmissionData::Owned(buffer), context, @@ -807,9 +888,9 @@ where pumped.waited.map(|_| pumped.ready) } - /// A duplicate of the event the thread pool sets whenever completions may be waiting, for the + /// A clone of the event the thread pool sets whenever completions may be waiting, for the /// consumer to wait on beside its own handles, and then to [`pump`](Self::pump) (DI-7.5). Each - /// call makes another duplicate of the one event. + /// call gives another clone, sharing the event's one handle. /// /// The event is the instance's own and shares nothing with the ring: it is auto-reset, so a wait /// takes the signal, and the consumer may set or reset it, which costs it nothing but its own @@ -819,7 +900,6 @@ where /// # Errors /// /// [`io::ErrorKind::Unsupported`] unless the instance was built with [`DeliveryMode::Signalled`]. - /// Otherwise the duplication's: the process cannot get another handle. pub fn completion_signal(&self) -> io::Result { self.delivery .as_ref() @@ -989,7 +1069,8 @@ fn region(span: RegisteredSpan) -> RegisteredRegion { /// The fault seam (`DI-3.2.4`): failures on demand, for tests and for the contract's worked /// examples. It transforms a commit's real completion through `windows-ioring-sys`' own seam, so /// the commit still reaches the kernel and only the answer dioring sees changes -- the backend's -/// `fail_next_commit`, under the contract's older word for a commit (DI-11.2). +/// `fail_next_commit`, under the contract's older word for a commit. It is kept beside the in-memory +/// backend's fault script, as another layer and not a copy of it (DI-D-75). #[cfg(feature = "fault-injection")] impl Dioring { /// Make the next flush of `file` to complete report the Win32 error `code` -- for example @@ -1022,9 +1103,9 @@ where self.engine().backend.registered_buffer_mut(i).map(f) } - fn write_registered_with( + fn write_registered_with( &mut self, - file: IoTargetKey, + file: T, offset: u64, span: RegisteredSpan, tag: Tag<'_, Self::Ids>, @@ -1032,7 +1113,7 @@ where options: WriteOptions, ) -> PushResult { self.engine().write( - file, + file.untyped(), offset, SubmissionData::Registered(region(span)), tag.epoch(), @@ -1041,16 +1122,16 @@ where ) } - fn read_registered_with( + fn read_registered_with( &mut self, - file: IoTargetKey, + file: T, offset: u64, span: RegisteredSpan, context: C, options: ReadOptions, ) -> PushResult { self.engine().read( - file, + file.untyped(), offset, SubmissionData::Registered(region(span)), context, diff --git a/crates/win-durable-ioring/src/dioring/delivery.rs b/crates/win-durable-ioring/src/dioring/delivery.rs index 2e83823cb..9c452c143 100644 --- a/crates/win-durable-ioring/src/dioring/delivery.rs +++ b/crates/win-durable-ioring/src/dioring/delivery.rs @@ -81,7 +81,7 @@ where } } - /// A duplicate of the event the pool sets, if the instance has one: `None` unless signalled. + /// A clone of the event the pool sets, if the instance has one: `None` unless signalled. pub(crate) fn signal(&self) -> Option> { match self { Self::Signalled(signalled) => Some(signalled.signal()), diff --git a/crates/win-durable-ioring/src/dioring/hub.rs b/crates/win-durable-ioring/src/dioring/hub.rs index bc67e80b1..4d3addff7 100644 --- a/crates/win-durable-ioring/src/dioring/hub.rs +++ b/crates/win-durable-ioring/src/dioring/hub.rs @@ -23,8 +23,8 @@ use durable_ioring::EpochId; use durable_ioring::Lineage; use durable_ioring::StampClock; use durable_ioring::driver::inconsistent; -use durable_ioring::driver::lock; use durable_ioring::driver::{Answer, DomainCall}; +use durable_ioring::driver::{ENGINE_LOCK, LockWatch, lock}; use durable_ioring::driver::{Home, InstanceId}; /// What an instance shares with its paths off the consumer's thread. @@ -42,7 +42,13 @@ where /// The instance, for what an inconsistency names. instance: InstanceId, diagnostics: Arc, + /// Times the waits for `engine`'s lock. + watch: LockWatch, /// Provider completions made and not yet answered, dropped ones included. + /// + /// Not taken through `watch`: it is held only for an increment or a decrement, so a wait for it + /// is not a delay worth reporting (the engineer, 2026-10-20). If more work comes to be done + /// with it held, that changes, and the wait for it may then warrant a lock name of its own. unanswered: Mutex, /// Notified when the last unanswered completion is answered. answered: Condvar, @@ -61,20 +67,22 @@ where readiness: Event, instance: InstanceId, diagnostics: Arc, + watch: LockWatch, ) -> Self { Self { engine, readiness, instance, diagnostics, + watch, unanswered: Mutex::new(0), answered: Condvar::new(), } } - /// A duplicate of the readiness event. + /// A clone of the readiness event, sharing its handle. pub(crate) fn readiness(&self) -> io::Result { - self.readiness.try_clone() + Ok(self.readiness.clone()) } /// Entries became poppable where there were none: set the readiness event (DI-D-28). Called @@ -106,7 +114,11 @@ where /// Act on the engine, if the instance has not ended, and signal if that made entries /// poppable. fn reach(&self, act: impl FnOnce(&mut PoolEngine) -> bool) { - let deliver = lock(&self.engine).as_mut().is_some_and(act); + let deliver = self + .watch + .lock(&self.engine, ENGINE_LOCK) + .as_mut() + .is_some_and(act); if deliver { self.signal(); } diff --git a/crates/win-durable-ioring/src/dioring/tests.rs b/crates/win-durable-ioring/src/dioring/tests.rs index e85057565..cd053bae8 100644 --- a/crates/win-durable-ioring/src/dioring/tests.rs +++ b/crates/win-durable-ioring/src/dioring/tests.rs @@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex, PoisonError}; use win_shared_os_owned_handle::SharedHandle; use win_time_sys::{InterruptTime, TimePoint}; +use wtf_string::Wtf16String; use super::{DeliveryMode, Dioring, Setup, SetupError, SetupRefusal, TargetSetup}; use crate::windows_backend::{FileOptions, OpenedFile, Win32FileTarget}; @@ -46,6 +47,12 @@ mod raw_disk; mod raw_volume; // A file opened unbuffered, through an instance: DI-D-60. mod unbuffered; +// A typed key at registration and at construction: DI-13.10. +mod keys; +// The lock-wait pairs through the driver: DI-12.1.1, DI-19.2.4. +mod delays; +// The default diagnostic sink, and a chosen one in its place: DI-12.1.6. +mod diagnostics; type Ring = Dioring>; type V = crate::WindowsIds; @@ -102,6 +109,11 @@ pub(crate) fn empty() -> Ring { .0 } +/// `path` as the open takes it (DI-13.7.1): the caller converts, and nothing is lost. +pub(crate) fn wtf(path: &Path) -> Wtf16String { + Wtf16String::from_os_str(path.as_os_str()) +} + /// A temporary file, removed when dropped. pub(crate) struct TempFile(pub(crate) PathBuf); @@ -118,7 +130,7 @@ impl TempFile { } pub(crate) fn open(&self) -> OpenedFile { - OpenedFile::open(&self.0, FileOptions::new().read(true).write(true)) + OpenedFile::open(wtf(&self.0), FileOptions::new().read().write()) .expect("open the temporary file") } @@ -147,10 +159,7 @@ fn domain(name: &str) -> FlushDomain { } fn file(key: u64, temp: &TempFile, domains: &[&str]) -> TargetSetup { - TargetSetup { - key: IoTargetKey(key), - target: target(temp.open(), domains), - } + TargetSetup::new(IoTargetKey(key), target(temp.open(), domains)) } /// A target over `file`, declared with `domains`. diff --git a/crates/win-durable-ioring/src/dioring/tests/delays.rs b/crates/win-durable-ioring/src/dioring/tests/delays.rs new file mode 100644 index 000000000..fd09a503f --- /dev/null +++ b/crates/win-durable-ioring/src/dioring/tests/delays.rs @@ -0,0 +1,141 @@ +// Copyright (c) 2026 Mike Grier +//! The lock-wait pairs through the driver (DI-12.1.1, DI-D-80): a wait for the engine's lock made by a +//! path of the instance's that is not the consumer's own -- the hub a lineage handle releases +//! through -- is reported to the instance's sink, as the consumer's own calls' are +//! (`windows_backend/pool/tests.rs`). + +use std::sync::mpsc::{self, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, ThreadId}; +use std::time::Duration; + +use super::{Ring, setup}; +use crate::dioring::{DeliveryMode, Setup}; +use durable_ioring::driver::{ENGINE_LOCK, Home, lock}; +use durable_ioring::oracle::PairingChecker; +use durable_ioring::{Diagnostic, DiagnosticKind, DiagnosticSink, Lineage}; + +const BOUND: Duration = Duration::from_secs(5); + +/// Says when the test's own thread has found a lock held -- the sink is asked whether it wants the +/// wait as soon as there is one -- and keeps what is reported, with the thread that waited. A pool's +/// own thread drains once on the ring's setup signal and may wait on the same lock, which is why the +/// thread is part of the record. +struct Contended { + test_thread: ThreadId, + found_held: Mutex>, + waits: Mutex>, + /// The pairing rule over every event reported (DI-D-87). + checker: Mutex, +} + +impl DiagnosticSink for Contended { + fn wants(&self, kind: DiagnosticKind) -> bool { + if kind == DiagnosticKind::LockWait && thread::current().id() == self.test_thread { + let _ = self.found_held.lock().expect("the signal").send(()); + } + kind == DiagnosticKind::LockWait + } + + fn report(&self, event: &Diagnostic<'_>) { + self.checker + .lock() + .expect("the checker") + .observe(event) + .expect("the pairing rule"); + match event { + Diagnostic::LockWaitBegan { instance, lock, .. } => { + self.waits.lock().expect("the record").push(( + thread::current().id(), + *instance, + (*lock).to_owned(), + "began", + )); + } + Diagnostic::LockWaitEnded { instance, lock, .. } => { + self.waits.lock().expect("the record").push(( + thread::current().id(), + *instance, + (*lock).to_owned(), + "ended", + )); + } + other => panic!("reported {other}"), + } + } +} + +fn watched(delivery: DeliveryMode<'static, 'static>) -> (Ring, Arc, mpsc::Receiver<()>) { + let (found_held, signal) = mpsc::channel(); + let sink = Arc::new(Contended { + test_thread: thread::current().id(), + found_held: Mutex::new(found_held), + waits: Mutex::default(), + checker: Mutex::default(), + }); + let ring = Ring::new(Setup { + delivery, + diagnostics: Some(sink.clone()), + ..setup(Vec::new(), Vec::new(), None) + }) + .expect("build an instance") + .0; + (ring, sink, signal) +} + +/// Run `act`, here, while another thread holds the engine's slot until `act` has found it held, and +/// check that the wait was reported by this thread, once, as a wait for the engine's lock. +fn waits_reported(delivery: DeliveryMode<'static, 'static>, act: impl Fn(&Ring, Lineage)) { + let (ring, sink, signal) = watched(delivery); + let lineage = ring.default.lineage(); + let instance = lineage.instance.number(); + let slot = ring.delivery.as_ref().expect("live").shared(); + let (holding, held) = mpsc::channel(); + thread::scope(|scope| { + let holder = scope.spawn(move || { + let guard = lock(&slot); + holding.send(()).expect("the test is listening"); + signal + .recv_timeout(BOUND) + .expect("the call found the engine held"); + drop(guard); + }); + held.recv_timeout(BOUND).expect("the slot was taken"); + act(&ring, lineage); + holder.join().expect("the holder finished"); + }); + let waits = sink.waits.lock().expect("the record"); + let ours: Vec<_> = waits + .iter() + .filter(|(waiter, ..)| *waiter == thread::current().id()) + .collect(); + // The pair's end is written on the thread that waited, and its identity is the checker's to hold. + let ours: Vec<_> = ours + .iter() + .map(|(_, instance, lock, edge)| (*instance, lock.as_str(), *edge)) + .collect(); + assert_eq!( + ours, + [ + (instance, ENGINE_LOCK, "began"), + (instance, ENGINE_LOCK, "ended") + ], + "{waits:?}" + ); +} + +fn each_delivery(act: impl Fn(&Ring, Lineage)) { + waits_reported(DeliveryMode::Pool(None), &act); + waits_reported(DeliveryMode::Pumped, &act); + waits_reported(DeliveryMode::Signalled(None), &act); +} + +#[test] +fn a_release_that_waits_for_the_engine_is_reported_as_a_wait_for_the_engines_lock() { + each_delivery(|ring, lineage| ring.hub.release(lineage)); +} + +#[test] +fn a_consumers_call_that_waits_for_the_engine_is_reported_as_a_wait_for_the_engines_lock() { + each_delivery(|ring, _| drop(ring.engine())); +} diff --git a/crates/win-durable-ioring/src/dioring/tests/diagnostics.rs b/crates/win-durable-ioring/src/dioring/tests/diagnostics.rs new file mode 100644 index 000000000..2feaa31cb --- /dev/null +++ b/crates/win-durable-ioring/src/dioring/tests/diagnostics.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2026 Mike Grier +//! The driver's diagnostic sink (DI-12.1.6, DI-D-51): a sink given at construction replaces the default, and an instance given +//! none writes its events to ETW. The registration the default makes is `tests/default_diagnostics.rs`, which needs a process of +//! its own. + +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use durable_ioring::oracle::PairingChecker; +use durable_ioring::{Diagnostic, DiagnosticKind, DiagnosticSink}; +use durable_ioring_events::{EtwDiagnostics, PROVIDER_ID, id}; +use win_etw_sys::{Session, level}; + +use super::pushes::{GIVEN, Harness, Ring, given, instance, temp}; +use super::setup; +use crate::dioring::Setup; + +/// A sink that keeps each event as text, wants every kind, and holds the stream to the pairing rule +/// (DI-D-87), which it takes from the core's checker and does not restate. +#[derive(Default)] +struct Recording(Mutex>, Mutex); + +impl Recording { + /// Every pair the instance began has ended. + fn finished(&self) { + let checker = std::mem::take(&mut *self.1.lock().expect("the checker")); + checker.finish(&[]).expect("every pair ended"); + } +} + +impl DiagnosticSink for Recording { + fn wants(&self, _: DiagnosticKind) -> bool { + true + } + + fn report(&self, event: &Diagnostic<'_>) { + self.1 + .lock() + .expect("the checker") + .observe(event) + .expect("the pairing rule"); + self.0.lock().expect("the record").push(event.to_string()); + } +} + +/// Writes to the file and seals through epoch 1, and waits for it to be durable. +fn write_and_seal(harness: &mut Harness) { + harness.write(GIVEN, 0, b"first", 1, 1); + harness.next(); + harness.seal(1); + match harness.next_entry() { + durable_ioring::Entry::Durable { .. } => {} + other => panic!("expected Durable, got {other:?}"), + } +} + +#[test] +fn a_chosen_sink_receives_the_instances_events_in_place_of_the_default() { + let temp = temp(&[0; 64]); + let sink = Arc::new(Recording::default()); + let ring = Ring::new(Setup { + diagnostics: Some(sink.clone()), + ..setup(vec![given(GIVEN, &temp)], Vec::new(), None) + }) + .expect("build an instance") + .0; + let mut harness = Harness::new(ring); + write_and_seal(&mut harness); + harness.finish(); + sink.finished(); + + let lines = sink.0.lock().expect("the record").clone(); + // Pair ids are minted per process, so each event is found by its shape around one. + let found = |before: &str, after: &str| { + lines + .iter() + .any(|line| line.contains(before) && line.ends_with(after)) + }; + assert!(found("seal through 1 (pair ", "): began"), "{lines:?}"); + assert!(found("flush of file 2 (pair ", ") began"), "{lines:?}"); + assert!(found("flush of file 2 (pair ", ") succeeded"), "{lines:?}"); + assert!(found("seal through 1 (pair ", "): durable"), "{lines:?}"); + let me = instance_named_by(&lines); + assert!( + lines + .iter() + .all(|line| line.starts_with(&format!("instance {me} "))), + "{lines:?}" + ); +} + +/// The instance number every line of an instance's events names. +fn instance_named_by(lines: &[String]) -> u64 { + lines[0] + .split_whitespace() + .nth(1) + .expect("a number after `instance`") + .parse() + .expect("a number") +} + +#[test] +fn a_chosen_sink_that_wants_nothing_is_not_asked_to_report() { + struct Nothing(Mutex); + impl DiagnosticSink for Nothing { + fn wants(&self, _: DiagnosticKind) -> bool { + false + } + fn report(&self, _: &Diagnostic<'_>) { + *self.0.lock().expect("the count") += 1; + } + } + let temp = temp(&[0; 64]); + let sink = Arc::new(Nothing(Mutex::new(0))); + let ring = Ring::new(Setup { + diagnostics: Some(sink.clone()), + ..setup(vec![given(GIVEN, &temp)], Vec::new(), None) + }) + .expect("build an instance") + .0; + let mut harness = Harness::new(ring); + write_and_seal(&mut harness); + harness.finish(); + assert_eq!(*sink.0.lock().expect("the count"), 0); +} + +/// Whether `condition` becomes true within a bound: a provider registered before a session enabled it is told so afterwards. +fn becomes(condition: impl Fn() -> bool) -> bool { + let started = Instant::now(); + while started.elapsed() < Duration::from_secs(10) { + if condition() { + return true; + } + std::thread::sleep(Duration::from_millis(5)); + } + false +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_instance_with_no_chosen_sink_writes_its_events_to_etw() { + let mut session = Session::start().expect("a trace session (this test needs privilege)"); + session + .enable(PROVIDER_ID, level::VERBOSE, u64::MAX) + .expect("enabled"); + // Other tests of this process share the provider's registration, which may have been made before the session was + // enabled, so the session's arrival is waited for. + let probe = EtwDiagnostics::new(); + assert!(becomes(|| probe.wants(DiagnosticKind::Flush))); + + let temp = temp(&[0; 64]); + let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); + let me = harness.lineage().instance.number(); + write_and_seal(&mut harness); + harness.finish(); + let recorded = session.stop().expect("stopped and read"); + + // A session records every instance of the process, so this one's events are those that name it first. + let ours: Vec = recorded + .iter() + .filter(|event| u64::from_ne_bytes(event.data[..8].try_into().expect("an instance")) == me) + .map(|event| event.descriptor.id) + .collect(); + for event in [ + id::SEAL_BEGAN, + id::FLUSH_BEGAN_FILE, + id::FLUSH_ENDED_FILE, + id::SEAL_ENDED, + ] { + assert!(ours.contains(&event), "{event} in {ours:?}"); + } +} diff --git a/crates/win-durable-ioring/src/dioring/tests/ending.rs b/crates/win-durable-ioring/src/dioring/tests/ending.rs index b62900641..d8ca92e5a 100644 --- a/crates/win-durable-ioring/src/dioring/tests/ending.rs +++ b/crates/win-durable-ioring/src/dioring/tests/ending.rs @@ -19,14 +19,11 @@ use durable_ioring::backend::SubmissionData; use durable_ioring::driver::{Answer, DomainCall, Home}; use durable_ioring::{DomainOutcome, DurabilityProvider}; use durable_ioring::{ - DurabilityRequest, Entry, Epoch, Failed, IoTargetBusy, IoTargetKey, OpKind, PushRefusal, - ReadOptions, RemoveTargetError, Resolution, WriteOptions, + DurabilityRequest, Epoch, IoTargetBusy, IoTargetKey, OpKind, PushRefusal, ReadOptions, + RemoveTargetError, WriteOptions, }; use durable_ioring::{DurableRing, RegisteredBufferRing}; -/// `ERROR_IO_DEVICE`, the failure the seam is armed with. -const IO_DEVICE: u32 = 1117; - /// `key`'s refusal, which must be `Busy`. fn busy(ring: &mut Ring, key: IoTargetKey) -> IoTargetBusy { match ring.remove_target(key) { @@ -36,13 +33,6 @@ fn busy(ring: &mut Ring, key: IoTargetKey) -> Io } } -fn next_failed(harness: &mut Harness) -> Failed { - match harness.next_entry() { - Entry::Failed(failed) => failed, - other => panic!("expected Failed, got {other:?}"), - } -} - /// The message a caught panic carried. fn message(payload: &(dyn std::any::Any + Send)) -> String { payload @@ -169,33 +159,6 @@ fn held_operations_hold_their_file_with_the_lowest_gate_in_each_lineage() { assert_eq!(left.held.len(), 3); } -#[test] -fn a_failure_suspecting_a_write_holds_its_file_until_it_is_resolved() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - harness.write(GIVEN, 0, b"one", 1, 1); - harness.next(); - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - assert_eq!(harness.seal(1), DurabilityRequest::Submitted); - let failed = next_failed(&mut harness); - - let refused = busy(&mut harness.ring, GIVEN); - assert_eq!(refused.failures, [failed.id]); - assert_eq!( - refused.uncovered, 0, - "its suspect write is reported by the failure" - ); - assert!(refused.to_string().contains("heal or abandon"), "{refused}"); - - harness - .ring - .resolve(vec![(failed.token, Resolution::Abandon)]) - .expect("abandon"); - assert!(matches!(harness.next_entry(), Entry::Abandoned { .. })); - harness.ring.remove_target(GIVEN).expect("resolved"); - harness.finish(); -} - /// An operation is held in flight on demand by holding the engine's lock: no pool drain can record /// its completion until the lock is released. #[test] @@ -325,21 +288,6 @@ fn closing_hands_back_held_operations_in_push_order_with_what_they_took() { ); } -#[test] -fn closing_hands_back_the_unresolved_failures() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - harness.write(GIVEN, 0, b"one", 1, 1); - harness.next(); - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - harness.seal(1); - let failed = next_failed(&mut harness); - let left = harness.ring.close(); - let ids: Vec<_> = left.failures.iter().map(|f| f.id).collect(); - assert_eq!(ids, [failed.id]); - assert_eq!(left.lineages[0].durable_through, None); -} - /// End an instance whose provider holds an unanswered request, answering it from another thread /// once `delay` has passed; `end` ends the instance. Returns whether the answer had been given by /// the time `end` returned. diff --git a/crates/win-durable-ioring/src/dioring/tests/failures.rs b/crates/win-durable-ioring/src/dioring/tests/failures.rs index 26229f7bb..d898308a8 100644 --- a/crates/win-durable-ioring/src/dioring/tests/failures.rs +++ b/crates/win-durable-ioring/src/dioring/tests/failures.rs @@ -1,19 +1,16 @@ // Copyright (c) 2026 Mike Grier -//! Failures, their reach and their resolution (DI-3.2.4), against real files, with flush failures -//! made on demand by the fault seam. Every entry is reported to the conformance oracle, so each -//! test also checks that `Durable` never passes an unresolved failure and that every suspect write -//! was accepted, in push order. +//! Failures against real files (DI-3.2.4): what the Windows driver adds to them -- a flush's failure +//! relayed from the ring, and the fault seam itself -- and imports. How the core answers a failed +//! flush is tested in `durable-ioring`, over its in-memory backend's script (DI-18.2). Every entry is +//! reported to the conformance oracle, so each test also checks that `Durable` never passes an +//! unresolved failure and that every suspect write was accepted, in push order. -use windows_ioring_sys::{IoRingErrorExt, RegisteredSpan}; +use windows_ioring_sys::IoRingErrorExt; use super::pushes::{ADDED, GIVEN, Harness, Ring, given, instance, temp}; use super::{empty, file, setup}; -use durable_ioring::{ - DurabilityRequest, Entry, EpochState, Failed, FailureCause, ImportScope, PushRefusal, - Resolution, ResolveRefusal, UnknownLineage, -}; -use durable_ioring::{DurableRing, RegisteredBufferRing}; -use durable_ioring::{FailureId, FailureToken}; +use durable_ioring::DurableRing; +use durable_ioring::{DurabilityRequest, Entry, Failed, FailureCause, ImportScope, UnknownLineage}; /// `ERROR_IO_DEVICE`, the failure the seam is armed with. const IO_DEVICE: u32 = 1117; @@ -33,19 +30,6 @@ fn suspects(failed: &Failed) -> Vec { failed.suspect.writes().iter().map(|w| w.op.seq).collect() } -/// Resolve, reporting each heal to the oracle. -fn resolve(harness: &mut Harness, items: Vec<(FailureToken, Resolution)>) { - let healed: Vec = items - .iter() - .filter(|(_, resolution)| *resolution == Resolution::Heal) - .map(|(token, _)| token.id()) - .collect(); - harness.ring.resolve(items).expect("resolve"); - for id in healed { - harness.oracle.healed(id); - } -} - /// Two writes tagged 1 to `GIVEN`, completed, then sealed through 1 with its flush failing. fn failed_seal(harness: &mut Harness) -> Failed { harness.write(GIVEN, 0, b"first", 1, 1); @@ -56,8 +40,10 @@ fn failed_seal(harness: &mut Harness) -> Failed { next_failed(harness) } +/// The Windows half of a test whose core half -- the suspects, the mark held, the inventory -- moved to +/// `durable-ioring` (DI-18.2): the failure's cause is the ring's, relayed. #[test] -fn a_failed_flush_is_reported_with_its_cause_and_holds_the_mark() { +fn a_failed_flush_is_reported_with_the_rings_cause() { let temp = temp(&[0; 64]); let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); let failed = failed_seal(&mut harness); @@ -77,126 +63,6 @@ fn a_failed_flush_is_reported_with_its_cause_and_holds_the_mark() { } other => panic!("expected a flush failure, got {other:?}"), } - assert_eq!(suspects(&failed), [0, 1]); - assert_eq!(harness.durable(), None); - assert_eq!(harness.state(1), EpochState::Blocked(failed.id)); - assert_eq!( - harness.seal(1), - DurabilityRequest::AlreadySealed(EpochState::Blocked(failed.id)) - ); - let inventory = harness.ring.failures(); - assert_eq!(inventory.len(), 1); - assert_eq!(inventory[0].id, failed.id); - assert!(inventory[0].token_live); - assert!(harness.ring.pop().expect("pop").is_none(), "one answer"); - drop(failed); - harness.finish(); -} - -/// CONTRACT.md's "Healing a failure", through the ring. -#[test] -fn a_healed_failure_is_passed_at_the_first_seal_after_the_heal() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - let failed = failed_seal(&mut harness); - let refused = harness - .ring - .write(GIVEN, 0, vec![1], harness.default.at(1), 9) - .expect_err("1 is sealed"); - assert_eq!(refused.context, 9); - - harness.write(GIVEN, 0, b"FIRST", 2, 3); - harness.write(GIVEN, 8, b"SECOND", 2, 4); - harness.next_n(2); - resolve(&mut harness, vec![(failed.token, Resolution::Heal)]); - assert!( - harness.ring.take_token(failed.id).is_none(), - "the heal holds it" - ); - assert_eq!(harness.seal(2), DurabilityRequest::Submitted); - match harness.next_entry() { - Entry::Healed { - failure, - suspect, - markings, - } => { - assert_eq!(failure, failed.id); - assert_eq!(suspect.writes(), failed.suspect.writes()); - assert!(markings.is_empty(), "nothing happened to 1's writes after"); - } - other => panic!("expected Healed before any Durable, got {other:?}"), - } - for id in [1, 2] { - match harness.next_entry() { - Entry::Durable { through } => assert_eq!(through, harness.epoch(id)), - other => panic!("expected Durable through {id}, got {other:?}"), - } - } - assert!(harness.ring.failures().is_empty()); - assert_eq!(harness.state(1), EpochState::Durable); - harness.finish(); -} - -#[test] -fn without_the_heal_a_later_seal_is_answered_blocked() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - let failed = failed_seal(&mut harness); - harness.write(GIVEN, 0, b"again", 2, 3); - harness.next(); - assert_eq!(harness.seal(2), DurabilityRequest::Submitted); - match harness.next_entry() { - Entry::Blocked { through, by } => { - assert_eq!((through, by), (harness.epoch(2), failed.id)); - } - other => panic!("expected Blocked, got {other:?}"), - } - assert_eq!(harness.state(2), EpochState::Blocked(failed.id)); - resolve(&mut harness, vec![(failed.token, Resolution::Abandon)]); - match harness.next_entry() { - Entry::Abandoned { - failure, suspect, .. - } => { - assert_eq!(failure, failed.id); - assert_eq!(suspect.writes().len(), 2); - } - other => panic!("expected Abandoned, got {other:?}"), - } - let mut durable = Vec::new(); - for _ in 0..2 { - match harness.next_entry() { - Entry::Durable { through } => durable.push(through), - other => panic!("expected Durable, got {other:?}"), - } - } - assert_eq!(durable, [harness.epoch(1), harness.epoch(2)]); - assert_eq!(harness.state(1), EpochState::Abandoned); - assert_eq!(harness.state(2), EpochState::Durable); - harness.finish(); -} - -#[test] -fn a_failed_flush_reaches_only_files_sharing_a_declared_domain() { - let log = temp(&[0; 16]); - let data = temp(&[0; 16]); - let ring = Ring::new(setup( - vec![ - file(GIVEN.0, &log, &["disk-1"]), - file(ADDED.0, &data, &["disk-2"]), - ], - Vec::new(), - None, - )) - .expect("an instance with two domains") - .0; - let mut harness = Harness::new(ring); - harness.write(GIVEN, 0, b"log", 1, 1); - harness.write(ADDED, 0, b"data", 1, 2); - harness.next_n(2); - harness.ring.fail_next_flush(ADDED, IO_DEVICE); - harness.seal(1); - let failed = next_failed(&mut harness); - assert_eq!(suspects(&failed), [1], "the log's disk is not the data's"); drop(failed); harness.finish(); } @@ -295,105 +161,6 @@ fn an_imported_domain_the_instance_was_never_given_is_reported_and_reaches_the_u harness.finish(); } -#[test] -fn a_resolution_naming_another_instances_failure_is_refused_whole() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - let ours = failed_seal(&mut harness); - let mut other = instance(Vec::new(), Vec::new()); - other.import_failure(ImportScope::All).expect("import"); - let theirs = match other.pop().expect("pop") { - Some(Entry::Failed(failed)) => failed, - other => panic!("expected Failed, got {other:?}"), - }; - let error = harness - .ring - .resolve(vec![ - (ours.token, Resolution::Abandon), - (theirs.token, Resolution::Abandon), - ]) - .expect_err("a foreign token"); - assert!(matches!(error.reason, ResolveRefusal::Foreign(id) if id == theirs.id)); - assert_eq!(error.returned.len(), 2); - assert!( - harness.ring.pop().expect("pop").is_none(), - "nothing was abandoned" - ); - assert_eq!(harness.ring.failures().len(), 1); - harness.finish(); -} - -#[test] -fn a_closed_failure_stays_and_its_token_can_be_taken_again() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], Vec::new())); - let failed = failed_seal(&mut harness); - assert!(harness.ring.take_token(failed.id).is_none(), "live"); - failed.token.close(); - assert!(!harness.ring.failures()[0].token_live); - let token = harness.ring.take_token(failed.id).expect("closed"); - resolve(&mut harness, vec![(token, Resolution::Abandon)]); - assert!(matches!(harness.next_entry(), Entry::Abandoned { .. })); - assert!(matches!(harness.next_entry(), Entry::Durable { .. })); - assert!( - harness.ring.take_token(failed.id).is_none(), - "resolved, and leaves no memory" - ); - harness.finish(); -} - -#[test] -fn a_write_into_an_abandoned_epoch_is_refused_with_what_it_took() { - let temp = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &temp)], vec![vec![0; 16]])); - harness.write(GIVEN, 0, b"sealed", 1, 1); - harness.write(GIVEN, 8, b"open", 3, 2); - harness.next_n(2); - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - harness.seal(1); - let failed = next_failed(&mut harness); - assert_eq!(suspects(&failed), [0, 1], "sealed 1 and open 3"); - resolve(&mut harness, vec![(failed.token, Resolution::Abandon)]); - assert!(matches!(harness.next_entry(), Entry::Abandoned { .. })); - assert!(matches!(harness.next_entry(), Entry::Durable { .. })); - - let three = harness.epoch(3); - let error = harness - .ring - .write(GIVEN, 0, vec![7], harness.default.at(3), 5) - .expect_err("3 was abandoned, though it is open"); - assert!(matches!(error.reason, PushRefusal::EpochAbandoned { epoch } if epoch == three)); - assert_eq!((error.buffer, error.context), (Some(vec![7]), 5)); - let span = RegisteredSpan { - buffer_index: 0, - offset: 0, - len: 4, - }; - let error = harness - .ring - .write_registered(GIVEN, 0, span, harness.default.at(3), 6) - .expect_err("a registered write to 3"); - assert!(matches!(error.reason, PushRefusal::EpochAbandoned { .. })); - assert_eq!((error.buffer, error.context), (None, 6)); - let error = harness - .ring - .write(GIVEN, 0, vec![7], harness.default.at(1), 7) - .expect_err("1 is sealed and abandoned"); - assert!( - matches!(error.reason, PushRefusal::Sealed { .. }), - "the seal is checked first: {:?}", - error.reason - ); - assert!( - harness.ring.pop().expect("pop").is_none(), - "a refused write completes nothing" - ); - - harness.write(GIVEN, 0, b"later", 4, 8); - harness.next(); - harness.finish(); -} - #[test] fn an_armed_failure_is_used_by_its_own_file_only() { let first = temp(&[0; 16]); diff --git a/crates/win-durable-ioring/src/dioring/tests/keys.rs b/crates/win-durable-ioring/src/dioring/tests/keys.rs new file mode 100644 index 000000000..5d321c3b6 --- /dev/null +++ b/crates/win-durable-ioring/src/dioring/tests/keys.rs @@ -0,0 +1,249 @@ +// Copyright (c) 2026 Mike Grier +//! Typed keys against a real ring (DI-13.10, DI-D-66): registration compares what a key declares +//! with what the target states, and a typed key then carries writes and reads like any other. +//! +//! That a write through a `Key` does not compile is held by the doctests on `WriteKey` +//! and `ReadKey` in `durable-ioring`; what is held here is what only a ring can show. + +use durable_ioring::{ + Access, AddTargetCause, DurableRing, IoTarget, IoTargetKey, Key, OpKind, PushRefusal, ReadOnly, + ReadWrite, WriteOnly, +}; + +use super::pushes::{ADDED, Harness, instance, read_only, temp, transferred}; +use super::{Ring, SetupRefusal, TargetSetup, refused, serves, setup}; +use crate::windows_backend::Win32FileTarget; + +/// A ring built from nothing, wrapped for pushes. +fn empty_harness() -> Harness { + Harness::new(instance(Vec::new(), Vec::new())) +} + +#[test] +fn typed_keys_register_a_target_and_carry_its_writes_and_reads() { + let temp = temp(&[0; 32]); + let mut harness = empty_harness(); + harness + .ring + .add_target( + Key::::new(ADDED), + Win32FileTarget::new(temp.open()), + ) + .expect("a read-write target under a read-write key"); + + let bytes = b"typed"; + let epoch = harness.epoch(1); + let write = harness + .ring + .write( + Key::::new(ADDED), + 4, + bytes.to_vec(), + harness.default.at(1), + 1, + ) + .expect("a write through a key that can write"); + harness.pushed(write, OpKind::Write { epoch }, 5, 1); + let written = harness.next(); + assert_eq!(transferred(&written), 5); + + let read = harness + .ring + .read(Key::::new(ADDED), 4, vec![0; 5], 2) + .expect("a read through a key that can read"); + harness.pushed(read, OpKind::Read, 5, 2); + let back = harness.next(); + assert_eq!(back.buffer.as_deref(), Some(&bytes[..])); + harness.finish(); +} + +#[test] +fn a_key_that_declares_more_than_the_target_states_is_refused_and_the_target_comes_back() { + let temp = temp(&[0; 8]); + let mut harness = empty_harness(); + let target = Win32FileTarget::new(read_only(&temp)); + let error = harness + .ring + .add_target(Key::::new(ADDED), target) + .expect_err("a read-only target is not covered by a read-write key"); + assert_eq!(error.key, ADDED); + assert_eq!( + error.cause, + AddTargetCause::AccessNotCovered { + declared: Access::ReadWrite, + actual: Access::ReadOnly, + } + ); + assert_eq!(error.target.access(), Access::ReadOnly); + assert!( + harness.ring.engine().core.target(ADDED).is_none(), + "nothing was registered, so the key is free" + ); + + harness + .ring + .add_target(Key::::new(ADDED), error.target) + .expect("the target registers under a key it covers"); + assert!(harness.ring.engine().core.target(ADDED).is_some()); +} + +#[test] +fn a_key_that_declares_less_is_a_view_and_does_not_restrict_the_target() { + let temp = temp(&[0; 16]); + let mut harness = empty_harness(); + harness + .ring + .add_target( + Key::::new(ADDED), + Win32FileTarget::new(temp.open()), + ) + .expect("a read-write target under a read-only key"); + + // The declaration is the holder's; the ring judges what is pushed by the target. + harness.write(ADDED, 0, b"still", 1, 3); + assert_eq!(transferred(&harness.next()), 5); + harness.finish(); +} + +#[test] +fn an_untyped_key_declares_nothing_so_any_target_registers_under_it() { + let temp = temp(&[0; 8]); + let mut harness = empty_harness(); + harness + .ring + .add_target(ADDED, Win32FileTarget::new(read_only(&temp))) + .expect("an untyped key is admitted by a read-only target"); +} + +#[test] +fn a_key_already_in_use_is_refused_as_such_before_its_declaration_is_judged() { + let (a, b) = (temp(&[0; 8]), temp(&[0; 8])); + let mut harness = empty_harness(); + harness + .ring + .add_target(ADDED, Win32FileTarget::new(a.open())) + .expect("the first target"); + let error = harness + .ring + .add_target( + Key::::new(ADDED), + Win32FileTarget::new(read_only(&b)), + ) + .expect_err("the key is taken"); + assert_eq!(error.cause, AddTargetCause::KeyInUse); +} + +#[test] +fn a_write_through_a_target_that_cannot_write_is_still_refused_at_the_push_by_an_untyped_key() { + let temp = temp(&[0; 8]); + let mut harness = empty_harness(); + harness + .ring + .add_target( + Key::::new(ADDED), + Win32FileTarget::new(read_only(&temp)), + ) + .expect("a read-only target under a read-only key"); + let refused = harness + .ring + .write(ADDED, 0, b"no".to_vec(), harness.default.at(1), 0) + .expect_err("the untyped key is judged when it is pushed"); + assert!(matches!(refused.reason, PushRefusal::Backend(_))); +} + +#[test] +fn construction_accepts_a_key_the_target_covers() { + let temp = temp(&[0; 8]); + let ring = Ring::new(setup( + vec![TargetSetup::new( + Key::::new(IoTargetKey(1)), + Win32FileTarget::new(temp.open()), + )], + Vec::new(), + None, + )) + .expect("a read-only key over a read-write target") + .0; + assert!(ring.engine().core.target(IoTargetKey(1)).is_some()); +} + +#[test] +fn construction_refuses_a_key_that_declares_more_than_its_target_states_and_all_comes_back() { + let (a, b) = (temp(&[0; 8]), temp(&[0; 8])); + let files = vec![ + TargetSetup::new( + Key::::new(IoTargetKey(1)), + Win32FileTarget::new(a.open()), + ), + TargetSetup::new( + Key::::new(IoTargetKey(2)), + Win32FileTarget::new(read_only(&b)), + ), + ]; + let error = refused(Ring::new(setup(files, vec![vec![1, 2]], serves(&["P"])))); + match error.reason { + SetupRefusal::AccessNotCovered { + key, + declared, + actual, + } => { + assert_eq!(key, IoTargetKey(2)); + assert_eq!(declared, Access::ReadWrite); + assert_eq!(actual, Access::ReadOnly); + } + other => panic!("refused for the wrong reason: {other:?}"), + } + assert_eq!( + error + .files + .iter() + .map(|file| (file.key, file.declared)) + .collect::>(), + [ + (IoTargetKey(1), Some(Access::ReadWrite)), + (IoTargetKey(2), Some(Access::ReadWrite)), + ], + "the files come back in order, each with what its key declared" + ); + assert_eq!(error.buffers, Some(vec![vec![1, 2]])); + assert!(error.provider.is_some()); + drop(error.files); + assert!(!a.is_held(), "no ring took a handle"); +} + +#[test] +fn a_construction_that_fails_later_hands_each_file_back_with_its_declaration() { + let temp = temp(&[0; 8]); + let mut given = setup( + vec![ + TargetSetup::new( + Key::::new(IoTargetKey(1)), + Win32FileTarget::new(temp.open()), + ), + TargetSetup::new(IoTargetKey(2), Win32FileTarget::new(temp.open())), + ], + Vec::new(), + None, + ); + // A queue no system can allocate: the failure comes after the declarations were checked. + given.submission_queue_size = u32::MAX; + given.completion_queue_size = u32::MAX; + let error = refused(Ring::new(given)); + assert!( + matches!(error.reason, SetupRefusal::Ring(_)), + "{:?}", + error.reason + ); + assert_eq!( + error + .files + .iter() + .map(|file| (file.key, file.declared)) + .collect::>(), + [ + (IoTargetKey(1), Some(Access::ReadOnly)), + (IoTargetKey(2), None) + ], + "a typed key's declaration survives the hand-back and an untyped key stays untyped" + ); +} diff --git a/crates/win-durable-ioring/src/dioring/tests/lineages.rs b/crates/win-durable-ioring/src/dioring/tests/lineages.rs index 9a8706779..2d070232c 100644 --- a/crates/win-durable-ioring/src/dioring/tests/lineages.rs +++ b/crates/win-durable-ioring/src/dioring/tests/lineages.rs @@ -8,16 +8,13 @@ use super::empty; use super::pushes::{ADDED, GIVEN, Harness, given, instance, temp}; use durable_ioring::driver::Home; use durable_ioring::{ - DurabilityRequest, EndLineageRefusal, Entry, Epoch, EpochState, ImportScope, Resolution, + DurabilityRequest, EndLineageRefusal, Entry, Epoch, ImportScope, Resolution, RetireLineageRefusal, UnknownLineage, }; use durable_ioring::{DurableRing, Identities, Lin}; use durable_ioring::{FailureToken, LineageHandle}; use std::sync::{Arc, Weak}; -/// `ERROR_IO_DEVICE`, the failure the seam is armed with. -const IO_DEVICE: u32 = 1117; - /// Written against the trait, as a consumer would: it must compile for any implementation. fn default_is_listed(ring: &D) -> bool { let default: Lin = D::Ids::handle_lineage(&ring.default_lineage()); @@ -297,54 +294,6 @@ fn a_handle_outliving_its_instance_releases_harmlessly() { ); } -/// A failure suspecting a held write of each lineage holds both, and a heal takes effect in each -/// at its own next seal (DI-D-41): the default's `Durable` comes before the failure's `Healed`, -/// which waits for the minted lineage's seal. -#[test] -fn a_failure_spanning_two_lineages_heals_in_each_at_its_own_seal() { - let (a, b) = (temp(&[0; 64]), temp(&[0; 64])); - let (mut harness, minted) = two(&a, &b); - let lineage = minted.lineage(); - let own = harness.write(GIVEN, 0, b"own", 1, 1); - let theirs = harness.write_in(&minted, ADDED, 0, b"theirs", 1, 2); - harness.next_n(2); - - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - harness.seal(1); - let failed = match harness.next_entry() { - Entry::Failed(failed) => failed, - other => panic!("expected Failed, got {other:?}"), - }; - let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); - assert_eq!(suspects, [own, theirs], "a held write of each lineage"); - heal(&mut harness, failed.token); - - assert_eq!(harness.seal(2), DurabilityRequest::Submitted); - let (one, two) = (harness.epoch(1), harness.epoch(2)); - expect_durable(&mut harness, one); - expect_durable(&mut harness, two); - assert_eq!(harness.ring.failures().len(), 1, "it still holds the other"); - assert_eq!( - harness.ring.epoch_state(Epoch::new(lineage, 1)), - Ok(EpochState::Open) - ); - - assert_eq!(harness.seal_in(&minted, 1), DurabilityRequest::Submitted); - loop { - match harness.next_entry() { - Entry::Marked { .. } => continue, - Entry::Healed { failure, .. } => { - assert_eq!(failure, failed.id); - break; - } - other => panic!("expected Healed, got {other:?}"), - } - } - expect_durable(&mut harness, Epoch::new(lineage, 1)); - assert!(harness.ring.failures().is_empty()); - harness.finish(); -} - /// Ending a lineage a heal still waits for lets the heal take effect. #[test] fn ending_a_lineage_a_heal_waits_for_reports_the_heal() { diff --git a/crates/win-durable-ioring/src/dioring/tests/markings.rs b/crates/win-durable-ioring/src/dioring/tests/markings.rs index 8c07845f9..41c54e755 100644 --- a/crates/win-durable-ioring/src/dioring/tests/markings.rs +++ b/crates/win-durable-ioring/src/dioring/tests/markings.rs @@ -75,55 +75,6 @@ fn a_suspect_write_flushed_after_an_import_is_marked_covered_and_abandoning_carr harness.finish(); } -#[test] -fn a_heal_reports_its_record_with_the_markings_its_failure_gained() { - let file = temp(&[0; 64]); - let mut harness = Harness::new(instance(vec![given(GIVEN, &file)], Vec::new())); - let sealed = harness.write(GIVEN, 0, b"first", 1, 1); - let later = harness.write(GIVEN, 8, b"second", 2, 2); - harness.next_n(2); - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - assert_eq!(harness.seal(1), DurabilityRequest::Submitted); - let failed = next_failed(&mut harness); - let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); - assert_eq!(suspects, [sealed, later]); - - harness - .ring - .resolve(vec![(failed.token, Resolution::Heal)]) - .expect("heal"); - harness.oracle.healed(failed.id); - assert_eq!(harness.seal(2), DurabilityRequest::Submitted); - let (_, covered) = next_marked(&mut harness); - assert_eq!( - (covered.write, covered.kind.clone()), - ( - later, - MarkingKind::Covered { - flushed: true, - provider: false - } - ), - "a healing failure goes on gaining markings" - ); - match harness.next_entry() { - Entry::Healed { - failure, markings, .. - } => { - assert_eq!(failure, failed.id); - assert_eq!(markings, [covered]); - } - other => panic!("expected Healed, got {other:?}"), - } - for id in [1, 2] { - match harness.next_entry() { - Entry::Durable { through } => assert_eq!(through, harness.epoch(id)), - other => panic!("expected Durable through {id}, got {other:?}"), - } - } - harness.finish(); -} - #[test] fn a_failed_flushs_code_reads_without_the_ring_crate() { let file = temp(&[0; 64]); diff --git a/crates/win-durable-ioring/src/dioring/tests/providers.rs b/crates/win-durable-ioring/src/dioring/tests/providers.rs index 42d6c8333..e721ea149 100644 --- a/crates/win-durable-ioring/src/dioring/tests/providers.rs +++ b/crates/win-durable-ioring/src/dioring/tests/providers.rs @@ -1,22 +1,26 @@ // Copyright (c) 2026 Mike Grier //! The consumer's durability provider (DI-3.2.6, DI-D-27, DI-D-44), against real files: what a //! seal asks it, its answers from inside `make_durable` and from another thread, what its failure -//! and its silence are reported as, and what a panic in it does. Every entry is reported to the -//! conformance oracle. +//! and its silence are reported as, and what a panic in it does; and the routing provider in that +//! place (DI-12.2.4), composing two providers. Every entry is reported to the conformance oracle. use std::panic::{self, AssertUnwindSafe}; -use std::sync::{Mutex, mpsc}; +use std::sync::{Arc, Mutex, mpsc}; use std::time::{Duration, Instant}; use windows_threadpool_sys::wait::ThreadpoolWait; use super::pushes::{BOUND, GIVEN, Harness, Ring, temp}; -use super::{FlushDomain, FlushRequest, Kept, TempFile, V, domain, file, keeper, setup}; +use super::{ + FlushDomain, FlushRequest, Keeper, Kept, TargetSetup, TempFile, V, domain, file, keeper, setup, +}; use durable_ioring::DurabilityProvider; use durable_ioring::DurableRing; +use durable_ioring::RoutingProvider; use durable_ioring::backend::SubmissionData; use durable_ioring::{ - DurabilityRequest, Entry, EpochState, Failed, FailureCause, IoTargetKey, OpKind, WriteOptions, + DurabilityRequest, Entry, EpochState, Failed, FailureCause, IoTargetKey, OpId, OpKind, + WriteOptions, }; /// `ERROR_IO_DEVICE`, the error a failing provider reports. @@ -354,3 +358,226 @@ fn a_panic_in_the_provider_reaches_the_consumers_call_and_abandons_what_it_dropp ); harness.finish(); } + +// The routing provider as the instance's provider (DI-12.2.4, DI-D-81, DI-D-83): two providers +// composed by a router, the log's and the disk's, each a keeper the test answers for. + +const LOG: IoTargetKey = IoTargetKey(2); +const DISK: IoTargetKey = IoTargetKey(3); + +/// A keeper serving `domains`, by itself, so a router can register it. +fn kept_by(domains: &[&str]) -> (Keeper, Kept) { + let kept = Arc::new(Mutex::new(Vec::new())); + let keeper = Keeper { + domains: domains.iter().map(|name| domain(name)).collect(), + kept: Arc::clone(&kept), + }; + (keeper, kept) +} + +/// An instance over `files`, whose provider is a router over a keeper serving `log` and one serving +/// `disk`, in that order; and what each keeps. +fn routed(files: Vec) -> (Harness, Kept, Kept) { + let (log, log_kept) = kept_by(&["log"]); + let (disk, disk_kept) = kept_by(&["disk"]); + let router = RoutingProvider::builder() + .register(log) + .register(disk) + .build() + .expect("no domain twice"); + let ring = Ring::new(setup(files, Vec::new(), Some(Box::new(router)))) + .expect("build an instance with a router") + .0; + (Harness::new(ring), log_kept, disk_kept) +} + +/// The files `requested` names, each with its writes. +fn named(request: &FlushRequest) -> Vec<(String, IoTargetKey, Vec)> { + request + .domains + .iter() + .flat_map(|part| { + let name = String::from_utf8_lossy(part.domain.bytes()).into_owned(); + part.files + .iter() + .map(move |file| (name.clone(), file.key, file.writes.clone())) + }) + .collect() +} + +#[test] +fn a_seal_touching_both_routed_providers_asks_each_once_for_its_own_domain() { + let temps = [temp(&[0; 16]), temp(&[0; 16])]; + let (mut harness, log_kept, disk_kept) = routed(vec![ + file(LOG.0, &temps[0], &["log"]), + file(DISK.0, &temps[1], &["disk"]), + ]); + let log_write = harness.write(LOG, 0, b"log", 1, 1); + let disk_write = harness.write(DISK, 0, b"disk", 1, 2); + harness.next_n(2); + assert_eq!(harness.seal(1), DurabilityRequest::Submitted); + + let log = only(&log_kept); + let disk = only(&disk_kept); + assert_eq!(named(&log), [("log".to_owned(), LOG, vec![log_write])]); + assert_eq!(named(&disk), [("disk".to_owned(), DISK, vec![disk_write])]); + + succeed(log); + nothing_for_a_while(&mut harness); + assert_eq!( + harness.state(1), + EpochState::Pending, + "the disk has not answered" + ); + succeed(disk); + expect_durable(&mut harness, 1); + harness.finish(); +} + +#[test] +fn a_file_whose_domains_span_routed_providers_is_durable_only_when_every_one_succeeds() { + // Its write is in each provider's call: the router merges nothing across providers, so a + // file whose coupled domains are split between them costs each a flush (DI-2.12 point 1). + let temp = temp(&[0; 16]); + let (mut harness, log_kept, disk_kept) = routed(vec![file(GIVEN.0, &temp, &["log", "disk"])]); + let write = harness.write(GIVEN, 0, b"both", 1, 1); + harness.next(); + assert_eq!(harness.seal(1), DurabilityRequest::Submitted); + + let log = only(&log_kept); + let disk = only(&disk_kept); + assert_eq!(named(&log), [("log".to_owned(), GIVEN, vec![write])]); + assert_eq!(named(&disk), [("disk".to_owned(), GIVEN, vec![write])]); + + succeed(disk); + nothing_for_a_while(&mut harness); + assert_eq!( + harness.state(1), + EpochState::Pending, + "the log has not answered" + ); + succeed(log); + expect_durable(&mut harness, 1); + harness.finish(); +} + +#[test] +fn one_routed_provider_failing_fails_only_the_files_in_its_domains() { + let temps = [temp(&[0; 16]), temp(&[0; 16])]; + let (mut harness, log_kept, disk_kept) = routed(vec![ + file(LOG.0, &temps[0], &["log"]), + file(DISK.0, &temps[1], &["disk"]), + ]); + let log_write = harness.write(LOG, 0, b"log", 1, 1); + harness.write(DISK, 0, b"disk", 1, 2); + harness.next_n(2); + assert_eq!(harness.seal(1), DurabilityRequest::Submitted); + + succeed(only(&disk_kept)); + let mut log = only(&log_kept); + log.domains + .remove(0) + .completion + .fail(std::io::Error::from_raw_os_error(IO_DEVICE)); + + let failed = next_failed(&mut harness); + match &failed.cause { + FailureCause::Provider { domain, error } => { + assert_eq!(domain.bytes(), b"log"); + assert_eq!(error.raw_os_error(), Some(IO_DEVICE)); + } + other => panic!("expected the log provider's failure, got {other:?}"), + } + let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); + assert_eq!( + suspects, + [log_write], + "the disk's file is not in the log's domain" + ); + assert_eq!(harness.state(1), EpochState::Blocked(failed.id)); + harness.finish(); +} + +#[test] +fn a_file_whose_domains_span_routed_providers_fails_when_either_fails() { + let temp = temp(&[0; 16]); + let (mut harness, log_kept, disk_kept) = routed(vec![file(GIVEN.0, &temp, &["log", "disk"])]); + let write = harness.write(GIVEN, 0, b"both", 1, 1); + harness.next(); + assert_eq!(harness.seal(1), DurabilityRequest::Submitted); + + succeed(only(&log_kept)); + let mut disk = only(&disk_kept); + disk.domains + .remove(0) + .completion + .fail(std::io::Error::from_raw_os_error(IO_DEVICE)); + + let failed = next_failed(&mut harness); + assert!( + matches!(&failed.cause, FailureCause::Provider { domain, .. } if domain.bytes() == b"disk"), + "{:?}", + failed.cause + ); + let suspects: Vec<_> = failed.suspect.writes().iter().map(|w| w.op).collect(); + assert_eq!(suspects, [write]); + assert_eq!(harness.state(1), EpochState::Blocked(failed.id)); + harness.finish(); +} + +#[test] +fn a_domain_no_routed_provider_serves_is_the_defaults() { + let temp = temp(&[0; 16]); + let (mut harness, log_kept, disk_kept) = routed(vec![file(GIVEN.0, &temp, &["other"])]); + harness.write(GIVEN, 0, b"other", 1, 1); + harness.next(); + assert_eq!(harness.seal(1), DurabilityRequest::Submitted); + expect_durable(&mut harness, 1); + assert!(taken(&log_kept).is_empty()); + assert!(taken(&disk_kept).is_empty()); + harness.finish(); +} + +#[test] +fn a_panic_in_a_routed_provider_abandons_its_domains_and_those_not_yet_handed_on() { + let temps = [temp(&[0; 16]), temp(&[0; 16])]; + let (disk, disk_kept) = kept_by(&["disk"]); + let router = RoutingProvider::builder() + .register(Panics(vec![domain("log")])) + .register(disk) + .build() + .expect("no domain twice"); + let ring = Ring::new(setup( + vec![ + file(LOG.0, &temps[0], &["log"]), + file(DISK.0, &temps[1], &["disk"]), + ], + Vec::new(), + Some(Box::new(router)), + )) + .expect("build an instance with a router") + .0; + let mut harness = Harness::new(ring); + harness.write(LOG, 0, b"log", 1, 1); + harness.write(DISK, 0, b"disk", 1, 2); + harness.next_n(2); + let at = harness.default.at(1); + let sealing = panic::catch_unwind(AssertUnwindSafe(|| harness.ring.make_durable_through(at))); + assert!( + sealing.is_err(), + "the provider's panic propagates through the router" + ); + harness.oracle.sealed(harness.epoch(1)); + assert!(taken(&disk_kept).is_empty(), "the disk was never called"); + + let mut abandoned = Vec::new(); + for _ in 0..2 { + match next_failed(&mut harness).cause { + FailureCause::ProviderAbandoned { domain } => abandoned.push(domain), + other => panic!("expected an abandoned domain, got {other:?}"), + } + } + abandoned.sort_by(|a, b| a.bytes().cmp(b.bytes())); + assert_eq!(abandoned, [domain("disk"), domain("log")]); + harness.finish(); +} diff --git a/crates/win-durable-ioring/src/dioring/tests/pushes.rs b/crates/win-durable-ioring/src/dioring/tests/pushes.rs index cbaed8d96..2dfc72cca 100644 --- a/crates/win-durable-ioring/src/dioring/tests/pushes.rs +++ b/crates/win-durable-ioring/src/dioring/tests/pushes.rs @@ -11,7 +11,7 @@ use std::time::{Duration, Instant}; use windows_ioring_sys::RegisteredSpan; -use super::{TempFile, V, empty, setup}; +use super::{TempFile, V, empty, setup, wtf}; /// A test buffer's length, as the oracle's push hook takes it. fn length(len: usize) -> u32 { @@ -20,10 +20,10 @@ fn length(len: usize) -> u32 { use crate::InterruptStamps; use crate::dioring::{Dioring, Setup, TargetSetup, TimeBase}; use crate::readiness::check_readiness; -use crate::windows_backend::{FileOptions, OpenedFile, Win32FileTarget}; -use durable_ioring::IoTarget; +use crate::windows_backend::{Cached, FileOptions, OpenedFile, ReadOnly, Win32FileTarget}; use durable_ioring::backend::RefusalCause; use durable_ioring::oracle::ConformanceOracle; +use durable_ioring::{Access, IoTarget}; use durable_ioring::{ DurabilityRequest, Entry, Epoch, EpochState, ImportScope, IoTargetKey, OpCompletion, OpKind, Outcome, PushRefusal, WriteCaching, WriteOptions, @@ -51,8 +51,8 @@ pub(super) fn temp(content: &[u8]) -> TempFile { temp } -pub(super) fn read_only(temp: &TempFile) -> OpenedFile { - OpenedFile::open(&temp.0, FileOptions::new().read(true)).expect("open read-only") +pub(super) fn read_only(temp: &TempFile) -> OpenedFile { + OpenedFile::open(wtf(&temp.0), FileOptions::new().read()).expect("open read-only") } pub(super) fn instance(files: Vec, buffers: Vec>) -> Ring { @@ -78,10 +78,7 @@ pub(super) fn instance_with_clock( } pub(super) fn given(key: IoTargetKey, temp: &TempFile) -> TargetSetup { - TargetSetup { - key, - target: Win32FileTarget::new(temp.open()), - } + TargetSetup::new(key, Win32FileTarget::new(temp.open())) } /// An instance and the oracle watching its stream. @@ -396,13 +393,69 @@ fn operation_identities_follow_push_order() { } #[test] -fn a_write_the_kernel_fails_completes_as_failed() { +fn a_write_to_a_read_only_target_is_refused_at_the_push_with_its_buffer() { let temp = temp(&[0; 16]); let mut harness = Harness::new(instance(Vec::new(), Vec::new())); harness .ring .add_target(ADDED, Win32FileTarget::new(read_only(&temp))) .expect("add a read-only file"); + let refused = harness + .ring + .write(ADDED, 0, b"refused".to_vec(), harness.default.at(1), 5) + .expect_err("a write the access does not cover"); + assert!(matches!( + refused.reason, + PushRefusal::Backend(RefusalCause::AccessDenied) + )); + assert_eq!(refused.buffer.as_deref(), Some(&b"refused"[..])); + assert_eq!(refused.context, 5); + assert_eq!( + fs::read(&temp.0).expect("read"), + [0; 16], + "nothing was written" + ); + harness.finish(); +} + +#[test] +fn a_read_from_a_write_only_target_is_refused_at_the_push_and_a_write_to_it_is_not() { + let temp = temp(&[0; 16]); + let mut harness = Harness::new(instance(Vec::new(), Vec::new())); + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new().write()) + .expect("open write-only"); + harness + .ring + .add_target(ADDED, Win32FileTarget::new(file)) + .expect("add a write-only file"); + let refused = harness + .ring + .read(ADDED, 0, vec![0; 4], 6) + .expect_err("a read the access does not cover"); + assert!(matches!( + refused.reason, + PushRefusal::Backend(RefusalCause::AccessDenied) + )); + assert_eq!(refused.buffer, Some(vec![0; 4])); + + harness.write(ADDED, 0, b"taken", 1, 7); + assert_eq!(transferred(&harness.next()), 5); + harness.finish(); +} + +#[test] +fn a_write_the_kernel_fails_completes_as_failed() { + // A handle that cannot write, stated by its target to be one that can: the core trusts what a + // target states, so what is exercised is the kernel's own refusal reaching the completion. + let temp = temp(&[0; 16]); + let mut harness = Harness::new(instance(Vec::new(), Vec::new())); + harness + .ring + .add_target( + ADDED, + Win32FileTarget::new(read_only(&temp)).claiming(Access::ReadWrite), + ) + .expect("add a read-only file that claims to be writable"); harness.write(ADDED, 0, b"refused", 1, 5); let completion = harness.next(); assert!( diff --git a/crates/win-durable-ioring/src/dioring/tests/raw_disk.rs b/crates/win-durable-ioring/src/dioring/tests/raw_disk.rs index af3afd55e..add2afadf 100644 --- a/crates/win-durable-ioring/src/dioring/tests/raw_disk.rs +++ b/crates/win-durable-ioring/src/dioring/tests/raw_disk.rs @@ -14,8 +14,9 @@ //! once and cannot be disk 0 ([`crate::scratch_volume::associated_disk`]), and every write is to //! that number. A test never names a disk number of its own. -use std::fs::OpenOptions; -use std::io::{Read, Seek, SeekFrom}; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::windows::io::AsHandle; use std::time::{Duration, Instant}; use io_buffers::AlignedBuf; @@ -24,7 +25,8 @@ use super::QUEUE; use crate::dioring::{DeliveryMode, Dioring, Setup, TargetSetup}; use crate::scratch_volume::ScratchDisk; use crate::windows_backend::{ - DiskPath, OpenedDisk, Unbuffered, Win32FileTarget, disk_flush_domain, + DiskOptions, DiskPath, OpenedDisk, ReadOnly, ShareMode, SharingViolation, Unbuffered, + Win32FileTarget, disk_flush_domain, }; use durable_ioring::backend::RefusalCause; use durable_ioring::{ @@ -42,10 +44,7 @@ fn raw_instance(disk: OpenedDisk) -> (DiskRing, LineageHandle) { DiskRing::new(Setup { submission_queue_size: QUEUE, completion_queue_size: QUEUE, - files: vec![TargetSetup { - key: DISK, - target: Win32FileTarget::from_disk(disk), - }], + files: vec![TargetSetup::new(DISK, Win32FileTarget::from_disk(disk))], buffers: Vec::>::new(), provider: None, diagnostics: None, @@ -89,10 +88,64 @@ fn read_back(disk_number: u32, offset: u64, length: usize) -> Vec { } fn open(scratch: &ScratchDisk) -> OpenedDisk { - OpenedDisk::::open(DiskPath::number(scratch.disk_number())) + OpenedDisk::::open(DiskPath::number(scratch.disk_number()), DiskOptions::new()) .expect("open the scratch disk") } +#[test] +#[ignore = "needs an administrator: creates and attaches a virtual disk"] +fn an_exclusive_raw_disk_refuses_a_second_open_until_it_closes() { + let scratch = ScratchDisk::create(MEGABYTES).expect("a scratch disk"); + let path = DiskPath::number(scratch.disk_number()); + let exclusive = OpenedDisk::::open(path, DiskOptions::new().share(ShareMode::NONE)) + .expect("open exclusively"); + let refused = OpenedDisk::::open(path, DiskOptions::new()) + .expect_err("an exclusive open is open"); + assert_eq!( + refused.kind(), + std::io::ErrorKind::ResourceBusy, + "{refused}" + ); + assert!( + refused + .get_ref() + .is_some_and(|inner| inner.is::()), + "{refused}" + ); + drop(exclusive); + OpenedDisk::::open(path, DiskOptions::new()).expect("nothing holds the disk now"); +} + +#[test] +#[ignore = "needs an administrator: creates and attaches a virtual disk"] +fn a_raw_disk_opened_for_reading_only_reads_and_is_refused_a_write_by_the_kernel() { + let scratch = ScratchDisk::create(MEGABYTES).expect("a scratch disk"); + let disk = OpenedDisk::::open( + DiskPath::number(scratch.disk_number()), + DiskOptions::new(), + ) + .expect("open the scratch disk for reading only"); + let sector = disk.geometry().offset_alignment() as usize; + let mut through = File::from(disk.as_handle().try_clone_to_owned().expect("duplicate")); + + let mut buffer = AlignedBuf::zeroed(sector, sector).expect("an aligned buffer"); + through + .read_exact(buffer.as_mut_slice()) + .expect("a read of the first sector"); + let refused = through + .seek(SeekFrom::Start(0)) + .and_then(|_| through.write_all(sector_of(1, sector).as_slice())); + assert_eq!( + refused + .expect_err("a write the access does not cover") + .raw_os_error(), + Some(5), + "ERROR_ACCESS_DENIED" + ); + // A disk opened for reading only still has a data right, so it is a target. + let _target = Win32FileTarget::from_disk(disk); +} + #[test] #[ignore = "needs an administrator: creates and attaches a virtual disk"] fn a_raw_disks_sector_is_found_and_its_target_names_the_disk() { diff --git a/crates/win-durable-ioring/src/dioring/tests/raw_volume.rs b/crates/win-durable-ioring/src/dioring/tests/raw_volume.rs index 6cb24b5aa..4b74fef64 100644 --- a/crates/win-durable-ioring/src/dioring/tests/raw_volume.rs +++ b/crates/win-durable-ioring/src/dioring/tests/raw_volume.rs @@ -13,8 +13,9 @@ //! the alignment the kernel holds a transfer to, that a write reaches it and is made durable, and //! that the lock is taken and given up. -use std::fs::OpenOptions; -use std::io::{Read, Seek, SeekFrom}; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::windows::io::AsHandle; use std::time::{Duration, Instant}; use io_buffers::AlignedBuf; @@ -23,7 +24,8 @@ use super::QUEUE; use crate::dioring::{DeliveryMode, Dioring, Setup, TargetSetup}; use crate::scratch_volume::ScratchVolume; use crate::windows_backend::{ - OpenedVolume, Unbuffered, VolumeOptions, Win32FileTarget, disk_flush_domain, + OpenedVolume, ReadOnly, ShareMode, SharingViolation, Unbuffered, VolumeOptions, VolumePath, + Win32FileTarget, disk_flush_domain, }; use durable_ioring::backend::RefusalCause; use durable_ioring::{ @@ -41,10 +43,10 @@ fn raw_instance(volume: OpenedVolume) -> (RawRing, LineageHandle) { RawRing::new(Setup { submission_queue_size: QUEUE, completion_queue_size: QUEUE, - files: vec![TargetSetup { - key: VOLUME, - target: Win32FileTarget::from_volume(volume), - }], + files: vec![TargetSetup::new( + VOLUME, + Win32FileTarget::from_volume(volume), + )], buffers: Vec::>::new(), provider: None, diagnostics: None, @@ -78,6 +80,29 @@ fn transferred(entry: EntryOf) -> u32 { } } +/// Open `path` as `options` say, trying again while something else holds the volume. The system +/// does for a moment, just after a volume appears and between two opens of it, and refuses the open +/// as a sharing violation that is not the test's: an exclusive open measured in an elevated run was +/// refused so, once in two runs, with nothing of the test's own open. +fn open_when_free(path: &VolumePath, options: VolumeOptions) -> OpenedVolume { + let mut attempts = 0; + loop { + match OpenedVolume::::open(path, options) { + Ok(volume) => return volume, + Err(error) + if attempts < 40 + && error + .get_ref() + .is_some_and(|inner| inner.is::()) => + { + attempts += 1; + std::thread::sleep(Duration::from_millis(100)); + } + Err(error) => panic!("open the volume: {error}"), + } + } +} + /// The first `length` bytes of the volume, read through a handle of the test's own. fn read_back(device_path: &str, length: usize) -> Vec { let mut volume = OpenOptions::new() @@ -90,6 +115,33 @@ fn read_back(device_path: &str, length: usize) -> Vec { bytes } +#[test] +#[ignore = "needs an administrator: creates and attaches a virtual disk"] +fn a_raw_volume_opened_for_reading_only_reads_and_is_refused_a_write_by_the_kernel() { + let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume"); + let volume = OpenedVolume::::open(&scratch.path(), VolumeOptions::new()) + .expect("open the raw volume for reading only"); + let sector = volume.geometry().offset_alignment() as usize; + let mut through = File::from(volume.as_handle().try_clone_to_owned().expect("duplicate")); + + let mut buffer = AlignedBuf::zeroed(sector, sector).expect("an aligned buffer"); + through + .read_exact(buffer.as_mut_slice()) + .expect("a read of the first sector"); + let refused = through + .seek(SeekFrom::Start(0)) + .and_then(|_| through.write_all(sector_of(1, sector).as_slice())); + assert_eq!( + refused + .expect_err("a write the access does not cover") + .raw_os_error(), + Some(5), + "ERROR_ACCESS_DENIED" + ); + // A volume opened for reading only still has a data right, so it is a target. + let _target = Win32FileTarget::from_volume(volume); +} + #[test] #[ignore = "needs an administrator: creates and attaches a virtual disk"] fn a_raw_volumes_disks_and_sector_are_found_and_its_target_reports_them() { @@ -222,6 +274,103 @@ fn a_locked_raw_volume_is_written_and_the_lock_is_given_up_when_it_closes() { .expect("the lock was released when the handle closed"); } +#[test] +#[ignore = "needs an administrator: creates and attaches a virtual disk"] +fn an_exclusive_raw_volume_refuses_a_second_open_until_it_closes() { + // Also the evidence that the `CreateFileW` page's "must have the FILE_SHARE_WRITE flag" for a + // volume is not what the kernel does, which is why `VolumeOptions::share` does not refuse a mode + // without it (DI-14.11). + let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume"); + let path = scratch.path(); + let exclusive = open_when_free(&path, VolumeOptions::new().share(ShareMode::NONE)); + let refused = OpenedVolume::::open(&path, VolumeOptions::new()) + .expect_err("an exclusive open is open"); + assert_eq!( + refused.kind(), + std::io::ErrorKind::ResourceBusy, + "{refused}" + ); + assert!( + refused + .get_ref() + .is_some_and(|inner| inner.is::()), + "{refused}" + ); + drop(exclusive); + OpenedVolume::::open(&path, VolumeOptions::new()) + .expect("nothing holds the volume now"); +} + +#[test] +#[ignore = "needs an administrator: creates and attaches a virtual disk"] +fn a_locked_volume_refuses_data_opens_as_access_denied_and_an_unlocked_one_as_the_share_mode_says() +{ + // DI-13.12, measured by an elevated run on 2026-10-16 on a 64 MiB scratch volume: the lock is a + // device control made after the open, and it is not independent of the share mode. A volume + // that is locked refuses every other open that asks for a data right with ERROR_ACCESS_DENIED, + // whether the holder shared everything or nothing, so a caller that locks never sees the share + // mode's SharingViolation from another open; an unlocked volume answers as the share mode says. + // An open that asks for no data right is admitted in every case. + #[derive(Debug, PartialEq, Eq)] + enum Meets { + Opened, + SharingViolation, + AccessDenied, + } + fn meets(result: std::io::Result<()>) -> Meets { + match result { + Ok(()) => Meets::Opened, + Err(error) + if error + .get_ref() + .is_some_and(|inner| inner.is::()) => + { + Meets::SharingViolation + } + Err(error) if error.raw_os_error() == Some(5) => Meets::AccessDenied, + Err(error) => panic!("an unexpected refusal: {error}"), + } + } + + let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume"); + let path = scratch.path(); + // (the holder locks, the holder's share mode) and what a read-write or read-only second open meets. + let table = [ + (false, ShareMode::NONE, Meets::SharingViolation), + (false, ShareMode::ALL, Meets::Opened), + (true, ShareMode::NONE, Meets::AccessDenied), + (true, ShareMode::ALL, Meets::AccessDenied), + ]; + for (lock, share, expected) in table { + let held = open_when_free(&path, VolumeOptions::new().lock(lock).share(share)); + let label = format!("holder lock={lock} share={share:?}"); + assert_eq!( + meets(OpenedVolume::::open(&path, VolumeOptions::new()).map(drop)), + expected, + "{label}: a read-write open" + ); + assert_eq!( + meets( + OpenedVolume::::open(&path, VolumeOptions::new()).map(drop) + ), + expected, + "{label}: a read-only open" + ); + assert_eq!( + meets( + OpenedVolume::::open( + &path, + VolumeOptions::new(), + ) + .map(drop) + ), + Meets::Opened, + "{label}: an open that asks for no data right" + ); + drop(held); + } +} + #[test] #[ignore = "needs an administrator: creates and attaches a virtual disk"] fn a_volume_with_another_handle_open_cannot_be_locked() { diff --git a/crates/win-durable-ioring/src/dioring/tests/seals.rs b/crates/win-durable-ioring/src/dioring/tests/seals.rs index 7d5447c40..790c35362 100644 --- a/crates/win-durable-ioring/src/dioring/tests/seals.rs +++ b/crates/win-durable-ioring/src/dioring/tests/seals.rs @@ -10,10 +10,10 @@ use windows_ioring_sys::RegisteredSpan; use super::pushes::{ADDED, GIVEN, Harness, Ring, given, instance, read_only, temp}; use super::{empty, file, keeper, setup}; use crate::windows_backend::Win32FileTarget; +use durable_ioring::{Access, DurableRing, RegisteredBufferRing}; use durable_ioring::{ DurabilityRequest, Entry, Epoch, EpochState, Outcome, PushRefusal, SealError, UnknownLineage, }; -use durable_ioring::{DurableRing, RegisteredBufferRing}; /// The next entry, which must be `Durable` through `id` of the default lineage. fn expect_durable(harness: &mut Harness, id: u64) { @@ -225,7 +225,10 @@ fn a_failed_write_is_not_covered_and_does_not_hold_its_seal() { let mut harness = Harness::new(instance(vec![given(GIVEN, &good)], Vec::new())); harness .ring - .add_target(ADDED, Win32FileTarget::new(read_only(&refused))) + .add_target( + ADDED, + Win32FileTarget::new(read_only(&refused)).claiming(Access::ReadWrite), + ) .expect("add a read-only file"); let failing = harness.write(ADDED, 0, b"refused", 1, 1); harness.write(GIVEN, 0, b"kept", 1, 2); diff --git a/crates/win-durable-ioring/src/dioring/tests/stamps.rs b/crates/win-durable-ioring/src/dioring/tests/stamps.rs index 9ff608aa6..52f0bbaf4 100644 --- a/crates/win-durable-ioring/src/dioring/tests/stamps.rs +++ b/crates/win-durable-ioring/src/dioring/tests/stamps.rs @@ -7,13 +7,10 @@ use win_time_sys::{Clock, InterruptClock}; use super::MockClock; -use super::pushes::{GIVEN, Harness, given, instance, instance_with_clock, temp}; +use super::pushes::{Harness, instance, instance_with_clock}; use crate::dioring::TimeBase; use durable_ioring::DurableRing; -use durable_ioring::{DurabilityRequest, Entry, Failed, ImportScope}; - -/// `ERROR_IO_DEVICE`, the failure the seam is armed with. -const IO_DEVICE: u32 = 1117; +use durable_ioring::{Entry, Failed, ImportScope}; type V = super::V; @@ -32,28 +29,6 @@ fn import(harness: &mut Harness) -> Failed { next_failed(harness) } -#[test] -fn an_instance_given_a_clock_stamps_a_failed_flush_with_its_reading_when_observed() { - let file = temp(b""); - let clock = MockClock::at(7_000); - let mut harness = Harness::new(instance_with_clock( - vec![given(GIVEN, &file)], - Vec::new(), - clock.clone(), - )); - harness.write(GIVEN, 0, b"stamped", 1, 1); - harness.next(); - clock.advance(3_000); - harness.ring.fail_next_flush(GIVEN, IO_DEVICE); - assert_eq!(harness.seal(1), DurabilityRequest::Submitted); - let failed = next_failed(&mut harness); - assert_eq!(failed.observed.ticks(), 10_000); - let inventory = harness.ring.failures(); - assert_eq!(inventory.len(), 1); - assert_eq!(inventory[0].observed, failed.observed); - harness.finish(); -} - #[test] fn stamps_follow_the_clock_and_the_oracle_accepts_them_equal_or_rising() { let clock = MockClock::at(40); diff --git a/crates/win-durable-ioring/src/dioring/tests/unbuffered.rs b/crates/win-durable-ioring/src/dioring/tests/unbuffered.rs index cecbc3f98..3f8787e6b 100644 --- a/crates/win-durable-ioring/src/dioring/tests/unbuffered.rs +++ b/crates/win-durable-ioring/src/dioring/tests/unbuffered.rs @@ -6,9 +6,9 @@ use std::time::{Duration, Instant}; use io_buffers::AlignedBuf; -use super::{QUEUE, TempFile}; +use super::{QUEUE, TempFile, wtf}; use crate::dioring::{DeliveryMode, Dioring, Setup, TargetSetup}; -use crate::windows_backend::{FileOptions, OpenedFile, Unbuffered, Win32FileTarget}; +use crate::windows_backend::{FileOptions, OpenedFile, ReadWrite, Unbuffered, Win32FileTarget}; use durable_ioring::backend::RefusalCause; use durable_ioring::{ DurableRing, Entry, IoTarget, IoTargetKey, LineageHandle, OpCompletion, Outcome, PushRefusal, @@ -21,21 +21,24 @@ const BOUND: Duration = Duration::from_secs(10); type AlignedRing = Dioring; -fn unbuffered(temp: &TempFile) -> Win32FileTarget { +fn unbuffered(temp: &TempFile, options: FileOptions) -> Win32FileTarget { Win32FileTarget::new( - OpenedFile::::open(&temp.0, FileOptions::new().read(true).write(true)) - .expect("open unbuffered"), + OpenedFile::::open(wtf(&temp.0), options).expect("open unbuffered"), ) } fn instance(temp: &TempFile) -> (AlignedRing, LineageHandle) { - let target = unbuffered(temp); + instance_with(temp, FileOptions::new().read().write()) +} + +fn instance_with(temp: &TempFile, options: FileOptions) -> (AlignedRing, LineageHandle) { + let target = unbuffered(temp, options); assert_eq!(target.warranty(), WarrantyClass::SharedDevice); assert!(target.capabilities().write_through()); AlignedRing::new(Setup { submission_queue_size: QUEUE, completion_queue_size: QUEUE, - files: vec![TargetSetup { key: FILE, target }], + files: vec![TargetSetup::new(FILE, target)], buffers: Vec::>::new(), provider: None, diagnostics: None, @@ -63,6 +66,40 @@ fn completion(ring: &mut AlignedRing) -> OpCompletion, Al } } +// A cache hint is passed to an unbuffered file and not refused (`DI-14.1`), so the ring has to be +// able to run it: every way of asking for the two hints, against the kernel and through the ring. +#[test] +fn an_unbuffered_file_opened_with_cache_hints_runs_through_the_ring() { + for (random, sequential) in [(false, false), (true, false), (false, true), (true, true)] { + let temp = TempFile::new("unbuffered-hints"); + let options = FileOptions::new() + .read() + .write() + .random_access(random) + .sequential_scan(sequential); + let (mut ring, lineage) = instance_with(&temp, options); + ring.write_with( + FILE, + 0, + sector(3), + lineage.at(1), + 8, + WriteOptions::new().caching(WriteCaching::WriteThrough), + ) + .expect("push an aligned write-through write"); + let done = completion(&mut ring); + assert!( + matches!(done.outcome, Outcome::Transferred(n) if n as usize == SECTOR), + "hints {random}, {sequential}: {:?}", + done.outcome + ); + assert_eq!( + std::fs::read(&temp.0).expect("read back")[..SECTOR], + vec![3u8; SECTOR] + ); + } +} + #[test] fn a_per_write_write_through_write_completes_on_an_unbuffered_file() { // The cached counterpart is `the_caching_choice_reaches_the_kernel`, where the kernel refuses diff --git a/crates/win-durable-ioring/src/fake.rs b/crates/win-durable-ioring/src/fake.rs index e13f09d9f..51cdb69cb 100644 --- a/crates/win-durable-ioring/src/fake.rs +++ b/crates/win-durable-ioring/src/fake.rs @@ -14,8 +14,9 @@ use durable_ioring::driver::InstanceId; use durable_ioring::{ AddTargetError, DurabilityRequest, EndLineageError, EndLineageRefusal, Entry, Epoch, EpochState, FailureInfo, ImportScope, Imported, IoTargetKey, Leftovers, LineageInfo, - OpCompletion, OpKind, Outcome, ReadOptions, RemoveTargetError, Resolution, ResolveError, - RetireLineageError, RetireLineageRefusal, SealError, Tag, UnknownLineage, WriteOptions, + OpCompletion, OpKind, Outcome, ReadKey, ReadOptions, RemoveTargetError, Resolution, + ResolveError, RetireLineageError, RetireLineageRefusal, SealError, Tag, TargetKey, + UnknownLineage, WriteKey, WriteOptions, }; use durable_ioring::{DurableRing, EntryOf, PushResult}; use durable_ioring::{FailureId, FailureToken, Lineage, LineageHandle, OpId}; @@ -104,14 +105,14 @@ impl DurableRing for Fake { fn readiness(&mut self) -> io::Result { if self.readiness_fails { - return Err(io::Error::other("the fake refuses to duplicate its signal")); + return Err(io::Error::other("the fake refuses to give its signal")); } - self.signal.try_clone() + Ok(self.signal.clone()) } - fn add_target( + fn add_target( &mut self, - _key: IoTargetKey, + _key: K, _target: MemoryTarget, ) -> Result<(), AddTargetError> { Ok(()) @@ -147,9 +148,9 @@ impl DurableRing for Fake { }) } - fn write_with( + fn write_with( &mut self, - _file: IoTargetKey, + _file: K, _offset: u64, _buffer: Vec, _tag: Tag<'_, V>, @@ -159,9 +160,9 @@ impl DurableRing for Fake { unimplemented!("tests complete the fake's operations with complete_one") } - fn read_with( + fn read_with( &mut self, - _file: IoTargetKey, + _file: K, _offset: u64, _buffer: Vec, _context: u32, diff --git a/crates/win-durable-ioring/src/lib.rs b/crates/win-durable-ioring/src/lib.rs index d293b6f34..39d6c95a2 100644 --- a/crates/win-durable-ioring/src/lib.rs +++ b/crates/win-durable-ioring/src/lib.rs @@ -6,6 +6,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] +mod debug_output; mod delivery; mod dioring; mod error_code; @@ -18,6 +19,7 @@ pub mod readiness; mod scratch_volume; pub mod windows_backend; +pub use debug_output::DebugOutputDiagnostics; pub use delivery::{DeliveryEvent, DeliveryHandle, DeliverySetupError, EntryDelivery}; pub use dioring::{ DeliveryMode, Dioring, FromClock, InterruptStamps, Setup, SetupError, SetupRefusal, @@ -28,8 +30,11 @@ pub use readiness::{ReadinessFailure, check_readiness}; // The portable crate's API, so a consumer needs no direct dependency on it to use this driver. pub use durable_ioring::*; +// The default diagnostic sink, so a consumer can name it, or build one to share, without a direct dependency. +pub use durable_ioring_events::EtwDiagnostics; // The types the driver's signatures name from the crates it is built on, so a consumer needs no // direct dependency on them to call it. pub use win_shared_os_owned_handle::SharedHandle; pub use win_sync_sys::Event; pub use win_time_sys::{InterruptTime, TimePoint}; +pub use wtf_string::Wtf16String; diff --git a/crates/win-durable-ioring/src/windows_backend.rs b/crates/win-durable-ioring/src/windows_backend.rs index 0a734ebe9..a6d0dfa94 100644 --- a/crates/win-durable-ioring/src/windows_backend.rs +++ b/crates/win-durable-ioring/src/windows_backend.rs @@ -31,7 +31,7 @@ use std::os::windows::io::OwnedHandle; use std::sync::Arc; use std::time::Duration; -use win_sync_sys::{Access, duplicate_handle_with_access}; +use win_handles_sys::{Access, duplicate_handle_with_access}; use windows_ioring_sys::{ Batch, FlushCoverage, FlushMode, IoBuf, IoBufMut, IoRing, IoRingErrorExt, OperationId, Owned, PushOptions, PushRefused, RegisteredBuffers, RegisteredFile, RegisteredSpan, RingCondition, @@ -47,22 +47,46 @@ use durable_ioring::backend::{ }; use durable_ioring::{IoFault, IoTargetKey, WriteCaching}; +mod disposition; +mod file_attributes; +mod file_id; +mod file_rights; mod file_target; mod opened_disk; mod opened_file; mod opened_volume; pub(crate) mod pool; +mod raw_open; +mod share_mode; pub(crate) mod signalled; mod wait; pub use wait::CompletionEvent; +// The markers are the core's (DI-D-66); this module states what each asks of Win32. +pub use disposition::Disposition; +pub use durable_ioring::{ + CanRead, CanWrite, DataAccess, HasData, NoData, ReadOnly, ReadWrite, WriteOnly, +}; +pub use file_attributes::FileAttributes; +pub use file_id::{FileId, FileIdentifier}; +pub use file_rights::FileRights; pub use file_target::Win32FileTarget; -pub use opened_disk::{DiskPath, OpenedDisk}; -pub use opened_file::{Buffering, Cached, FileOptions, OpenedFile, RemoteDevice, Unbuffered}; +pub use opened_disk::{DiskOptions, DiskPath, OpenedDisk}; +pub use opened_file::{ + Buffering, ByIdOptions, ByIdRequest, Cached, FileOptions, IdentityMismatch, OpenRequest, + OpenedBy, OpenedFile, RemoteDevice, ReopenOptions, ReopenRequest, SharingViolation, Unbuffered, + UnknownLocation, +}; pub use opened_volume::{ OpenedVolume, VolumeOptions, VolumePath, VolumePathError, disk_flush_domain, }; +pub use share_mode::ShareMode; +// The type `OpenedVolume::disk_extents` returns, so a caller who matches on one names it from here. +pub use win_volume_sys::DiskExtent; +// The descriptor a created file is given, owned and freed by the type that built it, so a caller +// never holds a raw pointer (DI-14.3). +pub use win_authorization_sys::{SecurityCaptureError, SecurityDescriptor}; #[cfg(test)] mod tests; @@ -482,7 +506,8 @@ impl WindowsBackend { }) } - /// Why `op` on `target` cannot be pushed, before the ring is asked: an unknown target, an owned + /// Why `op` on `target` cannot be pushed, before the ring is asked: an unknown target, an + /// operation its access does not cover (DI-D-66), an owned /// read on a backend without owned reads (DI-D-17), a registered region without a registration, /// or an operation its target's geometry does not admit. A registered region is checked for offset and length only; its buffer's address is /// the registration's, and whether it lies within the registration is the ring's own check at @@ -491,6 +516,16 @@ impl WindowsBackend { let Some(slot) = self.targets.get(&target) else { return Err(RefusalCause::UnknownTarget); }; + // What the target can do with its data, before the shape of the operation is looked at. + let access = slot.target.access(); + let covered = match op { + SubmissionOp::Write { .. } => access.can_write(), + SubmissionOp::Read { .. } => access.can_read(), + SubmissionOp::Commit => true, + }; + if !covered { + return Err(RefusalCause::AccessDenied); + } if self.owned_read.is_none() && matches!( op, diff --git a/crates/win-durable-ioring/src/windows_backend/disposition.rs b/crates/win-durable-ioring/src/windows_backend/disposition.rs new file mode 100644 index 000000000..34bfa2549 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/disposition.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2026 Mike Grier +//! [`Disposition`]: what an open does about a file that is there and a file that is not, as +//! `CreateFileW`'s `dwCreationDisposition` states it (DI-13.7.3, DI-D-64). +//! +//! `CreateFileW` has five dispositions. Three are offered here. `CREATE_ALWAYS` and +//! `TRUNCATE_EXISTING` are the two that empty a file that exists, and they are not offered in the +//! first release (the engineer's ruling of 2026-10-16, recorded with its measurements in DI-D-64); +//! the enum is `#[non_exhaustive]` so that offering them later is not a breaking change. A caller +//! that wants an existing file emptied does it through the handle the open lends, with +//! `File::set_len(0)`. + +use windows_sys::Win32::Storage::FileSystem::{CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING}; + +#[cfg(test)] +mod tests; + +/// The disposition of an open: what it does when the file exists and when it does not. +/// +/// None of the three changes the contents of a file that exists, so no data access is gated by the +/// choice. +/// +/// | Variant | `dwCreationDisposition` | File exists | File does not exist | +/// |---|---|---|---| +/// | [`CreateNew`](Self::CreateNew) | `CREATE_NEW` | fails, `ERROR_FILE_EXISTS` | created | +/// | [`OpenExisting`](Self::OpenExisting) | `OPEN_EXISTING` | opened | fails, `ERROR_FILE_NOT_FOUND` | +/// | [`OpenAlways`](Self::OpenAlways) | `OPEN_ALWAYS` | opened | created | +/// +/// A file that an open creates is created empty. +/// +/// There is deliberately no `Default`: the options start at [`OpenExisting`](Self::OpenExisting), +/// which creates nothing, and a disposition is stated where one is wanted. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Disposition { + /// `CREATE_NEW`: create the file, and fail if it exists. + CreateNew, + /// `OPEN_EXISTING`: open the file, and fail if it does not exist. + OpenExisting, + /// `OPEN_ALWAYS`: open the file if it exists, and create it if it does not. + OpenAlways, +} + +impl Disposition { + /// The value this is in `dwCreationDisposition`. + pub(super) const fn bits(self) -> u32 { + match self { + Self::CreateNew => CREATE_NEW, + Self::OpenExisting => OPEN_EXISTING, + Self::OpenAlways => OPEN_ALWAYS, + } + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/disposition/tests.rs b/crates/win-durable-ioring/src/windows_backend/disposition/tests.rs new file mode 100644 index 000000000..b40e1a3e8 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/disposition/tests.rs @@ -0,0 +1,26 @@ +// Copyright (c) 2026 Mike Grier + +use windows_sys::Win32::Storage::FileSystem::{CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING}; + +use super::Disposition; + +#[test] +fn each_variant_is_the_value_it_is_named_for() { + assert_eq!(Disposition::CreateNew.bits(), CREATE_NEW); + assert_eq!(Disposition::OpenExisting.bits(), OPEN_EXISTING); + assert_eq!(Disposition::OpenAlways.bits(), OPEN_ALWAYS); +} + +#[test] +fn the_three_variants_are_three_different_values() { + let all = [ + Disposition::CreateNew, + Disposition::OpenExisting, + Disposition::OpenAlways, + ]; + for (i, a) in all.iter().enumerate() { + for b in &all[i + 1..] { + assert_ne!(a.bits(), b.bits(), "{a:?} and {b:?}"); + } + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_attributes.rs b/crates/win-durable-ioring/src/windows_backend/file_attributes.rs new file mode 100644 index 000000000..5e1287675 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_attributes.rs @@ -0,0 +1,134 @@ +// Copyright (c) 2026 Mike Grier +//! [`FileAttributes`]: the attributes a file is created with, as a typed set (DI-14.3, DI-D-64). +//! +//! `CreateFileW` takes the attributes of a file it creates in `dwFlagsAndAttributes`. The set here is +//! the six the table keeps: `ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM` and `TEMPORARY`. +//! `FILE_ATTRIBUTE_NORMAL` is not a member, since the page says it is valid only alone and that any +//! other attribute overrides it, so it is the absence of attributes and the empty set stands for it. +//! `FILE_ATTRIBUTE_ENCRYPTED` is excluded (DI-D-64). There is no constructor from a raw number, so the +//! only masks that exist are subsets of the six. + +use std::fmt; +use std::ops::{BitOr, BitOrAssign}; + +use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, +}; + +#[cfg(test)] +mod tests; + +/// The attributes a file is created with: a set of `ARCHIVE`, `HIDDEN`, `OFFLINE`, `READONLY`, `SYSTEM` +/// and `TEMPORARY`, named as `CreateFileW` names `FILE_ATTRIBUTE_*` without their prefix. +/// +/// ``` +/// use win_durable_ioring::windows_backend::FileAttributes; +/// +/// let attributes = FileAttributes::HIDDEN | FileAttributes::SYSTEM; +/// +/// assert!(attributes.contains(FileAttributes::HIDDEN)); +/// assert!(!attributes.contains(FileAttributes::READONLY)); +/// assert!(FileAttributes::NONE.is_empty()); +/// ``` +/// +/// They apply to a file the open creates. The `CreateFileW` page says that for a file that exists the +/// supplied attributes are generally combined with it or ignored, which is Win32's and is not +/// refined here; [`FileOptions::attributes`](super::FileOptions::attributes) says what was measured. +/// The page also says applications should not change `OFFLINE` arbitrarily. +/// +/// The empty set asks for no attribute and is what an open passes when none is given; it is not +/// `FILE_ATTRIBUTE_NORMAL`, which the page defines as valid only alone. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct FileAttributes(u32); + +/// Names the members once, so the constants, the names `Debug` prints and the tests that walk every +/// subset are one list and not three that can disagree. +macro_rules! attributes { + ( $( $(#[$doc:meta])* $name:ident = $value:expr; )* ) => { + impl FileAttributes { + $( $(#[$doc])* pub const $name: Self = Self($value); )* + } + + /// Every named member, in the order they are declared. + const NAMED: &[(&str, FileAttributes)] = &[ $( (stringify!($name), FileAttributes::$name), )* ]; + }; +} + +attributes! { + /// `FILE_ATTRIBUTE_ARCHIVE`: the file is marked for backup or removal. + ARCHIVE = FILE_ATTRIBUTE_ARCHIVE; + /// `FILE_ATTRIBUTE_HIDDEN`: the file is hidden, and not included in an ordinary directory listing. + HIDDEN = FILE_ATTRIBUTE_HIDDEN; + /// `FILE_ATTRIBUTE_OFFLINE`: the data is not immediately available, because it has been moved to + /// offline storage. The `CreateFileW` page says an application should not change it arbitrarily. + OFFLINE = FILE_ATTRIBUTE_OFFLINE; + /// `FILE_ATTRIBUTE_READONLY`: the file can be read and not written or deleted. + READONLY = FILE_ATTRIBUTE_READONLY; + /// `FILE_ATTRIBUTE_SYSTEM`: the file is part of, or used exclusively by, the operating system. + SYSTEM = FILE_ATTRIBUTE_SYSTEM; + /// `FILE_ATTRIBUTE_TEMPORARY`: the file is for temporary storage. The page says it lets a file + /// system keep the data in the cache and avoid writing it back while memory allows. + TEMPORARY = FILE_ATTRIBUTE_TEMPORARY; +} + +impl FileAttributes { + /// No attribute: what an open asks for unless it is told otherwise. + pub const NONE: Self = Self(0); + + /// The union of two sets. + #[must_use] + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Whether every member of `other` is in this set. + #[must_use] + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + /// Whether the set has no member. + #[must_use] + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// The mask this set is in `dwFlagsAndAttributes`. + pub(super) const fn bits(self) -> u32 { + self.0 + } +} + +impl BitOr for FileAttributes { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + self.union(other) + } +} + +impl BitOrAssign for FileAttributes { + fn bitor_assign(&mut self, other: Self) { + *self = self.union(other); + } +} + +impl fmt::Debug for FileAttributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("FileAttributes(")?; + if self.is_empty() { + f.write_str("none")?; + } else { + let mut first = true; + for (name, _) in NAMED.iter().filter(|(_, member)| self.contains(*member)) { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str(name)?; + } + } + f.write_str(")") + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_attributes/tests.rs b/crates/win-durable-ioring/src/windows_backend/file_attributes/tests.rs new file mode 100644 index 000000000..f27824059 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_attributes/tests.rs @@ -0,0 +1,130 @@ +// Copyright (c) 2026 Mike Grier + +use std::collections::HashSet; + +use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, +}; + +use super::{FileAttributes, NAMED}; + +/// Every member with the value `windows-sys` gives the attribute it is named for. The test that walks +/// this and `NAMED` is what makes the two the same set. +const EXPECTED: [(&str, u32); 6] = [ + ("ARCHIVE", FILE_ATTRIBUTE_ARCHIVE), + ("HIDDEN", FILE_ATTRIBUTE_HIDDEN), + ("OFFLINE", FILE_ATTRIBUTE_OFFLINE), + ("READONLY", FILE_ATTRIBUTE_READONLY), + ("SYSTEM", FILE_ATTRIBUTE_SYSTEM), + ("TEMPORARY", FILE_ATTRIBUTE_TEMPORARY), +]; + +#[test] +fn each_member_is_the_attribute_it_is_named_for() { + for (name, expected) in EXPECTED { + let (_, member) = NAMED + .iter() + .find(|(candidate, _)| *candidate == name) + .unwrap_or_else(|| panic!("{name} is not a named member")); + assert_eq!(member.bits(), expected, "{name}"); + } +} + +#[test] +fn the_named_members_are_exactly_the_expected_ones() { + let named: HashSet<&str> = NAMED.iter().map(|(name, _)| *name).collect(); + let expected: HashSet<&str> = EXPECTED.iter().map(|(name, _)| *name).collect(); + assert_eq!(named, expected); + assert_eq!(NAMED.len(), EXPECTED.len(), "no name is listed twice"); +} + +#[test] +fn no_two_members_are_the_same_bit_and_none_is_empty() { + let values: HashSet = NAMED.iter().map(|(_, member)| member.bits()).collect(); + assert_eq!(values.len(), NAMED.len()); + assert!(NAMED.iter().all(|(_, member)| !member.is_empty())); + assert!( + NAMED + .iter() + .all(|(_, member)| member.bits().count_ones() == 1), + "each member is one attribute" + ); +} + +#[test] +fn normal_and_encrypted_are_not_members() { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_NORMAL, + }; + for (name, member) in NAMED { + assert_ne!(member.bits(), FILE_ATTRIBUTE_NORMAL, "{name}"); + assert_ne!(member.bits(), FILE_ATTRIBUTE_ENCRYPTED, "{name}"); + } +} + +#[test] +fn none_is_empty_and_is_not_a_member() { + assert!(FileAttributes::NONE.is_empty()); + assert_eq!(FileAttributes::NONE.bits(), 0); + for (name, member) in NAMED { + assert!(!FileAttributes::NONE.contains(*member), "{name}"); + } +} + +#[test] +fn a_set_is_joined_with_or_and_asked_with_contains() { + let set = FileAttributes::HIDDEN | FileAttributes::SYSTEM; + assert!(set.contains(FileAttributes::HIDDEN)); + assert!(set.contains(FileAttributes::HIDDEN | FileAttributes::SYSTEM)); + assert!(!set.contains(FileAttributes::READONLY)); + assert!(!set.contains(FileAttributes::READONLY | FileAttributes::SYSTEM)); + assert!( + set.contains(FileAttributes::NONE), + "every set holds nothing" + ); + + let mut grown = FileAttributes::NONE; + grown |= FileAttributes::TEMPORARY; + grown |= FileAttributes::OFFLINE; + assert_eq!( + grown, + FileAttributes::TEMPORARY.union(FileAttributes::OFFLINE) + ); + assert_eq!(grown | grown, grown); +} + +#[test] +fn every_subset_of_the_members_is_a_distinct_set() { + assert!(NAMED.len() < 16, "an exhaustive walk must stay small"); + let mut seen = HashSet::new(); + for mask in 0..(1u32 << NAMED.len()) { + let set = NAMED + .iter() + .enumerate() + .filter(|(index, _)| mask & (1 << index) != 0) + .fold(FileAttributes::NONE, |set, (_, (_, member))| set | *member); + assert!( + seen.insert(set.bits()), + "{set:?} is a second way to make a set" + ); + } + assert_eq!(seen.len(), 64); +} + +#[test] +fn a_set_prints_the_names_of_what_it_holds() { + assert_eq!( + format!("{:?}", FileAttributes::NONE), + "FileAttributes(none)" + ); + assert_eq!( + format!("{:?}", FileAttributes::HIDDEN), + "FileAttributes(HIDDEN)" + ); + assert_eq!( + format!("{:?}", FileAttributes::SYSTEM | FileAttributes::ARCHIVE), + "FileAttributes(ARCHIVE | SYSTEM)", + "in the order they are declared, not the order they were joined" + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_id.rs b/crates/win-durable-ioring/src/windows_backend/file_id.rs new file mode 100644 index 000000000..90bae6a2e --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_id.rs @@ -0,0 +1,175 @@ +// Copyright (c) 2026 Mike Grier +//! A file's identity, read from a handle to it (DI-15.1, DI-D-69). +//! +//! The identity is what `GetFileInformationByHandleEx` reports for its `FileIdInfo` class: the serial +//! number of the volume the file is on and a 128-bit file identifier. The `FILE_ID_INFO` page says that +//! "the file identifier and the volume serial number uniquely identify a file on a single computer". +//! The call is the request crate's `QueryFileIdInfo`, given the handle shared and not a duplicate of it. +//! +//! The 128-bit form is the one this class reports. The page for `FILE_ID_DESCRIPTOR`, which +//! `OpenFileById` takes, lists it as `ExtendedFileIdType`, beside the 64-bit `FileIdType` and +//! `ObjectIdType`, and says it is "used on ReFS file systems"; an NTFS file's 16 bytes were observed +//! to open it as that type (`D-31` in the request crate's design notes). +//! +//! [`FileIdentifier`] is what an open by identifier takes: a [`FileId`], or one of the page's two other +//! kinds (DI-15.2, DI-D-70). + +use std::io; +use std::ops::Range; + +use win_shared_os_owned_handle::SharedHandle; +use windows_namespace_request_sys::file_id::{FileIdInfo, QueryFileIdInfo}; +use windows_namespace_request_sys::open_by_id::FileIdentifier as RequestIdentifier; + +use super::{Buffering, DataAccess, OpenedFile}; + +#[cfg(test)] +mod tests; + +/// The identity of a file on one computer: the serial number of its volume and its 128-bit file +/// identifier, as `GetFileInformationByHandleEx`'s `FileIdInfo` class reported them. +/// +/// Two values are equal when both fields are. What the fields mean beyond that is the call's: the +/// identifier is carried as its 16 bytes and is not read as an integer, which would impose a byte +/// order the call does not state. +/// +/// # Example +/// +/// ``` +/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile}; +/// use win_durable_ioring::Wtf16String; +/// +/// let path = std::env::temp_dir().join(format!("file-id-doc-{}.tmp", std::process::id())); +/// std::fs::write(&path, b"x")?; +/// let name = || Wtf16String::from_os_str(path.as_os_str()); +/// +/// let first = OpenedFile::::open(name(), FileOptions::new().read())?; +/// let second = OpenedFile::::open(name(), FileOptions::new().read().write())?; +/// +/// // Two opens of one file report one identity. +/// assert_eq!(first.file_id()?, second.file_id()?); +/// # drop((first, second)); +/// # std::fs::remove_file(&path)?; +/// # Ok::<(), Box>(()) +/// ``` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct FileId { + volume_serial_number: u64, + id: [u8; 16], +} + +impl FileId { + /// An identity with these fields, for a caller that holds them from elsewhere, such as a + /// directory listing. + #[must_use] + pub const fn new(volume_serial_number: u64, id: [u8; 16]) -> Self { + Self { + volume_serial_number, + id, + } + } + + /// The identity of the file `handle` is open on, read on the calling thread through the handle, + /// which is shared for the call and not duplicated. + /// + /// # Errors + /// + /// The code the call failed with, unaltered. An event, a pipe, `NUL` and a volume were observed + /// refused; the `FILE_ID_INFO` page does not list the codes, so none is promised. + pub fn of(handle: &SharedHandle) -> io::Result { + QueryFileIdInfo::new(handle.clone()) + .perform() + .map(Self::from_info) + .map_err(|error| error.to_io_error()) + } + + /// `VolumeSerialNumber`: the serial number of the volume that contains the file. + #[must_use] + pub const fn volume_serial_number(&self) -> u64 { + self.volume_serial_number + } + + /// `FileId`: the 128-bit file identifier, as its 16 bytes. + #[must_use] + pub const fn id(&self) -> [u8; 16] { + self.id + } + + /// The request crate's value as this crate's, field for field. + fn from_info(info: FileIdInfo) -> Self { + Self::new(info.volume_serial_number(), info.file_id()) + } +} + +/// What an open by identifier names the file by: one of the three kinds the `FILE_ID_DESCRIPTOR` page +/// lists. Each is opened on the volume of the hint the open is given, which is the only volume the +/// call is told of. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FileIdentifier { + /// `ExtendedFileIdType`: a [`FileId`]'s 128-bit identifier. A `FileId` also names a volume, which + /// the call does not take, so the opened file's identity is read and must be this one, volume + /// serial included; a hint on another volume is refused with + /// [`IdentityMismatch`](super::IdentityMismatch) and not answered with another volume's file. + Extended(FileId), + /// `FileIdType`: a 64-bit file ID, which `GetFileInformationByHandle` reports as + /// `nFileIndexHigh` and `nFileIndexLow`. It names no volume, so there is nothing to check. + FileId64(u64), + /// `ObjectIdType`: an object ID, as the 16 bytes of the `GUID` in the order Windows stores one in + /// memory, which is how `FSCTL_CREATE_OR_GET_OBJECT_ID` reports it (`FILE_OBJECTID_BUFFER`'s + /// `ObjectId`) and how `fsutil objectid` prints it. It names no volume. + ObjectId([u8; 16]), +} + +impl From for FileIdentifier { + fn from(id: FileId) -> Self { + Self::Extended(id) + } +} + +impl FileIdentifier { + /// The identifier as the request crate's entry takes it. + pub(super) fn to_request(self) -> RequestIdentifier { + match self { + Self::Extended(id) => RequestIdentifier::ExtendedFileId(id.id()), + Self::FileId64(id) => RequestIdentifier::FileId(id), + Self::ObjectId(bytes) => RequestIdentifier::ObjectId(guid_value(bytes)), + } + } +} + +/// Where each field of a `GUID` lies in its 16 bytes, as Windows lays one out: `Data1`, a `u32`, and +/// `Data2` and `Data3`, each a `u16`, stored little-endian, then `Data4`, eight bytes in order. +mod guid_layout { + use std::ops::Range; + + pub const DATA1: Range = 0..4; + pub const DATA2: Range = 4..6; + pub const DATA3: Range = 6..8; +} + +/// The `GUID` that `bytes` lay out, as the value the request crate's `ObjectId` takes: the `GUID` +/// read as one big-endian number in the order of its canonical text form, which is what +/// `GUID::from_u128` reads. The three little-endian fields are turned around and `Data4` is kept. +fn guid_value(bytes: [u8; 16]) -> u128 { + let mut text_order = bytes; + for field in [guid_layout::DATA1, guid_layout::DATA2, guid_layout::DATA3] { + reverse(&mut text_order, field); + } + u128::from_be_bytes(text_order) +} + +fn reverse(bytes: &mut [u8; 16], field: Range) { + bytes[field].reverse(); +} + +impl OpenedFile { + /// The identity of the file, read through its handle on the calling thread, as [`FileId::of`] + /// reads it. Any data access reads it, [`NoData`](super::NoData) included. + /// + /// # Errors + /// + /// Those of [`FileId::of`]. + pub fn file_id(&self) -> io::Result { + FileId::of(self.handle()) + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_id/tests.rs b/crates/win-durable-ioring/src/windows_backend/file_id/tests.rs new file mode 100644 index 000000000..099b2bf06 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_id/tests.rs @@ -0,0 +1,218 @@ +// Copyright (c) 2026 Mike Grier +//! Tests for `FileId`: that an identity is the file's and not the open's, that two files' differ, +//! and that it is what the request crate reads through the same handle. What a real file system +//! reports is what was observed on the machine this was written on. + +use std::collections::HashSet; +use std::fs::{self, OpenOptions}; +use std::os::windows::fs::OpenOptionsExt; + +use win_shared_os_owned_handle::SharedHandle; +use win_sync_sys::{Event, ResetMode}; +use windows_namespace_request_sys::file_id::QueryFileIdInfo; +use windows_namespace_request_sys::open_by_id::FileIdentifier as RequestIdentifier; +use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; + +use super::{FileId, FileIdentifier, guid_value}; +use crate::dioring::tests::{TempFile, wtf}; +use crate::windows_backend::{Cached, FileOptions, NoData, OpenedFile, ReadOnly, Unbuffered}; + +fn id_of(file: &TempFile) -> FileId { + file.open().file_id().expect("the identity of an open file") +} + +#[test] +fn two_opens_of_one_file_report_one_identity() { + let temp = TempFile::new("fid-two-opens"); + let (first, second) = (temp.open(), temp.open()); + assert_eq!( + first.file_id().expect("first"), + second.file_id().expect("second") + ); +} + +#[test] +fn two_files_on_one_volume_report_one_serial_and_different_identifiers() { + let (one, two) = (TempFile::new("fid-one"), TempFile::new("fid-two")); + let (one, two) = (id_of(&one), id_of(&two)); + assert_eq!(one.volume_serial_number(), two.volume_serial_number()); + assert_ne!(one.id(), two.id()); + assert_ne!(one, two); +} + +#[test] +fn the_identity_is_what_the_request_crate_reads_through_the_same_handle() { + let temp = TempFile::new("fid-request"); + let file = temp.open(); + let info = QueryFileIdInfo::new(file.handle().clone()) + .perform() + .expect("the request crate's read"); + let id = file.file_id().expect("this crate's read"); + assert_eq!(id.volume_serial_number(), info.volume_serial_number()); + assert_eq!(id.id(), info.file_id()); +} + +#[test] +fn an_open_with_no_data_access_reads_the_identity_an_open_with_data_does() { + let temp = TempFile::new("fid-no-data"); + let none = OpenedFile::::open(wtf(&temp.0), FileOptions::new()).expect("open"); + let read = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read()) + .expect("open"); + assert_eq!(none.file_id().expect("no data"), id_of(&temp)); + assert_eq!(read.file_id().expect("read only"), id_of(&temp)); +} + +#[test] +fn an_unbuffered_open_reports_the_identity_a_cached_one_does() { + let temp = TempFile::new("fid-unbuffered"); + let unbuffered = + OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()) + .expect("open unbuffered"); + assert_eq!(unbuffered.file_id().expect("unbuffered"), id_of(&temp)); +} + +#[test] +fn a_clone_shares_the_handle_and_so_the_identity() { + let temp = TempFile::new("fid-clone"); + let file = temp.open(); + let clone = file.clone(); + assert_eq!( + clone.file_id().expect("clone"), + file.file_id().expect("file") + ); +} + +#[test] +fn the_handle_shared_out_of_a_dropped_file_still_reads_its_identity() { + let temp = TempFile::new("fid-dropped"); + let file = temp.open(); + let expected = file.file_id().expect("before"); + let handle = file.handle().clone(); + drop(file); + assert_eq!(FileId::of(&handle).expect("after"), expected); +} + +#[test] +fn the_identity_survives_a_rename_and_a_change_of_contents() { + let temp = TempFile::new("fid-rename"); + let file = temp.open(); + let before = file.file_id().expect("before"); + + let moved = temp.0.with_extension("moved"); + fs::rename(&temp.0, &moved).expect("rename while open, which the default share mode allows"); + fs::write(&moved, b"other contents, and longer").expect("rewrite"); + let reopened = OpenedFile::::open(wtf(&moved), FileOptions::new().read()) + .expect("open at the new name"); + let (through_old, through_new) = ( + file.file_id().expect("through the first handle"), + reopened.file_id().expect("through the new one"), + ); + drop((file, reopened)); + fs::rename(&moved, &temp.0).expect("rename back, so the file is removed"); + + assert_eq!(through_old, before); + assert_eq!(through_new, before); +} + +#[test] +fn a_hard_link_reports_the_identity_of_its_file() { + let temp = TempFile::new("fid-link"); + let link = temp.0.with_extension("link"); + fs::hard_link(&temp.0, &link).expect("a hard link"); + let through_link = OpenedFile::::open(wtf(&link), FileOptions::new().read()) + .expect("open the link") + .file_id(); + fs::remove_file(&link).expect("remove the link"); + assert_eq!(through_link.expect("through the link"), id_of(&temp)); +} + +#[test] +fn a_directory_has_an_identity_of_its_own() { + let temp = TempFile::new("fid-directory"); + let directory = temp.0.with_extension("dir"); + fs::create_dir(&directory).expect("a directory"); + let handle = OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(&directory) + .map(SharedHandle::from); + let id = handle.and_then(|handle| FileId::of(&handle)); + fs::remove_dir(&directory).expect("remove the directory"); + + let id = id.expect("a directory's identity"); + let file = id_of(&temp); + assert_eq!(id.volume_serial_number(), file.volume_serial_number()); + assert_ne!(id, file); +} + +#[test] +fn a_handle_that_is_not_a_file_is_refused_with_the_kernels_code() { + let event = Event::new(ResetMode::Manual, false).expect("an event"); + let error = FileId::of(&SharedHandle::from(event)).expect_err("an event has no file identity"); + assert!( + error.raw_os_error().is_some(), + "the refusal is the kernel's code, carried unaltered: {error:?}" + ); +} + +#[test] +fn new_carries_its_fields_and_equality_and_hashing_use_both() { + let id = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 9, 10, 11, 12, 13, 14, 15, 16, + ]; + let value = FileId::new(0x0123_4567_89ab_cdef, id); + assert_eq!(value.volume_serial_number(), 0x0123_4567_89ab_cdef); + assert_eq!(value.id(), id); + + let mut other_id = id; + other_id[15] ^= 1; + let other_volume = FileId::new(0x0123_4567_89ab_cdee, id); + let other_file = FileId::new(0x0123_4567_89ab_cdef, other_id); + assert_ne!(value, other_volume); + assert_ne!(value, other_file); + assert_eq!(value, FileId::new(0x0123_4567_89ab_cdef, id)); + + let set: HashSet = [value, other_volume, other_file, value] + .into_iter() + .collect(); + assert_eq!(set.len(), 3); +} + +#[test] +fn an_object_ids_bytes_are_the_guid_windows_lays_out_and_the_request_crate_reads() { + // `fsutil objectid create` printed these bytes for a file on Windows 11 build 26300; as a GUID + // they are 7383249c-cb6e-11f1-857d-7ced8d529076, its little-endian first three fields turned + // around. The open by object ID that confirms the reading is in the by-identifier tests. + let bytes = [ + 0x9c, 0x24, 0x83, 0x73, 0x6e, 0xcb, 0xf1, 0x11, 0x85, 0x7d, 0x7c, 0xed, 0x8d, 0x52, 0x90, + 0x76, + ]; + assert_eq!(guid_value(bytes), 0x7383_249c_cb6e_11f1_857d_7ced_8d52_9076); + assert_eq!(guid_value([0; 16]), 0); + let mut last = [0; 16]; + last[15] = 1; + assert_eq!(guid_value(last), 1, "Data4 is kept in order"); + let mut first = [0; 16]; + first[0] = 1; + assert_eq!(guid_value(first), 1 << 96, "Data1 is little-endian"); +} + +#[test] +fn each_identifier_reaches_the_request_crate_as_its_own_kind() { + let id = FileId::new(7, [3; 16]); + assert_eq!(FileIdentifier::from(id), FileIdentifier::Extended(id)); + assert_eq!( + FileIdentifier::Extended(id).to_request(), + RequestIdentifier::ExtendedFileId([3; 16]) + ); + assert_eq!( + FileIdentifier::FileId64(0x1234).to_request(), + RequestIdentifier::FileId(0x1234) + ); + let mut bytes = [0; 16]; + bytes[15] = 9; + assert_eq!( + FileIdentifier::ObjectId(bytes).to_request(), + RequestIdentifier::ObjectId(9) + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_rights.rs b/crates/win-durable-ioring/src/windows_backend/file_rights.rs new file mode 100644 index 000000000..b9535ae5d --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_rights.rs @@ -0,0 +1,156 @@ +// Copyright (c) 2026 Mike Grier +//! [`FileRights`]: the rights an open asks for beyond reading and writing the data, as a typed set +//! (DI-13.3, DI-13.8, DI-D-64, DI-D-65). +//! +//! `CreateFileW` takes a `dwDesiredAccess` mask. The two rights that decide what this crate's own API +//! may do with a handle, `FILE_READ_DATA` and `FILE_WRITE_DATA`, are the data access type +//! ([`DataAccess`](super::DataAccess)), and this set holds the rest: the attribute, extended +//! attribute, execute, delete and security rights, which gate only calls a consumer makes on the +//! handle it is lent. Each member is a named constant, and a set is built by naming them and joining +//! them. There is no constructor from a raw number, so the only masks that exist are ones this +//! module's table lists, which is the whole of how an option that DI-D-64 excludes stays excluded: +//! there is no member for `FILE_APPEND_DATA`, and no generic right, whose expansion the kernel makes, +//! so a handle that appends and cannot write at an offset cannot be asked for. + +use std::fmt; +use std::ops::{BitOr, BitOrAssign}; + +use windows_sys::Win32::Storage::FileSystem::{ + DELETE, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_READ_EA, FILE_WRITE_ATTRIBUTES, FILE_WRITE_EA, + READ_CONTROL, SYNCHRONIZE, WRITE_DAC, WRITE_OWNER, +}; +use windows_sys::Win32::System::SystemServices::ACCESS_SYSTEM_SECURITY; + +#[cfg(test)] +mod tests; + +/// A set of file access rights other than the data rights, which are the +/// [`DataAccess`](super::DataAccess) of the handle. +/// +/// Each member is one right the documentation lists for a file, named as `CreateFileW` names it +/// without its prefix: [`WRITE_ATTRIBUTES`](Self::WRITE_ATTRIBUTES) is `FILE_WRITE_ATTRIBUTES`, and +/// the standard rights keep their names. A set is made by joining them with `|`: +/// +/// ``` +/// use win_durable_ioring::windows_backend::FileRights; +/// +/// let rights = FileRights::READ_ATTRIBUTES | FileRights::WRITE_ATTRIBUTES; +/// +/// assert!(rights.contains(FileRights::WRITE_ATTRIBUTES)); +/// assert!(!rights.contains(FileRights::DELETE)); +/// assert!(FileRights::NONE.is_empty()); +/// ``` +/// +/// **There is no generic right and no `FILE_ALL_ACCESS`.** A generic right mixes the data rights, which +/// are a type, with others, and the kernel expands it when it opens, so a set could not say what it +/// holds. Ask for the data access with [`FileOptions::read`](super::FileOptions::read) and +/// [`write`](super::FileOptions::write), and for the rest with the members here. +/// +/// **Append-only access is not offered**, by construction: there is no member for `FILE_APPEND_DATA`, +/// and no data access asks for it. A write through a handle opened for appending only lands at the end +/// of the file whatever offset it names, and the core addresses every write by offset. +/// +/// The set has no constructor from a raw mask. A right Win32 defines and this table does not list +/// cannot be expressed until the table gains it. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct FileRights(u32); + +/// Names the rights once, so the constants, the names `Debug` prints and the tests that walk every +/// right are one list and not three that can disagree. +macro_rules! rights { + ( $( $(#[$doc:meta])* $name:ident = $value:expr; )* ) => { + impl FileRights { + $( $(#[$doc])* pub const $name: Self = Self($value); )* + } + + /// Every named right, in the order they are declared. + const NAMED: &[(&str, FileRights)] = &[ $( (stringify!($name), FileRights::$name), )* ]; + }; +} + +rights! { + /// `FILE_READ_ATTRIBUTES`: read the file's attributes. + READ_ATTRIBUTES = FILE_READ_ATTRIBUTES; + /// `FILE_WRITE_ATTRIBUTES`: write the file's attributes, among them its times. + WRITE_ATTRIBUTES = FILE_WRITE_ATTRIBUTES; + /// `FILE_READ_EA`: read the file's extended attributes. + READ_EA = FILE_READ_EA; + /// `FILE_WRITE_EA`: write the file's extended attributes. + WRITE_EA = FILE_WRITE_EA; + /// `FILE_EXECUTE`: execute the file, for a native code file. + EXECUTE = FILE_EXECUTE; + /// `DELETE`: delete or rename the file. + DELETE = DELETE; + /// `READ_CONTROL`: read the file's security descriptor, without its system ACL. + READ_CONTROL = READ_CONTROL; + /// `WRITE_DAC`: change the file's discretionary ACL. + WRITE_DAC = WRITE_DAC; + /// `WRITE_OWNER`: change the file's owner. + WRITE_OWNER = WRITE_OWNER; + /// `SYNCHRONIZE`: wait on the handle. + SYNCHRONIZE = SYNCHRONIZE; + /// `ACCESS_SYSTEM_SECURITY`: read and change the file's system ACL. A caller the kernel does + /// not allow it gets the kernel's error from the open. + ACCESS_SYSTEM_SECURITY = ACCESS_SYSTEM_SECURITY; +} + +impl FileRights { + /// No rights beyond the data access. + pub const NONE: Self = Self(0); + + /// The union of two sets. + #[must_use] + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Whether every right of `other` is in this set. + #[must_use] + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + /// Whether the set holds no right. + #[must_use] + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// The mask this set adds to the data access's, in `dwDesiredAccess`. + pub(super) const fn bits(self) -> u32 { + self.0 + } +} + +impl BitOr for FileRights { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + self.union(other) + } +} + +impl BitOrAssign for FileRights { + fn bitor_assign(&mut self, other: Self) { + *self = self.union(other); + } +} + +impl fmt::Debug for FileRights { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("FileRights(")?; + if self.is_empty() { + f.write_str("none")?; + } else { + let mut first = true; + for (name, _) in NAMED.iter().filter(|(_, right)| self.contains(*right)) { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str(name)?; + } + } + f.write_str(")") + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_rights/tests.rs b/crates/win-durable-ioring/src/windows_backend/file_rights/tests.rs new file mode 100644 index 000000000..06ff2ff69 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/file_rights/tests.rs @@ -0,0 +1,122 @@ +// Copyright (c) 2026 Mike Grier + +use std::collections::HashSet; + +use windows_sys::Win32::Storage::FileSystem::{ + DELETE, FILE_APPEND_DATA, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_READ_DATA, FILE_READ_EA, + FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, READ_CONTROL, SYNCHRONIZE, WRITE_DAC, + WRITE_OWNER, +}; +use windows_sys::Win32::System::SystemServices::ACCESS_SYSTEM_SECURITY; + +use super::{FileRights, NAMED}; + +/// Every right with the value `windows-sys` gives the constant it is named for. The test that walks +/// this and `NAMED` is what makes the two the same set: a right added to one and not the other fails +/// it. +const EXPECTED: [(&str, u32); 11] = [ + ("READ_ATTRIBUTES", FILE_READ_ATTRIBUTES), + ("WRITE_ATTRIBUTES", FILE_WRITE_ATTRIBUTES), + ("READ_EA", FILE_READ_EA), + ("WRITE_EA", FILE_WRITE_EA), + ("EXECUTE", FILE_EXECUTE), + ("DELETE", DELETE), + ("READ_CONTROL", READ_CONTROL), + ("WRITE_DAC", WRITE_DAC), + ("WRITE_OWNER", WRITE_OWNER), + ("SYNCHRONIZE", SYNCHRONIZE), + ("ACCESS_SYSTEM_SECURITY", ACCESS_SYSTEM_SECURITY), +]; + +#[test] +fn each_right_is_the_constant_it_is_named_for() { + for (name, expected) in EXPECTED { + let (_, right) = NAMED + .iter() + .find(|(candidate, _)| *candidate == name) + .unwrap_or_else(|| panic!("{name} is not a named right")); + assert_eq!(right.bits(), expected, "{name}"); + } +} + +#[test] +fn the_named_rights_are_exactly_the_expected_ones() { + let named: HashSet<&str> = NAMED.iter().map(|(name, _)| *name).collect(); + let expected: HashSet<&str> = EXPECTED.iter().map(|(name, _)| *name).collect(); + assert_eq!(named, expected); + assert_eq!(NAMED.len(), EXPECTED.len(), "no name is listed twice"); +} + +#[test] +fn no_two_rights_are_the_same_value_and_none_is_empty() { + let values: HashSet = NAMED.iter().map(|(_, right)| right.bits()).collect(); + assert_eq!(values.len(), NAMED.len()); + assert!(NAMED.iter().all(|(_, right)| !right.is_empty())); +} + +#[test] +fn a_set_is_joined_with_or_and_asked_with_contains() { + let rights = FileRights::READ_ATTRIBUTES | FileRights::WRITE_ATTRIBUTES | FileRights::DELETE; + assert!(rights.contains(FileRights::DELETE)); + assert!(rights.contains(FileRights::READ_ATTRIBUTES | FileRights::DELETE)); + assert!(!rights.contains(FileRights::WRITE_EA)); + assert!(!rights.contains(FileRights::DELETE | FileRights::WRITE_EA)); + assert!(rights.contains(FileRights::NONE), "every set holds nothing"); + assert!(!FileRights::NONE.contains(FileRights::DELETE)); + + let mut grown = FileRights::NONE; + grown |= FileRights::DELETE; + grown |= FileRights::WRITE_DAC; + assert_eq!(grown, FileRights::DELETE.union(FileRights::WRITE_DAC)); + assert_eq!( + grown | grown, + grown, + "joining a set with itself changes nothing" + ); +} + +#[test] +fn the_empty_set_is_the_default_and_the_only_empty_one() { + assert_eq!(FileRights::default(), FileRights::NONE); + assert!(FileRights::NONE.is_empty()); + assert!(!FileRights::DELETE.is_empty()); +} + +#[test] +fn no_right_is_a_data_right_and_none_is_the_append_bit() { + // The data rights are the data access type, so a member that carried one would make a handle's + // type disagree with what it was opened with. + let data = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA; + for (name, right) in NAMED { + assert_eq!(right.bits() & data, 0, "{name}"); + } +} + +#[test] +fn no_join_of_the_rights_carries_a_data_right_or_the_append_bit() { + // Every join of the named rights, which is every set there is to ask for. + let data = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA; + assert!(NAMED.len() < 16, "an exhaustive walk must stay small"); + for mask in 0..(1u32 << NAMED.len()) { + let set = NAMED + .iter() + .enumerate() + .filter(|(index, _)| mask & (1 << index) != 0) + .fold(FileRights::NONE, |set, (_, (_, right))| set | *right); + assert_eq!(set.bits() & data, 0, "{set:?}"); + } +} + +#[test] +fn a_set_prints_the_names_of_what_it_holds() { + assert_eq!(format!("{:?}", FileRights::NONE), "FileRights(none)"); + assert_eq!(format!("{:?}", FileRights::DELETE), "FileRights(DELETE)"); + assert_eq!( + format!( + "{:?}", + FileRights::WRITE_ATTRIBUTES | FileRights::READ_ATTRIBUTES + ), + "FileRights(READ_ATTRIBUTES | WRITE_ATTRIBUTES)", + "in the order they are declared, not the order they were joined" + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/file_target.rs b/crates/win-durable-ioring/src/windows_backend/file_target.rs index 2a0b50f0d..21c6348eb 100644 --- a/crates/win-durable-ioring/src/windows_backend/file_target.rs +++ b/crates/win-durable-ioring/src/windows_backend/file_target.rs @@ -5,10 +5,10 @@ use win_shared_os_owned_handle::SharedHandle; use super::opened_disk::OpenedDisk; -use super::opened_file::{Buffering, OpenedFile}; +use super::opened_file::{Buffering, OpenedFile, Unbuffered}; use super::opened_volume::OpenedVolume; use durable_ioring::FlushDomain; -use durable_ioring::{Capabilities, Geometry, IoTarget, WarrantyClass}; +use durable_ioring::{Access, Capabilities, Geometry, HasData, IoTarget, WarrantyClass}; /// A target backed by a Win32 file, a raw volume or a whole disk this crate opened, held as a /// `SharedHandle` -- an owning form, never a raw handle (DI-D-4). @@ -17,8 +17,25 @@ use durable_ioring::{Capabilities, Geometry, IoTarget, WarrantyClass}; /// warranty class, its alignment and its capabilities from how that was opened, not from a /// declaration (DI-D-60): one opened cached is [`WarrantyClass::BestEffort`], and one opened /// [`Unbuffered`](super::Unbuffered) is [`WarrantyClass::SharedDevice`], needs a sector's alignment, -/// and offers per-write write-through. [`WarrantyClass::Complete`] is never one's: it needs -/// knowledge of what else writes the device, which a volume's lock does not give (DI-D-61). +/// and offers per-write write-through. [`WarrantyClass::Complete`] is never derived from how a file +/// was opened: it needs knowledge of what else writes the device, which a volume's lock does not give +/// (DI-D-61). A consumer who has that knowledge says so through +/// [`new_attested_exclusive`](Self::new_attested_exclusive), +/// [`from_volume_attested_exclusive`](Self::from_volume_attested_exclusive) or +/// [`from_disk_attested_exclusive`](Self::from_disk_attested_exclusive) (DI-D-68). +/// +/// A target needs a data right, so it is made only from a handle whose data access is [`HasData`]: one +/// opened with [`NoData`](super::NoData) is refused when the program is compiled, not when the +/// first operation fails. +/// +/// ```compile_fail,E0277 +/// use win_durable_ioring::Wtf16String; +/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, Win32FileTarget}; +/// +/// let name = Wtf16String::from_os_str("a-file".as_ref()); +/// let no_data = OpenedFile::::open(name, FileOptions::new()).unwrap(); +/// let _ = Win32FileTarget::new(no_data); +/// ``` /// /// A volume's flush domains are found from its disks. A file's are what it cannot know, so they are /// declared with [`with_domains`](Self::with_domains); declaring them on a volume adds to the ones @@ -35,12 +52,14 @@ pub struct Win32FileTarget { warranty: WarrantyClass, geometry: Geometry, capabilities: Capabilities, + /// What the handle was opened for, stated to the core (DI-D-66). + access: Access, } impl Win32FileTarget { /// The target `file` makes, claiming what its open mode allows and no more. #[must_use] - pub fn new(file: OpenedFile) -> Self { + pub fn new(file: OpenedFile) -> Self { Self { geometry: file.geometry(), handle: file.handle().clone(), @@ -49,13 +68,14 @@ impl Win32FileTarget { domains: Vec::new(), warranty: B::WARRANTY, capabilities: B::CAPABILITIES, + access: A::ACCESS, } } /// The target `volume` makes: its sector's alignment, the flush domains of the disks it sits /// on, and what its open mode allows and no more. #[must_use] - pub fn from_volume(volume: OpenedVolume) -> Self { + pub fn from_volume(volume: OpenedVolume) -> Self { let found = volume.flush_domains(); Self { geometry: volume.geometry(), @@ -65,13 +85,14 @@ impl Win32FileTarget { declared: Vec::new(), warranty: B::WARRANTY, capabilities: B::CAPABILITIES, + access: A::ACCESS, } } /// The target `disk` makes: its sector's alignment, the disk itself as its flush domain, and /// what its open mode allows and no more. #[must_use] - pub fn from_disk(disk: OpenedDisk) -> Self { + pub fn from_disk(disk: OpenedDisk) -> Self { let found = vec![disk.flush_domain()]; Self { geometry: disk.geometry(), @@ -81,9 +102,66 @@ impl Win32FileTarget { declared: Vec::new(), warranty: B::WARRANTY, capabilities: B::CAPABILITIES, + access: A::ACCESS, } } + /// The target `file` makes, with the consumer's attestation that nothing else writes the device it + /// is on and that the flush domains it reports are all of them, so that it claims + /// [`WarrantyClass::Complete`] (DI-D-68). + /// + /// The claim is the consumer's and nothing here can check it: a handle cannot show what else writes + /// a device. A wrong claim mislabels what the target reports and changes nothing the layer does, + /// which behaves the same for every class (DI-D-50 P3). The flush domains, the alignment and the + /// capabilities are what [`new`](Self::new) gives the same file. + /// + /// Only a file opened [`Unbuffered`] can be attested. A cached file claims + /// [`WarrantyClass::BestEffort`] because the cache may report success for data already lost, and + /// what else writes the device does not change that, so there is nothing to attest; it is refused + /// when the program is compiled. + /// + /// ```compile_fail,E0308 + /// use win_durable_ioring::Wtf16String; + /// use win_durable_ioring::windows_backend::{ + /// Cached, FileOptions, OpenedFile, Win32FileTarget, + /// }; + /// + /// let name = Wtf16String::from_os_str("a-file".as_ref()); + /// let cached = OpenedFile::::open(name, FileOptions::new().read().write()).unwrap(); + /// let _ = Win32FileTarget::new_attested_exclusive(cached); + /// ``` + #[must_use] + pub fn new_attested_exclusive(file: OpenedFile) -> Self { + Self::new(file).attested_exclusive() + } + + /// The target `volume` makes, with the consumer's attestation that nothing else writes the disks it + /// sits on and that the flush domains it reports, which are those disks, are all of them, so that + /// it claims [`WarrantyClass::Complete`]. The claim, what it does not change and why a cached + /// volume cannot make it are those of [`new_attested_exclusive`](Self::new_attested_exclusive). + /// + /// A volume's lock keeps other handles off the volume, not other writers off its disks + /// (DI-D-61), so a locked volume is not thereby attested. + #[must_use] + pub fn from_volume_attested_exclusive(volume: OpenedVolume) -> Self { + Self::from_volume(volume).attested_exclusive() + } + + /// The target `disk` makes, with the consumer's attestation that nothing else writes the disk and + /// that the flush domain it reports, the disk itself, is all of them, so that it claims + /// [`WarrantyClass::Complete`]. The claim, what it does not change and why a cached disk cannot + /// make it are those of [`new_attested_exclusive`](Self::new_attested_exclusive). + #[must_use] + pub fn from_disk_attested_exclusive(disk: OpenedDisk) -> Self { + Self::from_disk(disk).attested_exclusive() + } + + /// The one place the attested claim is made. + fn attested_exclusive(mut self) -> Self { + self.warranty = WarrantyClass::Complete; + self + } + /// Declare every flush domain its writes depend on, beyond any the backing found for itself. /// Declaring again replaces what was declared before, and never what the backing found. #[must_use] @@ -104,6 +182,16 @@ impl Win32FileTarget { &self.handle } + /// State an access the handle was not opened with, to test what happens when a target + /// overstates it: the core trusts what a target states, so the kernel's own refusal is what is + /// left to meet. + #[cfg(test)] + #[must_use] + pub(crate) fn claiming(mut self, access: Access) -> Self { + self.access = access; + self + } + /// State a class a file's open mode cannot give, to test what the backend does with each. #[cfg(test)] #[must_use] @@ -136,4 +224,8 @@ impl IoTarget for Win32FileTarget { fn capabilities(&self) -> Capabilities { self.capabilities } + + fn access(&self) -> Access { + self.access + } } diff --git a/crates/win-durable-ioring/src/windows_backend/opened_disk.rs b/crates/win-durable-ioring/src/windows_backend/opened_disk.rs index 09f9c8a27..3a8203a79 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_disk.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_disk.rs @@ -14,10 +14,8 @@ //! partition and no volume, so that blocking is documented and not exercised by them. use std::fmt; -use std::fs::OpenOptions; use std::io; use std::marker::PhantomData; -use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle}; use win_shared_os_owned_handle::SharedHandle; @@ -25,7 +23,9 @@ use win_volume_sys::bytes_per_sector; use super::opened_file::{Buffering, Unbuffered}; use super::opened_volume::disk_flush_domain; -use durable_ioring::{FlushDomain, Geometry}; +use super::raw_open; +use super::share_mode::ShareMode; +use durable_ioring::{DataAccess, FlushDomain, Geometry, ReadWrite}; #[cfg(test)] mod tests; @@ -61,34 +61,68 @@ impl fmt::Display for DiskPath { } } +/// How a disk is opened beyond its path: what other opens of the disk may request meanwhile, as +/// [`VolumeOptions`](super::VolumeOptions) carries for a volume. +#[derive(Clone, Copy, Debug)] +#[must_use] +pub struct DiskOptions { + share: ShareMode, +} + +impl Default for DiskOptions { + fn default() -> Self { + Self::new() + } +} + +impl DiskOptions { + /// Nothing requested beyond opening the disk, which is shared for reading, writing and + /// deletion, as `std::fs::OpenOptions` opens one. + pub fn new() -> Self { + Self { + share: ShareMode::ALL, + } + } + + /// What other opens of the disk may request while this handle is open (`dwShareMode`), as + /// [`FileOptions::share`](super::FileOptions::share) is for a file. [`ShareMode::NONE`] opens the + /// disk exclusively. An open the sharing of either side does not allow fails with a + /// [`SharingViolation`](super::SharingViolation). Calling this again replaces the mode. + pub fn share(mut self, share: ShareMode) -> Self { + self.share = share; + self + } +} + /// A whole physical disk this crate opened, in buffering mode `B` ([`Unbuffered`] unless said -/// otherwise): a [`SharedHandle`], its number and the sector it is addressed in. Cloning shares the -/// one handle. +/// otherwise) and with data access `A` ([`ReadWrite`] unless said otherwise): a [`SharedHandle`], +/// its number and the sector it is addressed in. Cloning shares the one handle. /// -/// It is what [`Win32FileTarget::from_disk`](super::Win32FileTarget::from_disk) is made from. +/// It is what [`Win32FileTarget::from_disk`](super::Win32FileTarget::from_disk) is made from, when +/// `A` has a data right. The data access is the one the open was given, which no argument carries, so +/// it is named in the type at the call: `OpenedDisk::::open(path, options)`. #[derive(Clone, Debug)] -pub struct OpenedDisk { +pub struct OpenedDisk { handle: SharedHandle, geometry: Geometry, disk_number: u32, - buffering: PhantomData, + mode: PhantomData<(B, A)>, } -impl OpenedDisk { - /// Open `path` for reading and writing in buffering mode `B`, and find its sector size. +impl OpenedDisk { + /// Open `path` with data access `A` in buffering mode `B`, and find its sector size. /// - /// Opening a disk for writing needs an account that is an administrator. + /// Opening a disk for writing needs an account that is an administrator. Opening it with + /// [`NoData`](super::NoData) asks for no data right, which is enough to query the disk, and the + /// result cannot be made into a target. /// /// # Errors /// - /// Whatever the open reports, such as `ERROR_FILE_NOT_FOUND` for a number no disk has, and the - /// device's own error from the sector size control. - pub fn open(path: DiskPath) -> io::Result { - let file = OpenOptions::new() - .read(true) - .write(true) - .custom_flags(B::FLAGS) - .open(path.path())?; + /// Whatever the open reports, such as `ERROR_FILE_NOT_FOUND` for a number no disk has, a + /// [`SharingViolation`](super::SharingViolation) among it, and the device's own error from the + /// sector size control. + pub fn open(path: DiskPath, options: DiskOptions) -> io::Result { + let file = raw_open::open::(path.path(), options.share)?; let sector = bytes_per_sector(&file)?; let geometry = B::sector_geometry(sector) .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; @@ -96,7 +130,7 @@ impl OpenedDisk { handle: SharedHandle::from(file), geometry, disk_number: path.disk_number(), - buffering: PhantomData, + mode: PhantomData, }) } @@ -132,18 +166,18 @@ impl OpenedDisk { handle, geometry: B::sector_geometry(sector).expect("a power of two"), disk_number, - buffering: PhantomData, + mode: PhantomData, } } } -impl AsHandle for OpenedDisk { +impl AsHandle for OpenedDisk { fn as_handle(&self) -> BorrowedHandle<'_> { self.handle.as_handle() } } -impl AsRawHandle for OpenedDisk { +impl AsRawHandle for OpenedDisk { fn as_raw_handle(&self) -> RawHandle { self.handle.as_raw_handle() } diff --git a/crates/win-durable-ioring/src/windows_backend/opened_disk/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_disk/tests.rs index ef0cf459a..10dbda99b 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_disk/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_disk/tests.rs @@ -2,12 +2,13 @@ use std::io; -use super::{DiskPath, OpenedDisk}; -use crate::dioring::tests::TempFile; +use super::{DiskOptions, DiskPath, OpenedDisk}; +use crate::dioring::tests::{TempFile, wtf}; use crate::windows_backend::{ - Cached, FileOptions, OpenedFile, Unbuffered, Win32FileTarget, disk_flush_domain, + Cached, FileOptions, OpenedFile, ReadOnly, ReadWrite, ShareMode, Unbuffered, Win32FileTarget, + WriteOnly, disk_flush_domain, }; -use durable_ioring::{Capabilities, Geometry, IoTarget, WarrantyClass}; +use durable_ioring::{Access, Capabilities, Geometry, IoTarget, WarrantyClass}; /// A disk made of the parts a test gives it, over an ordinary file's handle: what is derived from a /// disk does not depend on the handle being one, and opening one needs an administrator. @@ -16,7 +17,7 @@ fn assembled( sector: u32, ) -> (OpenedDisk, TempFile) { let temp = TempFile::new("od-assembled"); - let file = OpenedFile::::open(&temp.0, FileOptions::new().read(true).write(true)) + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()) .expect("open a file"); ( OpenedDisk::assembled(file.handle().clone(), disk_number, sector), @@ -24,6 +25,38 @@ fn assembled( ) } +#[test] +fn a_disk_is_opened_sharing_every_member_unless_told_otherwise() { + assert_eq!(DiskOptions::new().share, ShareMode::ALL); + assert_eq!(DiskOptions::default().share, ShareMode::ALL); + assert_eq!( + DiskOptions::new().share(ShareMode::NONE).share, + ShareMode::NONE + ); + assert_eq!( + DiskOptions::new() + .share(ShareMode::NONE) + .share(ShareMode::READ) + .share, + ShareMode::READ, + "the last call replaces the mode" + ); +} + +#[test] +fn a_disks_target_states_the_access_the_disk_was_opened_with() { + let temp = TempFile::new("od-access"); + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()) + .expect("open a file"); + let handle = file.handle().clone(); + let disk = OpenedDisk::::assembled(handle.clone(), 1, 512); + assert_eq!(Win32FileTarget::from_disk(disk).access(), Access::ReadOnly); + let disk = OpenedDisk::::assembled(handle.clone(), 1, 512); + assert_eq!(Win32FileTarget::from_disk(disk).access(), Access::WriteOnly); + let disk = OpenedDisk::::assembled(handle, 1, 512); + assert_eq!(Win32FileTarget::from_disk(disk).access(), Access::ReadWrite); +} + #[test] fn a_disk_is_named_by_its_number_in_the_one_form_that_names_a_disk() { for (number, path) in [ @@ -83,7 +116,38 @@ fn a_cached_disks_target_claims_best_effort_and_no_write_through() { } #[test] -fn a_disks_target_is_never_complete() { +fn an_attested_disks_target_is_complete_and_nothing_else_changes() { + for (number, sector) in [(0u32, 512u32), (6, 4096), (17, 512)] { + let (disk, _temp) = assembled::(number, sector); + let (sibling, _temp2) = assembled::(number, sector); + let attested = Win32FileTarget::from_disk_attested_exclusive(disk); + let plain = Win32FileTarget::from_disk(sibling); + + assert_eq!(attested.warranty(), WarrantyClass::Complete); + assert_eq!(plain.warranty(), WarrantyClass::SharedDevice); + assert_eq!(attested.flush_domains(), [disk_flush_domain(number)]); + assert_eq!(attested.flush_domains(), plain.flush_domains()); + assert_eq!(attested.geometry(), plain.geometry()); + assert_eq!(attested.capabilities(), plain.capabilities()); + assert_eq!(attested.access(), plain.access()); + } +} + +#[test] +fn an_attested_disk_still_adds_declared_domains_and_never_loses_its_own() { + let (disk, _temp) = assembled::(5, 512); + let other = durable_ioring::FlushDomain::new(b"a share".to_vec()); + let target = Win32FileTarget::from_disk_attested_exclusive(disk).with_domains([other.clone()]); + assert_eq!(target.warranty(), WarrantyClass::Complete); + assert_eq!(target.flush_domains(), [disk_flush_domain(5), other]); + assert_eq!( + target.with_domains([]).flush_domains(), + [disk_flush_domain(5)] + ); +} + +#[test] +fn a_disks_target_is_not_complete_unless_attested() { let (disk, _temp) = assembled::(0, 512); assert_ne!( Win32FileTarget::from_disk(disk).warranty(), @@ -107,8 +171,8 @@ fn declared_domains_are_added_to_the_disks_own_and_never_take_it_away() { fn a_disk_number_nothing_has_is_refused() { // Not opened for any purpose but to be refused: no disk is numbered this, so the open fails // whatever the account, and never touches a real one. - let error = - OpenedDisk::::open(DiskPath::number(u32::MAX - 1)).expect_err("no such disk"); + let error = OpenedDisk::::open(DiskPath::number(u32::MAX - 1), DiskOptions::new()) + .expect_err("no such disk"); assert!( matches!( error.kind(), diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file.rs b/crates/win-durable-ioring/src/windows_backend/opened_file.rs index c334486d1..25ec2053e 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_file.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_file.rs @@ -12,24 +12,56 @@ //! The buffering mode is a type: [`OpenedFile`] or [`OpenedFile`]. What a mode //! implies is stated once, on [`Buffering`]; a consumer that needs the stronger warranty asks for //! `OpenedFile` in its own signature, and a cached file cannot be passed. +//! +//! The open reflects `CreateFileW` and not `std::fs::OpenOptions` (DI-D-64): the path is a +//! `Wtf16String` handed over as given, the [`Disposition`] is Win32's own, and no option implies +//! another (a new file does not set the reparse flag by itself). It is a build, +//! [`OpenedFile::request`], and a perform, [`OpenRequest::perform`], with +//! [`OpenedFile::open`] the two run together. use std::fmt; -use std::fs; use std::io; use std::marker::PhantomData; use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle}; use std::path::{Component, Path, PathBuf, Prefix}; +use win_authorization_sys::{SecurityAttributes, SecurityDescriptor}; use win_shared_os_owned_handle::SharedHandle; -use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; +use windows_namespace_request_sys::open::OpenFile; +use windows_namespace_request_sys::{FinalPathError, QueryFinalPath}; +use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION; use windows_sys::Win32::Storage::FileSystem::{ - CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, OPEN_ALWAYS, OPEN_EXISTING, TRUNCATE_EXISTING, + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, + FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_WRITE_DATA, +}; +use wtf_string::{Wtf16Str, Wtf16String}; + +use durable_ioring::{ + Access, Capabilities, DataAccess, Geometry, GeometryError, NoData, ReadWrite, WarrantyClass, }; -use durable_ioring::{Capabilities, Geometry, GeometryError, WarrantyClass}; +use super::disposition::Disposition; +use super::file_attributes::FileAttributes; +use super::file_id::FileIdentifier; +use super::file_rights::FileRights; +use super::share_mode::ShareMode; +mod by_id; mod open_request; +mod reopen; +pub use by_id::{ByIdOptions, ByIdRequest, IdentityMismatch}; +pub use reopen::{ReopenOptions, ReopenRequest}; + +#[cfg(test)] +mod creation_tests; +#[cfg(test)] +mod flag_tests; +#[cfg(test)] +mod request_tests; +#[cfg(test)] +mod share_tests; #[cfg(test)] mod tests; @@ -125,106 +157,275 @@ impl Buffering for Unbuffered { } } -/// What to open: access and creation, as `std::fs::OpenOptions` names them. Only these, because -/// the open mode is what an [`OpenedFile`] vouches for, and a caller's own flags could change it. -/// A file is shared for reading, writing and deletion, as std opens one. -#[derive(Clone, Copy, Debug, Default)] +/// What to open: the data access, the other rights, what other opens may do meanwhile, what the open +/// does about a file that is there or not, whether a link is opened or followed, and the cache hints. +/// Only these, because the open mode is what an [`OpenedFile`] vouches for, and a caller's own flags +/// could change it. A file is shared for reading, writing and deletion, as std opens one, until +/// [`share`](Self::share) says otherwise. +/// +/// The data access is the type parameter `A`: it starts as [`NoData`], and [`read`](Self::read) and +/// [`write`](Self::write) move it, so the handle an open makes carries the access it was asked +/// for. +/// +/// ``` +/// use win_durable_ioring::windows_backend::{FileOptions, FileRights, ReadWrite}; +/// +/// let options: FileOptions = FileOptions::new() +/// .read() +/// .write() +/// .rights(FileRights::WRITE_ATTRIBUTES); +/// ``` +#[derive(Clone, Copy, Debug)] #[must_use] -pub struct FileOptions { - read: bool, - write: bool, - create: bool, - create_new: bool, - truncate: bool, +pub struct FileOptions { + access: PhantomData, + rights: FileRights, + share: ShareMode, + disposition: Disposition, + open_reparse_point: bool, + attributes: FileAttributes, + delete_on_close: bool, + open_no_recall: bool, + posix_semantics: bool, + overlapped: bool, + random_access: bool, + sequential_scan: bool, +} + +impl Default for FileOptions { + fn default() -> Self { + Self::new() + } } -impl FileOptions { - /// Nothing requested: set at least read or write before opening. +impl FileOptions { + /// Nothing requested: no data access and no other right. Ask to [`read`](Self::read) and + /// [`write`](Self::write) before opening a file that is to become a target. pub fn new() -> Self { - Self::default() + Self { + access: PhantomData, + rights: FileRights::NONE, + share: ShareMode::ALL, + disposition: Disposition::OpenExisting, + open_reparse_point: false, + attributes: FileAttributes::NONE, + delete_on_close: false, + open_no_recall: false, + posix_semantics: false, + overlapped: false, + random_access: false, + sequential_scan: false, + } + } +} + +impl FileOptions { + /// Ask for the right to read the data (`FILE_READ_DATA`). The options carry the access this adds + /// to what they already had: [`NoData`] becomes [`ReadOnly`](super::ReadOnly) and + /// [`WriteOnly`](super::WriteOnly) becomes [`ReadWrite`]. + pub fn read(self) -> FileOptions { + self.with_access() } - /// Open for reading. - pub fn read(mut self, read: bool) -> Self { - self.read = read; + /// Ask for the right to write the data (`FILE_WRITE_DATA`), as [`read`](Self::read) asks to read. + pub fn write(self) -> FileOptions { + self.with_access() + } + + /// The rights to open with besides the data access (`dwDesiredAccess`): the attribute, delete and + /// security rights, which gate calls made on the handle this crate lends and not anything it + /// does itself. What the file's security allows is the kernel's to say, and its refusal reaches + /// the caller as its own error. + /// + /// Calling this again replaces the rights; join them with `|` to ask for several. + pub fn rights(mut self, rights: FileRights) -> Self { + self.rights = rights; self } - /// Open for writing. - pub fn write(mut self, write: bool) -> Self { - self.write = write; + /// What other opens of the file may request while this handle is open (`dwShareMode`). The + /// default is [`ShareMode::ALL`]; [`ShareMode::NONE`] is an exclusive open, which a database that + /// owns its file wants. + /// + /// An open the sharing of either side does not allow fails with a [`SharingViolation`], whichever + /// of the two opens came first. Calling this again replaces the mode. + pub fn share(mut self, share: ShareMode) -> Self { + self.share = share; self } - /// Create the file if it does not exist. - pub fn create(mut self, create: bool) -> Self { - self.create = create; + fn with_access(self) -> FileOptions { + FileOptions { + access: PhantomData, + rights: self.rights, + share: self.share, + disposition: self.disposition, + open_reparse_point: self.open_reparse_point, + attributes: self.attributes, + delete_on_close: self.delete_on_close, + open_no_recall: self.open_no_recall, + posix_semantics: self.posix_semantics, + overlapped: self.overlapped, + random_access: self.random_access, + sequential_scan: self.sequential_scan, + } + } + + /// What the open does about a file that is there and one that is not (`dwCreationDisposition`). + /// The default is [`Disposition::OpenExisting`]. Calling this again replaces the disposition. + /// + /// No disposition changes the contents of a file that exists, so none is refused for want of the + /// right to write. A caller that wants an existing file emptied does it through the handle this + /// crate lends, with `File::set_len(0)`, which needs the write right and is not atomic with the + /// open. + pub fn disposition(mut self, disposition: Disposition) -> Self { + self.disposition = disposition; self } - /// Create the file, and fail if it exists. - pub fn create_new(mut self, create_new: bool) -> Self { - self.create_new = create_new; + /// Open a link itself and not what it points to (`FILE_FLAG_OPEN_REPARSE_POINT`). Without it a + /// link is followed, including by [`Disposition::CreateNew`], which then creates the file the + /// link names. With it a link that is already there is what exists, so `CreateNew` fails and + /// `OpenAlways` opens the link. It has no effect on a path that is not a reparse point. + pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self { + self.open_reparse_point = open_reparse_point; self } - /// Truncate the file to nothing on opening. - pub fn truncate(mut self, truncate: bool) -> Self { - self.truncate = truncate; + /// The attributes a file the open creates is given (`FILE_ATTRIBUTE_*` in `dwFlagsAndAttributes`). + /// The default is none, which is not `FILE_ATTRIBUTE_NORMAL`: the empty set passes no attribute at + /// all, and a created file has the attributes the file system gives it, `ARCHIVE` among them + /// (measured: the same as an open that passes `NORMAL`). Calling this again replaces the set. + /// + /// They are for a file that is created, and what an open of a file that exists does with them is + /// Win32's: the `CreateFileW` page says they are generally combined with the file's own or + /// ignored, and nothing here changes an existing file's attributes (measured: the open leaves them + /// as they were). + pub fn attributes(mut self, attributes: FileAttributes) -> Self { + self.attributes = attributes; self } - /// What this asks of `CreateFileW`, with `flags` added to the flags it implies, or the same - /// refusal `std::fs::OpenOptions` gave for a combination that cannot mean anything: no - /// access, or creating or truncating without write access. - fn parameters(self, flags: u32) -> io::Result { - let access = match (self.read, self.write) { - (true, false) => GENERIC_READ, - (false, true) => GENERIC_WRITE, - (true, true) => GENERIC_READ | GENERIC_WRITE, - (false, false) => { - return Err(invalid_input(if self.creates_or_truncates() { - "creating or truncating a file requires write access" - } else { - "must specify at least one of read or write access" - })); - } - }; - if !self.write && self.creates_or_truncates() { - return Err(invalid_input( - "creating or truncating a file requires write access", - )); - } - // `CREATE_ALWAYS` is not used: a created-and-truncated file is opened with `OPEN_ALWAYS` - // and emptied once open, as std does. - let (disposition, truncate_after) = match (self.create, self.truncate, self.create_new) { - (false, false, false) => (OPEN_EXISTING, false), - (true, false, false) => (OPEN_ALWAYS, false), - (false, true, false) => (TRUNCATE_EXISTING, false), - (true, true, false) => (OPEN_ALWAYS, true), - (_, _, true) => (CREATE_NEW, false), - }; - // A new file is never created through a link that is already there. - let reparse = if self.create_new { + /// Delete the file when its last handle is closed (`FILE_FLAG_DELETE_ON_CLOSE`). A durability + /// report is then about data that does not outlive the handles, which is the caller's choice to + /// make. + /// + /// The file goes when the last handle to it, however that was opened, is closed. Measured on + /// Windows 11 build 26300: the open succeeds without the right to delete among the + /// [`rights`](Self::rights) (the `CreateFileW` pages do not say it is needed; `ZwCreateFile`'s + /// does, for its own flag, and that is not the call made here), and while the + /// handle is open a later open has to share deletion ([`ShareMode::DELETE`]) or it fails with a + /// [`SharingViolation`]. Nothing is added or checked here; which combinations are refused ahead + /// of the open is `DI-14.4`'s. + pub fn delete_on_close(mut self, delete_on_close: bool) -> Self { + self.delete_on_close = delete_on_close; + self + } + + /// Ask that the file's data not be brought back from remote storage to be read + /// (`FILE_FLAG_OPEN_NO_RECALL`). The `CreateFileW` page says the flag is for remote storage + /// systems; it is passed on and nothing about a target depends on it. What it does to a file on + /// a local disk was not measured. + pub fn open_no_recall(mut self, open_no_recall: bool) -> Self { + self.open_no_recall = open_no_recall; + self + } + + /// Access the file by POSIX rules (`FILE_FLAG_POSIX_SEMANTICS`). The `CreateFileW` page says + /// that this includes allowing names that differ only in case, and that a file made with the flag + /// may not be reachable by applications written for MS-DOS or 16-bit Windows. It is passed on and + /// nothing about a target depends on it. + pub fn posix_semantics(mut self, posix_semantics: bool) -> Self { + self.posix_semantics = posix_semantics; + self + } + + /// Open the file for overlapped I/O (`FILE_FLAG_OVERLAPPED`). Every operation this crate's targets + /// make is addressed by offset, so the file pointer the system then stops keeping is not one they + /// read. Measured on Windows 11 build 26300 (the run is in `DI-D-64`), the ring takes such a + /// handle and reports the same result for each operation as for one opened without the flag, in + /// each buffering mode. What differs is when a completion is there: a handle opened without the + /// flag completes during the submit, and an unbuffered one opened with it was not complete then + /// for its reads, which then completed in an order that was not always the order queued. + /// + /// The handle this crate lends is the same one, so I/O a caller makes on it directly without an + /// offset, with a `std::fs::File` for one, is I/O on an overlapped handle, where the + /// `CreateFileW` page says the file pointer is not maintained and operations are not serialised. + pub fn overlapped(mut self, overlapped: bool) -> Self { + self.overlapped = overlapped; + self + } + + /// Hint that the file will be accessed at random (`FILE_FLAG_RANDOM_ACCESS`), which the system + /// may use to tune its caching. + /// + /// It is passed to an unbuffered file as well as a cached one, and is never refused. The + /// `CreateFileW` documentation says the flag "has no effect if the file system does not support + /// cached I/O and `FILE_FLAG_NO_BUFFERING`", which can be read two ways, so what it does to an + /// unbuffered file is the kernel's to say. Measured on Windows 11 build 26300: every combination + /// of the two hints, cached and unbuffered, opens and carries a sector-aligned write and read, + /// and an unbuffered one also runs through the ring. Whether a hint changes the system's caching + /// was not measured, and nothing about the warranty class, the alignment or write-through + /// depends on it. Asking for this and [`sequential_scan`](Self::sequential_scan) together is + /// the documentation's "self-defeating", and is passed on all the same. + pub fn random_access(mut self, random_access: bool) -> Self { + self.random_access = random_access; + self + } + + /// Hint that the file will be read sequentially from beginning to end + /// (`FILE_FLAG_SEQUENTIAL_SCAN`), which the system may use to tune its caching. The + /// documentation says it should not be used if reverse scans will be. + /// + /// Passed through and documented as [`random_access`](Self::random_access) is. + pub fn sequential_scan(mut self, sequential_scan: bool) -> Self { + self.sequential_scan = sequential_scan; + self + } + + /// What this asks of `CreateFileW`, with `flags` added to the flags it implies. Every + /// combination of the options is a request, so nothing here is refused. + fn parameters(self, flags: u32) -> open_request::Parameters { + let reparse = if self.open_reparse_point { FILE_FLAG_OPEN_REPARSE_POINT } else { 0 }; - Ok(open_request::Parameters { - access, - disposition, - flags: flags | reparse, - truncate_after, - }) - } - - fn creates_or_truncates(self) -> bool { - self.create || self.create_new || self.truncate + let mut named = 0; + if self.delete_on_close { + named |= FILE_FLAG_DELETE_ON_CLOSE; + } + if self.open_no_recall { + named |= FILE_FLAG_OPEN_NO_RECALL; + } + if self.posix_semantics { + named |= FILE_FLAG_POSIX_SEMANTICS; + } + if self.overlapped { + named |= FILE_FLAG_OVERLAPPED; + } + let mut hints = 0; + if self.random_access { + hints |= FILE_FLAG_RANDOM_ACCESS; + } + if self.sequential_scan { + hints |= FILE_FLAG_SEQUENTIAL_SCAN; + } + open_request::Parameters { + access: data_rights::() | self.rights.bits(), + disposition: self.disposition.bits(), + flags: flags | reparse | named | hints | self.attributes.bits(), + share: self.share.bits(), + } } } -fn invalid_input(message: &'static str) -> io::Error { - io::Error::new(io::ErrorKind::InvalidInput, message) +/// What `A` asks `CreateFileW` for: `FILE_READ_DATA` as it reads and `FILE_WRITE_DATA` as it writes, +/// and no other right. The append bit is never among them, so a handle that appends and cannot write +/// at an offset cannot be asked for. The markers are the core's (DI-D-66); what they ask of Win32 is +/// stated here. +pub(super) const fn data_rights() -> u32 { + (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 }) } /// An unbuffered file was asked for on a device of another machine, which cannot keep the claim @@ -247,55 +448,246 @@ impl fmt::Display for RemoteDevice { impl std::error::Error for RemoteDevice {} -/// A file this crate opened, in buffering mode `B`: a [`SharedHandle`] and what is known of it -/// because of how it was opened. Cloning shares the one handle. +/// An unbuffered file was asked for on a handle whose location could not be read: it has no final +/// path (`NUL` has none), so it is not known to be a file on a local volume, which is what the +/// warranty of an unbuffered file rests on. +/// +/// This is this crate's own narrowing, not a condition `CreateFileW` documents: the open itself +/// succeeded, and the handle is closed. A cached open of the same name is not refused. The call's +/// own error is the [`source`](std::error::Error::source), kept as the call reported it, and is not +/// a statement of what the handle is. +#[derive(Debug)] +pub struct UnknownLocation { + /// What reading the handle's final path reported. + pub cause: io::Error, +} + +impl fmt::Display for UnknownLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "the handle's final path could not be read, so it is not known to be a file on a local volume, which an unbuffered file needs: {}", + self.cause + ) + } +} + +impl std::error::Error for UnknownLocation { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.cause) + } +} + +/// An open failed because of how the file is already open: either this open asked for an access that +/// an open already holding the file does not share, or it did not share an access that open asked for. +/// `CreateFileW` fails both ways with `ERROR_SHARING_VIOLATION` and says nothing of which, and no +/// documented call reports the other open's access or sharing, so this carries what *this* open asked +/// for and nothing about the other. +/// +/// It is the cause of an [`io::ErrorKind::ResourceBusy`] error from [`OpenedFile::open`], reached with +/// [`io::Error::get_ref`] and a downcast, and of the same error from [`OpenedFile::open_by_id`]. The +/// kernel's own error, which carries the Win32 code, is its [`source`](std::error::Error::source). +#[derive(Debug)] +pub struct SharingViolation { + /// What the open named the file by. + pub by: OpenedBy, + /// The data access the open asked for, or `None` for one that asked for no data right. + pub data: Option, + /// The rights the open asked for besides the data access. + pub rights: FileRights, + /// What this open allowed other opens to request. + pub share: ShareMode, + os_error: io::Error, +} + +impl fmt::Display for SharingViolation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} is open in a way that conflicts with this open (data access {}, {:?}, sharing {:?}): \ + the other open does not share what this one asked for, or this one does not share what \ + the other holds", + self.by, + self.data + .map_or_else(|| "none".to_owned(), |data| data.to_string()), + self.rights, + self.share, + ) + } +} + +impl std::error::Error for SharingViolation { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.os_error) + } +} + +/// What an open named the file by: the path it was given, the identifier it opened on the hint's +/// volume, or, for a reopen, the handle it was given. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OpenedBy { + /// A path, as it was given. + Path(PathBuf), + /// An identifier, by [`OpenedFile::open_by_id`]. + Identifier(FileIdentifier), + /// What the original handle of a reopen names, by [`OpenedFile::reopen`]. The handle is the + /// caller's own, so it is not carried. + Reopen, +} + +impl fmt::Display for OpenedBy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Path(path) => write!(f, "{}", path.display()), + Self::Identifier(identifier) => write!(f, "the file identified by {identifier:?}"), + Self::Reopen => write!(f, "the file the reopened handle names"), + } + } +} + +/// The data access `A` asks for, as a value, or `None` for one that asks for no data right. +pub(super) fn data_access() -> Option { + match (A::READ, A::WRITE) { + (false, false) => None, + (true, false) => Some(Access::ReadOnly), + (false, true) => Some(Access::WriteOnly), + (true, true) => Some(Access::ReadWrite), + } +} + +/// `error`, or the [`SharingViolation`] it is if the kernel said so. +pub(super) fn refine_sharing( + error: io::Error, + path: &Wtf16Str, + data: Option, + rights: FileRights, + share: ShareMode, +) -> io::Error { + refine_sharing_by( + error, + || OpenedBy::Path(PathBuf::from(path.to_os_string())), + data, + rights, + share, + ) +} + +/// `error`, or the [`SharingViolation`] it is if the kernel said so, naming the file as `by` does. +fn refine_sharing_by( + error: io::Error, + by: impl FnOnce() -> OpenedBy, + data: Option, + rights: FileRights, + share: ShareMode, +) -> io::Error { + if error.raw_os_error() != Some(ERROR_SHARING_VIOLATION.cast_signed()) { + return error; + } + io::Error::new( + io::ErrorKind::ResourceBusy, + SharingViolation { + by: by(), + data, + rights, + share, + os_error: error, + }, + ) +} + +/// A file this crate opened, in buffering mode `B` and with data access `A`: a [`SharedHandle`] and +/// what is known of it because of how it was opened. Cloning shares the one handle. /// /// It is what a [`Win32FileTarget`](super::Win32FileTarget) is made from, and the only thing it -/// can be made from; the module documentation says why. +/// can be made from; the module documentation says why. A target needs a data right, so a file +/// opened with [`NoData`] cannot be made into one. /// /// A consumer that needs the stronger warranty says so in its signature, and a cached file cannot /// be passed: /// /// ```compile_fail,E0308 +/// use win_durable_ioring::Wtf16String; /// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, Unbuffered}; /// /// fn needs_an_unbuffered_file(_: OpenedFile) {} /// -/// let cached = OpenedFile::::open("a-file", FileOptions::new().read(true)).unwrap(); +/// let options = FileOptions::new().read().write(); +/// let name = Wtf16String::from_os_str("a-file".as_ref()); +/// let cached = OpenedFile::::open(name, options).unwrap(); /// needs_an_unbuffered_file(cached); /// ``` +/// +/// Nor can one opened for reading only be given where reading and writing are asked for: +/// +/// ```compile_fail,E0308 +/// use win_durable_ioring::Wtf16String; +/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, ReadWrite}; +/// +/// fn needs_both(_: OpenedFile) {} +/// +/// let name = Wtf16String::from_os_str("a-file".as_ref()); +/// let read_only = OpenedFile::::open(name, FileOptions::new().read()).unwrap(); +/// needs_both(read_only); +/// ``` +/// +/// The data access is a type parameter after the buffering mode, and a parameter written in a path +/// takes its default, [`ReadWrite`], when it is left off, so a file opened with other options names it +/// or leaves it to be inferred with `_`: `OpenedFile::::open(path, options)`. #[derive(Clone, Debug)] -pub struct OpenedFile { +pub struct OpenedFile { handle: SharedHandle, geometry: Geometry, - buffering: PhantomData, + mode: PhantomData<(B, A)>, } -impl OpenedFile { - /// Open `path` as `options` says, in buffering mode `B`. +impl OpenedFile { + /// Open `path` as `options` says, in buffering mode `B`: [`request`](Self::request) and then + /// [`perform`](OpenRequest::perform), one after the other on this thread. The handle has the data + /// access the options were given, which is its type `A`. + /// + /// **This blocks for as long as `CreateFileW` does**, which on a network path or an opportunistic + /// lock break can be a long time. A caller that cannot block builds the request here and performs + /// it on a thread it chooses. + /// + /// **Where the name leads is the caller's to know.** A name can be redirected to a device by a + /// reparse point in an intermediate directory, which `FILE_FLAG_OPEN_REPARSE_POINT` does not stop + /// (it applies to the final component), or by an object-manager link such as a drive letter, which + /// is resolved before a file system sees the name, so no reparse flag applies to it. An open of + /// a serial port's name was observed not to return. This crate does not inspect the name, refuse it, + /// bound the open or cancel it: the open is `CreateFileW`'s, and `CancelSynchronousIo` is documented + /// as a use and not as a guarantee, and targets a thread and not a call. The design notes' `DI-D-67` + /// has the sources and the measurement. /// /// # Errors /// - /// Whatever the open reports, and [`RemoteDevice`] (as an [`io::ErrorKind::Unsupported`] error) - /// when `B` can claim its warranty only on a local device and `path` is on a remote one. That - /// is decided from the path's final location, which resolves links and mapped drives, before - /// anything is created; the file can still be replaced by a link in the time between. - pub fn open(path: impl AsRef, options: FileOptions) -> io::Result { - let path = path.as_ref(); - if B::LOCAL_DEVICE_ONLY { - let location = final_location(path)?; - if names_a_remote_device(&location) { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - RemoteDevice { location }, - )); - } - } - let handle = open_request::open(path, options.parameters(B::FLAGS)?)?; - Ok(Self { - handle: SharedHandle::from(handle), - geometry: B::geometry(), - buffering: PhantomData, + /// Those of [`request`](Self::request) and of [`perform`](OpenRequest::perform). + pub fn open(path: Wtf16String, options: FileOptions) -> io::Result { + Self::request(path, options)?.perform() + } + + /// The request to open `path` as `options` says, in buffering mode `B`, built here and made + /// wherever the caller chooses with [`perform`](OpenRequest::perform). Nothing is opened, and + /// nothing blocks. + /// + /// The path is handed to Win32 as it was given: this crate does not lengthen it, shorten it or + /// refuse it, so a path longer than `MAX_PATH` without a verbatim prefix, and an empty one, are + /// what `CreateFileW` makes of them. A relative path is fixed to the current directory as it is + /// when this is called, so the request names the same file wherever and whenever it is performed. + /// Convert a `Path` with [`Wtf16String::from_os_str`], which loses nothing. + /// + /// # Errors + /// + /// [`io::ErrorKind::InvalidInput`] if `path` has an interior NUL, and the error of + /// `GetCurrentDirectoryW` if the current directory had to be read and could not be, both before + /// anything is touched. + pub fn request(path: Wtf16String, options: FileOptions) -> io::Result> { + let (rights, share) = (options.rights, options.share); + let request = open_request::request(path, options.parameters(B::FLAGS))?; + Ok(OpenRequest { + request, + rights, + share, + mode: PhantomData, }) } @@ -312,7 +704,104 @@ impl OpenedFile { } } -impl OpenedFile { +/// The request to open one file, built by [`OpenedFile::request`] and made by +/// [`perform`](Self::perform): the path, fixed to the current directory if it was relative, and the +/// options, typed by the buffering mode and the data access of the [`OpenedFile`] it opens. +/// +/// It is `Send`, so it can be built on the thread that has the path and the current directory and +/// performed on one that can afford to block, and it can be performed more than once, each time +/// opening the file again. +#[derive(Debug)] +#[must_use = "a request that is not performed opens nothing"] +pub struct OpenRequest { + request: OpenFile, + rights: FileRights, + share: ShareMode, + mode: PhantomData (B, A)>, +} + +impl OpenRequest { + /// Give the file this open creates the security descriptor, built from SDDL with + /// [`SecurityDescriptor::from_sddl`]. The request owns it and frees it, and a caller never holds + /// a pointer to it. Calling this again replaces the descriptor. + /// + /// It is the request's and not a [`FileOptions`] field because a descriptor owns memory and + /// cannot be copied, and the options are a plain `Copy` description that is passed to several + /// opens. Handles are never inheritable: the attributes this passes to `CreateFileW` say so + /// (`bInheritHandle` is false), and a request given no descriptor passes none, which the + /// `CreateFileW` page documents as a handle that cannot be inherited. + /// + /// The `CreateFileW` page says the descriptor is for a file that is created and is ignored when + /// the file exists (measured: an open of an existing file with a descriptor that denies reading + /// leaves the file as it was). This crate does not apply the descriptor itself; a created + /// file's descriptor reads back as the one given, through `win-authorization-sys`'s + /// `get_security_info`, for a file opened with [`FileRights::READ_CONTROL`]. + pub fn with_security_descriptor(self, descriptor: SecurityDescriptor) -> Self { + Self { + request: self + .request + .with_security(Some(SecurityAttributes::new(Some(descriptor), false))), + ..self + } + } + + /// Open the file, on the calling thread. **This blocks for as long as `CreateFileW` does**, and + /// where the name leads is the caller's to know, as [`OpenedFile::open`] says. + /// + /// # Errors + /// + /// [`RemoteDevice`] (as an [`io::ErrorKind::Unsupported`] error) when `B` can claim its warranty + /// only on a local device and the opened handle is on a remote one. That is decided from the + /// handle's final path, which `GetFinalPathNameByHandleW` resolves through links and mapped + /// drives, so there is no window between the check and the open in which a link can be + /// swapped. It is made after the open, so a file created before the refusal stays created and + /// the open has already reached the network. [`UnknownLocation`] (also + /// [`io::ErrorKind::Unsupported`]) when the handle's final path cannot be read, since it is then + /// not known to be a file on a local volume; the call's own error is its source. That is this + /// crate's narrowing and not a condition of `CreateFileW`, and only an unbuffered open is subject + /// to it: a cached open of a device is not refused, and what a name leads to is the caller's + /// responsibility (DI-D-67). Otherwise whatever the open reports, among them [`SharingViolation`] (as an + /// [`io::ErrorKind::ResourceBusy`] error) when the file is open in a way that conflicts with + /// this open's access or sharing. + pub fn perform(&self) -> io::Result> { + let handle = open_request::perform(&self.request).map_err(|error| { + refine_sharing( + error, + self.request.path(), + data_access::(), + self.rights, + self.share, + ) + })?; + OpenedFile::admit(SharedHandle::from(handle)) + } +} + +impl OpenedFile { + /// The checks every open makes on the handle it opened, whether by path or by identifier, and + /// the file it is if they pass (DI-14.5): when `B` claims its warranty only on a local device, the + /// handle's final path must be readable and must not name a device of another machine. + fn admit(handle: SharedHandle) -> io::Result { + if B::LOCAL_DEVICE_ONLY { + let location = final_location(&handle).map_err(|cause| { + io::Error::new(io::ErrorKind::Unsupported, UnknownLocation { cause }) + })?; + if names_a_remote_device(&location) { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + RemoteDevice { location }, + )); + } + } + Ok(OpenedFile { + handle, + geometry: B::geometry(), + mode: PhantomData, + }) + } +} + +impl OpenedFile { /// State the device's sector size, for a device whose sectors are smaller than /// [`Unbuffered::DEFAULT_SECTOR_SIZE`]. A size that is too small for the device is refused by /// the device on the first transfer that needs more, as an error on that operation's @@ -327,30 +816,33 @@ impl OpenedFile { } } -impl AsHandle for OpenedFile { +impl AsHandle for OpenedFile { fn as_handle(&self) -> BorrowedHandle<'_> { self.handle.as_handle() } } -impl AsRawHandle for OpenedFile { +impl AsRawHandle for OpenedFile { fn as_raw_handle(&self) -> RawHandle { self.handle.as_raw_handle() } } -/// Where `path` finally lands: itself if it exists, else the directory it would be created in. -fn final_location(path: &Path) -> io::Result { - match fs::canonicalize(path) { - Err(error) if error.kind() == io::ErrorKind::NotFound => { - let parent = path - .parent() - .filter(|parent| !parent.as_os_str().is_empty()) - .unwrap_or(Path::new(".")); - fs::canonicalize(parent) - } - other => other, - } +/// Where an opened handle is: its final path, in `GetFinalPathNameByHandleW`'s default form, which +/// follows links and mapped drives to the object the handle names. A handle opened on a link itself +/// (`FILE_FLAG_OPEN_REPARSE_POINT`) names the link, on its own volume. +/// +/// The request is given the handle shared, not a duplicate of it (DI-16.1). +fn final_location(handle: &SharedHandle) -> io::Result { + let path = QueryFinalPath::new(handle.clone()) + .perform() + .map_err(|error| match error { + FinalPathError::Win32(code) => { + io::Error::from_raw_os_error(open_request::raw_code(code.code())) + } + other => io::Error::other(other), + })?; + Ok(PathBuf::from(path.to_os_string())) } /// Whether a resolved path names a device of another machine: a UNC share, in either of its diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/by_id.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/by_id.rs new file mode 100644 index 000000000..5406b1699 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/by_id.rs @@ -0,0 +1,349 @@ +// Copyright (c) 2026 Mike Grier +//! An open by identifier, through `OpenFileById` (DI-15.2, DI-D-70): the file is named by a +//! [`FileIdentifier`] on the volume of a hint, and not by a path. +//! +//! The options are a type of their own, [`ByIdOptions`], and not [`FileOptions`](super::FileOptions): +//! the call's page lists no creation disposition, takes no attributes, marks the security parameter +//! reserved, and lists eight flags, of which `FILE_FLAG_DELETE_ON_CLOSE` and +//! `FILE_FLAG_POSIX_SEMANTICS` are not two. What a handle this crate opened can claim depends on how +//! it was opened and not on how it was named, so the file is the same [`OpenedFile`], and the checks +//! made on the handle are the path open's (DI-14.5). +//! +//! The hint is any handle on the volume, whatever opened it: it is never the file opened, so its mode +//! reaches nothing the result claims (`DI-D-64`'s `hVolumeHint` row). + +use std::fmt; +use std::io; +use std::marker::PhantomData; + +use win_shared_os_owned_handle::SharedHandle; +use windows_namespace_request_sys::open_by_id::OpenFileByIdentifier; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, + FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, +}; + +use durable_ioring::{DataAccess, NoData, ReadWrite}; + +use super::{Buffering, Cached, OpenedBy, OpenedFile, data_access, data_rights, refine_sharing_by}; +use crate::windows_backend::file_id::{FileId, FileIdentifier}; +use crate::windows_backend::file_rights::FileRights; +use crate::windows_backend::share_mode::ShareMode; + +#[cfg(test)] +mod tests; + +/// What to open by identifier: the data access, the other rights, what other opens may do meanwhile, +/// whether a link is opened or followed, and the hints, which are what `OpenFileById` takes. A file is +/// shared for reading, writing and deletion until [`share`](Self::share) says otherwise, as +/// [`FileOptions`](super::FileOptions) shares it. +/// +/// There is no disposition, since the call opens only what exists; no attributes, which its page +/// says it ignores; no security descriptor, which it marks reserved; and no delete-on-close or POSIX +/// semantics, which it does not list. `FILE_FLAG_BACKUP_SEMANTICS`, which a directory needs, and +/// `FILE_FLAG_WRITE_THROUGH` are not in v1, as for an open by path. +/// +/// ``` +/// use win_durable_ioring::windows_backend::{ByIdOptions, ReadOnly, ShareMode}; +/// +/// let options: ByIdOptions = ByIdOptions::new().read().share(ShareMode::READ); +/// ``` +#[derive(Clone, Copy, Debug)] +#[must_use] +pub struct ByIdOptions { + access: PhantomData, + rights: FileRights, + share: ShareMode, + open_reparse_point: bool, + open_no_recall: bool, + overlapped: bool, + random_access: bool, + sequential_scan: bool, +} + +impl Default for ByIdOptions { + fn default() -> Self { + Self::new() + } +} + +impl ByIdOptions { + /// Nothing requested: no data access and no other right, shared for everything. + pub fn new() -> Self { + Self { + access: PhantomData, + rights: FileRights::NONE, + share: ShareMode::ALL, + open_reparse_point: false, + open_no_recall: false, + overlapped: false, + random_access: false, + sequential_scan: false, + } + } +} + +impl ByIdOptions { + /// Ask for the right to read the data, as [`FileOptions::read`](super::FileOptions::read) does. + pub fn read(self) -> ByIdOptions { + self.with_access() + } + + /// Ask for the right to write the data, as [`FileOptions::write`](super::FileOptions::write) does. + pub fn write(self) -> ByIdOptions { + self.with_access() + } + + /// The rights besides the data access, as [`FileOptions::rights`](super::FileOptions::rights) + /// takes them. Calling this again replaces them. + pub fn rights(mut self, rights: FileRights) -> Self { + self.rights = rights; + self + } + + /// What other opens may request while this handle is open (`dwShareMode`), as + /// [`FileOptions::share`](super::FileOptions::share) takes it. Calling this again replaces it. + pub fn share(mut self, share: ShareMode) -> Self { + self.share = share; + self + } + + /// Open a reparse point itself and not what it points to (`FILE_FLAG_OPEN_REPARSE_POINT`). The + /// page says the flag is ignored for a file that is not a reparse point. + pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self { + self.open_reparse_point = open_reparse_point; + self + } + + /// Ask that the data not be brought back from remote storage (`FILE_FLAG_OPEN_NO_RECALL`), as + /// [`FileOptions::open_no_recall`](super::FileOptions::open_no_recall) does. + pub fn open_no_recall(mut self, open_no_recall: bool) -> Self { + self.open_no_recall = open_no_recall; + self + } + + /// Open for overlapped I/O (`FILE_FLAG_OVERLAPPED`), as + /// [`FileOptions::overlapped`](super::FileOptions::overlapped) describes. + pub fn overlapped(mut self, overlapped: bool) -> Self { + self.overlapped = overlapped; + self + } + + /// The random-access hint (`FILE_FLAG_RANDOM_ACCESS`), as + /// [`FileOptions::random_access`](super::FileOptions::random_access) describes. + pub fn random_access(mut self, random_access: bool) -> Self { + self.random_access = random_access; + self + } + + /// The sequential-scan hint (`FILE_FLAG_SEQUENTIAL_SCAN`), as + /// [`FileOptions::sequential_scan`](super::FileOptions::sequential_scan) describes. + pub fn sequential_scan(mut self, sequential_scan: bool) -> Self { + self.sequential_scan = sequential_scan; + self + } + + fn with_access(self) -> ByIdOptions { + ByIdOptions { + access: PhantomData, + rights: self.rights, + share: self.share, + open_reparse_point: self.open_reparse_point, + open_no_recall: self.open_no_recall, + overlapped: self.overlapped, + random_access: self.random_access, + sequential_scan: self.sequential_scan, + } + } + + /// `dwDesiredAccess`: the data rights `A` names and the other rights. + fn access(self) -> u32 { + data_rights::() | self.rights.bits() + } + + /// `dwFlagsAndAttributes`: `mode`, the buffering mode's flags, and the flags the options name. + fn flags(self, mode: u32) -> u32 { + let mut flags = mode; + if self.open_reparse_point { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + if self.open_no_recall { + flags |= FILE_FLAG_OPEN_NO_RECALL; + } + if self.overlapped { + flags |= FILE_FLAG_OVERLAPPED; + } + if self.random_access { + flags |= FILE_FLAG_RANDOM_ACCESS; + } + if self.sequential_scan { + flags |= FILE_FLAG_SEQUENTIAL_SCAN; + } + flags + } +} + +/// The request to open a file by identifier, built by [`OpenedFile::request_by_id`] and made by +/// [`perform`](Self::perform). It holds the hint, shared, so the handle it was built from can be +/// closed first; it is `Send`, so it can be performed on a thread that can afford to block; and it +/// can be performed more than once, each time opening the file again. +#[derive(Debug)] +#[must_use = "an unperformed request opens nothing"] +pub struct ByIdRequest { + request: OpenFileByIdentifier, + identifier: FileIdentifier, + rights: FileRights, + share: ShareMode, + mode: PhantomData (B, A)>, +} + +impl ByIdRequest { + /// The identifier this request opens. + #[must_use] + pub fn identifier(&self) -> FileIdentifier { + self.identifier + } + + /// The hint whose volume the identifier is opened on, shared with its other holders. + #[must_use] + pub fn volume_hint(&self) -> &SharedHandle { + self.request.volume_hint() + } + + /// Open the file, on the calling thread. **This blocks for as long as `OpenFileById` does.** + /// + /// # Errors + /// + /// - [`SharingViolation`](super::SharingViolation) (as an [`io::ErrorKind::ResourceBusy`] error), + /// naming the file by [`OpenedBy::Identifier`], when the file is open in a way that conflicts + /// with this open's access or sharing. + /// - For a [`FileIdentifier::Extended`], [`IdentityMismatch`] (as an + /// [`io::ErrorKind::InvalidInput`] error) when the file opened is not the identity asked for, + /// which is a hint on another volume; the handle is closed. An error reading the opened file's + /// identity is returned as it came. + /// - The path open's checks, [`RemoteDevice`](super::RemoteDevice) and + /// [`UnknownLocation`](super::UnknownLocation), made on the opened handle by the same code. No + /// test reaches them by identifier: the page lists SMB as not supported, so an open by + /// identifier on a share is the kernel's to refuse, and the call opens only a file on a file + /// system, whose final path is readable. + /// - Otherwise whatever `OpenFileById` reports, unaltered. The page names one, `ERROR_ACCESS_DENIED` + /// for a file pending deletion; an identifier that names nothing is the kernel's error, and the + /// page does not say which. + pub fn perform(&self) -> io::Result> { + let handle = self.request.perform().map_err(|error| { + refine_sharing_by( + error.to_io_error(), + || OpenedBy::Identifier(self.identifier), + data_access::(), + self.rights, + self.share, + ) + })?; + let handle = SharedHandle::from(handle); + if let FileIdentifier::Extended(asked) = self.identifier { + let opened = FileId::of(&handle)?; + if opened != asked { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + IdentityMismatch { asked, opened }, + )); + } + } + OpenedFile::admit(handle) + } +} + +impl OpenedFile { + /// Open the file `identifier` names on the volume `volume_hint` is on, as `options` say, in + /// buffering mode `B`: [`request_by_id`](Self::request_by_id) and then + /// [`perform`](ByIdRequest::perform), one after the other on this thread. + /// + /// ``` + /// use win_durable_ioring::windows_backend::{ByIdOptions, Cached, FileOptions, OpenedFile}; + /// use win_durable_ioring::Wtf16String; + /// + /// let path = std::env::temp_dir().join(format!("by-id-doc-{}.tmp", std::process::id())); + /// std::fs::write(&path, b"x")?; + /// let name = Wtf16String::from_os_str(path.as_os_str()); + /// let file = OpenedFile::::open(name, FileOptions::new().read())?; + /// + /// // Any handle on the volume is a hint; here, the file's own. + /// let again = OpenedFile::::open_by_id( + /// file.handle().clone(), + /// file.file_id()?, + /// ByIdOptions::new().read(), + /// )?; + /// assert_eq!(again.file_id()?, file.file_id()?); + /// # drop((file, again)); + /// # std::fs::remove_file(&path)?; + /// # Ok::<(), Box>(()) + /// ``` + /// + /// **An identifier can name a directory, and it is opened.** `FILE_FLAG_BACKUP_SEMANTICS`, which + /// the page says a directory needs, is not in v1, and an open by identifier was observed to reach a + /// directory without it. It is not refused: a target made from it faults every read and write with + /// the kernel's error, and a write that faulted is covered by no durability report, so the commits + /// it completes vouch for nothing. On a directory the data rights are `FILE_LIST_DIRECTORY` and + /// `FILE_ADD_FILE`, the same bits, and that is what the lent handle carries. What a flush of a + /// directory makes durable no page says. The design notes' `DI-D-72` has the measurement. + /// + /// # Errors + /// + /// Those of [`perform`](ByIdRequest::perform). + pub fn open_by_id( + volume_hint: impl Into, + identifier: impl Into, + options: ByIdOptions, + ) -> io::Result { + Self::request_by_id(volume_hint, identifier, options).perform() + } + + /// The request to open the file `identifier` names on the volume `volume_hint` is on, built here + /// and made wherever the caller chooses with [`perform`](ByIdRequest::perform). Nothing is opened + /// and nothing blocks. The hint is held, shared, and not duplicated. + pub fn request_by_id( + volume_hint: impl Into, + identifier: impl Into, + options: ByIdOptions, + ) -> ByIdRequest { + let identifier = identifier.into(); + let request = OpenFileByIdentifier::new(volume_hint, identifier.to_request()) + .with_desired_access(options.access()) + .with_share_mode(options.share.bits()) + .with_flags_and_attributes(options.flags(B::FLAGS)); + ByIdRequest { + request, + identifier, + rights: options.rights, + share: options.share, + mode: PhantomData, + } + } +} + +/// An open by a [`FileIdentifier::Extended`] opened a file whose identity is not the one asked for: +/// the call takes the volume from the hint and not from the [`FileId`], so a hint on another volume +/// opens whatever has that identifier there, or nothing. The handle is closed. +/// +/// It is the cause of an [`io::ErrorKind::InvalidInput`] error from [`OpenedFile::open_by_id`], +/// reached with [`io::Error::get_ref`] and a downcast. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct IdentityMismatch { + /// The identity the open was asked for. + pub asked: FileId, + /// The identity of the file it opened. + pub opened: FileId, +} + +impl fmt::Display for IdentityMismatch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "the open by identifier asked for {:?} and opened {:?}: the volume hint is not on the \ + volume the identity names", + self.asked, self.opened + ) + } +} + +impl std::error::Error for IdentityMismatch {} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/by_id/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/by_id/tests.rs new file mode 100644 index 000000000..e072e486c --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/by_id/tests.rs @@ -0,0 +1,599 @@ +// Copyright (c) 2026 Mike Grier +//! Tests for the open by identifier: each kind of identifier opens the file it names, from a hint of +//! each kind; what the options ask the kernel for; and what is refused. What the kernel does with an +//! identifier that names nothing, a directory and an inheritable handle is what was observed on the +//! machine this was written on, and is recorded in `DI-D-70`. + +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::os::windows::fs::OpenOptionsExt; +use std::os::windows::io::AsHandle; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use win_handles_sys::{HandleFlags, get_handle_information}; +use win_shared_os_owned_handle::SharedHandle; +use win_sync_sys::{Event, ResetMode}; +use windows_file_enumeration_sys::{Completion, EnumerationRequest, FileIdentityMode, Session}; +use windows_namespace_request_sys::file_info::QueryFileInformationByHandle; +use windows_namespace_request_sys::open::OpenFile; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL, + FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_RANDOM_ACCESS, + FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_SHARE_DELETE, FILE_SHARE_READ, + FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_EXISTING, +}; +use wtf_string::Wtf16String; + +use super::{ByIdOptions, IdentityMismatch}; +use crate::dioring::tests::{TempFile, wtf}; +use crate::windows_backend::{ + Cached, FileId, FileIdentifier, FileOptions, FileRights, NoData, OpenedBy, OpenedFile, + ReadOnly, ReadWrite, ShareMode, SharingViolation, Unbuffered, +}; +use durable_ioring::{Access, Geometry}; + +/// `ERROR_INVALID_PARAMETER`, which an unbuffered file answers an unaligned transfer with. +const INVALID_PARAMETER: i32 = 87; + +/// `ERROR_ACCESS_DENIED`. +const ACCESS_DENIED: i32 = 5; + +fn read_write() -> ByIdOptions { + ByIdOptions::new().read().write() +} + +/// A second handle to what `opened` holds, as a `std::fs::File`, so the kernel judges the I/O. +fn io_through(opened: &impl AsHandle) -> File { + File::from( + opened + .as_handle() + .try_clone_to_owned() + .expect("duplicate the handle"), + ) +} + +/// A handle to `directory`, which `FILE_FLAG_BACKUP_SEMANTICS` lets std open. +fn directory_handle(directory: &Path) -> SharedHandle { + OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(directory) + .map(SharedHandle::from) + .expect("open the directory") +} + +/// The directory `TempFile`s are made in, on the volume the tests' files are on. +fn temp_directory() -> SharedHandle { + directory_handle(&std::env::temp_dir()) +} + +fn contents_through( + file: &OpenedFile, +) -> Vec { + let mut contents = Vec::new(); + io_through(file) + .read_to_end(&mut contents) + .expect("read through the handle"); + contents +} + +/// A directory of its own under the temporary directory, removed with what is in it when dropped. +struct Directory(PathBuf); + +impl Directory { + fn new(tag: &str) -> Self { + let path = TempFile::new(tag).0.with_extension("dir"); + fs::create_dir(&path).expect("create the directory"); + Self(path) + } +} + +impl Drop for Directory { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +// --- What each kind of identifier opens --------------------------------------------------------- + +#[test] +fn a_file_opened_by_path_and_again_by_its_identity_is_the_same_file() { + let temp = TempFile::new("bi-round-trip"); + let by_path = temp.open(); + let id = by_path.file_id().expect("the identity"); + + let by_id = OpenedFile::::open_by_id(temp_directory(), id, read_write()) + .expect("open by identity"); + assert_eq!(by_id.file_id().expect("its identity"), id); + + io_through(&by_id) + .write_all(b"written by id") + .expect("write through the handle opened by identity"); + let mut seen = String::new(); + let mut through_path = io_through(&by_path); + through_path.seek(SeekFrom::Start(0)).expect("rewind"); + through_path.read_to_string(&mut seen).expect("read back"); + assert_eq!(seen, "written by id"); +} + +#[test] +fn the_files_own_handle_a_volume_handle_and_a_directory_are_each_a_hint() { + let temp = TempFile::new("bi-hints"); + let file = temp.open(); + let id = file.file_id().expect("the identity"); + let volume = volume_of(&temp.0); + + for hint in [file.handle().clone(), temp_directory(), volume] { + let opened = OpenedFile::::open_by_id(hint, id, ByIdOptions::new().read()) + .expect("open by identity"); + assert_eq!(opened.file_id().expect("its identity"), id); + } +} + +/// A handle to the volume `path` is on, opened asking for no access, which the hint needs none of. +fn volume_of(path: &Path) -> SharedHandle { + let drive: String = path.to_string_lossy().chars().take(2).collect(); + assert!( + drive.ends_with(':'), + "the temporary directory is on a drive letter: {}", + path.display() + ); + OpenFile::new(Wtf16String::from(format!(r"\\.\{drive}").as_str())) + .expect("the path has no NUL") + .with_desired_access(0) + .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_creation_disposition(OPEN_EXISTING) + .perform() + .map(SharedHandle::from) + .expect("open the volume asking for no access") +} + +#[test] +fn the_64_bit_file_id_opens_the_file() { + let temp = TempFile::new("bi-64"); + fs::write(&temp.0, b"sixty-four").expect("write the file"); + let file = temp.open(); + let information = QueryFileInformationByHandle::new(file.handle().clone()) + .perform() + .expect("GetFileInformationByHandle"); + let id64 = (u64::from(information.nFileIndexHigh) << 32) | u64::from(information.nFileIndexLow); + + let opened = OpenedFile::::open_by_id( + temp_directory(), + FileIdentifier::FileId64(id64), + ByIdOptions::new().read(), + ) + .expect("open by the 64-bit file ID"); + assert_eq!( + opened.file_id().expect("its identity"), + file.file_id().expect("the file's") + ); + assert_eq!(contents_through(&opened), b"sixty-four"); +} + +#[test] +fn an_object_id_as_fsutil_reports_it_opens_the_file() { + let temp = TempFile::new("bi-object"); + let output = Command::new("fsutil") + .args(["objectid", "create"]) + .arg(&temp.0) + .output() + .expect("run fsutil"); + assert!( + output.status.success(), + "fsutil objectid create: {output:?}" + ); + let object_id = first_hex_identifier(&String::from_utf8_lossy(&output.stdout)) + .expect("fsutil printed an object ID"); + + let opened = OpenedFile::::open_by_id( + temp_directory(), + FileIdentifier::ObjectId(object_id), + ByIdOptions::new().read(), + ) + .expect("open by object ID"); + assert_eq!( + opened.file_id().expect("its identity"), + temp.open().file_id().expect("the file's") + ); +} + +/// The first 32 hex digits standing alone in `text`, as bytes in the order printed. +fn first_hex_identifier(text: &str) -> Option<[u8; 16]> { + let token = text + .split_whitespace() + .find(|token| token.len() == 32 && token.chars().all(|c| c.is_ascii_hexdigit()))?; + let mut bytes = [0; 16]; + for (index, byte) in bytes.iter_mut().enumerate() { + *byte = u8::from_str_radix(&token[index * 2..index * 2 + 2], 16).ok()?; + } + Some(bytes) +} + +#[test] +fn an_identity_from_a_directory_listing_opens_the_entry() { + let directory = Directory::new("bi-listing"); + let path = directory.0.join("entry.txt"); + fs::write(&path, b"listed").expect("create the entry"); + + let (session, receiver) = Session::new(8, 8).expect("a session"); + let request = EnumerationRequest::for_path(&directory.0) + .expect("a request") + .with_file_identity(FileIdentityMode::Required); + session.try_begin(request).expect("begin").detach(); + let mut identity = None; + while let Some(completion) = receiver.recv() { + match completion { + Completion::Entry { entry, .. } if entry.name().to_os_string() == "entry.txt" => { + identity = Some(entry.identity()); + } + Completion::Entry { .. } => {} + Completion::Terminal { .. } => break, + } + } + let identity = identity.expect("the listing has the entry"); + let id = FileId::new( + identity + .volume_serial() + .expect("the identity is volume-qualified"), + identity.id_bytes(), + ); + + let opened = OpenedFile::::open_by_id( + directory_handle(&directory.0), + id, + ByIdOptions::new().read(), + ) + .expect("open the listed identity"); + assert_eq!(contents_through(&opened), b"listed"); + drop(opened); + drop(session); +} + +// --- What is refused ---------------------------------------------------------------------------- + +#[test] +fn an_identity_whose_volume_is_not_the_hints_is_refused_and_nothing_is_held() { + let temp = TempFile::new("bi-mismatch"); + let real = temp.open().file_id().expect("the identity"); + let asked = FileId::new(real.volume_serial_number() ^ 1, real.id()); + + let error = + OpenedFile::::open_by_id(temp_directory(), asked, ByIdOptions::new().read()) + .expect_err("the hint's volume is not the identity's"); + assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + let mismatch = error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is an IdentityMismatch"); + assert_eq!( + *mismatch, + IdentityMismatch { + asked, + opened: real + } + ); + assert!( + !temp.is_held(), + "the handle the refused open made is closed" + ); +} + +#[test] +fn the_identity_asked_for_on_its_own_volume_passes_the_check_even_with_no_access() { + // The check reads the opened file's identity, which needs no data right, and compares the whole + // identity: the same identity on the same volume opens. + let temp = TempFile::new("bi-match"); + let id = temp.open().file_id().expect("the identity"); + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new()) + .expect("the identity asked for is the one opened"); +} + +#[test] +fn an_identity_that_names_nothing_is_the_kernels_error() { + let temp = TempFile::new("bi-stale"); + let id = temp.open().file_id().expect("the identity"); + let path = temp.0.clone(); + drop(temp); + assert!(!path.exists(), "the file is gone"); + + let error = + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new().read()) + .expect_err("nothing has that identity"); + assert!( + error.raw_os_error().is_some(), + "the kernel's code, unaltered: {error:?}" + ); + assert!( + error.get_ref().is_none(), + "not one of this crate's errors: {error:?}" + ); +} + +#[test] +fn a_directory_was_observed_to_open_by_identity_where_by_path_it_is_refused() { + // `FILE_FLAG_BACKUP_SEMANTICS`, which the `OpenFileById` page says "You must set ... to obtain a + // handle to a directory", is not in v1, and an open by path of a directory without it is refused + // by the kernel. An open by identity was observed to open the directory anyway, for every data + // access and in either buffering mode. The engineer ruled it allowed (`DI-D-72`): a target made + // from one faults every read and write and completes every commit, which the backend's tests show. + let directory = Directory::new("bi-directory"); + let id = FileId::of(&directory_handle(&directory.0)).expect("the directory's identity"); + + let by_path = OpenedFile::::open(wtf(&directory.0), FileOptions::new()) + .expect_err("a directory is not opened by path without the backup flag"); + assert_eq!(by_path.raw_os_error(), Some(ACCESS_DENIED)); + + let opened = [ + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new()) + .and_then(|file| file.file_id()), + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new().read()) + .and_then(|file| file.file_id()), + OpenedFile::::open_by_id(temp_directory(), id, read_write()) + .and_then(|file| file.file_id()), + OpenedFile::::open_by_id( + temp_directory(), + id, + ByIdOptions::new().read(), + ) + .and_then(|file| file.file_id()), + ]; + for result in opened { + assert_eq!(result.expect("the directory opens by identity"), id); + } +} + +#[test] +fn a_hint_that_is_not_on_a_file_system_is_the_kernels_error() { + let temp = TempFile::new("bi-event-hint"); + let id = temp.open().file_id().expect("the identity"); + let event = Event::new(ResetMode::Manual, false).expect("an event"); + let error = OpenedFile::::open_by_id(event, id, ByIdOptions::new().read()) + .expect_err("an event is on no volume"); + assert!( + error.raw_os_error().is_some(), + "the kernel's code, unaltered: {error:?}" + ); +} + +#[test] +fn an_open_that_conflicts_with_the_sharing_of_one_already_open_is_a_sharing_violation() { + let temp = TempFile::new("bi-share"); + let exclusive = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().read().share(ShareMode::NONE), + ) + .expect("an exclusive open"); + let id = exclusive.file_id().expect("the identity"); + + let error = OpenedFile::::open_by_id( + temp_directory(), + id, + ByIdOptions::new() + .read() + .rights(FileRights::READ_ATTRIBUTES), + ) + .expect_err("the first open shares nothing"); + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); + let violation = error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is a SharingViolation"); + assert_eq!( + violation.by, + OpenedBy::Identifier(FileIdentifier::Extended(id)) + ); + assert_eq!(violation.data, Some(Access::ReadOnly)); + assert_eq!(violation.rights, FileRights::READ_ATTRIBUTES); + assert_eq!(violation.share, ShareMode::ALL); + assert!( + violation.to_string().contains("the file identified by"), + "{violation}" + ); +} + +#[test] +fn a_file_deleted_by_name_while_held_was_observed_to_open_by_identity() { + // The page: "If you call OpenFileById on a file that is pending deletion as a result of a + // previous call to DeleteFile, the function fails", with ERROR_ACCESS_DENIED. Here the file is + // held by a handle that shares deletion and deleted by name with `std::fs::remove_file`; its + // name is then gone, and an open by identity was observed to open it. What state the deletion + // left the file in was not examined, and the page is the authority. Nothing here depends on it. + let temp = TempFile::new("bi-pending"); + let held = temp.open(); + let id = held.file_id().expect("the identity"); + fs::remove_file(&temp.0).expect("delete while it is held"); + assert!(!temp.0.exists(), "the name is gone"); + + let opened = + OpenedFile::::open_by_id(held.handle().clone(), id, ByIdOptions::new()) + .expect("observed to open"); + assert_eq!(opened.file_id().expect("its identity"), id); +} + +// --- What the handle is ------------------------------------------------------------------------- + +#[test] +fn the_kernel_allows_the_data_access_asked_for_and_no_other() { + let temp = TempFile::new("bi-access"); + let id = temp.open().file_id().expect("the identity"); + let read_only = + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new().read()) + .expect("open read-only"); + let mut through = io_through(&read_only); + let mut byte = [0; 1]; + through + .read_exact(&mut byte) + .expect("a read-only handle reads"); + let refused = through + .write_all(b"no") + .expect_err("a read-only handle does not write"); + assert_eq!(refused.raw_os_error(), Some(ACCESS_DENIED)); +} + +#[test] +fn an_unbuffered_open_by_identity_is_unbuffered_to_the_kernel_and_needs_a_sector() { + let temp = TempFile::new("bi-unbuffered"); + let id = temp.open().file_id().expect("the identity"); + let unbuffered = OpenedFile::::open_by_id(temp_directory(), id, read_write()) + .expect("open unbuffered by identity"); + assert_eq!( + unbuffered.geometry(), + Geometry::uniform(Unbuffered::DEFAULT_SECTOR_SIZE).expect("a power of two") + ); + let mut through = io_through(&unbuffered); + let refused = through + .seek(SeekFrom::Start(3)) + .and_then(|_| through.write_all(b"seven b")) + .expect_err("an unbuffered handle refuses an unaligned write"); + assert_eq!(refused.raw_os_error(), Some(INVALID_PARAMETER)); + + let cached = OpenedFile::::open_by_id(temp_directory(), id, read_write()) + .expect("open cached by identity"); + assert_eq!(cached.geometry(), Geometry::UNALIGNED); + let mut through = io_through(&cached); + through + .seek(SeekFrom::Start(3)) + .and_then(|_| through.write_all(b"seven b")) + .expect("a cached handle takes it"); +} + +#[test] +fn the_handle_an_open_by_identity_makes_is_not_inheritable() { + // The page does not say; the request passes no security attributes, as the page marks the + // parameter reserved, and the handle that results was observed not inheritable. + let temp = TempFile::new("bi-inherit"); + let id = temp.open().file_id().expect("the identity"); + let opened = + OpenedFile::::open_by_id(temp_directory(), id, ByIdOptions::new().read()) + .expect("open by identity"); + assert_eq!( + get_handle_information(opened.handle().as_handle()).expect("query"), + HandleFlags::NONE + ); +} + +#[test] +fn a_request_is_built_on_one_thread_and_performed_on_another_and_again() { + let temp = TempFile::new("bi-thread"); + let id = temp.open().file_id().expect("the identity"); + let hint = temp_directory(); + let request = + OpenedFile::::request_by_id(hint.clone(), id, ByIdOptions::new().read()); + assert_eq!(request.identifier(), FileIdentifier::Extended(id)); + drop(hint); + + let request = std::thread::spawn(move || { + let first = request.perform().expect("performed on another thread"); + let second = request.perform().expect("and again"); + assert_eq!(first.file_id().expect("first"), id); + assert_eq!(second.file_id().expect("second"), id); + request + }) + .join() + .expect("the thread"); + assert_eq!(request.identifier(), FileIdentifier::Extended(id)); +} + +// --- What the options ask for ------------------------------------------------------------------- + +#[test] +fn each_option_sets_its_own_flag_and_no_other() { + let base = ByIdOptions::new(); + assert_eq!(base.flags(0), 0); + let cases: [(ByIdOptions, u32); 5] = [ + (base.open_reparse_point(true), FILE_FLAG_OPEN_REPARSE_POINT), + (base.open_no_recall(true), FILE_FLAG_OPEN_NO_RECALL), + (base.overlapped(true), FILE_FLAG_OVERLAPPED), + (base.random_access(true), FILE_FLAG_RANDOM_ACCESS), + (base.sequential_scan(true), FILE_FLAG_SEQUENTIAL_SCAN), + ]; + for (options, flag) in cases { + assert_eq!(options.flags(0), flag); + assert_eq!( + options.flags(FILE_FLAG_NO_BUFFERING), + flag | FILE_FLAG_NO_BUFFERING + ); + } + let every = cases.iter().fold(0, |flags, (_, flag)| flags | flag); + let all = base + .open_reparse_point(true) + .open_no_recall(true) + .overlapped(true) + .random_access(true) + .sequential_scan(true); + assert_eq!(all.flags(0), every); + assert_eq!( + all.open_no_recall(false).flags(0), + every & !FILE_FLAG_OPEN_NO_RECALL + ); +} + +#[test] +fn the_access_is_the_data_access_and_the_other_rights_and_survives_a_change_of_access() { + let base = ByIdOptions::new(); + assert_eq!(base.access(), 0); + assert_eq!(base.read().access(), FILE_READ_DATA); + assert_eq!(base.write().access(), FILE_WRITE_DATA); + assert_eq!( + base.read().write().access(), + FILE_READ_DATA | FILE_WRITE_DATA + ); + assert_eq!( + base.write().read().access(), + FILE_READ_DATA | FILE_WRITE_DATA + ); + + let with_rights = base + .rights(FileRights::READ_ATTRIBUTES) + .share(ShareMode::READ) + .sequential_scan(true); + let moved = with_rights.read().write(); + assert_eq!( + moved.access(), + FILE_READ_DATA | FILE_WRITE_DATA | FileRights::READ_ATTRIBUTES.bits() + ); + assert_eq!(moved.share, ShareMode::READ); + assert_eq!(moved.flags(0), FILE_FLAG_SEQUENTIAL_SCAN); +} + +#[test] +fn a_file_is_shared_for_everything_unless_told_otherwise() { + assert_eq!(ByIdOptions::new().share, ShareMode::ALL); + assert_eq!(ByIdOptions::default().share, ShareMode::ALL); + assert_eq!( + ByIdOptions::new().share(ShareMode::NONE).share, + ShareMode::NONE + ); +} + +#[test] +fn the_request_carries_what_the_options_and_the_buffering_mode_ask_for() { + let temp = TempFile::new("bi-request"); + let options = ByIdOptions::new() + .read() + .rights(FileRights::READ_ATTRIBUTES) + .share(ShareMode::READ | ShareMode::DELETE) + .random_access(true); + let id = FileIdentifier::FileId64(42); + let cached = OpenedFile::::request_by_id(temp_directory(), id, options); + let unbuffered = + OpenedFile::::request_by_id(temp_directory(), id, options); + for (request, mode) in [ + (&cached.request, 0), + (&unbuffered.request, FILE_FLAG_NO_BUFFERING), + ] { + assert_eq!( + request.desired_access(), + FILE_READ_DATA | FileRights::READ_ATTRIBUTES.bits() + ); + assert_eq!(request.share_mode(), FILE_SHARE_READ | FILE_SHARE_DELETE); + assert_eq!( + request.flags_and_attributes(), + FILE_FLAG_RANDOM_ACCESS | mode + ); + assert!(request.security().is_none(), "the parameter is reserved"); + } + drop(temp); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/creation_tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/creation_tests.rs new file mode 100644 index 000000000..6a3132533 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/creation_tests.rs @@ -0,0 +1,405 @@ +// Copyright (c) 2026 Mike Grier +//! What a created file is given (DI-14.3): its attributes and its security descriptor, and the one +//! thing a handle is never given, inheritance. + +use std::fs; +use std::os::windows::fs::MetadataExt; +use std::os::windows::io::AsHandle; +use std::path::Path; + +use win_authorization_sys::{ObjectType, SecurityInformation, get_security_info}; +use win_handles_sys::{HandleFlags, get_handle_information}; +use windows_namespace_request_sys::OpenFile; +use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED; +use windows_sys::Win32::Storage::FileSystem::{ + CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_SYSTEM, FILE_FLAG_NO_BUFFERING, FILE_READ_DATA, + FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, +}; +use wtf_string::Wtf16String; + +use super::{ + Cached, DataAccess, Disposition, FileAttributes, FileOptions, FileRights, OpenedFile, + ReadWrite, SecurityDescriptor, Unbuffered, +}; +use crate::dioring::tests::{TempFile, wtf}; + +fn create_new() -> FileOptions { + FileOptions::new() + .read() + .write() + .disposition(Disposition::CreateNew) +} + +fn flags(options: FileOptions) -> u32 { + options.parameters(0).flags +} + +fn attributes_of(path: &Path) -> u32 { + fs::metadata(path).expect("metadata").file_attributes() +} + +fn sddl(text: &str) -> SecurityDescriptor { + SecurityDescriptor::from_sddl(&Wtf16String::from(text)).expect("a valid descriptor") +} + +/// Removes a file whatever attributes it was created with. +// The lint is about Unix, where this would make a file world writable; this crate is Windows only, +// where it clears the read-only attribute. +#[allow(clippy::permissions_set_readonly_false)] +fn remove(path: &Path) { + let mut permissions = fs::metadata(path).expect("metadata").permissions(); + permissions.set_readonly(false); + fs::set_permissions(path, permissions).expect("clear read-only"); + fs::remove_file(path).expect("remove"); +} + +#[test] +fn the_attributes_are_added_to_the_flags_and_none_is_no_bits() { + let base = FileOptions::new().read().write(); + assert_eq!(flags(base), 0, "none asked for, none passed"); + assert_eq!( + flags(base.attributes(FileAttributes::NONE)), + 0, + "the empty set is not FILE_ATTRIBUTE_NORMAL" + ); + assert_eq!( + flags(base.attributes(FileAttributes::HIDDEN | FileAttributes::SYSTEM)), + FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM + ); + assert_eq!( + flags( + base.attributes(FileAttributes::HIDDEN) + .attributes(FileAttributes::SYSTEM) + ), + FILE_ATTRIBUTE_SYSTEM, + "the last set stated replaces the earlier" + ); + let all = base + .attributes(FileAttributes::HIDDEN) + .delete_on_close(true) + .random_access(true) + .open_reparse_point(true) + .parameters(FILE_FLAG_NO_BUFFERING) + .flags; + assert_eq!( + all, + FILE_FLAG_NO_BUFFERING + | FILE_ATTRIBUTE_HIDDEN + | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_DELETE_ON_CLOSE + | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_RANDOM_ACCESS + | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT, + "added to the others and never in place of them" + ); +} + +#[test] +fn changing_the_data_access_keeps_the_attributes() { + let moved = FileOptions::new() + .attributes(FileAttributes::HIDDEN | FileAttributes::TEMPORARY) + .read() + .write(); + assert_eq!( + flags(moved), + FILE_ATTRIBUTE_HIDDEN | windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_TEMPORARY + ); +} + +#[test] +fn a_created_file_carries_the_attributes_asked_for() { + let temp = TempFile::new("cr-attributes"); + let fresh = temp.0.with_extension("fresh"); + let attributes = FileAttributes::HIDDEN | FileAttributes::SYSTEM; + let opened = OpenedFile::::open(wtf(&fresh), create_new().attributes(attributes)) + .expect("create"); + let found = attributes_of(&fresh); + assert_eq!( + found & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM), + FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM + ); + drop(opened); + remove(&fresh); +} + +#[test] +fn a_file_created_read_only_can_be_written_through_the_handle_that_created_it() { + // Measured on Windows 11 build 26300: the create succeeds with write access although the file is + // read-only, and the handle that made it can write; a later open for writing is refused. + let temp = TempFile::new("cr-readonly"); + let fresh = temp.0.with_extension("fresh"); + let opened = OpenedFile::::open( + wtf(&fresh), + create_new().attributes(FileAttributes::READONLY), + ) + .expect("create read-only with write access"); + assert_ne!(attributes_of(&fresh) & FILE_ATTRIBUTE_READONLY, 0); + { + use std::io::Write; + use std::os::windows::io::AsHandle; + let mut through = fs::File::from( + opened + .as_handle() + .try_clone_to_owned() + .expect("duplicate the handle"), + ); + through.write_all(b"written").expect("write through it"); + } + let refused = OpenedFile::::open(wtf(&fresh), FileOptions::new().write()) + .expect_err("a read-only file"); + assert_eq!( + refused.raw_os_error(), + Some(ERROR_ACCESS_DENIED.cast_signed()) + ); + drop(opened); + remove(&fresh); +} + +#[test] +fn no_attributes_and_the_normal_attribute_create_the_same_file() { + // What `FILE_ATTRIBUTE_NORMAL` asks for is the absence of attributes, so the empty set passes + // zero; the two are compared here through the request crate, which can pass either. + let temp = TempFile::new("cr-normal"); + let none = temp.0.with_extension("none"); + let normal = temp.0.with_extension("normal"); + drop(OpenedFile::::open(wtf(&none), create_new()).expect("none")); + + let share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + let request = OpenFile::new(wtf(&normal)) + .expect("a path") + .with_desired_access(FILE_READ_DATA | FILE_WRITE_DATA) + .with_share_mode(share) + .with_creation_disposition(CREATE_NEW) + .with_flags_and_attributes(FILE_ATTRIBUTE_NORMAL); + drop(request.perform().expect("normal")); + + assert_eq!(attributes_of(&none), attributes_of(&normal)); + assert_ne!(attributes_of(&none) & FILE_ATTRIBUTE_ARCHIVE, 0); + remove(&none); + remove(&normal); +} + +#[test] +fn an_open_of_a_file_that_exists_leaves_its_attributes_as_they_were() { + // Measured on Windows 11 build 26300, for every disposition that opens a file that is there. + let temp = TempFile::new("cr-existing"); + let before = attributes_of(&temp.0); + for disposition in [Disposition::OpenExisting, Disposition::OpenAlways] { + let options = FileOptions::new() + .read() + .write() + .disposition(disposition) + .attributes(FileAttributes::HIDDEN | FileAttributes::SYSTEM); + drop(OpenedFile::::open(wtf(&temp.0), options).expect("open")); + assert_eq!(attributes_of(&temp.0), before, "{disposition:?}"); + } +} + +#[test] +fn the_attributes_reach_an_unbuffered_create_as_they_reach_a_cached_one() { + let temp = TempFile::new("cr-unbuffered"); + let fresh = temp.0.with_extension("fresh"); + let opened = OpenedFile::::open( + wtf(&fresh), + create_new().attributes(FileAttributes::HIDDEN), + ) + .expect("create unbuffered"); + assert_ne!(attributes_of(&fresh) & FILE_ATTRIBUTE_HIDDEN, 0); + drop(opened); + remove(&fresh); +} + +#[test] +fn a_request_with_no_descriptor_passes_no_security_attributes() { + // Passing none is what the `CreateFileW` page documents as a handle that cannot be inherited. + let temp = TempFile::new("cr-none"); + let request = OpenedFile::::request(wtf(&temp.0), create_new()).expect("build"); + assert!(request.request.security().is_none()); +} + +#[test] +fn a_descriptor_is_carried_and_the_handle_is_never_asked_to_be_inheritable() { + let temp = TempFile::new("cr-carried"); + let request = OpenedFile::::request(wtf(&temp.0), create_new()) + .expect("build") + .with_security_descriptor(sddl("D:P(A;;GA;;;WD)")); + let attributes = request.request.security().expect("attributes"); + assert!(!attributes.inherit_handle()); + let descriptor = attributes.descriptor().expect("the descriptor"); + assert_eq!( + descriptor + .to_sddl(windows_namespace_request_sys::SecurityInformation::DACL) + .expect("text") + .to_string_lossy(), + "D:P(A;;GA;;;WD)" + ); +} + +#[test] +fn the_last_descriptor_given_is_the_one_used() { + let temp = TempFile::new("cr-replaced"); + let request = OpenedFile::::request(wtf(&temp.0), create_new()) + .expect("build") + .with_security_descriptor(sddl("D:P(A;;GA;;;WD)")) + .with_security_descriptor(sddl("D:P(A;;GR;;;WD)")); + let text = request + .request + .security() + .and_then(|attributes| attributes.descriptor()) + .expect("the descriptor") + .to_sddl(windows_namespace_request_sys::SecurityInformation::DACL) + .expect("text") + .to_string_lossy(); + assert_eq!(text, "D:P(A;;GR;;;WD)"); +} + +fn create_with(path: &Path, descriptor: &str) -> OpenedFile { + OpenedFile::::request(wtf(path), create_new()) + .expect("build") + .with_security_descriptor(sddl(descriptor)) + .perform() + .expect("create with the descriptor") +} + +#[test] +fn a_created_file_is_given_the_descriptor_and_a_later_open_is_judged_by_it() { + // The descriptor is judged by behaviour as well as read back (below): a DACL that denies read to + // everyone refuses a later open for reading, and one that allows it admits it. + let temp = TempFile::new("cr-judged"); + let denied = temp.0.with_extension("denied"); + let allowed = temp.0.with_extension("allowed"); + + let held = create_with(&denied, "D:P(D;;GR;;;WD)(A;;GA;;;WD)"); + let error = OpenedFile::::open(wtf(&denied), FileOptions::new().read()) + .expect_err("read is denied by the descriptor"); + assert_eq!( + error.raw_os_error(), + Some(ERROR_ACCESS_DENIED.cast_signed()) + ); + + let kept = create_with(&allowed, "D:P(A;;GA;;;WD)"); + OpenedFile::::open(wtf(&allowed), FileOptions::new().read()) + .expect("read is allowed by the descriptor"); + + drop((held, kept)); + fs::remove_file(&denied).expect("remove"); + fs::remove_file(&allowed).expect("remove"); +} + +/// The DACL of the file `file` holds a handle to, as the text Windows renders it as, read through +/// `win-authorization-sys`'s `GetSecurityInfo`. The file has to have been opened with the right to +/// read its security. +fn dacl_of(file: &OpenedFile) -> String { + get_security_info( + file.as_handle(), + ObjectType::FILE, + SecurityInformation::DACL, + ) + .expect("read the file's security") + .to_sddl(SecurityInformation::DACL) + .expect("write the descriptor as text") + .to_string_lossy() +} + +#[test] +fn a_created_file_is_given_exactly_the_descriptor_it_was_asked_for() { + // Protected, so nothing is inherited from the directory and added; and file rights by name, + // because the file system stores a generic right as the file rights it maps to. + let temp = TempFile::new("cr-read-back"); + for (index, descriptor) in [ + "D:P(A;;FA;;;WD)", + "D:P(A;;FR;;;WD)", + "D:P(A;;FR;;;WD)(A;;FA;;;BA)", + "D:P(D;;FW;;;WD)(A;;FR;;;WD)", + "D:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FR;;;WD)", + ] + .into_iter() + .enumerate() + { + let path = temp.0.with_extension(format!("read-back-{index}")); + let file = OpenedFile::::request( + wtf(&path), + create_new().rights(FileRights::READ_CONTROL), + ) + .expect("build") + .with_security_descriptor(sddl(descriptor)) + .perform() + .expect("create with the descriptor"); + + assert_eq!(dacl_of(&file), descriptor); + + drop(file); + fs::remove_file(&path).expect("remove"); + } +} + +#[test] +fn an_unbuffered_create_is_given_the_descriptor_as_a_cached_one_is() { + let temp = TempFile::new("cr-read-back-unbuffered"); + let path = temp.0.with_extension("fresh"); + let file = OpenedFile::::request( + wtf(&path), + create_new().rights(FileRights::READ_CONTROL), + ) + .expect("build") + .with_security_descriptor(sddl("D:P(A;;FR;;;WD)(A;;FA;;;BA)")) + .perform() + .expect("create unbuffered with the descriptor"); + + assert_eq!(dacl_of(&file), "D:P(A;;FR;;;WD)(A;;FA;;;BA)"); + + drop(file); + fs::remove_file(&path).expect("remove"); +} + +#[test] +fn the_handle_an_open_makes_is_not_inheritable_with_a_descriptor_or_without() { + // `bInheritHandle` is false whether the request carries a descriptor or none (the request is + // where the choice is made), and the handle that results says so. + let temp = TempFile::new("cr-not-inheritable"); + let with_descriptor = temp.0.with_extension("described"); + let described = create_with(&with_descriptor, "D:P(A;;FA;;;WD)"); + assert_eq!( + get_handle_information(described.as_handle()).expect("query"), + HandleFlags::NONE + ); + + let plain = OpenedFile::::open( + wtf(&temp.0), + create_new().disposition(Disposition::OpenExisting), + ) + .expect("open the file that is there"); + assert_eq!( + get_handle_information(plain.as_handle()).expect("query"), + HandleFlags::NONE + ); + + drop((described, plain)); + fs::remove_file(&with_descriptor).expect("remove"); +} + +#[test] +fn the_descriptor_is_ignored_for_a_file_that_exists() { + // The `CreateFileW` page says so, and it is measured here: a descriptor that would deny reading + // does not change the file an open finds. + let temp = TempFile::new("cr-ignored"); + OpenedFile::::request( + wtf(&temp.0), + FileOptions::new() + .read() + .write() + .disposition(Disposition::OpenAlways), + ) + .expect("build") + .with_security_descriptor(sddl("D:P(D;;GR;;;WD)(A;;GA;;;WD)")) + .perform() + .expect("open the file that is there"); + OpenedFile::::open(wtf(&temp.0), FileOptions::new().read()) + .expect("reading is still allowed"); +} + +#[test] +fn a_descriptor_that_windows_refuses_is_refused_before_any_open() { + let error = SecurityDescriptor::from_sddl(&Wtf16String::from("not sddl")) + .expect_err("not a descriptor"); + assert!(error.raw_os_error().is_some()); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/flag_tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/flag_tests.rs new file mode 100644 index 000000000..0025ee22a --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/flag_tests.rs @@ -0,0 +1,189 @@ +// Copyright (c) 2026 Mike Grier +//! The namespace, lifetime and I/O-mode flags of an open (DI-14.2, DI-14.9): +//! `FILE_FLAG_DELETE_ON_CLOSE`, `FILE_FLAG_OPEN_NO_RECALL`, `FILE_FLAG_POSIX_SEMANTICS` and +//! `FILE_FLAG_OVERLAPPED`. + +use std::fs; +use std::io; + +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, +}; + +use super::{ + Cached, DataAccess, Disposition, FILE_FLAG_NO_BUFFERING, FileOptions, FileRights, OpenedFile, + ReadWrite, ShareMode, SharingViolation, Unbuffered, +}; +use crate::dioring::tests::{TempFile, wtf}; + +fn read_write() -> FileOptions { + FileOptions::new().read().write() +} + +fn flags(options: FileOptions) -> u32 { + options.parameters(0).flags +} + +#[test] +fn each_flag_sets_its_own_bit_and_no_other() { + let base = read_write(); + assert_eq!(flags(base), 0, "no flag asked for, none set"); + assert_eq!(flags(base.delete_on_close(true)), FILE_FLAG_DELETE_ON_CLOSE); + assert_eq!(flags(base.open_no_recall(true)), FILE_FLAG_OPEN_NO_RECALL); + assert_eq!(flags(base.posix_semantics(true)), FILE_FLAG_POSIX_SEMANTICS); + assert_eq!(flags(base.overlapped(true)), FILE_FLAG_OVERLAPPED); + assert_eq!( + flags( + base.delete_on_close(true) + .open_no_recall(true) + .posix_semantics(true) + .overlapped(true) + ), + FILE_FLAG_DELETE_ON_CLOSE + | FILE_FLAG_OPEN_NO_RECALL + | FILE_FLAG_POSIX_SEMANTICS + | FILE_FLAG_OVERLAPPED, + "together, they are joined and none replaces another" + ); + assert_eq!( + flags(base.overlapped(true).overlapped(false)), + 0, + "the flag can be taken back" + ); + assert_eq!( + flags(base.delete_on_close(true).delete_on_close(false)), + 0, + "a flag can be taken back" + ); +} + +#[test] +fn the_flags_are_added_to_the_modes_and_the_other_options_never_in_place_of_them() { + let flags = read_write() + .open_reparse_point(true) + .random_access(true) + .delete_on_close(true) + .parameters(FILE_FLAG_NO_BUFFERING) + .flags; + assert_eq!( + flags, + FILE_FLAG_NO_BUFFERING + | FILE_FLAG_OPEN_REPARSE_POINT + | FILE_FLAG_RANDOM_ACCESS + | FILE_FLAG_DELETE_ON_CLOSE + ); +} + +#[test] +fn changing_the_data_access_keeps_the_flags() { + let moved = FileOptions::new() + .delete_on_close(true) + .open_no_recall(true) + .posix_semantics(true) + .overlapped(true) + .read() + .write(); + assert_eq!( + flags(moved), + FILE_FLAG_DELETE_ON_CLOSE + | FILE_FLAG_OPEN_NO_RECALL + | FILE_FLAG_POSIX_SEMANTICS + | FILE_FLAG_OVERLAPPED + ); +} + +#[test] +fn a_file_opened_delete_on_close_is_gone_when_its_last_handle_closes() { + let temp = TempFile::new("fl-delete"); + let fresh = temp.0.with_extension("fresh"); + let options = read_write() + .rights(FileRights::DELETE) + .disposition(Disposition::OpenAlways) + .delete_on_close(true); + + let first = OpenedFile::::open(wtf(&fresh), options).expect("create it"); + assert!(fresh.exists()); + // A second handle to the file, opened without the flag, keeps it alive. + let second = OpenedFile::::open(wtf(&fresh), read_write()).expect("a second open"); + drop(first); + assert!(fresh.exists(), "a handle is still open"); + drop(second); + assert!(!fresh.exists(), "the last handle closed"); +} + +#[test] +fn a_file_opened_without_the_flag_stays_when_closed() { + let temp = TempFile::new("fl-stays"); + let fresh = temp.0.with_extension("fresh"); + let options = read_write().disposition(Disposition::OpenAlways); + drop(OpenedFile::::open(wtf(&fresh), options).expect("create it")); + assert!(fresh.exists()); + fs::remove_file(&fresh).expect("remove"); +} + +#[test] +fn delete_on_close_needs_no_right_to_delete_among_the_rights_asked_for() { + // Measured on Windows 11 build 26300, and evidence, not a contract: the `CreateFileW` pages do not + // say the right to delete is needed (`ZwCreateFile`'s does, for its own flag), and the kernel does + // not refuse an open that leaves it out: the file goes all the same. + let temp = TempFile::new("fl-no-delete"); + let opened = OpenedFile::::open(wtf(&temp.0), read_write().delete_on_close(true)) + .expect("opens without the right to delete"); + drop(opened); + assert!(!temp.0.exists(), "and the file is deleted when it closes"); +} + +#[test] +fn a_later_open_that_does_not_share_deletion_is_a_sharing_violation_while_the_flag_is_held() { + // Measured on Windows 11 build 26300: the handle that sets the flag holds the right to delete, + // so a later open has to share deletion, whatever it asks for itself. + let temp = TempFile::new("fl-share"); + let _held = OpenedFile::::open(wtf(&temp.0), read_write().delete_on_close(true)) + .expect("the first"); + let no_delete = ShareMode::READ | ShareMode::WRITE; + + let error = OpenedFile::::open(wtf(&temp.0), read_write().share(no_delete)) + .expect_err("does not share deletion"); + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); + assert!( + error + .get_ref() + .is_some_and(|inner| inner.downcast_ref::().is_some()) + ); + + // And one that shares it, with the right to delete or without, is admitted. + OpenedFile::::open(wtf(&temp.0), read_write()).expect("shares deletion"); + OpenedFile::::open(wtf(&temp.0), read_write().rights(FileRights::DELETE)) + .expect("asks for the right to delete and shares it"); +} + +#[test] +fn the_other_two_flags_are_accepted_by_the_kernel_on_a_local_file() { + // Passed on and nothing a target depends on; what the kernel does with them is measured only as + // far as that the open succeeds and the file is usable. + for options in [ + read_write().open_no_recall(true), + read_write().posix_semantics(true), + read_write().open_no_recall(true).posix_semantics(true), + ] { + let temp = TempFile::new("fl-others"); + let opened = OpenedFile::::open(wtf(&temp.0), options).expect("open"); + drop(opened); + assert!(temp.0.exists()); + } +} + +#[test] +fn the_flags_are_passed_to_an_unbuffered_file_as_to_a_cached_one() { + let temp = TempFile::new("fl-unbuffered"); + let options = read_write() + .rights(FileRights::DELETE) + .disposition(Disposition::OpenAlways) + .delete_on_close(true); + let fresh = temp.0.with_extension("fresh"); + let file = OpenedFile::::open(wtf(&fresh), options).expect("create it"); + assert!(fresh.exists()); + drop(file); + assert!(!fresh.exists(), "the last handle closed"); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/open_request.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/open_request.rs index 8f3a4fd3b..3b961b769 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_file/open_request.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/open_request.rs @@ -3,27 +3,18 @@ //! no `unsafe` of its own for it (DI-13.2). //! //! That crate carries every parameter of `CreateFileW` and decides none of them, so what an open -//! means is decided here: [`Parameters`] is what [`FileOptions`](super::FileOptions) asks for, and -//! [`open`] makes the call. Opening used to go through `std::fs::OpenOptions`, and a caller must -//! see no difference, so the things std does that `CreateFileW` alone does not are done here -//! too: an empty path is refused as invalid input, a path longer than `MAX_PATH` is opened as a -//! verbatim one, and a file that is both created -//! and truncated is opened with `OPEN_ALWAYS` and then emptied, as std does to avoid -//! `CREATE_ALWAYS`'s semantics (rust-lang/rust#115745). +//! means is decided here: [`Parameters`] is what [`FileOptions`](super::FileOptions) asks for, +//! [`request`] builds the call and [`perform`] makes it, which are two steps so that the second can +//! be made on another thread (DI-13.7.4). The path is handed to the request as the caller gave it, a +//! [`Wtf16String`] (DI-13.7.1): nothing here lengthens, shortens or refuses it, so a path longer than +//! `MAX_PATH` without a verbatim prefix and an empty one are what Win32 makes of them. The +//! disposition is Win32's own, and nothing is done to the file after the open (DI-13.7.3). -use std::ffi::OsString; -use std::fs::File; use std::io; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::io::OwnedHandle; -use std::path::{Path, PathBuf}; use windows_namespace_request_sys::PathCaptureError; use windows_namespace_request_sys::open::OpenFile; -use windows_sys::Win32::Foundation::MAX_PATH; -use windows_sys::Win32::Storage::FileSystem::{ - FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, -}; use wtf_string::Wtf16String; #[cfg(test)] @@ -39,78 +30,27 @@ pub(super) struct Parameters { pub disposition: u32, /// `dwFlagsAndAttributes`. pub flags: u32, - /// Whether to empty the file once it is open, for a file that is created and truncated. - pub truncate_after: bool, + /// `dwShareMode`. + pub share: u32, } -/// The verbatim prefix of a path, `\\?\`, as UTF-16. -const VERBATIM: [u16; 4] = [b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]; - -/// Open `path` as `parameters` says, shared for reading, writing and deletion as std opens a file. -pub(super) fn open(path: &Path, parameters: Parameters) -> io::Result { - let path = verbatim_if_long(path)?; - // An empty path names nothing, and std reports it as invalid input; the request carries it to - // Win32 unaltered, which would answer with a code for a missing path instead. - if path.as_os_str().is_empty() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "the path was empty", - )); - } - let request = OpenFile::new(Wtf16String::from_os_str(path.as_os_str())) +/// The request to open `path` as `parameters` says, built on the calling thread. A path with an +/// interior NUL is refused as invalid input, and a relative one is fixed to the current directory as +/// it is now, before anything is touched. +pub(super) fn request(path: Wtf16String, parameters: Parameters) -> io::Result { + Ok(OpenFile::new(path) .map_err(path_error)? .with_desired_access(parameters.access) - .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_share_mode(parameters.share) .with_creation_disposition(parameters.disposition) - .with_flags_and_attributes(parameters.flags); - let handle = request - .perform() - .map_err(|error| io::Error::from_raw_os_error(raw_code(error.code())))?; - if !parameters.truncate_after { - return Ok(handle); - } - let file = File::from(handle); - file.set_len(0)?; - Ok(OwnedHandle::from(file)) -} - -/// `path` as it should be handed to the request: itself, unless it or its resolved form is longer -/// than an ordinary path may be, in which case its verbatim form, which has no such limit. -/// -/// The request no longer limits the length of a path itself. The conversion is kept because std -/// makes it, and an open here must not differ from one made through std. -fn verbatim_if_long(path: &Path) -> io::Result { - let absolute = std::path::absolute(path)?; - let longest = MAX_PATH as usize - 1; - if path.as_os_str().encode_wide().count() <= longest - && absolute.as_os_str().encode_wide().count() <= longest - { - return Ok(path.to_path_buf()); - } - Ok(verbatim(&absolute)) + .with_flags_and_attributes(parameters.flags)) } -/// The verbatim form of an absolute path: unchanged if it already is one, or names a device, and -/// otherwise with the prefix that turns path parsing off. -fn verbatim(absolute: &Path) -> PathBuf { - let units: Vec = absolute.as_os_str().encode_wide().collect(); - let backslash = u16::from(b'\\'); - let question = u16::from(b'?'); - let dot = u16::from(b'.'); - let starts_with_two_backslashes = units.starts_with(&[backslash, backslash]); - let third = units.get(2).copied(); - if starts_with_two_backslashes && matches!(third, Some(unit) if unit == question || unit == dot) - { - return absolute.to_path_buf(); - } - let mut verbatim = VERBATIM.to_vec(); - if starts_with_two_backslashes { - verbatim.extend("UNC\\".encode_utf16()); - verbatim.extend_from_slice(&units[2..]); - } else { - verbatim.extend_from_slice(&units); - } - PathBuf::from(OsString::from_wide(&verbatim)) +/// Make the call `request` describes, on the calling thread. +pub(super) fn perform(request: &OpenFile) -> io::Result { + request + .perform() + .map_err(|error| io::Error::from_raw_os_error(raw_code(error.code()))) } /// A path the request refused, as the error an open reports: the Windows code if the refusal came @@ -125,6 +65,6 @@ fn path_error(error: PathCaptureError) -> io::Error { } /// A Win32 error code as the signed value `io::Error` holds, bit for bit. -fn raw_code(code: u32) -> i32 { +pub(super) fn raw_code(code: u32) -> i32 { i32::from_ne_bytes(code.to_ne_bytes()) } diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/open_request/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/open_request/tests.rs index 7c3d8daf5..4df6f9d3a 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_file/open_request/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/open_request/tests.rs @@ -7,18 +7,28 @@ use std::path::{Path, PathBuf}; use windows_namespace_request_sys::{OpenFile, PathCaptureError}; use windows_sys::Win32::Foundation::GENERIC_READ; -use windows_sys::Win32::Storage::FileSystem::{CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING}; +use windows_sys::Win32::Storage::FileSystem::{ + CREATE_NEW, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_ALWAYS, OPEN_EXISTING, +}; use wtf_string::Wtf16String; -use super::{Parameters, open, path_error, raw_code, verbatim, verbatim_if_long}; -use crate::dioring::tests::TempFile; +use super::{Parameters, path_error, perform, raw_code, request}; +use crate::dioring::tests::{TempFile, wtf}; + +/// The two steps run one after the other, as `OpenedFile::open` runs them. +fn open(path: Wtf16String, parameters: Parameters) -> io::Result { + perform(&request(path, parameters)?) +} + +/// What a file was always opened with before the share mode was a parameter. +const SHARE_ALL: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; fn read_existing() -> Parameters { Parameters { access: GENERIC_READ, disposition: OPEN_EXISTING, flags: 0, - truncate_after: false, + share: SHARE_ALL, } } @@ -30,118 +40,97 @@ fn directory_deeper_than_an_ordinary_path(under: &Path) -> PathBuf { deep } -#[test] -fn a_short_path_is_handed_over_as_it_was_given() { - let short = Path::new(r"C:\folder\file.txt"); - assert_eq!(verbatim_if_long(short).expect("short"), short); - let relative = Path::new(r"folder\file.txt"); - assert_eq!(verbatim_if_long(relative).expect("relative"), relative); +/// The verbatim form of an absolute drive path. +fn verbatim(path: &Path) -> PathBuf { + let mut prefixed = std::ffi::OsString::from(r"\\?\"); + prefixed.push(path.as_os_str()); + PathBuf::from(prefixed) } #[test] -fn a_long_path_is_made_verbatim_and_a_short_one_never_is() { - let long = PathBuf::from(format!(r"C:\{}\file.txt", "x".repeat(300))); - let converted = verbatim_if_long(&long).expect("long"); - assert_eq!( - converted, - PathBuf::from(format!(r"\\?\C:\{}\file.txt", "x".repeat(300))) - ); -} - -#[test] -fn a_relative_path_that_resolves_long_is_made_verbatim() { - // Short as given, long once rooted: the resolved form is what the limit is judged against. - let cwd = std::env::current_dir().expect("a current directory"); - let room = 259usize.saturating_sub(cwd.as_os_str().len()); - if room < 4 { - eprintln!("skipped: the current directory leaves no room under the limit"); - return; - } - let relative = PathBuf::from("y".repeat(room)); - assert!(relative.as_os_str().len() <= 259); - let converted = verbatim_if_long(&relative).expect("resolved long"); - assert!( - converted.to_string_lossy().starts_with(r"\\?\"), - "{} was not made verbatim", - converted.display() - ); -} +fn a_file_under_a_path_longer_than_an_ordinary_one_opens_when_the_caller_made_it_verbatim() { + let temp = TempFile::new("or-long"); + let root = temp.0.with_extension("deep"); + let directory = directory_deeper_than_an_ordinary_path(&root); + fs::create_dir_all(verbatim(&directory)).expect("create the deep directory"); + let file = verbatim(&directory.join("file.txt")); + fs::write(&file, b"deep").expect("create the file"); -#[test] -fn verbatim_prefixes_a_drive_path_and_a_unc_path_and_leaves_the_rest_alone() { - assert_eq!(verbatim(Path::new(r"C:\a\b")), PathBuf::from(r"\\?\C:\a\b")); + let handle = open(wtf(&file), read_existing()).expect("open the verbatim path"); assert_eq!( - verbatim(Path::new(r"\\server\share\a")), - PathBuf::from(r"\\?\UNC\server\share\a") - ); - assert_eq!(verbatim(Path::new(r"\\?\C:\a")), PathBuf::from(r"\\?\C:\a")); - assert_eq!( - verbatim(Path::new(r"\\?\UNC\server\share")), - PathBuf::from(r"\\?\UNC\server\share") + fs::File::from(handle).metadata().expect("metadata").len(), + 4 ); - assert_eq!(verbatim(Path::new(r"\\.\C:")), PathBuf::from(r"\\.\C:")); + + fs::remove_dir_all(verbatim(&root)).expect("remove the deep tree"); } #[test] -fn a_file_under_a_path_longer_than_an_ordinary_one_opens() { - let temp = TempFile::new("or-long"); +fn a_long_path_without_its_prefix_is_what_win32_makes_of_it_and_not_this_crates_doing() { + let temp = TempFile::new("or-unprefixed"); let root = temp.0.with_extension("deep"); let directory = directory_deeper_than_an_ordinary_path(&root); - fs::create_dir_all(&directory).expect("create the deep directory"); + fs::create_dir_all(verbatim(&directory)).expect("create the deep directory"); let file = directory.join("file.txt"); - fs::write(&file, b"deep").expect("create the file"); + fs::write(verbatim(&file), b"deep").expect("create the file"); + + // `std` hands `CreateFileW` the same bytes this open does, so what it answers is the answer + // of the call itself, whether or not the host lets an unprefixed long path through. + let given = open(wtf(&file), read_existing()); + let std_made = fs::File::open(&file); + match (given, std_made) { + (Ok(_), Ok(_)) => {} + (Err(error), Err(_)) => assert!(error.raw_os_error().is_some(), "{error}"), + (given, std_made) => { + // `std` lengthens a long path itself, so it can succeed where the bytes alone do not. + assert!( + given.is_err() && std_made.is_ok(), + "an open of the unprefixed path succeeded where std's did not" + ); + } + } - let handle = open(&file, read_existing()).expect("open through the verbatim form"); - assert_eq!( - fs::File::from(handle).metadata().expect("metadata").len(), - 4 - ); + fs::remove_dir_all(verbatim(&root)).expect("remove the deep tree"); +} - fs::remove_dir_all(&root).expect("remove the deep tree"); +#[test] +fn an_empty_path_is_refused_by_win32_with_an_os_error_and_not_by_this_crate() { + let error = open(Wtf16String::from_os_str("".as_ref()), read_existing()).expect_err("empty"); + assert!(error.raw_os_error().is_some(), "{error}"); + assert_ne!(error.kind(), io::ErrorKind::InvalidInput); } #[test] -fn a_created_and_truncated_file_is_emptied_and_a_created_one_is_not() { - let temp = TempFile::new("or-truncate"); +fn an_open_that_creates_leaves_an_existing_file_as_it_was_and_makes_a_new_one_empty() { + let temp = TempFile::new("or-always"); fs::write(&temp.0, b"content").expect("fill"); let read_write = GENERIC_READ | windows_sys::Win32::Foundation::GENERIC_WRITE; + let always = Parameters { + access: read_write, + disposition: OPEN_ALWAYS, + flags: 0, + share: SHARE_ALL, + }; - let kept: OwnedHandle = open( - &temp.0, - Parameters { - access: read_write, - disposition: OPEN_ALWAYS, - flags: 0, - truncate_after: false, - }, - ) - .expect("create"); + let kept: OwnedHandle = open(wtf(&temp.0), always).expect("open"); drop(kept); assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 7); - let emptied = open( - &temp.0, - Parameters { - access: read_write, - disposition: OPEN_ALWAYS, - flags: 0, - truncate_after: true, - }, - ) - .expect("create and truncate"); - drop(emptied); - assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 0); + let fresh = temp.0.with_extension("fresh"); + drop(open(wtf(&fresh), always).expect("create")); + assert_eq!(fs::metadata(&fresh).expect("metadata").len(), 0); + fs::remove_file(&fresh).expect("remove"); } #[test] fn what_the_kernel_refuses_reaches_the_caller_as_its_own_error() { let temp = TempFile::new("or-errors"); let missing = temp.0.with_extension("missing"); - let error = open(&missing, read_existing()).expect_err("no such file"); + let error = open(wtf(&missing), read_existing()).expect_err("no such file"); assert_eq!(error.kind(), io::ErrorKind::NotFound); let error = open( - &temp.0, + wtf(&temp.0), Parameters { disposition: CREATE_NEW, ..read_existing() @@ -152,18 +141,31 @@ fn what_the_kernel_refuses_reaches_the_caller_as_its_own_error() { } #[test] -fn a_path_that_cannot_name_a_file_is_invalid_input() { - let error = open(Path::new(""), read_existing()).expect_err("empty"); - assert_eq!(error.kind(), io::ErrorKind::InvalidInput); - +fn a_path_with_an_interior_nul_is_invalid_input() { let with_nul = OpenFile::new(Wtf16String::from("a\0b")).expect_err("interior NUL"); assert!(matches!(with_nul, PathCaptureError::InteriorNul)); assert_eq!(path_error(with_nul).kind(), io::ErrorKind::InvalidInput); - let error = open(Path::new("a\0b"), read_existing()).expect_err("interior NUL"); + let error = open(Wtf16String::from("a\0b"), read_existing()).expect_err("interior NUL"); assert_eq!(error.kind(), io::ErrorKind::InvalidInput); } +#[test] +fn a_path_that_is_not_unicode_reaches_the_request_unaltered() { + use std::os::windows::ffi::OsStringExt; + + // A lone surrogate is a legal Windows file name and no `String`: the open must not lose it. + let temp = TempFile::new("or-lone"); + let mut name = std::ffi::OsString::from(format!("{}-", std::process::id())); + name.push(std::ffi::OsString::from_wide(&[0x61, 0xD800, 0x62])); + let file = temp.0.with_file_name(name); + fs::write(&file, b"x").expect("create a file with a lone surrogate in its name"); + + let opened = open(wtf(&file), read_existing()); + fs::remove_file(&file).expect("remove it"); + opened.expect("the same name opens"); +} + #[test] fn a_win32_code_is_the_same_bits_as_the_signed_one_an_io_error_holds() { assert_eq!(raw_code(5), 5); diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/reopen.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/reopen.rs new file mode 100644 index 000000000..84849b90a --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/reopen.rs @@ -0,0 +1,323 @@ +// Copyright (c) 2026 Mike Grier +//! A reopen, through `ReOpenFile` (DI-15.4, DI-D-71): a new handle to what a handle already names, in +//! a buffering mode, with an access and a sharing this crate states anew. +//! +//! The original is any handle, an [`OpenedFile`]'s or one opened elsewhere (`DI-D-68`): the new +//! handle's mode is this crate's choice and is recorded by its type, so the original's provenance is +//! not what the result claims. What a reopen cannot convert, such as a handle opened with sharing that +//! refuses the reopen's access, is the kernel's refusal; the residue that matters is `DI-10.6`. +//! +//! The options are a type of their own, [`ReopenOptions`]: the page lists no disposition and says the +//! flags cannot include attributes. The checks made on the reopened handle are the path open's, by +//! the same function (DI-14.5), and here they can be reached: the original may be on a share, which +//! the page lists as supported, or may be a device. + +use std::io; +use std::marker::PhantomData; + +use win_shared_os_owned_handle::SharedHandle; +use windows_namespace_request_sys::reopen::ReOpenFile; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, + FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, + FILE_FLAG_SEQUENTIAL_SCAN, +}; + +use durable_ioring::{DataAccess, NoData, ReadWrite}; + +use super::{Buffering, Cached, OpenedBy, OpenedFile, data_access, data_rights, refine_sharing_by}; +use crate::windows_backend::file_rights::FileRights; +use crate::windows_backend::share_mode::ShareMode; + +#[cfg(test)] +mod tests; + +/// What to reopen with: the data access, the other rights, what other opens may do meanwhile, and the +/// flags `ReOpenFile` takes that an open by path also offers. A reopen is shared for reading, writing +/// and deletion until [`share`](Self::share) says otherwise, as [`FileOptions`](super::FileOptions) +/// shares an open. +/// +/// There is no disposition, since a reopen reaches only what is open, and no attributes, which the +/// page says cannot be given. `FILE_FLAG_BACKUP_SEMANTICS` and `FILE_FLAG_WRITE_THROUGH`, which the +/// page lists, are not in v1, as for an open by path; the named-pipe security flags it lists for a +/// pipe's client are not offered either, since what this reopens is a file. +/// +/// The share mode is judged against every handle still open to the file, **the original among them**: +/// a reopen that does not share what the original asked for, or asks for what the original does not +/// share, is refused, as the page says. +/// +/// ``` +/// use win_durable_ioring::windows_backend::{ReadOnly, ReopenOptions}; +/// +/// let options: ReopenOptions = ReopenOptions::new().read(); +/// ``` +#[derive(Clone, Copy, Debug)] +#[must_use] +pub struct ReopenOptions { + access: PhantomData, + rights: FileRights, + share: ShareMode, + open_reparse_point: bool, + delete_on_close: bool, + open_no_recall: bool, + posix_semantics: bool, + overlapped: bool, + random_access: bool, + sequential_scan: bool, +} + +impl Default for ReopenOptions { + fn default() -> Self { + Self::new() + } +} + +impl ReopenOptions { + /// Nothing requested: no data access and no other right, shared for everything. + pub fn new() -> Self { + Self { + access: PhantomData, + rights: FileRights::NONE, + share: ShareMode::ALL, + open_reparse_point: false, + delete_on_close: false, + open_no_recall: false, + posix_semantics: false, + overlapped: false, + random_access: false, + sequential_scan: false, + } + } +} + +impl ReopenOptions { + /// Ask for the right to read the data, as [`FileOptions::read`](super::FileOptions::read) does. + pub fn read(self) -> ReopenOptions { + self.with_access() + } + + /// Ask for the right to write the data, as [`FileOptions::write`](super::FileOptions::write) does. + pub fn write(self) -> ReopenOptions { + self.with_access() + } + + /// The rights besides the data access, as [`FileOptions::rights`](super::FileOptions::rights) + /// takes them. Calling this again replaces them. + pub fn rights(mut self, rights: FileRights) -> Self { + self.rights = rights; + self + } + + /// What other opens may request while this handle is open (`dwShareMode`), as + /// [`FileOptions::share`](super::FileOptions::share) takes it. Calling this again replaces it. + pub fn share(mut self, share: ShareMode) -> Self { + self.share = share; + self + } + + /// Reopen a reparse point itself (`FILE_FLAG_OPEN_REPARSE_POINT`). The page says the flag is + /// ignored for a file that is not a reparse point. + pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self { + self.open_reparse_point = open_reparse_point; + self + } + + /// Delete the file when its last handle is closed (`FILE_FLAG_DELETE_ON_CLOSE`), as + /// [`FileOptions::delete_on_close`](super::FileOptions::delete_on_close) describes. The page says + /// the file goes when all of its handles are closed, the original's and its duplicates' too. + pub fn delete_on_close(mut self, delete_on_close: bool) -> Self { + self.delete_on_close = delete_on_close; + self + } + + /// Ask that the data not be brought back from remote storage (`FILE_FLAG_OPEN_NO_RECALL`), as + /// [`FileOptions::open_no_recall`](super::FileOptions::open_no_recall) does. + pub fn open_no_recall(mut self, open_no_recall: bool) -> Self { + self.open_no_recall = open_no_recall; + self + } + + /// Access the file by POSIX rules (`FILE_FLAG_POSIX_SEMANTICS`), as + /// [`FileOptions::posix_semantics`](super::FileOptions::posix_semantics) describes. + pub fn posix_semantics(mut self, posix_semantics: bool) -> Self { + self.posix_semantics = posix_semantics; + self + } + + /// Reopen for overlapped I/O (`FILE_FLAG_OVERLAPPED`), as + /// [`FileOptions::overlapped`](super::FileOptions::overlapped) describes. + pub fn overlapped(mut self, overlapped: bool) -> Self { + self.overlapped = overlapped; + self + } + + /// The random-access hint (`FILE_FLAG_RANDOM_ACCESS`), as + /// [`FileOptions::random_access`](super::FileOptions::random_access) describes. + pub fn random_access(mut self, random_access: bool) -> Self { + self.random_access = random_access; + self + } + + /// The sequential-scan hint (`FILE_FLAG_SEQUENTIAL_SCAN`), as + /// [`FileOptions::sequential_scan`](super::FileOptions::sequential_scan) describes. + pub fn sequential_scan(mut self, sequential_scan: bool) -> Self { + self.sequential_scan = sequential_scan; + self + } + + fn with_access(self) -> ReopenOptions { + ReopenOptions { + access: PhantomData, + rights: self.rights, + share: self.share, + open_reparse_point: self.open_reparse_point, + delete_on_close: self.delete_on_close, + open_no_recall: self.open_no_recall, + posix_semantics: self.posix_semantics, + overlapped: self.overlapped, + random_access: self.random_access, + sequential_scan: self.sequential_scan, + } + } + + /// `dwDesiredAccess`: the data rights `A` names and the other rights. + fn access(self) -> u32 { + data_rights::() | self.rights.bits() + } + + /// `dwFlagsAndAttributes`: `mode`, the buffering mode's flags, and the flags the options name. + fn flags(self, mode: u32) -> u32 { + let mut flags = mode; + if self.open_reparse_point { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + if self.delete_on_close { + flags |= FILE_FLAG_DELETE_ON_CLOSE; + } + if self.open_no_recall { + flags |= FILE_FLAG_OPEN_NO_RECALL; + } + if self.posix_semantics { + flags |= FILE_FLAG_POSIX_SEMANTICS; + } + if self.overlapped { + flags |= FILE_FLAG_OVERLAPPED; + } + if self.random_access { + flags |= FILE_FLAG_RANDOM_ACCESS; + } + if self.sequential_scan { + flags |= FILE_FLAG_SEQUENTIAL_SCAN; + } + flags + } +} + +/// The request to reopen a handle, built by [`OpenedFile::request_reopen`] and made by +/// [`perform`](Self::perform). It holds the original, shared, so the handle it was built from can be +/// closed first and the file stays reachable; it is `Send`, so it can be performed on a thread that +/// can afford to block; and it can be performed more than once, each time making a new handle. +#[derive(Clone, Debug)] +#[must_use = "an unperformed request reopens nothing"] +pub struct ReopenRequest { + request: ReOpenFile, + rights: FileRights, + share: ShareMode, + mode: PhantomData (B, A)>, +} + +impl ReopenRequest { + /// The handle being reopened, shared with its other holders. + #[must_use] + pub fn original(&self) -> &SharedHandle { + self.request.original() + } + + /// Reopen, on the calling thread. **This blocks for as long as `ReOpenFile` does.** + /// + /// # Errors + /// + /// - [`SharingViolation`](super::SharingViolation) (as an [`io::ErrorKind::ResourceBusy`] error), + /// naming the file by [`OpenedBy::Reopen`], when the reopen's access or sharing conflicts with + /// a handle still open to the file, the original among them. + /// - The path open's checks, made on the reopened handle by the same code: + /// [`RemoteDevice`](super::RemoteDevice) when `B` is unbuffered and the original is on a share, + /// and [`UnknownLocation`](super::UnknownLocation) when `B` is unbuffered and the reopened + /// handle has no final path, as a device's does not. + /// - Otherwise whatever `ReOpenFile` reports, unaltered. The page says the original must have been + /// created by `CreateFile`, and lists no codes. + pub fn perform(&self) -> io::Result> { + let handle = self.request.perform().map_err(|error| { + refine_sharing_by( + error.to_io_error(), + || OpenedBy::Reopen, + data_access::(), + self.rights, + self.share, + ) + })?; + OpenedFile::admit(SharedHandle::from(handle)) + } +} + +impl OpenedFile { + /// Reopen what `original` names, in buffering mode `B` and as `options` say: + /// [`request_reopen`](Self::request_reopen) and then [`perform`](ReopenRequest::perform), one after + /// the other on this thread. + /// + /// The original is any handle: an `OpenedFile`'s, passed as `file.handle().clone()`, or one opened + /// elsewhere. The result is a new file of its own type, with the alignment of `B` and not the + /// original's: an unbuffered file starts at [`Unbuffered::DEFAULT_SECTOR_SIZE`](super::Unbuffered) + /// again, whatever sector size the original was given. + /// + /// ``` + /// use win_durable_ioring::windows_backend::{ + /// Cached, FileOptions, OpenedFile, ReadOnly, ReopenOptions, Unbuffered, + /// }; + /// use win_durable_ioring::Wtf16String; + /// + /// let path = std::env::temp_dir().join(format!("reopen-doc-{}.tmp", std::process::id())); + /// std::fs::write(&path, [0u8; 4096])?; + /// let name = Wtf16String::from_os_str(path.as_os_str()); + /// let writer = OpenedFile::::open(name, FileOptions::new().read().write())?; + /// + /// // A read-only, unbuffered handle beside the writer, for a scrubber. + /// let scrubber = OpenedFile::::reopen( + /// writer.handle().clone(), + /// ReopenOptions::new().read(), + /// )?; + /// assert_eq!(scrubber.file_id()?, writer.file_id()?); + /// # drop((writer, scrubber)); + /// # std::fs::remove_file(&path)?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Those of [`perform`](ReopenRequest::perform). + pub fn reopen( + original: impl Into, + options: ReopenOptions, + ) -> io::Result { + Self::request_reopen(original, options).perform() + } + + /// The request to reopen what `original` names, built here and made wherever the caller chooses + /// with [`perform`](ReopenRequest::perform). Nothing is opened and nothing blocks. The original is + /// held, shared, and not duplicated. + pub fn request_reopen( + original: impl Into, + options: ReopenOptions, + ) -> ReopenRequest { + let request = ReOpenFile::new(original) + .with_desired_access(options.access()) + .with_share_mode(options.share.bits()) + .with_flags_and_attributes(options.flags(B::FLAGS)); + ReopenRequest { + request, + rights: options.rights, + share: options.share, + mode: PhantomData, + } + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/reopen/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/reopen/tests.rs new file mode 100644 index 000000000..b206cb6c1 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/reopen/tests.rs @@ -0,0 +1,579 @@ +// Copyright (c) 2026 Mike Grier +//! Tests for the reopen: what it makes of an owned file and of a handle opened elsewhere, in each +//! buffering mode and data access; the documented sharing rule over every pair of an original and a +//! reopen, judged by the share-mode tests' own statement of it; and what the kernel does with the +//! originals the page leaves open, which is what was observed on the machine this was written on and +//! is recorded in `DI-D-71`. + +use std::fs::{self, File}; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::os::windows::io::{AsHandle, AsRawHandle}; +use std::path::Path; + +use win_handles_sys::{HandleFlags, get_handle_information}; +use win_shared_os_owned_handle::SharedHandle; +use win_volume_sys::storage_device_number; +use windows_namespace_request_sys::open::OpenFile; +use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL, + FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, + FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_EXISTING, +}; +use wtf_string::Wtf16String; + +use super::super::share_tests::{ + WANTS, Wants, documented_to_conflict, every_share_mode, kernel_code, sharing_violation, +}; +use super::super::tests::admin_share; +use super::ReopenOptions; +use crate::dioring::tests::{TempFile, wtf}; +use crate::windows_backend::{ + ByIdOptions, Cached, FileOptions, FileRights, NoData, OpenedBy, OpenedFile, ReadOnly, + ReadWrite, RemoteDevice, ShareMode, Unbuffered, UnknownLocation, WriteOnly, +}; +use durable_ioring::Geometry; + +/// `ERROR_INVALID_PARAMETER`, which an unbuffered file answers an unaligned transfer with. +const INVALID_PARAMETER: i32 = 87; + +/// `ERROR_ACCESS_DENIED`. +const ACCESS_DENIED: i32 = 5; + +fn read_write() -> ReopenOptions { + ReopenOptions::new().read().write() +} + +/// A second handle to what `opened` holds, as a `std::fs::File`, so the kernel judges the I/O. +fn io_through(opened: &impl AsHandle) -> File { + File::from( + opened + .as_handle() + .try_clone_to_owned() + .expect("duplicate the handle"), + ) +} + +/// The same unaligned seven-byte write at offset three: a cached handle takes it, and the kernel +/// refuses it on an unbuffered one. +fn unaligned_write(opened: &impl AsHandle) -> io::Result<()> { + let mut file = io_through(opened); + file.seek(SeekFrom::Start(3))?; + file.write_all(b"seven b") +} + +/// A handle to a device opened by `path`, asking for `access` and sharing everything. +fn device(path: &str, access: u32) -> SharedHandle { + OpenFile::new(Wtf16String::from(path)) + .expect("the path has no NUL") + .with_desired_access(access) + .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_creation_disposition(OPEN_EXISTING) + .perform() + .map(SharedHandle::from) + .unwrap_or_else(|error| panic!("open {path}: {error:?}")) +} + +/// The volume the temporary directory is on, as `\\.\X:`. +fn temp_volume() -> String { + let temp = std::env::temp_dir(); + let drive: String = temp.to_string_lossy().chars().take(2).collect(); + assert!(drive.ends_with(':'), "a drive letter: {}", temp.display()); + format!(r"\\.\{drive}") +} + +// --- An owned file, reopened ---------------------------------------------------------------------- + +#[test] +fn a_cached_file_reopens_unbuffered_and_back_and_the_kernel_treats_each_as_its_type_says() { + let temp = TempFile::new("ro-modes"); + fs::write(&temp.0, [0u8; 8192]).expect("size the file"); + let cached = temp.open(); + unaligned_write(&cached).expect("a cached handle takes an unaligned write"); + + let unbuffered = OpenedFile::::reopen(cached.handle().clone(), read_write()) + .expect("reopen unbuffered"); + assert_eq!( + unbuffered.geometry(), + Geometry::uniform(Unbuffered::DEFAULT_SECTOR_SIZE).expect("a power of two"), + "the alignment is recomputed for the new mode" + ); + let refused = unaligned_write(&unbuffered).expect_err("an unbuffered handle refuses it"); + assert_eq!(refused.raw_os_error(), Some(INVALID_PARAMETER)); + + let back = OpenedFile::::reopen(unbuffered.handle().clone(), read_write()) + .expect("reopen cached from the unbuffered handle"); + assert_eq!(back.geometry(), Geometry::UNALIGNED); + unaligned_write(&back).expect("cached again"); + assert_eq!( + back.file_id().expect("its identity"), + cached.file_id().expect("the file's") + ); +} + +#[test] +fn a_stated_sector_size_is_not_carried_to_a_reopen() { + let temp = TempFile::new("ro-sector"); + let original = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read()) + .expect("open unbuffered") + .with_sector_size(512) + .expect("a power of two"); + let reopened = + OpenedFile::::reopen(original.handle().clone(), ReopenOptions::new().read()) + .expect("reopen"); + assert_eq!( + original.geometry(), + Geometry::uniform(512).expect("a power of two") + ); + assert_eq!( + reopened.geometry(), + Geometry::uniform(Unbuffered::DEFAULT_SECTOR_SIZE).expect("a power of two") + ); +} + +#[test] +fn a_read_only_handle_beside_the_writer_reads_what_it_wrote_and_cannot_write() { + let temp = TempFile::new("ro-scrubber"); + let writer = temp.open(); + let scrubber = OpenedFile::::reopen( + writer.handle().clone(), + ReopenOptions::new().read(), + ) + .expect("a read-only reopen beside the writer"); + + io_through(&writer).write_all(b"written").expect("write"); + let mut seen = String::new(); + io_through(&scrubber) + .read_to_string(&mut seen) + .expect("the scrubber reads"); + assert_eq!(seen, "written"); + let refused = io_through(&scrubber) + .write_all(b"no") + .expect_err("a read-only handle does not write"); + assert_eq!(refused.raw_os_error(), Some(ACCESS_DENIED)); +} + +#[test] +fn a_reopen_widens_read_only_to_read_write_where_the_files_security_allows() { + // The request crate's tests show a reopen widening read to write on a `std` file; this is the same + // on this crate's types. The access is checked against the caller afresh (`DI-D-68`'s broker + // case), so a file the caller may write reopens for writing from a read-only handle. + let temp = TempFile::new("ro-widen"); + let reader = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read()) + .expect("open read-only"); + let widened = OpenedFile::::reopen(reader.handle().clone(), read_write()) + .expect("widen to read and write"); + io_through(&widened) + .write_all(b"widened") + .expect("the reopened handle writes"); + assert_eq!(fs::read(&temp.0).expect("read back"), b"widened"); +} + +#[test] +fn each_data_access_reopens_and_the_kernel_allows_what_it_names() { + let temp = TempFile::new("ro-access"); + let original = temp.open(); + let none = + OpenedFile::::reopen(original.handle().clone(), ReopenOptions::new()) + .expect("no data"); + let read = OpenedFile::::reopen( + original.handle().clone(), + ReopenOptions::new().read(), + ) + .expect("read"); + let write = OpenedFile::::reopen( + original.handle().clone(), + ReopenOptions::new().write(), + ) + .expect("write"); + let mut byte = [0; 1]; + assert_eq!( + io_through(&none) + .read(&mut byte) + .map_err(|e| e.raw_os_error()), + Err(Some(ACCESS_DENIED)) + ); + io_through(&read) + .read_exact(&mut byte) + .expect("a read-only handle reads"); + io_through(&write) + .write_all(b"w") + .expect("a write-only handle writes"); + assert_eq!( + io_through(&write) + .read(&mut byte) + .map_err(|e| e.raw_os_error()), + Err(Some(ACCESS_DENIED)) + ); +} + +#[test] +fn delete_on_close_given_to_a_reopen_deletes_the_file_when_every_handle_is_closed() { + let temp = TempFile::new("ro-delete"); + let original = temp.open(); + let doomed = OpenedFile::::reopen( + original.handle().clone(), + ReopenOptions::new().read().delete_on_close(true), + ) + .expect("reopen to delete on close"); + drop(doomed); + assert!(temp.0.exists(), "the original still holds the file"); + drop(original); + assert!(!temp.0.exists(), "gone with its last handle"); +} + +// --- Any handle, reopened ---------------------------------------------------------------------- + +#[test] +fn a_handle_opened_by_std_reopens_into_a_file_of_this_crates_type() { + let temp = TempFile::new("ro-std"); + let std_file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(&temp.0) + .expect("open with std"); + let unbuffered = OpenedFile::::reopen(std_file, read_write()) + .expect("a foreign handle reopens unbuffered"); + let refused = unaligned_write(&unbuffered).expect_err("and is unbuffered to the kernel"); + assert_eq!(refused.raw_os_error(), Some(INVALID_PARAMETER)); +} + +#[test] +fn a_handle_from_an_open_by_identity_reopens() { + let temp = TempFile::new("ro-by-id"); + let original = temp.open(); + let id = original.file_id().expect("the identity"); + let by_id = OpenedFile::::open_by_id( + original.handle().clone(), + id, + ByIdOptions::new().read().write(), + ) + .expect("open by identity"); + // `DI-D-64`'s unverified list asked whether a handle from `OpenFileById` reopens; the page says + // the original must have been created by `CreateFile`. Observed to reopen. + let reopened = OpenedFile::::reopen( + by_id.handle().clone(), + ReopenOptions::new().read(), + ) + .expect("observed to reopen"); + assert_eq!(reopened.file_id().expect("its identity"), id); +} + +#[test] +fn the_original_need_not_outlive_the_request() { + let temp = TempFile::new("ro-request"); + let original = temp.open(); + let request = OpenedFile::::request_reopen( + original.handle().clone(), + ReopenOptions::new().read(), + ); + let id = original.file_id().expect("the identity"); + drop(original); + let request = std::thread::spawn(move || { + let first = request.perform().expect("performed on another thread"); + let second = request.perform().expect("and again"); + assert_eq!(first.file_id().expect("first"), id); + assert_eq!(second.file_id().expect("second"), id); + request + }) + .join() + .expect("the thread"); + drop(request); +} + +#[test] +fn the_reopened_handle_is_not_inheritable() { + let temp = TempFile::new("ro-inherit"); + let original = temp.open(); + let reopened = OpenedFile::::reopen( + original.handle().clone(), + ReopenOptions::new().read(), + ) + .expect("reopen"); + assert_eq!( + get_handle_information(reopened.handle().as_handle()).expect("query"), + HandleFlags::NONE + ); +} + +// --- What the checks on the reopened handle refuse ------------------------------------------------ + +#[test] +fn nul_reopens_cached_and_is_refused_unbuffered_as_a_handle_of_unknown_location() { + // The check a reopen from any handle can meet: the original is a device, the reopened handle has + // no final path, and an unbuffered file is refused for it, as an open by path of `NUL` is. + let nul = OpenedFile::::open(wtf(Path::new("NUL")), FileOptions::new().read().write()) + .expect("open NUL cached"); + OpenedFile::::reopen(nul.handle().clone(), read_write()) + .expect("a cached reopen of NUL was observed to succeed, and is not refused here"); + let error = OpenedFile::::reopen(nul.handle().clone(), read_write()) + .expect_err("an unbuffered reopen of NUL"); + assert_eq!(error.kind(), io::ErrorKind::Unsupported); + let unknown = error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is an UnknownLocation"); + assert!(unknown.cause.raw_os_error().is_some(), "{unknown:?}"); +} + +#[test] +fn a_volume_and_a_disk_were_observed_not_to_reopen_where_a_file_opened_with_no_access_does() { + // The page says the original must have been created by `CreateFile`, which a volume's and a + // disk's handles were. Unelevated, a reopen of either was refused by the kernel whatever the + // original or the reopen asked for, while a file opened with no access reopens: the refusal is + // the device's, not the access's. Elevated, and with a data access, was not measured. + let temp = TempFile::new("ro-devices"); + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new()).expect("open"); + OpenedFile::::reopen(file.handle().clone(), ReopenOptions::new()) + .expect("a file opened with no access reopens"); + OpenedFile::::reopen(file.handle().clone(), ReopenOptions::new().read()) + .expect("and reopens for reading"); + + let read_attributes = FileRights::READ_ATTRIBUTES.bits(); + let volume = device(&temp_volume(), 0); + let number = storage_device_number(&volume).expect("the volume's disk"); + let disk = device(&format!(r"\\.\PhysicalDrive{}", number.device_number), 0); + for (what, handle) in [ + ("a volume", volume.clone()), + ( + "a volume opened to read attributes", + device(&temp_volume(), read_attributes), + ), + ("a disk", disk), + ] { + for result in [ + OpenedFile::::reopen(handle.clone(), ReopenOptions::new()).map(drop), + OpenedFile::::reopen(handle.clone(), ReopenOptions::new()) + .map(drop), + OpenedFile::::reopen( + handle.clone(), + ReopenOptions::new().rights(FileRights::READ_ATTRIBUTES), + ) + .map(drop), + ] { + let error = result.expect_err(what); + assert!( + error.raw_os_error().is_some(), + "{what}: the kernel's code: {error:?}" + ); + } + } +} + +#[test] +#[ignore = "needs the administrative share \\\\localhost\\C$, which a CI runner has"] +fn a_file_on_a_share_reopens_cached_and_is_refused_unbuffered() { + let temp = TempFile::new("ro-remote"); + let remote = admin_share(&temp.0); + let on_share = OpenedFile::::open(wtf(&remote), FileOptions::new().read().write()) + .expect("a cached file on a share"); + + OpenedFile::::reopen(on_share.handle().clone(), read_write()) + .expect("a cached reopen on a share is allowed"); + let error = OpenedFile::::reopen(on_share.handle().clone(), read_write()) + .expect_err("an unbuffered reopen on a share is refused"); + assert_eq!(error.kind(), io::ErrorKind::Unsupported); + assert!( + error + .get_ref() + .is_some_and(|inner| inner.is::()), + "{error:?}" + ); +} + +// --- Sharing --------------------------------------------------------------------------------------- + +/// An original opened by path asking for `wants` and sharing `share`, as its shared handle. +fn original(path: &Path, wants: Wants, share: ShareMode) -> SharedHandle { + let opened = match wants { + Wants::Read => { + OpenedFile::::open(wtf(path), FileOptions::new().read().share(share)) + .map(|file| file.handle().clone()) + } + Wants::Write => { + OpenedFile::::open(wtf(path), FileOptions::new().write().share(share)) + .map(|file| file.handle().clone()) + } + Wants::Delete => OpenedFile::::open( + wtf(path), + FileOptions::new().rights(FileRights::DELETE).share(share), + ) + .map(|file| file.handle().clone()), + }; + opened.unwrap_or_else(|error| panic!("the original {wants:?}/{share:?}: {error}")) +} + +/// A reopen of `handle` asking for `wants` and sharing `share`. +fn reopened(handle: &SharedHandle, wants: Wants, share: ShareMode) -> io::Result<()> { + match wants { + Wants::Read => OpenedFile::::reopen( + handle.clone(), + ReopenOptions::new().read().share(share), + ) + .map(drop), + Wants::Write => OpenedFile::::reopen( + handle.clone(), + ReopenOptions::new().write().share(share), + ) + .map(drop), + Wants::Delete => OpenedFile::::reopen( + handle.clone(), + ReopenOptions::new().rights(FileRights::DELETE).share(share), + ) + .map(drop), + } +} + +#[test] +fn every_pair_of_an_original_and_a_reopen_is_accepted_or_refused_as_the_documentation_says() { + let temp = TempFile::new("ro-matrix"); + let modes = every_share_mode(); + let mut refusals = 0; + for first_wants in WANTS { + for &first_share in &modes { + let handle = original(&temp.0, first_wants, first_share); + for second_wants in WANTS { + for &second_share in &modes { + let outcome = reopened(&handle, second_wants, second_share); + let conflict = documented_to_conflict( + (first_wants, first_share), + (second_wants, second_share), + ); + assert_eq!( + outcome.is_err(), + conflict, + "{first_wants:?} sharing {first_share:?}, then a reopen of {second_wants:?} \ + sharing {second_share:?}: {outcome:?}" + ); + if let Err(error) = outcome { + refusals += 1; + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); + let violation = sharing_violation(&error); + assert_eq!(violation.by, OpenedBy::Reopen); + assert_eq!( + kernel_code(violation), + Some(ERROR_SHARING_VIOLATION.cast_signed()) + ); + } + } + } + } + } + assert!( + refusals > 0, + "the matrix reached refusals as well as acceptances" + ); +} + +// --- What the options ask for ------------------------------------------------------------------- + +#[test] +fn each_option_sets_its_own_flag_and_no_other() { + let base = ReopenOptions::new(); + assert_eq!(base.flags(0), 0); + let cases: [(ReopenOptions, u32); 7] = [ + (base.open_reparse_point(true), FILE_FLAG_OPEN_REPARSE_POINT), + (base.delete_on_close(true), FILE_FLAG_DELETE_ON_CLOSE), + (base.open_no_recall(true), FILE_FLAG_OPEN_NO_RECALL), + (base.posix_semantics(true), FILE_FLAG_POSIX_SEMANTICS), + (base.overlapped(true), FILE_FLAG_OVERLAPPED), + (base.random_access(true), FILE_FLAG_RANDOM_ACCESS), + (base.sequential_scan(true), FILE_FLAG_SEQUENTIAL_SCAN), + ]; + for (options, flag) in cases { + assert_eq!(options.flags(0), flag); + assert_eq!( + options.flags(FILE_FLAG_NO_BUFFERING), + flag | FILE_FLAG_NO_BUFFERING + ); + } + let every = cases.iter().fold(0, |flags, (_, flag)| flags | flag); + let all = base + .open_reparse_point(true) + .delete_on_close(true) + .open_no_recall(true) + .posix_semantics(true) + .overlapped(true) + .random_access(true) + .sequential_scan(true); + assert_eq!(all.flags(0), every); + assert_eq!( + all.delete_on_close(false).flags(0), + every & !FILE_FLAG_DELETE_ON_CLOSE + ); +} + +#[test] +fn the_access_is_the_data_access_and_the_other_rights_and_survives_a_change_of_access() { + let base = ReopenOptions::new(); + assert_eq!(base.access(), 0); + assert_eq!(base.read().access(), FILE_READ_DATA); + assert_eq!(base.write().access(), FILE_WRITE_DATA); + assert_eq!( + base.read().write().access(), + FILE_READ_DATA | FILE_WRITE_DATA + ); + assert_eq!( + base.write().read().access(), + FILE_READ_DATA | FILE_WRITE_DATA + ); + + let configured = base + .rights(FileRights::READ_ATTRIBUTES) + .share(ShareMode::READ) + .delete_on_close(true) + .posix_semantics(true); + let moved = configured.read().write(); + assert_eq!( + moved.access(), + FILE_READ_DATA | FILE_WRITE_DATA | FileRights::READ_ATTRIBUTES.bits() + ); + assert_eq!(moved.share, ShareMode::READ); + assert_eq!( + moved.flags(0), + FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_POSIX_SEMANTICS + ); +} + +#[test] +fn a_reopen_is_shared_for_everything_unless_told_otherwise() { + assert_eq!(ReopenOptions::new().share, ShareMode::ALL); + assert_eq!(ReopenOptions::default().share, ShareMode::ALL); + assert_eq!( + ReopenOptions::new().share(ShareMode::NONE).share, + ShareMode::NONE + ); +} + +#[test] +fn the_request_carries_what_the_options_and_the_buffering_mode_ask_for() { + let temp = TempFile::new("ro-request-fields"); + let original = temp.open(); + let options = ReopenOptions::new() + .read() + .rights(FileRights::READ_ATTRIBUTES) + .share(ShareMode::READ | ShareMode::DELETE) + .random_access(true); + let cached = OpenedFile::::request_reopen(original.handle().clone(), options); + let unbuffered = + OpenedFile::::request_reopen(original.handle().clone(), options); + for (request, mode) in [ + (&cached.request, 0), + (&unbuffered.request, FILE_FLAG_NO_BUFFERING), + ] { + assert_eq!( + request.desired_access(), + FILE_READ_DATA | FileRights::READ_ATTRIBUTES.bits() + ); + assert_eq!(request.share_mode(), FILE_SHARE_READ | FILE_SHARE_DELETE); + assert_eq!( + request.flags_and_attributes(), + FILE_FLAG_RANDOM_ACCESS | mode + ); + } + assert!( + cached.original().as_raw_handle() == original.handle().as_raw_handle(), + "the original is held shared, not duplicated" + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/request_tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/request_tests.rs new file mode 100644 index 000000000..5f4726fac --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/request_tests.rs @@ -0,0 +1,195 @@ +// Copyright (c) 2026 Mike Grier +//! The open as a build and a perform (DI-13.7.4). + +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::os::windows::io::AsHandle; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +use wtf_string::Wtf16String; + +use super::{ + Cached, Disposition, FileOptions, OpenRequest, OpenedFile, ReadWrite, ShareMode, Unbuffered, +}; +use crate::dioring::tests::{TempFile, wtf}; +use durable_ioring::{NoData, ReadOnly}; + +fn read_write() -> FileOptions { + FileOptions::new().read().write() +} + +fn io_through(opened: &impl AsHandle) -> File { + File::from( + opened + .as_handle() + .try_clone_to_owned() + .expect("duplicate the handle"), + ) +} + +/// The current directory is the process's, so a test that changes it holds this, and puts it back. +static CURRENT_DIRECTORY: Mutex<()> = Mutex::new(()); + +struct Directory(PathBuf); + +impl Directory { + fn enter(to: &Path) -> Self { + let before = std::env::current_dir().expect("a current directory"); + std::env::set_current_dir(to).expect("change directory"); + Self(before) + } +} + +impl Drop for Directory { + fn drop(&mut self) { + std::env::set_current_dir(&self.0).expect("restore the directory"); + } +} + +#[test] +fn a_request_can_be_sent_to_another_thread() { + fn is_send() {} + is_send::>(); + is_send::>(); + is_send::>(); +} + +#[test] +fn a_request_built_on_one_thread_opens_the_file_its_path_named_when_it_was_built() { + let _held = CURRENT_DIRECTORY.lock().unwrap_or_else(|e| e.into_inner()); + let temp = TempFile::new("rq-dir"); + let here = temp.0.with_extension("here"); + let there = temp.0.with_extension("there"); + fs::create_dir_all(&here).expect("create a directory"); + fs::create_dir_all(&there).expect("create another"); + fs::write(here.join("file"), b"here").expect("fill"); + fs::write(there.join("file"), b"there, longer").expect("fill"); + + let request = { + let _in = Directory::enter(&here); + OpenedFile::::request(Wtf16String::from("file"), read_write()).expect("build") + }; + // The directory has changed by the time the request is made, on another thread. + let _in = Directory::enter(&there); + let opened = std::thread::spawn(move || request.perform()) + .join() + .expect("the thread") + .expect("perform"); + assert_eq!( + io_through(&opened).metadata().expect("metadata").len(), + 4, + "it opened the file in the directory the request was built in" + ); + + drop((opened, _in)); + fs::remove_dir_all(&here).expect("remove"); + fs::remove_dir_all(&there).expect("remove"); +} + +#[test] +fn performing_twice_opens_twice() { + let temp = TempFile::new("rq-twice"); + let request = OpenedFile::::request(wtf(&temp.0), read_write()).expect("build"); + let first = request.perform().expect("the first"); + let second = request.perform().expect("the second"); + assert!( + !first.handle().same_handle(second.handle()), + "each perform is its own open" + ); +} + +#[test] +fn a_second_perform_is_judged_against_the_first_as_a_second_open_is() { + let temp = TempFile::new("rq-exclusive"); + let request = OpenedFile::::request(wtf(&temp.0), read_write().share(ShareMode::NONE)) + .expect("build"); + let _first = request.perform().expect("the first"); + let error = request.perform().expect_err("the file is held exclusively"); + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); +} + +#[test] +fn an_error_from_the_build_and_one_from_the_perform_are_distinguishable() { + let temp = TempFile::new("rq-errors"); + let missing = temp.0.with_extension("missing"); + + // The build refuses a path that cannot mean a file, before anything is touched. + let built = OpenedFile::::request(Wtf16String::from("a\0b"), read_write()); + assert_eq!( + built.expect_err("an interior NUL").kind(), + io::ErrorKind::InvalidInput + ); + + // The build accepts a path to a file that is not there: whether it is is the perform's to find. + let request = OpenedFile::::request(wtf(&missing), read_write()) + .expect("a request for a missing file builds"); + assert!(!missing.exists(), "building opened nothing"); + let error = request.perform().expect_err("no such file"); + assert_eq!(error.kind(), io::ErrorKind::NotFound); + + // And what failed to open can be made to succeed by changing the world, not the request. + fs::write(&missing, b"now").expect("create it"); + request.perform().expect("it opens once the file is there"); + fs::remove_file(&missing).expect("remove"); +} + +#[test] +fn building_creates_nothing_and_performing_creates_what_the_disposition_says() { + let temp = TempFile::new("rq-create"); + let fresh = temp.0.with_extension("fresh"); + let request = OpenedFile::::request( + wtf(&fresh), + read_write().disposition(Disposition::CreateNew), + ) + .expect("build"); + assert!(!fresh.exists()); + drop(request.perform().expect("create")); + assert!(fresh.exists()); + let error = request.perform().expect_err("it exists now"); + assert_eq!(error.kind(), io::ErrorKind::AlreadyExists); + fs::remove_file(&fresh).expect("remove"); +} + +#[test] +fn open_is_the_build_and_the_perform_run_together() { + let temp = TempFile::new("rq-same"); + let by_open = OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); + let by_request = OpenedFile::::request(wtf(&temp.0), read_write()) + .expect("build") + .perform() + .expect("perform"); + + // Two handles to the one file: what is written through one is there through the other. + io_through(&by_open) + .write_all(b"through open") + .expect("write"); + assert_eq!( + io_through(&by_request).metadata().expect("metadata").len(), + b"through open".len() as u64 + ); + + // And a failure is the same failure by either route. + let missing = temp.0.with_extension("missing"); + let through_open = + OpenedFile::::open(wtf(&missing), read_write()).expect_err("no such file"); + let through_request = OpenedFile::::request(wtf(&missing), read_write()) + .expect("build") + .perform() + .expect_err("no such file"); + assert_eq!(through_open.kind(), through_request.kind()); + assert_eq!(through_open.raw_os_error(), through_request.raw_os_error()); +} + +#[test] +fn an_unbuffered_request_for_a_path_on_another_machine_builds_and_is_judged_when_performed() { + // Nothing is looked at until the perform, so the build of a path on a device that does not + // exist succeeds; the refusal itself is + // `an_unbuffered_file_is_refused_on_a_remote_device_and_a_cached_one_is_not`. + let request = OpenedFile::::request( + Wtf16String::from(r"\\no-such-server-for-a-test\share\file"), + read_write(), + ) + .expect("the build checks nothing"); + request.perform().expect_err("the server is not there"); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/share_tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/share_tests.rs new file mode 100644 index 000000000..25b38cbaf --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/share_tests.rs @@ -0,0 +1,299 @@ +// Copyright (c) 2026 Mike Grier +//! What the share mode asks of the kernel, and what the kernel does with two opens of one file +//! (DI-13.4, DI-D-64). The two opens are real: each combination of access and sharing on each side is +//! made and judged against what the `CreateFileW` documentation says of the share modes. + +use std::any::Any; +use std::io; +use std::path::Path; + +use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION; + +use super::super::{FileRights, NoData, ReadWrite, ShareMode}; +use super::{Cached, FileOptions, OpenedBy, OpenedFile, SharingViolation, data_access}; +use crate::dioring::tests::{TempFile, wtf}; +use durable_ioring::Access; + +/// What an open can ask for that a share mode is judged against: one of the three things the share +/// mode names. An open that asks for none of them is not judged at all. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) enum Wants { + Read, + Write, + Delete, +} + +pub(super) const WANTS: [Wants; 3] = [Wants::Read, Wants::Write, Wants::Delete]; + +/// Every subset of the three members, the empty one first. +pub(super) fn every_share_mode() -> Vec { + (0..8) + .map(|mask| { + [ShareMode::READ, ShareMode::WRITE, ShareMode::DELETE] + .iter() + .enumerate() + .filter(|(index, _)| mask & (1 << index) != 0) + .fold(ShareMode::NONE, |set, (_, member)| set | *member) + }) + .collect() +} + +/// The member of the share mode that allows another open to ask for `wants`. +pub(super) fn shares(wants: Wants) -> ShareMode { + match wants { + Wants::Read => ShareMode::READ, + Wants::Write => ShareMode::WRITE, + Wants::Delete => ShareMode::DELETE, + } +} + +/// Open `path` asking for `wants` and sharing `share`; the handle is boxed so that two of different +/// types can be held at once. +fn open(path: &Path, wants: Wants, share: ShareMode) -> io::Result> { + Ok(match wants { + Wants::Read => Box::new(OpenedFile::::open( + wtf(path), + FileOptions::new().read().share(share), + )?), + Wants::Write => Box::new(OpenedFile::::open( + wtf(path), + FileOptions::new().write().share(share), + )?), + Wants::Delete => Box::new(OpenedFile::::open( + wtf(path), + FileOptions::new().rights(FileRights::DELETE).share(share), + )?), + }) +} + +/// The `CreateFileW` documentation's rule for two opens of one file, one from each side: the later +/// open fails when it asks for something the earlier open does not share, and when the earlier open +/// asked for something the later one does not share. +pub(super) fn documented_to_conflict( + first: (Wants, ShareMode), + second: (Wants, ShareMode), +) -> bool { + !first.1.contains(shares(second.0)) || !second.1.contains(shares(first.0)) +} + +pub(super) fn sharing_violation(error: &io::Error) -> &SharingViolation { + error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is a SharingViolation") +} + +/// The Win32 code of the kernel's error, which a violation keeps as its source. +pub(super) fn kernel_code(violation: &SharingViolation) -> Option { + std::error::Error::source(violation) + .and_then(|source| source.downcast_ref::()) + .and_then(io::Error::raw_os_error) +} + +#[test] +fn every_combination_of_two_opens_is_accepted_or_refused_as_the_documentation_says() { + let temp = TempFile::new("share-matrix"); + let modes = every_share_mode(); + assert_eq!(modes.len(), 8, "the empty set and the seven others"); + let mut conflicts = 0; + for first_wants in WANTS { + for &first_share in &modes { + let first = open(&temp.0, first_wants, first_share).unwrap_or_else(|e| { + panic!("the first open of {first_wants:?}/{first_share:?}: {e}") + }); + for second_wants in WANTS { + for &second_share in &modes { + let outcome = open(&temp.0, second_wants, second_share); + let conflict = documented_to_conflict( + (first_wants, first_share), + (second_wants, second_share), + ); + assert_eq!( + outcome.is_err(), + conflict, + "{first_wants:?} sharing {first_share:?}, then {second_wants:?} sharing \ + {second_share:?}" + ); + if let Err(error) = outcome { + conflicts += 1; + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); + assert_eq!( + kernel_code(sharing_violation(&error)), + Some(ERROR_SHARING_VIOLATION.cast_signed()) + ); + } + } + } + drop(first); + } + } + assert!( + conflicts > 0, + "the matrix reached refusals as well as acceptances" + ); +} + +#[test] +fn a_few_combinations_by_name_so_the_matrix_is_not_the_only_statement() { + use Wants::{Delete, Read, Write}; + let both = ShareMode::READ | ShareMode::WRITE; + // (first open, second open, whether the second is refused) + let cases = [ + // A reader sharing read admits another reader, and refuses a writer and a deleter. + ((Read, ShareMode::READ), (Read, ShareMode::READ), false), + ((Read, ShareMode::READ), (Write, ShareMode::ALL), true), + ((Read, ShareMode::READ), (Delete, ShareMode::ALL), true), + // A writer sharing everything still refuses a reader that does not share its write. + ((Write, ShareMode::ALL), (Read, ShareMode::READ), true), + ((Write, ShareMode::ALL), (Read, both), false), + // Sharing delete is what admits a deleter, and the deleter must share what is held. + ((Read, ShareMode::ALL), (Delete, ShareMode::READ), false), + ((Read, ShareMode::ALL), (Delete, ShareMode::NONE), true), + ( + (Delete, ShareMode::DELETE), + (Delete, ShareMode::DELETE), + false, + ), + ((Delete, ShareMode::READ), (Read, ShareMode::DELETE), false), + // An open that shares nothing is refused by every other that asks for something. + ((Write, ShareMode::NONE), (Write, ShareMode::ALL), true), + ]; + for (index, (first, second, refused)) in cases.into_iter().enumerate() { + let temp = TempFile::new(&format!("share-named{index}")); + let _held = open(&temp.0, first.0, first.1).expect("the first open"); + assert_eq!( + open(&temp.0, second.0, second.1).is_err(), + refused, + "{first:?} then {second:?}" + ); + } +} + +#[test] +fn an_open_that_shares_nothing_is_exclusive_to_everything_that_asks_for_data_or_delete() { + let temp = TempFile::new("share-exclusive"); + let exclusive = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().read().write().share(ShareMode::NONE), + ) + .expect("the first open of an unopened file"); + for wants in WANTS { + let error = open(&temp.0, wants, ShareMode::ALL) + .err() + .unwrap_or_else(|| panic!("{wants:?} was allowed beside an exclusive open")); + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy, "{wants:?}"); + } + drop(exclusive); + for wants in WANTS { + assert!( + open(&temp.0, wants, ShareMode::ALL).is_ok(), + "{wants:?} once the exclusive open is gone" + ); + } +} + +#[test] +fn an_open_that_asks_for_no_data_and_no_right_is_not_judged_by_the_share_mode() { + let temp = TempFile::new("share-nothing"); + let _exclusive = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().read().write().share(ShareMode::NONE), + ) + .expect("an exclusive open"); + OpenedFile::::open(wtf(&temp.0), FileOptions::new()) + .expect("an open that asks for nothing can address the file all the same"); +} + +#[test] +fn a_file_is_shared_for_reading_writing_and_deletion_unless_told_otherwise() { + let temp = TempFile::new("share-default"); + let _first = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().read().write().rights(FileRights::DELETE), + ) + .expect("a default open"); + for wants in WANTS { + assert!(open(&temp.0, wants, ShareMode::ALL).is_ok(), "{wants:?}"); + } +} + +#[test] +fn the_error_names_what_this_open_asked_for_and_keeps_the_kernels_error() { + let temp = TempFile::new("share-error"); + let _held = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().read().write().share(ShareMode::NONE), + ) + .expect("an exclusive open"); + let error = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new() + .read() + .write() + .rights(FileRights::DELETE) + .share(ShareMode::READ), + ) + .expect_err("an exclusive open is open"); + let violation = sharing_violation(&error); + assert_eq!(violation.by, OpenedBy::Path(temp.0.clone())); + assert_eq!(violation.data, Some(Access::ReadWrite)); + assert_eq!(violation.rights, FileRights::DELETE); + assert_eq!(violation.share, ShareMode::READ); + assert_eq!( + kernel_code(violation), + Some(ERROR_SHARING_VIOLATION.cast_signed()) + ); + assert!(error.to_string().contains("conflicts"), "{error}"); +} + +#[test] +fn a_failure_that_is_not_a_sharing_violation_is_the_kernels_error_as_it_was() { + let temp = TempFile::new("share-other"); + let missing = temp.0.with_extension("missing"); + let error = OpenedFile::::open( + wtf(&missing), + FileOptions::new().read().write().share(ShareMode::NONE), + ) + .expect_err("there is no such file"); + assert_eq!(error.kind(), io::ErrorKind::NotFound); + assert!( + error.get_ref().is_none(), + "no wrapper: the kernel's own error" + ); +} + +#[test] +fn the_data_access_of_each_marker_is_named_as_a_value_and_no_data_is_none() { + use super::super::{ReadOnly, WriteOnly}; + assert_eq!(data_access::(), None); + assert_eq!(data_access::(), Some(Access::ReadOnly)); + assert_eq!(data_access::(), Some(Access::WriteOnly)); + assert_eq!(data_access::(), Some(Access::ReadWrite)); +} + +#[test] +fn the_share_mode_survives_the_options_changing_their_data_access() { + let temp = TempFile::new("share-types"); + // `share` is set before `read` and `write`, which rebuild the options for the new access type. + let _exclusive = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new().share(ShareMode::NONE).read().write(), + ) + .expect("an exclusive open"); + assert!(open(&temp.0, Wants::Read, ShareMode::ALL).is_err()); +} + +#[test] +fn the_share_mode_reaches_the_request_as_it_is_and_defaults_to_every_member() { + let share_of = |options: FileOptions| options.parameters(0).share; + let read_write = || FileOptions::new().read().write(); + assert_eq!(share_of(read_write()), ShareMode::ALL.bits()); + for mode in every_share_mode() { + assert_eq!(share_of(read_write().share(mode)), mode.bits(), "{mode:?}"); + } + assert_eq!( + share_of(read_write().share(ShareMode::NONE).share(ShareMode::READ)), + ShareMode::READ.bits(), + "the last call replaces the mode" + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_file/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_file/tests.rs index 0293cd8ce..699902392 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_file/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_file/tests.rs @@ -1,29 +1,36 @@ // Copyright (c) 2026 Mike Grier use std::fs::{self, File}; -use std::io::{self, Seek, SeekFrom, Write}; +use std::io::{self, Read, Seek, SeekFrom, Write}; use std::os::windows::io::{AsHandle, AsRawHandle}; use std::path::{Path, PathBuf}; use io_buffers::AlignedBuf; +use win_shared_os_owned_handle::SharedHandle; -use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; use windows_sys::Win32::Storage::FileSystem::{ - CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, OPEN_ALWAYS, OPEN_EXISTING, TRUNCATE_EXISTING, + CREATE_NEW, FILE_APPEND_DATA, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_RANDOM_ACCESS, + FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_SHARE_DELETE, FILE_SHARE_READ, + FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS, OPEN_EXISTING, }; +use super::super::{ + DataAccess, FileRights, NoData, ReadOnly, ReadWrite, Win32FileTarget, WriteOnly, +}; use super::{ - Buffering, Cached, FILE_FLAG_NO_BUFFERING, FileOptions, OpenedFile, RemoteDevice, Unbuffered, - final_location, names_a_remote_device, open_request, + Buffering, Cached, Disposition, FILE_FLAG_NO_BUFFERING, FileOptions, OpenedFile, RemoteDevice, + Unbuffered, UnknownLocation, data_rights, final_location, names_a_remote_device, open_request, +}; +use crate::dioring::tests::{TempFile, wtf}; +use durable_ioring::{ + Access, Capabilities, Geometry, GeometryError, HasData, IoTarget, WarrantyClass, }; -use crate::dioring::tests::TempFile; -use durable_ioring::{Capabilities, Geometry, GeometryError, WarrantyClass}; /// `ERROR_INVALID_PARAMETER`, which an unbuffered file answers an unaligned transfer with. const INVALID_PARAMETER: i32 = 87; -fn read_write() -> FileOptions { - FileOptions::new().read(true).write(true) +fn read_write() -> FileOptions { + FileOptions::new().read().write() } /// A second handle to the file `opened` holds, as a `std::fs::File`, to do I/O the kernel itself @@ -73,9 +80,10 @@ fn the_kernel_treats_a_cached_file_as_cached_and_an_unbuffered_one_as_unbuffered file.write_all(b"seven b") }; let (cached_file, unbuffered_file) = (TempFile::new("of-cached"), TempFile::new("of-raw")); - let cached = OpenedFile::::open(&cached_file.0, read_write()).expect("open cached"); - let unbuffered = - OpenedFile::::open(&unbuffered_file.0, read_write()).expect("open unbuffered"); + let cached = + OpenedFile::::open(wtf(&cached_file.0), read_write()).expect("open cached"); + let unbuffered = OpenedFile::::open(wtf(&unbuffered_file.0), read_write()) + .expect("open unbuffered"); unaligned(&mut io_through(&cached)).expect("a cached file takes an unaligned write"); let refused = @@ -86,7 +94,7 @@ fn the_kernel_treats_a_cached_file_as_cached_and_an_unbuffered_one_as_unbuffered #[test] fn an_aligned_write_to_an_unbuffered_file_succeeds() { let temp = TempFile::new("of-aligned"); - let unbuffered = OpenedFile::::open(&temp.0, read_write()).expect("open"); + let unbuffered = OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); let sector = Unbuffered::DEFAULT_SECTOR_SIZE as usize; let buffer = AlignedBuf::from_bytes(&vec![7u8; sector], sector).expect("an aligned buffer"); assert!(unbuffered.geometry().admits(0, sector as u64)); @@ -105,10 +113,126 @@ fn an_aligned_write_to_an_unbuffered_file_succeeds() { ); } +#[test] +fn a_hint_sets_its_own_flag_and_nothing_else() { + fn flags(options: FileOptions, mode: u32) -> u32 { + options.parameters(mode).flags + } + let read = FileOptions::new().read(); + + assert_eq!(flags(read, 0), 0, "no hint, no flag"); + assert_eq!(flags(read.random_access(true), 0), FILE_FLAG_RANDOM_ACCESS); + assert_eq!( + flags(read.sequential_scan(true), 0), + FILE_FLAG_SEQUENTIAL_SCAN + ); + assert_eq!( + flags(read.random_access(true).sequential_scan(true), 0), + FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_SEQUENTIAL_SCAN, + "both together are passed on, not refused" + ); + assert_eq!( + flags(read.random_access(true).random_access(false), 0), + 0, + "a hint can be taken back" + ); + // Added to the mode's own flags and to the reparse flag, never in place of them. + assert_eq!( + flags(read.random_access(true), FILE_FLAG_NO_BUFFERING), + FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS + ); + assert_eq!( + flags( + FileOptions::new() + .write() + .open_reparse_point(true) + .sequential_scan(true), + 0 + ), + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_SEQUENTIAL_SCAN + ); +} + +/// The four ways of asking for the two hints. +const HINTS: [(bool, bool); 4] = [(false, false), (true, false), (false, true), (true, true)]; + +/// Opens a file in mode `B` with the hints, writes one aligned sector through it and reads it +/// back, which is what the kernel does with the pair, judged by the kernel and not by a flag this +/// crate recorded. +fn open_write_and_read_back( + temp: &TempFile, + (random, sequential): (bool, bool), +) -> io::Result<()> { + let options = read_write() + .random_access(random) + .sequential_scan(sequential); + let opened = OpenedFile::::open(wtf(&temp.0), options)?; + let sector = Unbuffered::DEFAULT_SECTOR_SIZE as usize; + let written = AlignedBuf::from_bytes(&vec![0x5a; sector], sector).expect("an aligned buffer"); + let mut through = io_through(&opened); + through.seek(SeekFrom::Start(0))?; + through.write_all(written.as_slice())?; + through.seek(SeekFrom::Start(0))?; + let mut read = AlignedBuf::zeroed(sector, sector).expect("an aligned buffer"); + through.read_exact(read.as_mut_slice())?; + assert_eq!(read.as_slice(), written.as_slice()); + Ok(()) +} + +#[test] +fn the_kernel_takes_each_hint_on_a_cached_file() { + for hints in HINTS { + let temp = TempFile::new("of-hint-cached"); + open_write_and_read_back::(&temp, hints) + .unwrap_or_else(|error| panic!("cached, hints {hints:?}: {error}")); + } +} + +// Passed to an unbuffered file and not refused (`DI-14.1`). The `CreateFileW` page says the hints +// "have no effect if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING", which +// can be read two ways, so this is the measurement: every pair opens and does aligned I/O. +#[test] +fn the_kernel_takes_each_hint_on_an_unbuffered_file() { + for hints in HINTS { + let temp = TempFile::new("of-hint-unbuffered"); + open_write_and_read_back::(&temp, hints) + .unwrap_or_else(|error| panic!("unbuffered, hints {hints:?}: {error}")); + } +} + +#[test] +fn a_hint_changes_neither_the_class_nor_the_alignment_nor_what_is_offered() { + // Judged on the target made from each file, which is what a consumer reads. + let claims = |options: FileOptions| { + let (unbuffered, cached) = (TempFile::new("of-claims-u"), TempFile::new("of-claims-c")); + let u = Win32FileTarget::new( + OpenedFile::::open(wtf(&unbuffered.0), options).expect("open unbuffered"), + ); + let c = Win32FileTarget::new( + OpenedFile::::open(wtf(&cached.0), options).expect("open cached"), + ); + ( + (u.warranty(), u.geometry(), u.capabilities()), + (c.warranty(), c.geometry(), c.capabilities()), + ) + }; + let plain = claims(read_write()); + for (random, sequential) in HINTS { + let hinted = claims( + read_write() + .random_access(random) + .sequential_scan(sequential), + ); + assert_eq!(hinted, plain, "hints {random}, {sequential}"); + } + assert_eq!(plain.0.0, WarrantyClass::SharedDevice); + assert_eq!(plain.1.0, WarrantyClass::BestEffort); +} + #[test] fn a_sector_size_replaces_the_default_and_must_be_a_power_of_two() { let temp = TempFile::new("of-sector"); - let open = || OpenedFile::::open(&temp.0, read_write()).expect("open"); + let open = || OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); let small = open().with_sector_size(512).expect("512 is a power of two"); assert_eq!(small.geometry(), Geometry::uniform(512).expect("valid")); for bad in [0, 3, 1000, 4097] { @@ -124,7 +248,7 @@ fn a_cached_file_has_no_sector_size_to_state() { // `with_sector_size` exists only for `OpenedFile`; a cached file's geometry is // fixed, and is read back as it was given. let temp = TempFile::new("of-fixed"); - let cached = OpenedFile::::open(&temp.0, read_write()).expect("open"); + let cached = OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); assert_eq!(cached.geometry(), Geometry::UNALIGNED); } @@ -133,48 +257,55 @@ fn the_options_decide_what_is_opened_and_created() { let temp = TempFile::new("of-options"); let missing = temp.0.with_extension("missing"); - let error = OpenedFile::::open(&missing, read_write()).expect_err("not created"); + let error = OpenedFile::::open(wtf(&missing), read_write()).expect_err("not created"); assert_eq!(error.kind(), io::ErrorKind::NotFound); - let created = OpenedFile::::open(&missing, read_write().create(true)).expect("create"); + let created = OpenedFile::::open( + wtf(&missing), + read_write().disposition(Disposition::OpenAlways), + ) + .expect("create"); assert!(missing.exists()); drop(created); - let error = - OpenedFile::::open(&missing, read_write().create_new(true)).expect_err("it exists"); + let error = OpenedFile::::open( + wtf(&missing), + read_write().disposition(Disposition::CreateNew), + ) + .expect_err("it exists"); assert_eq!(error.kind(), io::ErrorKind::AlreadyExists); - fs::write(&missing, b"content").expect("fill"); - drop(OpenedFile::::open(&missing, read_write().truncate(true)).expect("truncate")); - assert_eq!(fs::metadata(&missing).expect("metadata").len(), 0); - - let error = OpenedFile::::open(&missing, FileOptions::new()).expect_err("no access"); - assert_eq!(error.kind(), io::ErrorKind::InvalidInput); fs::remove_file(&missing).expect("remove"); } #[test] fn an_unbuffered_file_can_be_created_where_none_exists_yet() { - // The remote check looks at the directory a file would be created in, so a new path is judged - // before anything is created, and a missing directory is the open's own NotFound. + // The remote check is made on the opened handle, so a file that is created is judged where it + // was created, and a missing directory is the open's own NotFound. let temp = TempFile::new("of-new"); let fresh = temp.0.with_extension("fresh"); - let created = OpenedFile::::open(&fresh, read_write().create_new(true)) - .expect("create unbuffered"); + let created = OpenedFile::::open( + wtf(&fresh), + read_write().disposition(Disposition::CreateNew), + ) + .expect("create unbuffered"); assert!(fresh.exists()); drop(created); fs::remove_file(&fresh).expect("remove"); let nowhere = temp.0.join("no-such-directory").join("file"); - let error = OpenedFile::::open(&nowhere, read_write().create(true)) - .expect_err("no directory"); + let error = OpenedFile::::open( + wtf(&nowhere), + read_write().disposition(Disposition::OpenAlways), + ) + .expect_err("no directory"); assert_eq!(error.kind(), io::ErrorKind::NotFound); } #[test] fn clones_share_one_handle_and_lending_gives_that_handle() { let temp = TempFile::new("of-clone"); - let file = OpenedFile::::open(&temp.0, read_write()).expect("open"); + let file = OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); let copy = file.clone(); assert!(file.handle().same_handle(copy.handle())); assert_eq!(file.as_raw_handle(), copy.as_raw_handle()); @@ -212,32 +343,85 @@ fn a_path_on_a_local_volume_does_not() { } #[test] -fn where_a_path_lands_is_itself_when_it_exists_and_its_directory_when_it_does_not() { +fn where_a_handle_is_is_the_final_path_of_the_file_it_names() { let temp = TempFile::new("of-location"); - let existing = final_location(&temp.0).expect("an existing file"); - assert_eq!(existing, fs::canonicalize(&temp.0).expect("canonical")); + let file = File::open(&temp.0).expect("open"); + let location = + final_location(&SharedHandle::from(file)).expect("the final path of an open file"); + assert_eq!(location, fs::canonicalize(&temp.0).expect("canonical")); + assert!(!names_a_remote_device(&location)); +} - let absent = temp.0.with_extension("absent"); - let landed = final_location(&absent).expect("the directory of a new file"); - let directory = temp.0.parent().expect("a directory"); - assert_eq!(landed, fs::canonicalize(directory).expect("canonical")); +#[test] +fn the_location_of_a_handle_with_no_final_path_is_the_calls_own_error() { + // `NUL` is not a file of a volume, so it has no final path, and the error read is the call's. + let nul = File::open("NUL").expect("open NUL"); + let error = final_location(&SharedHandle::from(nul)).expect_err("NUL has no final path"); + assert!(error.raw_os_error().is_some(), "{error:?}"); + assert_ne!(error.kind(), io::ErrorKind::Unsupported); +} + +#[test] +fn an_unbuffered_open_of_a_handle_with_no_final_path_is_refused_as_this_crates_own_narrowing() { + // Without the refusal an unbuffered `NUL` would claim `WarrantyClass::SharedDevice`, that its + // writes reach a device, which they do not. The open succeeds and the refusal follows it. + let error = OpenedFile::::open(wtf(Path::new("NUL")), read_write()) + .expect_err("NUL cannot be opened unbuffered"); + assert_eq!(error.kind(), io::ErrorKind::Unsupported); + let refusal = error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is an UnknownLocation, and not the final-path call's own error"); + assert!( + refusal.cause.raw_os_error().is_some(), + "{:?}", + refusal.cause + ); + assert!(refusal.to_string().contains("not known to be a file")); + assert!(std::error::Error::source(refusal).is_some()); +} - let relative = final_location(Path::new("absent-in-the-current-directory")) - .expect("a bare name is judged by the current directory"); +#[test] +fn a_cached_open_of_a_handle_with_no_final_path_is_not_refused() { + // Only the warranty of an unbuffered file needs a local file. A cached open claims best effort, + // and where a name leads is the caller's (DI-D-67). + let nul = OpenedFile::::open(wtf(Path::new("NUL")), read_write()) + .expect("a cached open of NUL"); + assert_eq!(nul.geometry(), Cached::geometry()); +} + +#[test] +fn an_open_is_judged_where_the_handle_is_so_a_link_is_followed_unless_the_link_is_opened() { + let temp = TempFile::new("of-link-location"); + let link = temp.0.with_extension("link"); + if let Err(error) = std::os::windows::fs::symlink_file(&temp.0, &link) { + eprintln!("skipped: a symbolic link cannot be made here: {error}"); + return; + } + + // Followed: the handle is the target's. + let followed = OpenedFile::::open(wtf(&link), read_write()).expect("open the target"); assert_eq!( - relative, - fs::canonicalize(".").expect("the current directory") + final_location(followed.handle()).expect("a location"), + fs::canonicalize(&temp.0).expect("canonical") ); - let error = final_location(&temp.0.join("no-such-directory").join("file")) - .expect_err("its directory is missing too"); - assert_eq!(error.kind(), io::ErrorKind::NotFound); + // Not followed: the handle is the link's own. + let own = OpenedFile::::open(wtf(&link), read_write().open_reparse_point(true)) + .expect("open the link"); + let expected = fs::canonicalize(link.parent().expect("a directory")) + .expect("canonical") + .join(link.file_name().expect("a name")); + assert_eq!(final_location(own.handle()).expect("a location"), expected); + + drop((followed, own)); + fs::remove_file(&link).expect("remove the link"); } /// The administrative share of this volume, `\\localhost\X$\...`, naming `local`. Reachable on an /// account that is an administrator, as the CI runner's is; the test that uses it is ignored by /// default for that reason, as `windows-namespace-request-sys`'s UNC test is. -fn admin_share(local: &Path) -> PathBuf { +pub(super) fn admin_share(local: &Path) -> PathBuf { let text = local.to_str().expect("a UTF-8 temp path"); // A temp directory named with the verbatim prefix still has its drive after it. let plain = text.strip_prefix(r"\\?\").unwrap_or(text); @@ -251,7 +435,7 @@ fn an_unbuffered_file_is_refused_on_a_remote_device_and_a_cached_one_is_not() { let temp = TempFile::new("of-remote"); let remote = admin_share(&temp.0); - let error = OpenedFile::::open(&remote, read_write()) + let error = OpenedFile::::open(wtf(&remote), read_write()) .expect_err("an unbuffered file on a share"); assert_eq!(error.kind(), io::ErrorKind::Unsupported); let refusal = error @@ -265,152 +449,586 @@ fn an_unbuffered_file_is_refused_on_a_remote_device_and_a_cached_one_is_not() { ); assert!(refusal.to_string().contains("remote device")); - OpenedFile::::open(&remote, read_write()) + OpenedFile::::open(wtf(&remote), read_write()) .expect("a cached file on a share claims only best effort, and is allowed"); } +#[test] +#[ignore = "needs the administrative share \\\\localhost\\C$, which a CI runner has"] +fn a_refusal_made_on_the_handle_comes_after_a_new_file_was_created_and_a_link_is_judged_by_the_handle() + { + let temp = TempFile::new("of-remote-late"); + + // The documented cost of checking the opened handle: the create has happened when it is refused. + let fresh = temp.0.with_extension("fresh"); + let fresh_share = admin_share(&fresh); + let error = OpenedFile::::open( + wtf(&fresh_share), + read_write().disposition(Disposition::CreateNew), + ) + .expect_err("an unbuffered file on a share"); + assert_eq!(error.kind(), io::ErrorKind::Unsupported); + assert!(fresh.exists(), "the file was created before the refusal"); + fs::remove_file(&fresh).expect("remove"); + + // A local link to a file on a share: followed, the handle is on the share and is refused; + // opened, the handle is the link's own, on this volume, and is not. + let link = temp.0.with_extension("link"); + if let Err(error) = std::os::windows::fs::symlink_file(admin_share(&temp.0), &link) { + eprintln!("skipped: a symbolic link cannot be made here: {error}"); + return; + } + let error = OpenedFile::::open(wtf(&link), read_write()) + .expect_err("the link leads to a share"); + assert_eq!(error.kind(), io::ErrorKind::Unsupported); + let own = OpenedFile::::open(wtf(&link), read_write().open_reparse_point(true)) + .expect("the link itself is on a local volume"); + drop(own); + fs::remove_file(&link).expect("remove the link"); +} + // --- What an open asks of CreateFileW ------------------------------------------------------- -// -// Opening went from `std::fs::OpenOptions` to `windows-namespace-request-sys`'s `OpenFile`, and -// a caller must see no difference, so what std asked of the kernel is pinned here. -fn parameters_of(options: FileOptions) -> open_request::Parameters { - options.parameters(0).expect("a valid combination") +fn parameters_of(options: FileOptions) -> open_request::Parameters { + options.parameters(0) } -fn parameters( - access: u32, - disposition: u32, - flags: u32, - truncate_after: bool, -) -> open_request::Parameters { +/// What a file opened for reading and writing asks `CreateFileW` for, and nothing else. +const READ_WRITE: u32 = FILE_READ_DATA | FILE_WRITE_DATA; + +fn parameters(access: u32, disposition: u32, flags: u32) -> open_request::Parameters { open_request::Parameters { access, disposition, flags, - truncate_after, + // Every member, unless the options say otherwise. + share: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, } } #[test] -fn the_options_map_to_the_access_and_disposition_std_asked_for() { +fn the_options_map_to_the_access_and_disposition_they_name() { assert_eq!( - parameters_of(FileOptions::new().read(true)), - parameters(GENERIC_READ, OPEN_EXISTING, 0, false) + parameters_of(FileOptions::new().read()), + parameters(FILE_READ_DATA, OPEN_EXISTING, 0) ); assert_eq!( - parameters_of(FileOptions::new().write(true)), - parameters(GENERIC_WRITE, OPEN_EXISTING, 0, false) + parameters_of(FileOptions::new().write()), + parameters(FILE_WRITE_DATA, OPEN_EXISTING, 0) ); assert_eq!( parameters_of(read_write()), - parameters(GENERIC_READ | GENERIC_WRITE, OPEN_EXISTING, 0, false) + parameters(READ_WRITE, OPEN_EXISTING, 0), + "OpenExisting is what the options start at" ); assert_eq!( - parameters_of(read_write().create(true)), - parameters(GENERIC_READ | GENERIC_WRITE, OPEN_ALWAYS, 0, false) + parameters_of(read_write().disposition(Disposition::OpenExisting)), + parameters(READ_WRITE, OPEN_EXISTING, 0) ); assert_eq!( - parameters_of(read_write().truncate(true)), - parameters(GENERIC_READ | GENERIC_WRITE, TRUNCATE_EXISTING, 0, false) + parameters_of(read_write().disposition(Disposition::OpenAlways)), + parameters(READ_WRITE, OPEN_ALWAYS, 0) + ); + assert_eq!( + parameters_of(read_write().disposition(Disposition::CreateNew)), + parameters(READ_WRITE, CREATE_NEW, 0) ); -} - -#[test] -fn a_created_and_truncated_file_is_opened_always_and_emptied_afterwards() { - // Not `CREATE_ALWAYS`, whose semantics std avoids the same way (rust-lang/rust#115745). assert_eq!( - parameters_of(read_write().create(true).truncate(true)), - parameters(GENERIC_READ | GENERIC_WRITE, OPEN_ALWAYS, 0, true) + parameters_of( + read_write() + .disposition(Disposition::CreateNew) + .disposition(Disposition::OpenAlways) + ), + parameters(READ_WRITE, OPEN_ALWAYS, 0), + "the last disposition stated is the one used" ); } #[test] -fn a_new_file_is_created_without_following_a_link_and_whatever_else_is_asked() { - let expected = parameters( - GENERIC_READ | GENERIC_WRITE, - CREATE_NEW, - FILE_FLAG_OPEN_REPARSE_POINT, - false, - ); - assert_eq!(parameters_of(read_write().create_new(true)), expected); - // `create_new` wins over `create` and `truncate`, as it does in std. - assert_eq!( - parameters_of(read_write().create_new(true).create(true).truncate(true)), - expected - ); +fn the_reparse_flag_is_set_by_its_option_and_by_no_disposition() { + for disposition in [ + Disposition::CreateNew, + Disposition::OpenExisting, + Disposition::OpenAlways, + ] { + let options = read_write().disposition(disposition); + assert_eq!(parameters_of(options).flags, 0, "{disposition:?}"); + assert_eq!( + parameters_of(options.open_reparse_point(true)).flags, + FILE_FLAG_OPEN_REPARSE_POINT, + "{disposition:?}" + ); + assert_eq!( + parameters_of(options.open_reparse_point(true).open_reparse_point(false)).flags, + 0, + "{disposition:?}: the option can be taken back" + ); + } } #[test] fn the_buffering_flags_are_added_to_what_the_options_imply() { let flags = read_write() - .create_new(true) + .open_reparse_point(true) .parameters(FILE_FLAG_NO_BUFFERING) - .expect("valid") .flags; assert_eq!(flags, FILE_FLAG_NO_BUFFERING | FILE_FLAG_OPEN_REPARSE_POINT); assert_eq!( - read_write() - .parameters(FILE_FLAG_NO_BUFFERING) - .expect("valid") - .flags, + read_write().parameters(FILE_FLAG_NO_BUFFERING).flags, FILE_FLAG_NO_BUFFERING ); } #[test] -fn a_combination_that_cannot_mean_anything_is_invalid_input() { - for (label, options) in [ - ("nothing", FileOptions::new()), - ("create only", FileOptions::new().create(true)), - ("create_new only", FileOptions::new().create_new(true)), - ("truncate only", FileOptions::new().truncate(true)), - ( - "read and create", - FileOptions::new().read(true).create(true), - ), +fn every_disposition_is_a_request_whatever_the_data_access() { + fn requested(options: FileOptions) { + for (disposition, bits) in [ + (Disposition::CreateNew, CREATE_NEW), + (Disposition::OpenExisting, OPEN_EXISTING), + (Disposition::OpenAlways, OPEN_ALWAYS), + ] { + let asked = parameters_of(options.disposition(disposition)); + assert_eq!(asked.disposition, bits, "{disposition:?}"); + assert_eq!(asked.access, data_rights::() | options.rights.bits()); + } + } + requested(FileOptions::new()); + requested(FileOptions::new().read()); + requested(FileOptions::new().write()); + requested(read_write()); + // The other rights are no stand-in for a data right, and none is wanted: nothing is refused. + requested(FileOptions::new().rights(FileRights::DELETE | FileRights::WRITE_ATTRIBUTES)); +} + +/// What the kernel does with each disposition, for a file that is there and for one that is not, +/// judged by the kernel and not by the parameters the options produced. +fn each_disposition_does_what_win32_says(options: FileOptions) { + const CONTENT: &[u8] = b"content"; + let temp = TempFile::new("of-dispositions"); + let absent = temp.0.with_extension("absent"); + let open = |path: &Path, disposition| { + OpenedFile::::open(wtf(path), options.disposition(disposition)).map(drop) + }; + + // (disposition, what it does to a file that is there, what it does to one that is not) + let table = [ ( - "read and create_new", - FileOptions::new().read(true).create_new(true), + Disposition::CreateNew, + Some(io::ErrorKind::AlreadyExists), + None, ), ( - "read and truncate", - FileOptions::new().read(true).truncate(true), + Disposition::OpenExisting, + None, + Some(io::ErrorKind::NotFound), ), - ] { - let error = options.parameters(0).expect_err(label); - assert_eq!(error.kind(), io::ErrorKind::InvalidInput, "{label}"); + (Disposition::OpenAlways, None, None), + ]; + for (disposition, on_present, on_absent) in table { + fs::write(&temp.0, CONTENT).expect("fill"); + let result = open(&temp.0, disposition); + assert_eq!( + result.err().map(|e| e.kind()), + on_present, + "{disposition:?} present" + ); + assert_eq!( + fs::read(&temp.0).expect("read"), + CONTENT, + "{disposition:?} changed a file that was there" + ); + + let _ = fs::remove_file(&absent); + let result = open(&absent, disposition); + assert_eq!( + result.err().map(|e| e.kind()), + on_absent, + "{disposition:?} absent" + ); + if on_absent.is_none() { + assert_eq!( + fs::metadata(&absent).expect("created").len(), + 0, + "{disposition:?} created a file that was not empty" + ); + fs::remove_file(&absent).expect("remove"); + } else { + assert!( + !absent.exists(), + "{disposition:?} created a file it should not" + ); + } } } #[test] -fn creating_and_truncating_empties_an_existing_file_on_either_buffering_mode() { - // The emptying is a call made after the open, so it is tried on an unbuffered handle too. - let temp = TempFile::new("of-truncate"); - let options = read_write().create(true).truncate(true); +fn each_disposition_does_what_win32_says_for_each_typed_access() { + each_disposition_does_what_win32_says(FileOptions::new()); + each_disposition_does_what_win32_says(FileOptions::new().read()); + each_disposition_does_what_win32_says(FileOptions::new().write()); + each_disposition_does_what_win32_says(read_write()); +} + +#[test] +fn a_new_file_is_created_through_a_link_that_is_already_there_unless_the_link_is_opened() { + let temp = TempFile::new("of-link"); + let target = temp.0.with_extension("target"); + let link = temp.0.with_extension("link"); + if let Err(error) = std::os::windows::fs::symlink_file(&target, &link) { + eprintln!("skipped: a symbolic link cannot be made here: {error}"); + return; + } + let create = read_write().disposition(Disposition::CreateNew); - fs::write(&temp.0, b"content").expect("fill"); - drop(OpenedFile::::open(&temp.0, options).expect("cached")); - assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 0); + // With the option the link is what is there, so the file it names is not created. + let error = OpenedFile::::open(wtf(&link), create.open_reparse_point(true)) + .expect_err("the link exists"); + assert_eq!(error.kind(), io::ErrorKind::AlreadyExists); + assert!(!target.exists()); - fs::write(&temp.0, b"content").expect("fill"); - drop(OpenedFile::::open(&temp.0, options).expect("unbuffered")); - assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 0); + // Without it the link is followed, and the file it names is created. + drop(OpenedFile::::open(wtf(&link), create).expect("created through the link")); + assert!(target.exists()); - // And where there is nothing to empty, the file is created. - let fresh = temp.0.with_extension("fresh"); - drop(OpenedFile::::open(&fresh, options).expect("created")); - assert_eq!(fs::metadata(&fresh).expect("metadata").len(), 0); - fs::remove_file(&fresh).expect("remove"); + fs::remove_file(&link).expect("remove the link"); + fs::remove_file(&target).expect("remove the target"); +} + +#[test] +fn an_existing_file_is_the_same_length_after_each_disposition_on_either_buffering_mode() { + // Opening does nothing to the contents, so an unbuffered handle is no different. + let temp = TempFile::new("of-length"); + for disposition in [Disposition::OpenExisting, Disposition::OpenAlways] { + fs::write(&temp.0, b"content").expect("fill"); + let options = read_write().disposition(disposition); + drop(OpenedFile::::open(wtf(&temp.0), options).expect("cached")); + assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 7); + drop(OpenedFile::::open(wtf(&temp.0), options).expect("unbuffered")); + assert_eq!(fs::metadata(&temp.0).expect("metadata").len(), 7); + } } #[test] fn an_opened_file_is_shared_for_reading_writing_and_deletion_as_std_opened_one() { let temp = TempFile::new("of-share"); - let held = OpenedFile::::open(&temp.0, read_write()).expect("first"); - let second = OpenedFile::::open(&temp.0, read_write()).expect("a second open"); + let held = OpenedFile::::open(wtf(&temp.0), read_write()).expect("first"); + let second = OpenedFile::::open(wtf(&temp.0), read_write()).expect("a second open"); drop(second); // Deleting a file that is open needs the delete share, which std's default gave. fs::remove_file(&temp.0).expect("a file opened shared for deletion can be deleted"); drop(held); } + +// DI-13.8: the data access is the type of the handle, and what the kernel makes of each is judged +// here through a second handle to the same file, as the kernel enforces it. + +/// `ERROR_ACCESS_DENIED`, which a handle answers an operation its rights do not cover with. +const ACCESS_DENIED: i32 = 5; + +/// What the kernel allowed through a duplicate of a handle, one field for each call a consumer or this +/// crate makes on it that depends on the data rights. +#[derive(Debug, PartialEq, Eq)] +struct Allowed { + read: bool, + write: bool, + extend: bool, + flush: bool, +} + +/// Whether `result` succeeded, and a panic for any failure other than the one a missing right gives. +fn allowed(call: &str, result: io::Result) -> bool { + match result { + Ok(_) => true, + Err(error) if error.raw_os_error() == Some(ACCESS_DENIED) => false, + Err(error) => panic!("{call} failed with something other than access denied: {error}"), + } +} + +/// Opens a file of one's own cached with `options` and asks the kernel what the handle allows. +fn judged(options: FileOptions) -> Allowed { + let temp = TempFile::new("of-judged"); + fs::write(&temp.0, vec![0u8; 8192]).expect("fill"); + let opened = OpenedFile::::open(wtf(&temp.0), options).expect("open"); + let mut through = io_through(&opened); + Allowed { + read: allowed("read", through.read(&mut [0u8; 1])), + write: allowed("write", through.write(b"x")), + extend: allowed("extend", through.set_len(16384)), + flush: allowed("flush", through.sync_all()), + } +} + +#[test] +fn the_kernel_allows_what_each_data_access_names_and_nothing_else() { + assert_eq!( + judged(FileOptions::new()), + Allowed { + read: false, + write: false, + extend: false, + flush: false + }, + "NoData" + ); + assert_eq!( + judged(FileOptions::new().read()), + Allowed { + read: true, + write: false, + extend: false, + flush: false + }, + "ReadOnly" + ); + assert_eq!( + judged(FileOptions::new().write()), + Allowed { + read: false, + write: true, + extend: true, + flush: true + }, + "WriteOnly" + ); + assert_eq!( + judged(FileOptions::new().read().write()), + Allowed { + read: true, + write: true, + extend: true, + flush: true + }, + "ReadWrite" + ); +} + +#[test] +fn each_data_access_opens_a_file_of_ones_own_on_either_buffering_mode() { + fn opens(name: &str, options: FileOptions) { + let temp = TempFile::new("of-access-opens"); + OpenedFile::::open(wtf(&temp.0), options) + .unwrap_or_else(|error| panic!("{name}, cached: {error}")); + OpenedFile::::open(wtf(&temp.0), options) + .unwrap_or_else(|error| panic!("{name}, unbuffered: {error}")); + } + opens("NoData", FileOptions::new()); + opens("ReadOnly", FileOptions::new().read()); + opens("WriteOnly", FileOptions::new().write()); + opens("ReadWrite", FileOptions::new().read().write()); +} + +#[test] +fn each_marker_asks_the_kernel_for_exactly_the_data_rights_it_names() { + assert_eq!(data_rights::(), 0); + assert_eq!(data_rights::(), FILE_READ_DATA); + assert_eq!(data_rights::(), FILE_WRITE_DATA); + assert_eq!(data_rights::(), FILE_READ_DATA | FILE_WRITE_DATA); + for asked in [ + data_rights::(), + data_rights::(), + data_rights::(), + data_rights::(), + ] { + assert_eq!(asked & FILE_APPEND_DATA, 0, "{asked:#x}"); + } +} + +#[test] +fn a_files_target_states_the_access_the_file_was_opened_with() { + fn stated(options: FileOptions) -> Access { + let temp = TempFile::new("of-stated"); + let file = OpenedFile::::open(wtf(&temp.0), options).expect("open"); + Win32FileTarget::new(file).access() + } + assert_eq!(stated(FileOptions::new().read()), Access::ReadOnly); + assert_eq!(stated(FileOptions::new().write()), Access::WriteOnly); + assert_eq!(stated(FileOptions::new().read().write()), Access::ReadWrite); + // The other rights do not change what a target can do with its data. + assert_eq!( + stated(FileOptions::new().read().rights(FileRights::DELETE)), + Access::ReadOnly + ); +} + +#[test] +fn the_access_an_open_asks_the_kernel_for_is_the_data_access_and_the_other_rights() { + fn access(options: FileOptions) -> u32 { + options.parameters(0).access + } + let other = FileRights::DELETE | FileRights::WRITE_ATTRIBUTES; + assert_eq!(access(FileOptions::new()), 0); + assert_eq!(access(FileOptions::new().read()), FILE_READ_DATA); + assert_eq!(access(FileOptions::new().write()), FILE_WRITE_DATA); + assert_eq!( + access(FileOptions::new().read().write()), + FILE_READ_DATA | FILE_WRITE_DATA + ); + assert_eq!(access(FileOptions::new().rights(other)), other.bits()); + assert_eq!( + access(FileOptions::new().read().rights(other)), + FILE_READ_DATA | other.bits() + ); + assert_eq!( + access(FileOptions::new().rights(other).read().write()), + FILE_READ_DATA | FILE_WRITE_DATA | other.bits(), + "the other rights survive a change of data access" + ); +} + +#[test] +fn no_options_ask_for_the_append_bit() { + fn access(options: FileOptions) -> u32 { + options.parameters(0).access + } + let everything = FileRights::READ_ATTRIBUTES + | FileRights::WRITE_ATTRIBUTES + | FileRights::READ_EA + | FileRights::WRITE_EA + | FileRights::EXECUTE + | FileRights::DELETE + | FileRights::READ_CONTROL + | FileRights::WRITE_DAC + | FileRights::WRITE_OWNER + | FileRights::SYNCHRONIZE + | FileRights::ACCESS_SYSTEM_SECURITY; + for asked in [ + access(FileOptions::new().rights(everything)), + access(FileOptions::new().read().rights(everything)), + access(FileOptions::new().write().rights(everything)), + access(FileOptions::new().read().write().rights(everything)), + ] { + assert_eq!(asked & FILE_APPEND_DATA, 0, "{asked:#x}"); + } +} + +#[test] +fn read_and_write_reach_the_same_access_in_either_order_and_keep_everything_else() { + // The types are the assertion: a mismatch does not compile. + let _: FileOptions = FileOptions::new().read().write(); + let _: FileOptions = FileOptions::new().write().read(); + let _: FileOptions = FileOptions::new().read().read(); + let _: FileOptions = FileOptions::new().write().write(); + let _: FileOptions = FileOptions::new().read().write().read().write(); + + // Changing the access carries the disposition, the reparse flag and the hints along. + let moved = FileOptions::new() + .disposition(Disposition::CreateNew) + .open_reparse_point(true) + .random_access(true) + .sequential_scan(true) + .write(); + let parameters = moved.parameters(0); + assert_eq!(parameters.disposition, CREATE_NEW); + assert_eq!( + parameters.flags, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_SEQUENTIAL_SCAN + ); +} + +#[test] +fn the_attribute_right_decides_whether_the_times_can_be_set() { + let temp = TempFile::new("of-attributes"); + let modified = + std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000_000); + let times = fs::FileTimes::new().set_modified(modified); + + let without = OpenedFile::::open(wtf(&temp.0), read_write()).expect("open"); + assert_eq!( + io_through(&without) + .set_times(times) + .expect_err("no attribute right") + .raw_os_error(), + Some(ACCESS_DENIED) + ); + + let with = OpenedFile::::open( + wtf(&temp.0), + read_write().rights(FileRights::WRITE_ATTRIBUTES | FileRights::READ_ATTRIBUTES), + ) + .expect("open"); + let through = io_through(&with); + through + .set_times(times) + .expect("WRITE_ATTRIBUTES sets the time"); + assert_eq!( + through + .metadata() + .expect("READ_ATTRIBUTES") + .modified() + .expect("a time"), + modified + ); +} + +#[test] +fn each_other_right_opens_a_file_of_ones_own_on_either_buffering_mode() { + // `ACCESS_SYSTEM_SECURITY` needs a privilege, so it has its own test. + for (name, right) in [ + ("READ_ATTRIBUTES", FileRights::READ_ATTRIBUTES), + ("WRITE_ATTRIBUTES", FileRights::WRITE_ATTRIBUTES), + ("READ_EA", FileRights::READ_EA), + ("WRITE_EA", FileRights::WRITE_EA), + ("EXECUTE", FileRights::EXECUTE), + ("DELETE", FileRights::DELETE), + ("READ_CONTROL", FileRights::READ_CONTROL), + ("WRITE_DAC", FileRights::WRITE_DAC), + ("WRITE_OWNER", FileRights::WRITE_OWNER), + ("SYNCHRONIZE", FileRights::SYNCHRONIZE), + ] { + let options = FileOptions::new().read().rights(right); + let temp = TempFile::new("of-right-opens"); + OpenedFile::::open(wtf(&temp.0), options) + .unwrap_or_else(|error| panic!("{name}, cached: {error}")); + OpenedFile::::open(wtf(&temp.0), options) + .unwrap_or_else(|error| panic!("{name}, unbuffered: {error}")); + } +} + +#[test] +fn a_data_access_the_files_security_refuses_reaches_the_caller_as_the_kernels_error() { + let temp = TempFile::new("of-refused"); + let mut permissions = fs::metadata(&temp.0).expect("metadata").permissions(); + permissions.set_readonly(true); + fs::set_permissions(&temp.0, permissions.clone()).expect("read-only"); + + let refused = OpenedFile::::open(wtf(&temp.0), read_write()) + .expect_err("a read-only file refuses a data write"); + assert_eq!(refused.raw_os_error(), Some(ACCESS_DENIED)); + + // What the file's security allows is the kernel's to say, so an access it does allow is granted. + OpenedFile::::open(wtf(&temp.0), FileOptions::new().read()) + .expect("reading is allowed"); + + #[allow(clippy::permissions_set_readonly_false)] + { + permissions.set_readonly(false); + fs::set_permissions(&temp.0, permissions).expect("writable again"); + } +} + +#[test] +fn the_system_security_right_is_the_kernels_to_refuse_without_its_privilege() { + let temp = TempFile::new("of-system-security"); + let refused = OpenedFile::::open( + wtf(&temp.0), + FileOptions::new() + .read() + .rights(FileRights::ACCESS_SYSTEM_SECURITY), + ); + // `ERROR_PRIVILEGE_NOT_HELD`. A process that holds and has enabled the privilege is granted it, + // which is the kernel's answer and not this crate's. + match refused { + Ok(_) => {} + Err(error) => assert_eq!(error.raw_os_error(), Some(1314), "{error}"), + } +} + +#[test] +fn a_file_with_no_data_right_opens_and_is_not_a_target() { + // A target needs `A: HasData`, which the `compile_fail` doctest on `Win32FileTarget` pins. What + // is checked here is that a file with no data right is still a file the kernel opens, since a + // query needs no data right. + let temp = TempFile::new("of-no-data"); + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new()).expect("open"); + assert_eq!(file.geometry(), Geometry::UNALIGNED); +} diff --git a/crates/win-durable-ioring/src/windows_backend/opened_volume.rs b/crates/win-durable-ioring/src/windows_backend/opened_volume.rs index cfb1be484..ae374f17f 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_volume.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_volume.rs @@ -15,17 +15,17 @@ //! disks. use std::fmt; -use std::fs::OpenOptions; use std::io; use std::marker::PhantomData; -use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle}; use win_shared_os_owned_handle::SharedHandle; use win_volume_sys::{DiskExtent, bytes_per_sector, lock_volume, volume_disk_extents}; use super::opened_file::{Buffering, Unbuffered}; -use durable_ioring::{FlushDomain, Geometry}; +use super::raw_open; +use super::share_mode::ShareMode; +use durable_ioring::{DataAccess, FlushDomain, Geometry, ReadWrite}; #[cfg(test)] mod tests; @@ -117,61 +117,96 @@ impl VolumePath { } } -/// How a volume is opened beyond its path. -#[derive(Clone, Copy, Debug, Default)] +/// How a volume is opened beyond its path: whether it is locked, and what other opens of the volume +/// may request meanwhile. +#[derive(Clone, Copy, Debug)] #[must_use] pub struct VolumeOptions { lock: bool, + share: ShareMode, +} + +impl Default for VolumeOptions { + fn default() -> Self { + Self::new() + } } impl VolumeOptions { - /// Nothing requested beyond opening the volume for reading and writing. + /// Nothing requested beyond opening the volume, which is shared for reading, writing and + /// deletion, as `std::fs::OpenOptions` opens one. pub fn new() -> Self { - Self::default() + Self { + lock: false, + share: ShareMode::ALL, + } + } + + /// What other opens of the volume may request while this handle is open (`dwShareMode`), as + /// [`FileOptions::share`](super::FileOptions::share) is for a file. [`ShareMode::NONE`] opens the + /// volume exclusively. An open the sharing of either side does not allow fails with a + /// [`SharingViolation`](super::SharingViolation). Calling this again replaces the mode. + /// + /// The `CreateFileW` page says that "When opening a volume or floppy disk, the *dwShareMode* + /// parameter must have the `FILE_SHARE_WRITE` flag" (read 2026-10-18). A mode without it is not + /// refused here, because the measurement contradicts the page: on Windows 11 build 26300, elevated, + /// an exclusive open of a volume succeeded and refused a second open until it closed (the test + /// `an_exclusive_raw_volume_refuses_a_second_open_until_it_closes`). Where the documentation is + /// shown to be wrong, this crate does not build the documented condition in as a refusal. + /// + /// This is not the lock: [`lock`](Self::lock) is a device control made once the volume is open, + /// and the share mode is judged by the open itself. + pub fn share(mut self, share: ShareMode) -> Self { + self.share = share; + self } /// Lock the volume once it is open, so that this handle is the only one that can use it until /// the handle closes. A file system on the volume is flushed and writes nothing more, which is /// what lets a raw write to its own sectors be made; a volume with no file system needs none. /// Opening fails, and nothing is held, if anything else has the volume open. + /// + /// The `FSCTL_LOCK_VOLUME` page names no right the handle needs, so the lock is offered with any + /// data access and the kernel's error is the answer if one is needed; which it is was not + /// measured, since it needs an administrator. pub fn lock(mut self, lock: bool) -> Self { self.lock = lock; self } } -/// A raw volume this crate opened, in buffering mode `B` ([`Unbuffered`] unless said otherwise): a -/// [`SharedHandle`], the disks it sits on, the sector it is addressed in and whether it is locked. -/// Cloning shares the one handle. +/// A raw volume this crate opened, in buffering mode `B` ([`Unbuffered`] unless said otherwise) and +/// with data access `A` ([`ReadWrite`] unless said otherwise): a [`SharedHandle`], the disks it sits +/// on, the sector it is addressed in and whether it is locked. Cloning shares the one handle. /// -/// It is what [`Win32FileTarget::from_volume`](super::Win32FileTarget::from_volume) is made from. +/// It is what [`Win32FileTarget::from_volume`](super::Win32FileTarget::from_volume) is made from, when +/// `A` has a data right. The data access is the one the open was given, which `options` do not carry, +/// so it is named in the type at the call: `OpenedVolume::::open(..)`. #[derive(Clone, Debug)] -pub struct OpenedVolume { +pub struct OpenedVolume { handle: SharedHandle, geometry: Geometry, extents: Vec, locked: bool, - buffering: PhantomData, + mode: PhantomData<(B, A)>, } -impl OpenedVolume { - /// Open `path` for reading and writing in buffering mode `B`, find its disks and sector size, - /// and lock it if `options` say so. +impl OpenedVolume { + /// Open `path` with data access `A` in buffering mode `B`, find its disks and sector size, and + /// lock it if `options` say so. /// - /// Opening a volume for writing needs an account that is an administrator. + /// Opening a volume for writing needs an account that is an administrator. Opening it with + /// [`NoData`](super::NoData) asks for no data right, which is enough to query the volume, and the + /// result cannot be made into a target. /// /// # Errors /// - /// Whatever the open reports, and the device's own error from the extents or sector size - /// control: a volume that is remote, or a path that is not a volume, does not answer them, so + /// Whatever the open reports, a [`SharingViolation`](super::SharingViolation) among it, and the + /// device's own error from the extents or sector size control: a volume that is remote, or a path that is not a volume, does not answer them, so /// it is refused here. When the lock is asked for, the lock's own error, and then nothing is /// held. pub fn open(path: &VolumePath, options: VolumeOptions) -> io::Result { - let file = OpenOptions::new() - .read(true) - .write(true) - .custom_flags(B::FLAGS) - .open(path.as_str())?; + let file = raw_open::open::(path.as_str(), options.share)?; let extents = volume_disk_extents(&file)?; let sector = bytes_per_sector(&file)?; let geometry = B::sector_geometry(sector) @@ -184,7 +219,7 @@ impl OpenedVolume { geometry, extents, locked: options.lock, - buffering: PhantomData, + mode: PhantomData, }) } @@ -232,18 +267,18 @@ impl OpenedVolume { geometry: B::sector_geometry(sector).expect("a power of two"), extents, locked, - buffering: PhantomData, + mode: PhantomData, } } } -impl AsHandle for OpenedVolume { +impl AsHandle for OpenedVolume { fn as_handle(&self) -> BorrowedHandle<'_> { self.handle.as_handle() } } -impl AsRawHandle for OpenedVolume { +impl AsRawHandle for OpenedVolume { fn as_raw_handle(&self) -> RawHandle { self.handle.as_raw_handle() } diff --git a/crates/win-durable-ioring/src/windows_backend/opened_volume/tests.rs b/crates/win-durable-ioring/src/windows_backend/opened_volume/tests.rs index 66730c93f..a91e3a826 100644 --- a/crates/win-durable-ioring/src/windows_backend/opened_volume/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/opened_volume/tests.rs @@ -7,9 +7,42 @@ use win_volume_sys::DiskExtent; use super::{ OpenedVolume, VolumeOptions, VolumePath, VolumePathError, disk_flush_domain, domains_of, }; -use crate::dioring::tests::TempFile; -use crate::windows_backend::{Cached, FileOptions, OpenedFile, Unbuffered, Win32FileTarget}; -use durable_ioring::{Capabilities, FlushDomain, Geometry, IoTarget, WarrantyClass}; +use crate::dioring::tests::{TempFile, wtf}; +use crate::windows_backend::{ + Cached, FileOptions, OpenedFile, ReadOnly, ReadWrite, ShareMode, Unbuffered, Win32FileTarget, + WriteOnly, +}; +use durable_ioring::{Access, Capabilities, FlushDomain, Geometry, IoTarget, WarrantyClass}; + +#[test] +fn a_volume_is_opened_sharing_every_member_unless_told_otherwise() { + assert_eq!(VolumeOptions::new().share, ShareMode::ALL); + assert_eq!(VolumeOptions::default().share, ShareMode::ALL); + assert_eq!( + VolumeOptions::new().share(ShareMode::NONE).share, + ShareMode::NONE + ); + assert_eq!( + VolumeOptions::new() + .share(ShareMode::NONE) + .share(ShareMode::READ) + .share, + ShareMode::READ, + "the last call replaces the mode" + ); +} + +#[test] +fn the_share_mode_and_the_lock_are_independent_options() { + let locked_exclusively = VolumeOptions::new().lock(true).share(ShareMode::NONE); + assert!(locked_exclusively.lock); + assert_eq!(locked_exclusively.share, ShareMode::NONE); + let the_other_order = VolumeOptions::new().share(ShareMode::NONE).lock(true); + assert!(the_other_order.lock); + assert_eq!(the_other_order.share, ShareMode::NONE); + assert!(!VolumeOptions::new().share(ShareMode::NONE).lock); + assert_eq!(VolumeOptions::new().lock(true).share, ShareMode::ALL); +} fn extent(disk_number: u32) -> DiskExtent { DiskExtent { @@ -27,7 +60,7 @@ fn assembled( locked: bool, ) -> (OpenedVolume, TempFile) { let temp = TempFile::new("ov-assembled"); - let file = OpenedFile::::open(&temp.0, FileOptions::new().read(true).write(true)) + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()) .expect("open a file"); ( OpenedVolume::assembled(file.handle().clone(), extents, sector, locked), @@ -50,6 +83,40 @@ fn a_disk_is_named_as_it_answers_and_two_targets_share_a_domain_by_equal_bytes() assert_ne!(disk_flush_domain(1), disk_flush_domain(2)); } +#[test] +fn a_volumes_target_states_the_access_the_volume_was_opened_with() { + let temp = TempFile::new("ov-access"); + let file = OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()) + .expect("open a file"); + let handle = file.handle().clone(); + let volume = OpenedVolume::::assembled( + handle.clone(), + vec![extent(1)], + 512, + false, + ); + assert_eq!( + Win32FileTarget::from_volume(volume).access(), + Access::ReadOnly + ); + let volume = OpenedVolume::::assembled( + handle.clone(), + vec![extent(1)], + 512, + false, + ); + assert_eq!( + Win32FileTarget::from_volume(volume).access(), + Access::WriteOnly + ); + let volume = + OpenedVolume::::assembled(handle, vec![extent(1)], 512, false); + assert_eq!( + Win32FileTarget::from_volume(volume).access(), + Access::ReadWrite + ); +} + #[test] fn a_drive_letter_names_its_volume_in_either_case() { for (letter, path) in [ @@ -193,7 +260,45 @@ fn a_cached_volumes_target_claims_best_effort_and_no_write_through() { } #[test] -fn a_volumes_target_is_never_complete() { +fn an_attested_volumes_target_is_complete_and_nothing_else_changes() { + for locked in [false, true] { + let (volume, _temp) = assembled::(vec![extent(5), extent(2)], 512, locked); + let (sibling, _temp2) = assembled::(vec![extent(5), extent(2)], 512, locked); + let attested = Win32FileTarget::from_volume_attested_exclusive(volume); + let plain = Win32FileTarget::from_volume(sibling); + + assert_eq!(attested.warranty(), WarrantyClass::Complete); + assert_eq!(plain.warranty(), WarrantyClass::SharedDevice); + assert_eq!(attested.flush_domains(), plain.flush_domains()); + assert_eq!( + attested.flush_domains(), + [disk_flush_domain(2), disk_flush_domain(5)] + ); + assert_eq!(attested.geometry(), plain.geometry()); + assert_eq!(attested.capabilities(), plain.capabilities()); + assert_eq!(attested.access(), plain.access()); + } +} + +#[test] +fn an_attested_volume_still_adds_declared_domains_and_never_loses_a_found_one() { + let (volume, _temp) = assembled::(vec![extent(1), extent(2)], 512, false); + let other = FlushDomain::new(b"a share".to_vec()); + let target = + Win32FileTarget::from_volume_attested_exclusive(volume).with_domains([other.clone()]); + assert_eq!(target.warranty(), WarrantyClass::Complete); + assert_eq!( + target.flush_domains(), + [disk_flush_domain(1), disk_flush_domain(2), other] + ); + assert_eq!( + target.with_domains([]).flush_domains(), + [disk_flush_domain(1), disk_flush_domain(2)] + ); +} + +#[test] +fn a_volumes_target_is_not_complete_unless_attested() { // However it was opened and whether or not it is locked: a lock keeps other handles off the // volume, not other writers off its disks. for locked in [false, true] { @@ -237,8 +342,8 @@ fn declared_domains_are_added_to_a_volumes_and_never_take_one_away() { #[test] fn a_file_targets_domains_are_only_what_is_declared() { let temp = TempFile::new("ov-file-domains"); - let file = OpenedFile::::open(&temp.0, FileOptions::new().read(true).write(true)) - .expect("open"); + let file = + OpenedFile::::open(wtf(&temp.0), FileOptions::new().read().write()).expect("open"); let target = Win32FileTarget::new(file).with_domains([disk_flush_domain(9)]); assert_eq!(target.flush_domains(), [disk_flush_domain(9)]); } diff --git a/crates/win-durable-ioring/src/windows_backend/pool.rs b/crates/win-durable-ioring/src/windows_backend/pool.rs index cbfaf5f5b..c1c5eff1f 100644 --- a/crates/win-durable-ioring/src/windows_backend/pool.rs +++ b/crates/win-durable-ioring/src/windows_backend/pool.rs @@ -21,7 +21,9 @@ use windows_threadpool_sys::callback_env::CallbackEnviron; use super::WindowsBackend; use durable_ioring::EpochId; use durable_ioring::StampClock; -use durable_ioring::driver::{Engine, EngineGuard, Slot, lock, present}; +use durable_ioring::driver::{ + ENGINE_LOCK, Engine, EngineGuard, LockWatch, Slot, lock, present, present_watched, +}; #[cfg(test)] mod tests; @@ -49,6 +51,8 @@ where /// drain touches the engine once it goes. wake: CompletionWake, engine: Shared, + /// Times the waits for `engine`'s lock. + watch: LockWatch, } impl PoolDelivery @@ -71,11 +75,16 @@ where engine: PoolEngine, on_ready: impl Fn() + Send + Sync + 'static, env: Option<&mut CallbackEnviron<'_>>, + watch: LockWatch, ) -> PoolSetup { let engine = Arc::new(Mutex::new(Some(engine))); let drained = Arc::clone(&engine); + let drain_watch = watch.clone(); let on_wake = move || { - let deliver = lock(&drained).as_mut().is_some_and(Engine::drain); + let deliver = drain_watch + .lock(&drained, ENGINE_LOCK) + .as_mut() + .is_some_and(Engine::drain); if deliver { on_ready(); } @@ -84,7 +93,11 @@ where // signal starts waits for the lock until this returns. let woken = present(lock(&engine)).backend.wake(on_wake, env); match woken { - Ok(wake) => Ok(Self { wake, engine }), + Ok(wake) => Ok(Self { + wake, + engine, + watch, + }), Err(error) => { let engine = lock(&engine).take().expect("put there above"); Err((error, Box::new(engine))) @@ -102,7 +115,7 @@ where { /// The engine, locked: what a consumer call acts through. pub(crate) fn engine(&self) -> EngineGuard<'_, PoolEngine> { - present(lock(&self.engine)) + present_watched(self.watch.lock(&self.engine, ENGINE_LOCK)) } /// The engine's slot, for a path of the driver's own that reaches it on another thread. @@ -113,7 +126,7 @@ where /// Stop delivering, handing the engine back: the wake is stopped and any running drain waited /// for first, then the engine is taken out of its slot, so a shared path finds it empty. pub(crate) fn into_engine(self) -> PoolEngine { - let Self { wake, engine } = self; + let Self { wake, engine, .. } = self; drop(wake); lock(&engine) .take() diff --git a/crates/win-durable-ioring/src/windows_backend/pool/tests.rs b/crates/win-durable-ioring/src/windows_backend/pool/tests.rs index ab81c64a6..105a16a3d 100644 --- a/crates/win-durable-ioring/src/windows_backend/pool/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/pool/tests.rs @@ -3,8 +3,9 @@ //! entries become poppable, a backlog reached at setup, a callback environment's pool, and the //! engine handed back. -use std::sync::Arc; -use std::sync::mpsc::{self, Receiver}; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, ThreadId}; use std::time::{Duration, Instant}; use crate::InterruptStamps; @@ -19,8 +20,9 @@ use durable_ioring::DurableIoCore; use durable_ioring::IoTarget; use durable_ioring::NoDiagnostics; use durable_ioring::backend::SubmissionData; -use durable_ioring::driver::Engine; -use durable_ioring::oracle::ConformanceOracle; +use durable_ioring::driver::{ENGINE_LOCK, Engine, LockWatch, lock}; +use durable_ioring::oracle::{ConformanceOracle, PairingChecker}; +use durable_ioring::{Diagnostic, DiagnosticKind, DiagnosticSink}; use durable_ioring::{DurabilityRequest, Entry, Epoch, IoTargetKey, OpKind, WriteOptions}; const KEY: IoTargetKey = IoTargetKey(1); @@ -54,6 +56,15 @@ fn engine(file: &TempFile) -> Eng { /// A pool delivery of `engine` that sends on every readiness. fn delivered(engine: Eng, env: Option<&mut CallbackEnviron<'_>>) -> (Pool, Receiver<()>) { + delivered_watched(engine, env, LockWatch::new(0, Arc::new(NoDiagnostics))) +} + +/// As [`delivered`], with the waits for the engine's lock reported through `watch`. +fn delivered_watched( + engine: Eng, + env: Option<&mut CallbackEnviron<'_>>, + watch: LockWatch, +) -> (Pool, Receiver<()>) { let (tx, rx) = mpsc::channel(); let pool = PoolDelivery::new( engine, @@ -61,6 +72,7 @@ fn delivered(engine: Eng, env: Option<&mut CallbackEnviron<'_>>) -> (Pool, Recei let _ = tx.send(()); }, env, + watch, ) .map_err(|(error, _)| error) .expect("wire the delivery"); @@ -244,3 +256,99 @@ fn the_engine_comes_back_once_delivery_stops() { std::thread::sleep(Duration::from_millis(1)); } } + +/// Says when the test's own thread has found a lock held -- the sink is asked whether it wants the +/// wait as soon as there is one -- and keeps what is reported, with the thread that waited. A pool's +/// own thread drains once on the ring's setup signal and may wait on the same lock, which is why the +/// thread is part of the record. +struct Contended { + test_thread: ThreadId, + found_held: Mutex>, + waits: Mutex>, + /// The pairing rule over every event reported (DI-D-87). + checker: Mutex, +} + +impl DiagnosticSink for Contended { + fn wants(&self, kind: DiagnosticKind) -> bool { + if kind == DiagnosticKind::LockWait && thread::current().id() == self.test_thread { + let _ = self.found_held.lock().expect("the signal").send(()); + } + kind == DiagnosticKind::LockWait + } + + fn report(&self, event: &Diagnostic<'_>) { + self.checker + .lock() + .expect("the checker") + .observe(event) + .expect("the pairing rule"); + match event { + Diagnostic::LockWaitBegan { instance, lock, .. } => { + self.waits.lock().expect("the record").push(( + thread::current().id(), + *instance, + (*lock).to_owned(), + "began", + )); + } + Diagnostic::LockWaitEnded { instance, lock, .. } => { + self.waits.lock().expect("the record").push(( + thread::current().id(), + *instance, + (*lock).to_owned(), + "ended", + )); + } + other => panic!("reported {other}"), + } + } +} + +#[test] +fn a_consumer_call_that_waits_for_the_engine_reports_the_wait() { + let file = TempFile::new("pool-lock-wait"); + let (found_held, signal) = mpsc::channel(); + let sink = Arc::new(Contended { + test_thread: thread::current().id(), + found_held: Mutex::new(found_held), + waits: Mutex::default(), + checker: Mutex::default(), + }); + let watch = LockWatch::new(5, sink.clone()); + let (pool, _ready) = delivered_watched(engine(&file), None, watch); + + // Another thread holds the slot until this one's call has found it held. + let slot = pool.shared(); + let (holding, held) = mpsc::channel(); + thread::scope(|scope| { + let holder = scope.spawn(move || { + let guard = lock(&slot); + holding.send(()).expect("the test is listening"); + signal + .recv_timeout(BOUND) + .expect("the call found the lock held"); + drop(guard); + }); + held.recv_timeout(BOUND).expect("the slot was taken"); + drop(pool.engine()); + holder.join().expect("the holder finished"); + }); + let waits = sink.waits.lock().expect("the record"); + let ours: Vec<_> = waits + .iter() + .filter(|(waiter, ..)| *waiter == thread::current().id()) + .collect(); + // The pair's end is written on the thread that waited, and its identity is the checker's to hold. + let ours: Vec<_> = ours + .iter() + .map(|(_, instance, lock, edge)| (*instance, lock.as_str(), *edge)) + .collect(); + assert_eq!( + ours, + [(5, ENGINE_LOCK, "began"), (5, ENGINE_LOCK, "ended")], + "{waits:?}" + ); + drop(waits); + let _ = pool.into_engine(); +} diff --git a/crates/win-durable-ioring/src/windows_backend/raw_open.rs b/crates/win-durable-ioring/src/windows_backend/raw_open.rs new file mode 100644 index 000000000..3eeab06f3 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/raw_open.rs @@ -0,0 +1,62 @@ +// Copyright (c) 2026 Mike Grier +//! The open a raw volume and a raw disk share (DI-13.11, DI-D-61, DI-D-62). +//! +//! Both are opened by a path the system takes as a device, with the data access and buffering mode +//! the type names and a share mode the caller chooses, and nothing else. The call is made here, once, +//! so what a device open asks of the kernel is one decision and not two that can drift, and so that +//! it can be tried on a file, which needs no administrator: the open is the same call whatever the +//! path names, and only what the kernel makes of the device is the device's. + +use std::fs::{File, OpenOptions}; +use std::io; +use std::os::windows::fs::OpenOptionsExt; +use std::path::Path; + +use durable_ioring::DataAccess; +use wtf_string::Wtf16String; + +use super::file_rights::FileRights; +use super::opened_file::{Buffering, data_access, data_rights, refine_sharing}; +use super::share_mode::ShareMode; + +#[cfg(test)] +mod tests; + +/// What a device open asks of `CreateFileW`: the three parameters a raw volume or disk chooses. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) struct RawOpen { + /// `dwDesiredAccess`. + pub access: u32, + /// `dwFlagsAndAttributes`. + pub flags: u32, + /// `dwShareMode`. + pub share: u32, +} + +/// What a device opened with data access `A` in buffering mode `B` and sharing `share` asks for. +pub(super) fn raw_open_of(share: ShareMode) -> RawOpen { + RawOpen { + access: data_rights::(), + flags: B::FLAGS, + share: share.bits(), + } +} + +/// Open the device or file at `path` as `raw_open_of` says. A conflict with how the device is +/// already open is a [`SharingViolation`](super::SharingViolation), as it is for a file. +pub(super) fn open( + path: impl AsRef, + share: ShareMode, +) -> io::Result { + let path = path.as_ref(); + let asked = raw_open_of::(share); + OpenOptions::new() + .access_mode(asked.access) + .share_mode(asked.share) + .custom_flags(asked.flags) + .open(path) + .map_err(|error| { + let given = Wtf16String::from_os_str(path.as_os_str()); + refine_sharing(error, &given, data_access::(), FileRights::NONE, share) + }) +} diff --git a/crates/win-durable-ioring/src/windows_backend/raw_open/tests.rs b/crates/win-durable-ioring/src/windows_backend/raw_open/tests.rs new file mode 100644 index 000000000..a81a2d5fd --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/raw_open/tests.rs @@ -0,0 +1,181 @@ +// Copyright (c) 2026 Mike Grier + +use std::io; + +use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION; +use windows_sys::Win32::Storage::FileSystem::{FILE_READ_DATA, FILE_WRITE_DATA}; + +use super::{RawOpen, open, raw_open_of}; +use crate::dioring::tests::TempFile; +use crate::windows_backend::{ + Buffering, Cached, FileRights, NoData, OpenedBy, ReadOnly, ReadWrite, ShareMode, + SharingViolation, Unbuffered, WriteOnly, +}; +use durable_ioring::{Access, DataAccess}; + +fn every_share_mode() -> Vec { + (0..8) + .map(|mask| { + [ShareMode::READ, ShareMode::WRITE, ShareMode::DELETE] + .iter() + .enumerate() + .filter(|(index, _)| mask & (1 << index) != 0) + .fold(ShareMode::NONE, |set, (_, member)| set | *member) + }) + .collect() +} + +#[test] +fn a_device_open_asks_for_the_data_rights_of_its_marker_and_the_flags_of_its_mode() { + fn asked() -> RawOpen { + raw_open_of::(ShareMode::ALL) + } + let table = [ + (asked::().access, 0), + (asked::().access, FILE_READ_DATA), + (asked::().access, FILE_WRITE_DATA), + ( + asked::().access, + FILE_READ_DATA | FILE_WRITE_DATA, + ), + ( + asked::().access, + FILE_READ_DATA | FILE_WRITE_DATA, + ), + ]; + for (index, (actual, expected)) in table.into_iter().enumerate() { + assert_eq!(actual, expected, "row {index}"); + } + assert_eq!(asked::().flags, Unbuffered::FLAGS); + assert_eq!(asked::().flags, Cached::FLAGS); + assert_ne!(Unbuffered::FLAGS, Cached::FLAGS, "the two modes differ"); +} + +#[test] +fn the_share_mode_is_passed_to_the_open_as_it_is() { + for share in every_share_mode() { + assert_eq!( + raw_open_of::(share).share, + share.bits(), + "{share:?}" + ); + } +} + +/// What the `CreateFileW` documentation says of two opens, restricted to the accesses a device open +/// has (it offers no delete right): the later open fails when it asks for what the earlier does not +/// share, and when the earlier asked for what the later does not share. +fn documented_to_conflict(first: (bool, bool, ShareMode), second: (bool, bool, ShareMode)) -> bool { + let (first_read, first_write, first_share) = first; + let (second_read, second_write, second_share) = second; + (second_read && !first_share.contains(ShareMode::READ)) + || (second_write && !first_share.contains(ShareMode::WRITE)) + || (first_read && !second_share.contains(ShareMode::READ)) + || (first_write && !second_share.contains(ShareMode::WRITE)) +} + +/// Open `path` with the access of `kind` (read, write) and `share`, boxed so that two kinds can be +/// held at once. The open is the one a raw volume and a raw disk make, here on a file. +fn open_as( + path: &std::path::Path, + (read, write): (bool, bool), + share: ShareMode, +) -> io::Result> { + Ok(match (read, write) { + (true, false) => Box::new(open::(path, share)?), + (false, true) => Box::new(open::(path, share)?), + _ => Box::new(open::(path, share)?), + }) +} + +#[test] +fn every_pair_of_device_opens_is_accepted_or_refused_as_the_documentation_says() { + let temp = TempFile::new("raw-open-matrix"); + let kinds = [(true, false), (false, true), (true, true)]; + let modes = every_share_mode(); + let mut refused = 0; + for first_kind in kinds { + for &first_share in &modes { + let first = open_as(&temp.0, first_kind, first_share).expect("the first open"); + for second_kind in kinds { + for &second_share in &modes { + let outcome = open_as(&temp.0, second_kind, second_share); + let conflict = documented_to_conflict( + (first_kind.0, first_kind.1, first_share), + (second_kind.0, second_kind.1, second_share), + ); + assert_eq!( + outcome.is_err(), + conflict, + "{first_kind:?} sharing {first_share:?}, then {second_kind:?} sharing {second_share:?}" + ); + if let Err(error) = outcome { + refused += 1; + assert_eq!(error.kind(), io::ErrorKind::ResourceBusy); + } + } + } + drop(first); + } + } + assert!( + refused > 0, + "the matrix reached refusals as well as acceptances" + ); +} + +#[test] +fn a_conflict_is_the_same_sharing_violation_a_file_reports() { + let temp = TempFile::new("raw-open-error"); + let _exclusive = + open::(&temp.0, ShareMode::NONE).expect("an exclusive open"); + let error = open::(&temp.0, ShareMode::READ) + .expect_err("an exclusive open is open"); + let violation = error + .get_ref() + .and_then(|inner| inner.downcast_ref::()) + .expect("the cause is a SharingViolation"); + assert_eq!(violation.by, OpenedBy::Path(temp.0.clone())); + assert_eq!(violation.data, Some(Access::ReadOnly)); + assert_eq!( + violation.rights, + FileRights::NONE, + "a device open offers no other right" + ); + assert_eq!(violation.share, ShareMode::READ); + let kernel = std::error::Error::source(violation) + .and_then(|source| source.downcast_ref::()) + .and_then(io::Error::raw_os_error); + assert_eq!(kernel, Some(ERROR_SHARING_VIOLATION.cast_signed())); +} + +#[test] +fn an_open_that_asks_for_no_data_right_is_not_judged_by_the_share_mode() { + let temp = TempFile::new("raw-open-nodata"); + let _exclusive = + open::(&temp.0, ShareMode::NONE).expect("an exclusive open"); + open::(&temp.0, ShareMode::NONE) + .expect("an open that asks for nothing can address the device all the same"); +} + +#[test] +fn a_failure_that_is_not_a_sharing_violation_is_the_kernels_error_as_it_was() { + let temp = TempFile::new("raw-open-other"); + let missing = temp.0.with_extension("missing"); + let error = open::(&missing, ShareMode::NONE) + .expect_err("there is no such file"); + assert_eq!(error.kind(), io::ErrorKind::NotFound); + assert!( + error.get_ref().is_none(), + "no wrapper: the kernel's own error" + ); +} + +#[test] +fn the_default_sharing_lets_every_other_open_in() { + let temp = TempFile::new("raw-open-default"); + let _first = open::(&temp.0, ShareMode::ALL).expect("a default open"); + for kind in [(true, false), (false, true), (true, true)] { + assert!(open_as(&temp.0, kind, ShareMode::ALL).is_ok(), "{kind:?}"); + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/share_mode.rs b/crates/win-durable-ioring/src/windows_backend/share_mode.rs new file mode 100644 index 000000000..f913a0fc4 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/share_mode.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2026 Mike Grier +//! [`ShareMode`]: what an open lets other opens of the same file do, as a typed set (DI-13.4, +//! DI-D-64). +//! +//! `CreateFileW` takes a `dwShareMode` mask of three flags, and any subset of them, including none, is +//! meaningful. Each member names one thing a later open may ask for while this handle is open; a set +//! is built by naming them and joining them. There is no constructor from a raw number, so the only +//! masks that exist are the eight subsets of the three flags the documentation lists. + +use std::fmt; +use std::ops::{BitOr, BitOrAssign}; + +use windows_sys::Win32::Storage::FileSystem::{ + FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, +}; + +#[cfg(test)] +mod tests; + +/// What an open allows other opens of the same file to request while its handle is open: a set of +/// [`READ`](Self::READ), [`WRITE`](Self::WRITE) and [`DELETE`](Self::DELETE), named as `CreateFileW` +/// names `FILE_SHARE_READ`, `FILE_SHARE_WRITE` and `FILE_SHARE_DELETE` without their prefix. +/// +/// ``` +/// use win_durable_ioring::windows_backend::ShareMode; +/// +/// let share = ShareMode::READ | ShareMode::WRITE; +/// +/// assert!(share.contains(ShareMode::READ)); +/// assert!(!share.contains(ShareMode::DELETE)); +/// assert!(ShareMode::NONE.is_empty()); +/// assert!(ShareMode::ALL.contains(share)); +/// ``` +/// +/// The members are what a *later* open may ask for, and the table in the `CreateFileW` documentation +/// has two sides: a later open fails if it asks for an access this set does not share, and fails if +/// this open asked for an access that the later open's own set does not share. The empty set shares +/// nothing, so no other open may request read, write or delete access while the handle is open: an +/// exclusive open. [`FileOptions`](super::FileOptions) opens with [`ALL`](Self::ALL) unless told +/// otherwise, which is how `std::fs::OpenOptions` opens a file. +/// +/// There is deliberately no `Default`: the empty set is the exclusive one and the set a file is opened +/// with is every member, so either would be a surprising thing for `default()` to answer. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct ShareMode(u32); + +/// Names the flags once, so the constants, the names `Debug` prints and the tests that walk every +/// subset are one list and not three that can disagree. +macro_rules! shares { + ( $( $(#[$doc:meta])* $name:ident = $value:expr; )* ) => { + impl ShareMode { + $( $(#[$doc])* pub const $name: Self = Self($value); )* + } + + /// Every named member, in the order they are declared. + const NAMED: &[(&str, ShareMode)] = &[ $( (stringify!($name), ShareMode::$name), )* ]; + }; +} + +shares! { + /// `FILE_SHARE_READ`: later opens may request read access. + READ = FILE_SHARE_READ; + /// `FILE_SHARE_WRITE`: later opens may request write access. + WRITE = FILE_SHARE_WRITE; + /// `FILE_SHARE_DELETE`: later opens may request delete access, which allows both delete and + /// rename. + DELETE = FILE_SHARE_DELETE; +} + +impl ShareMode { + /// No sharing: no other open may request read, write or delete access while the handle is open. + pub const NONE: Self = Self(0); + + /// Every member: what a file is opened with unless it is told otherwise. + pub const ALL: Self = Self(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE); + + /// The union of two sets. + #[must_use] + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Whether every member of `other` is in this set. + #[must_use] + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + /// Whether the set shares nothing. + #[must_use] + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// The mask this set is in `dwShareMode`. + pub(super) const fn bits(self) -> u32 { + self.0 + } +} + +impl BitOr for ShareMode { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + self.union(other) + } +} + +impl BitOrAssign for ShareMode { + fn bitor_assign(&mut self, other: Self) { + *self = self.union(other); + } +} + +impl fmt::Debug for ShareMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ShareMode(")?; + if self.is_empty() { + f.write_str("none")?; + } else { + let mut first = true; + for (name, _) in NAMED.iter().filter(|(_, member)| self.contains(*member)) { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str(name)?; + } + } + f.write_str(")") + } +} diff --git a/crates/win-durable-ioring/src/windows_backend/share_mode/tests.rs b/crates/win-durable-ioring/src/windows_backend/share_mode/tests.rs new file mode 100644 index 000000000..872878252 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/share_mode/tests.rs @@ -0,0 +1,121 @@ +// Copyright (c) 2026 Mike Grier + +use std::collections::HashSet; + +use windows_sys::Win32::Storage::FileSystem::{ + FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, +}; + +use super::{NAMED, ShareMode}; + +/// Every member with the value `windows-sys` gives the flag it is named for. The test that walks +/// this and `NAMED` is what makes the two the same set: a member added to one and not the other fails +/// it. +const EXPECTED: [(&str, u32); 3] = [ + ("READ", FILE_SHARE_READ), + ("WRITE", FILE_SHARE_WRITE), + ("DELETE", FILE_SHARE_DELETE), +]; + +#[test] +fn each_member_is_the_flag_it_is_named_for() { + for (name, expected) in EXPECTED { + let (_, member) = NAMED + .iter() + .find(|(candidate, _)| *candidate == name) + .unwrap_or_else(|| panic!("{name} is not a named member")); + assert_eq!(member.bits(), expected, "{name}"); + } +} + +#[test] +fn the_named_members_are_exactly_the_expected_ones() { + let named: HashSet<&str> = NAMED.iter().map(|(name, _)| *name).collect(); + let expected: HashSet<&str> = EXPECTED.iter().map(|(name, _)| *name).collect(); + assert_eq!(named, expected); + assert_eq!(NAMED.len(), EXPECTED.len(), "no name is listed twice"); +} + +#[test] +fn no_two_members_are_the_same_bit_and_none_is_empty() { + let values: HashSet = NAMED.iter().map(|(_, member)| member.bits()).collect(); + assert_eq!(values.len(), NAMED.len()); + assert!(NAMED.iter().all(|(_, member)| !member.is_empty())); + assert!( + NAMED + .iter() + .all(|(_, member)| member.bits().count_ones() == 1), + "each member is one flag" + ); +} + +#[test] +fn none_shares_nothing_and_all_shares_every_member() { + assert!(ShareMode::NONE.is_empty()); + assert_eq!(ShareMode::NONE.bits(), 0); + assert!(!ShareMode::ALL.is_empty()); + for (name, member) in NAMED { + assert!(ShareMode::ALL.contains(*member), "{name}"); + assert!(!ShareMode::NONE.contains(*member), "{name}"); + } + assert_eq!( + ShareMode::ALL.bits(), + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + "what every file was opened with before the mode was an option" + ); +} + +#[test] +fn a_set_is_joined_with_or_and_asked_with_contains() { + let share = ShareMode::READ | ShareMode::DELETE; + assert!(share.contains(ShareMode::READ)); + assert!(share.contains(ShareMode::READ | ShareMode::DELETE)); + assert!(!share.contains(ShareMode::WRITE)); + assert!(!share.contains(ShareMode::WRITE | ShareMode::DELETE)); + assert!(share.contains(ShareMode::NONE), "every set holds nothing"); + assert!(!ShareMode::NONE.contains(ShareMode::READ)); + + let mut grown = ShareMode::NONE; + grown |= ShareMode::WRITE; + grown |= ShareMode::DELETE; + assert_eq!(grown, ShareMode::WRITE.union(ShareMode::DELETE)); + assert_eq!( + grown | grown, + grown, + "joining a set with itself changes nothing" + ); +} + +#[test] +fn every_subset_of_the_members_is_a_distinct_set_within_all() { + assert!(NAMED.len() < 16, "an exhaustive walk must stay small"); + let mut seen = HashSet::new(); + for mask in 0..(1u32 << NAMED.len()) { + let set = NAMED + .iter() + .enumerate() + .filter(|(index, _)| mask & (1 << index) != 0) + .fold(ShareMode::NONE, |set, (_, (_, member))| set | *member); + assert!(ShareMode::ALL.contains(set), "{set:?}"); + assert!( + seen.insert(set.bits()), + "{set:?} is a second way to make a set" + ); + } + assert_eq!(seen.len(), 8); +} + +#[test] +fn a_set_prints_the_names_of_what_it_holds() { + assert_eq!(format!("{:?}", ShareMode::NONE), "ShareMode(none)"); + assert_eq!(format!("{:?}", ShareMode::DELETE), "ShareMode(DELETE)"); + assert_eq!( + format!("{:?}", ShareMode::ALL), + "ShareMode(READ | WRITE | DELETE)" + ); + assert_eq!( + format!("{:?}", ShareMode::DELETE | ShareMode::READ), + "ShareMode(READ | DELETE)", + "in the order they are declared, not the order they were joined" + ); +} diff --git a/crates/win-durable-ioring/src/windows_backend/signalled.rs b/crates/win-durable-ioring/src/windows_backend/signalled.rs index 0e0493922..25b8d5027 100644 --- a/crates/win-durable-ioring/src/windows_backend/signalled.rs +++ b/crates/win-durable-ioring/src/windows_backend/signalled.rs @@ -14,7 +14,7 @@ //! blocked while completions sit in the ring. use std::io; -use std::os::windows::io::OwnedHandle; +use std::os::windows::io::AsHandle; use std::time::Duration; use win_sync_sys::{Event, ResetMode}; @@ -28,7 +28,7 @@ use durable_ioring::StampClock; use super::CompletionEvent; use durable_ioring::EpochId; use durable_ioring::driver::EngineGuard; -use durable_ioring::driver::{Pumped, PumpedDelivery}; +use durable_ioring::driver::{LockWatch, Pumped, PumpedDelivery}; /// A failed construction: why, and the engine it was given. pub(crate) type SignalledSetup = @@ -64,15 +64,19 @@ where /// /// # Errors /// - /// With the engine handed back: the event could not be made or duplicated, or the ring crate's + /// With the engine handed back: the event could not be made, or duplicated for the notification + /// that owns its handle, or the ring crate's /// wake could not be made or armed, as `CompletionWake::new`. pub(crate) fn new( mut engine: PoolEngine, env: Option<&mut CallbackEnviron<'_>>, + watch: LockWatch, ) -> SignalledSetup { let made = Event::new(ResetMode::Auto, false).and_then(|signal| { - let waker = signal.try_clone()?; - let notification = CompletionEvent::new(OwnedHandle::from(signal.try_clone()?)); + let waker = signal.clone(); + // A `CompletionEvent` owns its handle outright, so it is given a duplicate of its own; + // the waker and the signal handed out share the event's one handle. + let notification = CompletionEvent::new(signal.as_handle().try_clone_to_owned()?); Ok((signal, waker, notification)) }); let (signal, waker, notification) = match made { @@ -90,18 +94,18 @@ where }; Ok(Self { wake, - pumped: PumpedDelivery::with_notification(engine, notification), + pumped: PumpedDelivery::with_notification(engine, notification).with_lock_watch(watch), signal, }) } - /// A duplicate of the event the pool sets, for the consumer to wait on. + /// A clone of the event the pool sets, sharing its handle, for the consumer to wait on. /// /// # Errors /// - /// The duplication's: the process cannot get another handle. + /// None: a clone makes no handle. The `Result` is the shape of the call it answers. pub(crate) fn signal(&self) -> io::Result { - self.signal.try_clone() + Ok(self.signal.clone()) } /// As [`PumpedDelivery::pump`], waiting on the signal. diff --git a/crates/win-durable-ioring/src/windows_backend/tests.rs b/crates/win-durable-ioring/src/windows_backend/tests.rs index 6db8a6a44..0522ebbd0 100644 --- a/crates/win-durable-ioring/src/windows_backend/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/tests.rs @@ -11,7 +11,7 @@ use super::{ FileOptions, OpenedFile, Unbuffered, Win32FileTarget, WindowsBackend, WindowsBackendError, WindowsBackendRefusal, WindowsBackendSetup, refusal_cause, transfer, }; -use crate::dioring::tests::TempFile; +use crate::dioring::tests::{TempFile, wtf}; use durable_ioring::backend::{ Completion, IoBackend, RefusalCause, RegisteredRegion, Submission, SubmissionData, SubmissionOp, SubmissionToken, @@ -27,6 +27,7 @@ const BOUND: Duration = Duration::from_secs(5); type Backend = WindowsBackend>; mod construction; +mod directory; fn setup( targets: Vec<(IoTargetKey, Win32FileTarget)>, @@ -554,7 +555,7 @@ fn a_file_target_claims_what_its_open_mode_allows() { assert!(!cached.capabilities().write_through()); let unbuffered = Win32FileTarget::new( - OpenedFile::::open(&file.0, FileOptions::new().read(true).write(true)) + OpenedFile::::open(wtf(&file.0), FileOptions::new().read().write()) .expect("open unbuffered"), ) .with_domains([FlushDomain::new(b"disk".as_slice())]); @@ -571,6 +572,41 @@ fn a_file_target_claims_what_its_open_mode_allows() { assert!(description.capabilities.write_through()); } +#[test] +fn an_attested_file_targets_is_complete_and_nothing_else_changes() { + let file = TempFile::new("wb-attested"); + let open = || { + OpenedFile::::open(wtf(&file.0), FileOptions::new().read().write()) + .expect("open unbuffered") + }; + let domain = FlushDomain::new(b"disk".as_slice()); + let attested = Win32FileTarget::new_attested_exclusive(open()).with_domains([domain.clone()]); + let plain = Win32FileTarget::new(open()).with_domains([domain.clone()]); + + assert_eq!(attested.warranty(), WarrantyClass::Complete); + assert_eq!(plain.warranty(), WarrantyClass::SharedDevice); + assert_eq!(attested.flush_domains(), [domain]); + assert_eq!(attested.flush_domains(), plain.flush_domains()); + assert_eq!(attested.geometry(), plain.geometry()); + assert_eq!( + attested.geometry(), + Geometry::uniform(Unbuffered::DEFAULT_SECTOR_SIZE).expect("a power of two") + ); + assert_eq!(attested.capabilities(), plain.capabilities()); + assert!(attested.capabilities().write_through()); + assert_eq!(attested.access(), plain.access()); +} + +#[test] +fn an_attested_file_target_reports_complete_to_the_core() { + let file = TempFile::new("wb-attested-describe"); + let target = Win32FileTarget::new_attested_exclusive( + OpenedFile::::open(wtf(&file.0), FileOptions::new().read().write()) + .expect("open unbuffered"), + ); + assert_eq!(target.describe().warranty, WarrantyClass::Complete); +} + #[test] fn declaring_a_file_targets_domains_again_replaces_them() { // Carried over from `FileOptions`, which the target replaced: a declaration is the whole set. diff --git a/crates/win-durable-ioring/src/windows_backend/tests/directory.rs b/crates/win-durable-ioring/src/windows_backend/tests/directory.rs new file mode 100644 index 000000000..5af1b8744 --- /dev/null +++ b/crates/win-durable-ioring/src/windows_backend/tests/directory.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2026 Mike Grier +//! A directory as a target, through the ring (DI-15.5, DI-D-72): what the backend reports for each +//! operation on a directory reached by an open by identity, registered at construction and attached +//! later. An open by identity reaches a directory without `FILE_FLAG_BACKUP_SEMANTICS` (DI-D-70), and +//! the engineer ruled that it is allowed and its behaviour documented, not refused. What is asserted +//! is what was observed on the machine this was written on; the pages do not describe I/O on a +//! directory handle. + +use std::fs; +use std::os::windows::fs::OpenOptionsExt; + +use win_shared_os_owned_handle::SharedHandle; +use windows_sys::Win32::Foundation::ERROR_INVALID_FUNCTION; +use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; + +use super::{Backend, GIVEN, commit, read, run, setup, write}; +use crate::dioring::tests::TempFile; +use crate::error_code::error_code_from_win32; +use crate::windows_backend::{ByIdOptions, Cached, FileId, OpenedFile, Win32FileTarget}; +use durable_ioring::IoTargetKey; +use durable_ioring::backend::IoBackend; + +const ATTACHED: IoTargetKey = IoTargetKey(2); + +#[test] +fn a_directory_target_faults_every_read_and_write_and_completes_every_commit() { + let temp = TempFile::new("wb-directory"); + let directory = temp.0.with_extension("dir"); + fs::create_dir(&directory).expect("create the directory"); + fs::write(directory.join("child.txt"), b"x").expect("a child"); + let id = fs::OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(&directory) + .map(SharedHandle::from) + .and_then(|handle| FileId::of(&handle)) + .expect("the directory's identity"); + let open = || { + OpenedFile::::open_by_id( + temp.open().handle().clone(), + id, + ByIdOptions::new().read().write(), + ) + .expect("the directory opens by identity") + }; + + let mut backend = Backend::new(setup( + vec![(GIVEN, Win32FileTarget::new(open()))], + Vec::new(), + )) + .expect("a directory registers with the ring"); + backend + .attach(ATTACHED, Win32FileTarget::new(open())) + .expect("and attaches"); + + let invalid_function = Some(error_code_from_win32(ERROR_INVALID_FUNCTION)); + for key in [GIVEN, ATTACHED] { + let written = run(&mut backend, write(1, key, 0, b"abc")); + let fault = written.result.expect_err("a write to a directory faults"); + assert_eq!(fault.code, invalid_function, "{key:?} write: {fault:?}"); + + let back = run(&mut backend, read(2, key, 0, 16)); + let fault = back.result.expect_err("a read from a directory faults"); + assert_eq!(fault.code, invalid_function, "{key:?} read: {fault:?}"); + + let committed = run(&mut backend, commit(3, key)); + assert_eq!( + committed.result.map_err(|fault| fault.code), + Ok(0), + "{key:?}: a commit of a directory was observed to complete" + ); + } + + drop(backend); + let children: Vec<_> = fs::read_dir(&directory) + .expect("list") + .map(|entry| entry.expect("an entry").file_name()) + .collect(); + assert_eq!(children, ["child.txt"], "the writes changed nothing"); + fs::remove_dir_all(&directory).expect("remove the directory"); +} diff --git a/crates/win-durable-ioring/src/windows_backend/wait/tests.rs b/crates/win-durable-ioring/src/windows_backend/wait/tests.rs index fd198fc8f..0d90e771e 100644 --- a/crates/win-durable-ioring/src/windows_backend/wait/tests.rs +++ b/crates/win-durable-ioring/src/windows_backend/wait/tests.rs @@ -1,6 +1,7 @@ // Copyright (c) 2026 Mike Grier //! The Windows backend's notification, waited on (DI-7.3): the `Waitable` a `CompletionEvent` is. +use std::os::windows::io::AsHandle; use std::time::{Duration, Instant}; use win_sync_sys::{Access, Event, ResetMode, duplicate_handle_with_access}; @@ -13,7 +14,7 @@ const BOUND: Duration = Duration::from_secs(5); /// An event, and the `CompletionEvent` a backend would hand out for it. fn event(reset: ResetMode, signalled: bool) -> (Event, CompletionEvent) { let event = Event::new(reset, signalled).expect("an event"); - let handle = CompletionEvent::new(event.try_clone().expect("a duplicate").into()); + let handle = CompletionEvent::new(event.as_handle().try_clone_to_owned().expect("a duplicate")); (event, handle) } diff --git a/crates/win-durable-ioring/tests/debug_output.rs b/crates/win-durable-ioring/tests/debug_output.rs new file mode 100644 index 000000000..e5f7c3fe1 --- /dev/null +++ b/crates/win-durable-ioring/tests/debug_output.rs @@ -0,0 +1,148 @@ +// Copyright (c) 2026 Mike Grier +//! The debugger sink's emission (DI-12.1.3): this test binary starts itself as a process being debugged, runs +//! `a_process_that_reports_events` in it, and keeps each line the sink writes, through `win-debugging-sys`'s +//! `test-util` debugger, which uses only documented debugger functions (its WD-D-2 and WD-D-3). +//! +//! # Why this is an integration test +//! +//! It crosses a process boundary: a debugger is a second process, and what it receives is the operating system's. +//! +//! The expected lines are written out here and not built by the function under test, so the test says what a +//! reader of the debugger's output sees. + +#![cfg(windows)] + +use std::time::Duration; + +use win_debugging_sys::run_debugged; +use win_durable_ioring::{ + CorrelationId, DebugOutputDiagnostics, Diagnostic, DiagnosticKind, DiagnosticSink, FlushDomain, + FlushOutcome, FlushPart, IoTargetKey, SealOutcome, +}; + +/// The name of the test the process being debugged runs. +const REPORTER: &str = "a_process_that_reports_events"; + +const BOUND: Duration = Duration::from_secs(30); + +/// Run in the process being debugged: reports one event of each kind, and two whose text has a NUL. Run on its own, +/// with no debugger, the call does nothing, so this is harmless in an ordinary run of the tests. It is the only code +/// that mints pair ids in its process, so they are 1 to 5, in the order minted here, which the expected lines say. +#[test] +fn a_process_that_reports_events() { + let sink = DebugOutputDiagnostics; + let mint = || CorrelationId::for_pair(&sink, DiagnosticKind::Seal).expect("wanted"); + let domain = FlushDomain::new(b"ssd-4".to_vec()); + let nul_domain = FlushDomain::new(vec![b'a', 0, b'b']); + sink.report(&Diagnostic::Inconsistency { + instance: 7, + what: "a write completed twice", + detail: format_args!("lineage 3"), + }); + let lock = mint(); + sink.report(&Diagnostic::LockWaitBegan { + instance: 4, + lock: "engine", + id: lock, + }); + sink.report(&Diagnostic::LockWaitEnded { + instance: 4, + lock: "engine", + id: lock, + }); + let seal = mint(); + sink.report(&Diagnostic::SealBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + id: seal, + }); + let flush = mint(); + sink.report(&Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(7)), + id: flush, + }); + sink.report(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::File(IoTargetKey(7)), + id: flush, + outcome: FlushOutcome::Failed, + }); + let part = mint(); + sink.report(&Diagnostic::FlushBegan { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + }); + sink.report(&Diagnostic::FlushEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + part: FlushPart::Domain(&domain), + id: part, + outcome: FlushOutcome::Succeeded, + }); + sink.report(&Diagnostic::SealEnded { + instance: 2, + lineage: 1, + through: format_args!("9"), + id: seal, + outcome: SealOutcome::Failed, + }); + sink.report(&Diagnostic::Inconsistency { + instance: 1, + what: "x", + detail: format_args!("a\0b"), + }); + let odd = mint(); + sink.report(&Diagnostic::FlushBegan { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&nul_domain), + id: odd, + }); + sink.report(&Diagnostic::FlushEnded { + instance: 1, + lineage: 0, + through: format_args!("1"), + part: FlushPart::Domain(&nul_domain), + id: odd, + outcome: FlushOutcome::Abandoned, + }); +} + +#[test] +fn a_debugger_receives_each_event_as_a_dioring_line_in_order() { + let exe = std::env::current_exe().expect("the test binary's path"); + let observed = run_debugged(&exe, &["--exact", REPORTER, "--test-threads=1"], BOUND) + .expect("the process was debugged"); + assert_eq!(observed.exit_code, Some(0), "the reporter did not pass"); + + let received: Vec = observed.strings.iter().map(|line| line.text()).collect(); + assert_eq!( + received, + [ + "dioring: instance 7 found its own state inconsistent: a write completed twice (lineage 3)", + "dioring: instance 4 (pair 1): waits for its engine lock", + "dioring: instance 4 (pair 1): took its engine lock", + "dioring: instance 2 lineage 1 seal through 9 (pair 2): began", + "dioring: instance 2 lineage 1 seal through 9: flush of file 7 (pair 3) began", + "dioring: instance 2 lineage 1 seal through 9: flush of file 7 (pair 3) failed", + "dioring: instance 2 lineage 1 seal through 9: flush of domain \"ssd-4\" (pair 4) began", + "dioring: instance 2 lineage 1 seal through 9: flush of domain \"ssd-4\" (pair 4) succeeded", + "dioring: instance 2 lineage 1 seal through 9 (pair 2): failed", + "dioring: instance 1 found its own state inconsistent: x (a\\0b)", + "dioring: instance 1 lineage 0 seal through 1: flush of domain \"a\\0b\" (pair 5) began", + "dioring: instance 1 lineage 0 seal through 1: flush of domain \"a\\0b\" (pair 5) abandoned", + ] + ); + assert!(observed.strings.iter().all(|line| line.unicode)); +} diff --git a/crates/win-durable-ioring/tests/default_diagnostics.rs b/crates/win-durable-ioring/tests/default_diagnostics.rs new file mode 100644 index 000000000..1adf3dc25 --- /dev/null +++ b/crates/win-durable-ioring/tests/default_diagnostics.rs @@ -0,0 +1,74 @@ +// Copyright (c) 2026 Mike Grier +//! The driver's default diagnostic sink registers the ETW provider (DI-12.1.6): an instance built with no sink of its own does, +//! and one built with a sink of its own does not. What the default's events are, once a session listens, is +//! `src/dioring/tests/diagnostics.rs`. +//! +//! # Why this is an integration test +//! +//! It asks whether the provider is registered in the process, which any other instance alive in the same process would make +//! true, so it needs a process of its own, which an integration test file is. + +#![cfg(windows)] + +use std::sync::Arc; + +use win_durable_ioring::{ + DeliveryMode, Dioring, EtwDiagnostics, NoDiagnostics, Setup, StderrDiagnostics, +}; + +type Ring = Dioring, u64, (), Vec>; + +fn setup( + diagnostics: Option>, +) -> Setup<'static, 'static, u64, Vec> { + Setup { + submission_queue_size: 16, + completion_queue_size: 16, + files: Vec::new(), + buffers: Vec::new(), + provider: None, + diagnostics, + delivery: DeliveryMode::Pool(None), + } +} + +#[test] +fn the_default_registers_the_provider_and_a_chosen_sink_does_not() { + assert!( + !EtwDiagnostics::provider_is_registered_in_process(), + "before any instance" + ); + + // A sink of the consumer's own: nothing registers the provider. + for chosen in [ + Arc::new(NoDiagnostics) as Arc, + Arc::new(StderrDiagnostics), + ] { + let ring = Ring::new(setup(Some(chosen))).expect("an instance").0; + assert!( + !EtwDiagnostics::provider_is_registered_in_process(), + "registered by an instance with a sink of its own" + ); + drop(ring); + } + + // None given: the default, which is the ETW sink, registers it, and gives it back with the instance. + let ring = Ring::new(setup(None)).expect("an instance").0; + assert!( + EtwDiagnostics::provider_is_registered_in_process(), + "the default did not register the provider" + ); + drop(ring); + assert!( + !EtwDiagnostics::provider_is_registered_in_process(), + "the registration outlived the instance" + ); + + // The sink itself, chosen: the same provider, and the same registration. + let ring = Ring::new(setup(Some(Arc::new(EtwDiagnostics::new())))) + .expect("an instance") + .0; + assert!(EtwDiagnostics::provider_is_registered_in_process()); + drop(ring); + assert!(!EtwDiagnostics::provider_is_registered_in_process()); +} diff --git a/crates/win-durable-ioring/tests/overlapped_evidence.rs b/crates/win-durable-ioring/tests/overlapped_evidence.rs new file mode 100644 index 000000000..e0947fcd2 --- /dev/null +++ b/crates/win-durable-ioring/tests/overlapped_evidence.rs @@ -0,0 +1,433 @@ +// Copyright (c) 2026 Mike Grier +//! Evidence for DI-14.7: the ring on a handle opened with `FILE_FLAG_OVERLAPPED`, against the same +//! handle opened without it. The handles are opened through the request crate with the flag written +//! out, which is how it was measured, and then (DI-14.9) through `FileOptions::overlapped`, which +//! must give the ring the same. +//! +//! # Why this is an integration test +//! +//! It opens real files and drives a real `IoRing`, an operating-system boundary this repository's +//! quality rule places in `tests/`. +//! +//! # What it does +//! +//! Each of four handles (cached or unbuffered, with the flag or without) is opened through the +//! request crate's `OpenFile` with the flag in its raw flags, and the same sequence is driven through +//! the ring: a write, a flush, a read, a read at the end of the file, a read across it, a write that +//! fails, an unaligned read, four reads in one batch, and a read through a registered file. What is +//! compared is each completion's result and byte count, the order a batch completes in, whether the +//! ring accepted the handle, and whether each completion was already there when the submit returned. +//! +//! The measurements are in `DI-D-64`'s "What building DI-14.7 settled" in `durable-ioring/DESIGN-NOTES.md`. +//! This file asserts the parts that do not depend on the host: each step's result, that a plain handle +//! completes inline, and that a batch's completions are the four queued. Whether an overlapped handle +//! completes a read inline is host-dependent and is reported there, not asserted here. + +#![cfg(windows)] + +use std::os::windows::io::OwnedHandle; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use io_buffers::AlignedBuf; +use win_durable_ioring::windows_backend::{ + Buffering, Cached, Disposition, FileOptions, OpenedFile, ReadOnly, ReadWrite, Unbuffered, +}; +use windows_ioring_sys::{ + Batch, FlushCoverage, FlushMode, IoRing, IoRingErrorExt, PushOptions, SharedHandle, + WriteCaching, +}; +use windows_namespace_request_sys::OpenFile; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, FILE_READ_DATA, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS, OPEN_EXISTING, +}; +use wtf_string::Wtf16String; + +const BOUND: Duration = Duration::from_secs(10); +const SECTOR: usize = 4096; + +type Ring = IoRing; + +/// What one operation's completion said: a byte count, or the `HRESULT` of its failure. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Outcome { + Done(usize), + Failed(i32), +} + +/// One step of the sequence, and whether its completion was already poppable when the submit +/// returned, which is what a synchronous handle does. +#[derive(Clone, Copy, Debug)] +struct Step { + label: &'static str, + outcome: Outcome, + already_there: bool, +} + +struct Scratch(PathBuf); + +impl Drop for Scratch { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.0); + } +} + +fn open(path: &Path, access: u32, disposition: u32, flags: u32) -> SharedHandle { + let request = OpenFile::new(Wtf16String::from_os_str(path.as_os_str())) + .expect("a path") + .with_desired_access(access) + .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_creation_disposition(disposition) + .with_flags_and_attributes(flags); + let handle: OwnedHandle = request.perform().expect("the open"); + SharedHandle::from(handle) +} + +fn filled(byte: u8) -> AlignedBuf { + AlignedBuf::from_bytes(&vec![byte; SECTOR], SECTOR).expect("an aligned buffer") +} + +fn outcome(completion: &windows_ioring_sys::Completion) -> Outcome { + match completion.result() { + Ok(count) => Outcome::Done(count), + Err(error) => Outcome::Failed( + error + .as_ioring_error() + .map(|native| native.code()) + .or(error.raw_os_error()) + .expect("a code"), + ), + } +} + +/// Submits what `push` queues and pops its one completion, noting whether it was there at once. +fn drive(ring: &mut Ring, label: &'static str, push: F) -> (Step, Option) +where + F: FnOnce(&mut Batch<'_, AlignedBuf, usize>), +{ + { + let mut batch = Batch::new(ring); + push(&mut batch); + batch.submit().expect("submit"); + } + let (popped, already_there) = match ring.try_pop().expect("try_pop") { + Some(popped) => (popped, true), + None => ( + ring.pop_within(BOUND) + .expect("pop_within") + .expect("a completion within the bound"), + false, + ), + }; + let (completion, held) = popped; + let buffer = held.and_then(|(buffer, _extra)| buffer); + ( + Step { + label, + outcome: outcome(&completion), + already_there, + }, + buffer, + ) +} + +/// Where the handles of a sequence come from: the request crate's `OpenFile` with the flags written +/// out, which is how DI-14.7 measured, or `OpenedFile` with `FileOptions::overlapped`. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Opener { + Raw, + Typed, +} + +/// A read-write handle and a read-only one to the file at `path`, created if it is not there. +fn handles( + opener: Opener, + path: &Path, + unbuffered: bool, + overlapped: bool, +) -> (SharedHandle, SharedHandle) { + match opener { + Opener::Raw => { + let flags = (if unbuffered { + FILE_FLAG_NO_BUFFERING + } else { + 0 + }) | (if overlapped { FILE_FLAG_OVERLAPPED } else { 0 }); + ( + open(path, FILE_READ_DATA | FILE_WRITE_DATA, OPEN_ALWAYS, flags), + open(path, FILE_READ_DATA, OPEN_EXISTING, flags), + ) + } + Opener::Typed if unbuffered => typed::(path, overlapped), + Opener::Typed => typed::(path, overlapped), + } +} + +fn typed(path: &Path, overlapped: bool) -> (SharedHandle, SharedHandle) { + let name = || Wtf16String::from_os_str(path.as_os_str()); + let file = OpenedFile::::open( + name(), + FileOptions::new() + .read() + .write() + .disposition(Disposition::OpenAlways) + .overlapped(overlapped), + ) + .expect("the typed open"); + let reader = + OpenedFile::::open(name(), FileOptions::new().read().overlapped(overlapped)) + .expect("the typed read-only open"); + (file.handle().clone(), reader.handle().clone()) +} + +/// The sequence against one pair of handles, as `(steps, the order a batch of four completed in)`. +fn sequence( + opener: Opener, + unbuffered: bool, + overlapped: bool, + tag: &str, +) -> (Vec, Vec) { + let scratch = Scratch(std::env::temp_dir().join(format!( + "durable-ioring-overlapped-{}-{tag}.tmp", + std::process::id() + ))); + let (file, reader) = handles(opener, &scratch.0, unbuffered, overlapped); + + let mut ring: Ring = IoRing::with_inventory(16, 32).expect("a ring"); + let mut steps = Vec::new(); + let none = PushOptions::default(); + + let (step, _) = drive(&mut ring, "write A at 0", |batch| { + batch + .write_owned(&file, filled(0xA5), 0, 0, none, WriteCaching::Cached) + .expect("queue"); + }); + steps.push(step); + let (step, _) = drive(&mut ring, "write B at 4096", |batch| { + batch + .write_owned( + &file, + filled(0x5A), + 0, + SECTOR as u64, + none, + WriteCaching::Cached, + ) + .expect("queue"); + }); + steps.push(step); + let (step, _) = drive(&mut ring, "flush", |batch| { + batch + .flush_owned(&file, 0, FlushCoverage::Unordered, FlushMode::Default) + .expect("queue"); + }); + steps.push(step); + + let (step, buffer) = drive(&mut ring, "read at 0", |batch| { + batch + .read_owned(&file, filled(0), 0, 0, none) + .expect("queue"); + }); + assert_eq!(buffer.expect("a buffer").as_slice(), &[0xA5; SECTOR][..]); + steps.push(step); + let (step, buffer) = drive(&mut ring, "read at 4096", |batch| { + batch + .read_owned(&file, filled(0), 0, SECTOR as u64, none) + .expect("queue"); + }); + assert_eq!(buffer.expect("a buffer").as_slice(), &[0x5A; SECTOR][..]); + steps.push(step); + let (step, _) = drive(&mut ring, "read at the end of the file", |batch| { + batch + .read_owned(&file, filled(0), 0, 2 * SECTOR as u64, none) + .expect("queue"); + }); + steps.push(step); + let (step, _) = drive(&mut ring, "read across the end", |batch| { + let buffer = AlignedBuf::zeroed(2 * SECTOR, SECTOR).expect("a buffer"); + batch + .read_owned(&file, buffer, 0, SECTOR as u64, none) + .expect("queue"); + }); + steps.push(step); + let (step, _) = drive(&mut ring, "write through a read-only handle", |batch| { + batch + .write_owned(&reader, filled(0xFF), 0, 0, none, WriteCaching::Cached) + .expect("queue"); + }); + steps.push(step); + let (step, _) = drive(&mut ring, "read at an unaligned offset", |batch| { + batch + .read_owned(&file, filled(0), 0, 1, none) + .expect("queue"); + }); + steps.push(step); + + // Four reads in one batch, and the order they complete in. + { + let mut batch = Batch::new(&mut ring); + for index in 0..4_usize { + let offset = if index % 2 == 0 { 0 } else { SECTOR as u64 }; + batch + .read_owned(&file, filled(0), index, offset, none) + .expect("queue"); + } + batch.submit().expect("submit"); + } + let mut order = Vec::new(); + let mut sizes = Vec::new(); + for _ in 0..4 { + let (completion, held) = ring + .pop_within(BOUND) + .expect("pop_within") + .expect("a completion"); + sizes.push(outcome(&completion)); + let (_buffer, index) = held.expect("held"); + order.push(index); + } + assert!(sizes.iter().all(|size| *size == Outcome::Done(SECTOR))); + + // A read through a registered file. + let mut batch = Batch::new(&mut ring); + let pending = batch + .register_shared_files(vec![file.clone()]) + .expect("the registration builds"); + batch.submit().expect("submit"); + let (completion, _) = ring + .pop_within(BOUND) + .expect("pop_within") + .expect("a completion"); + let registered = pending + .claim_if(&completion) + .expect("its own completion") + .expect("the kernel registered the handle"); + let registered = registered.get(0).expect("one file"); + let (step, buffer) = drive(&mut ring, "read through a registered file", |batch| { + batch + .read_owned(®istered, filled(0), 0, 0, none) + .expect("queue"); + }); + assert_eq!(buffer.expect("a buffer").as_slice(), &[0xA5; SECTOR][..]); + steps.push(step); + + ring.run_down().expect("the ring runs down"); + drop((file, reader)); + (steps, order) +} + +const HANDLE_EOF: i32 = 0x8007_0026_u32 as i32; +const ACCESS_DENIED: i32 = 0x8007_0005_u32 as i32; +const INVALID_PARAMETER: i32 = 0x8007_0057_u32 as i32; + +fn outcomes(steps: &[Step]) -> Vec<(&'static str, Outcome)> { + steps + .iter() + .map(|step| (step.label, step.outcome)) + .collect() +} + +/// What every step of the sequence reports, for a handle that is `unbuffered` or not. Only the +/// unaligned read differs between the two modes. +fn expected(unbuffered: bool) -> Vec<(&'static str, Outcome)> { + let unaligned = if unbuffered { + Outcome::Failed(INVALID_PARAMETER) + } else { + Outcome::Done(SECTOR) + }; + vec![ + ("write A at 0", Outcome::Done(SECTOR)), + ("write B at 4096", Outcome::Done(SECTOR)), + ("flush", Outcome::Done(0)), + ("read at 0", Outcome::Done(SECTOR)), + ("read at 4096", Outcome::Done(SECTOR)), + ("read at the end of the file", Outcome::Failed(HANDLE_EOF)), + ("read across the end", Outcome::Done(SECTOR)), + ( + "write through a read-only handle", + Outcome::Failed(ACCESS_DENIED), + ), + ("read at an unaligned offset", unaligned), + ("read through a registered file", Outcome::Done(SECTOR)), + ] +} + +fn is_a_permutation(order: &[usize]) -> bool { + let mut sorted = order.to_vec(); + sorted.sort_unstable(); + sorted == [0, 1, 2, 3] +} + +/// The ring takes a handle opened with `FILE_FLAG_OVERLAPPED` and reports, for every operation in the +/// sequence, exactly what it reports for the same handle opened without it, in each buffering mode. +/// The plain handle is the control: its completions are inline, as `windows-ioring-sys`' +/// `bounded_pop.rs` records. +#[test] +fn the_ring_reports_the_same_results_on_an_overlapped_handle() { + for unbuffered in [false, true] { + let (plain, plain_order) = sequence( + Opener::Raw, + unbuffered, + false, + if unbuffered { "up" } else { "cp" }, + ); + let (overlapped, overlapped_order) = sequence( + Opener::Raw, + unbuffered, + true, + if unbuffered { "uo" } else { "co" }, + ); + + assert_eq!( + outcomes(&plain), + expected(unbuffered), + "plain, unbuffered={unbuffered}" + ); + assert_eq!( + outcomes(&overlapped), + expected(unbuffered), + "overlapped, unbuffered={unbuffered}" + ); + + // A synchronous handle completes during the submit, so its steps are all poppable at once + // and a batch completes in the order it was queued. + assert!( + plain.iter().all(|step| step.already_there), + "a plain handle completes inline, unbuffered={unbuffered}" + ); + assert_eq!(plain_order, [0, 1, 2, 3]); + + // An overlapped handle may complete asynchronously, so only the set of completions is fixed. + assert!(is_a_permutation(&overlapped_order), "{overlapped_order:?}"); + } +} + +/// `FileOptions::overlapped` (DI-14.9) gives the ring a handle that does what the raw one does: the +/// same result for every step, in each buffering mode, with the flag and without it. Without it the +/// completions are inline, as for the raw plain handle, so the option does not set the flag unasked. +#[test] +fn the_typed_open_offers_the_flag_and_the_ring_reports_the_same_results() { + for unbuffered in [false, true] { + let tag = if unbuffered { "t-u" } else { "t-c" }; + let (plain, plain_order) = sequence(Opener::Typed, unbuffered, false, &format!("{tag}p")); + let (overlapped, overlapped_order) = + sequence(Opener::Typed, unbuffered, true, &format!("{tag}o")); + + assert_eq!( + outcomes(&plain), + expected(unbuffered), + "typed, plain, unbuffered={unbuffered}" + ); + assert_eq!( + outcomes(&overlapped), + expected(unbuffered), + "typed, overlapped, unbuffered={unbuffered}" + ); + assert!( + plain.iter().all(|step| step.already_there), + "a typed open without the flag completes inline, unbuffered={unbuffered}" + ); + assert_eq!(plain_order, [0, 1, 2, 3]); + assert!(is_a_permutation(&overlapped_order), "{overlapped_order:?}"); + } +} diff --git a/crates/win-error-codes/CHECKLIST.md b/crates/win-error-codes/CHECKLIST.md index b9df5e4cf..a1b0dfeb2 100644 --- a/crates/win-error-codes/CHECKLIST.md +++ b/crates/win-error-codes/CHECKLIST.md @@ -10,4 +10,4 @@ further is planned in this crate. The work it leads to is in other crates: > **-> CROSS-COMPONENT HANDOFF:** next work is in components `windows-namespace-request-sys` -> > `NR-4` -> `NR-4.1`, `windows-file-enumeration-sys` -> `ERR` -> `ERR-1`, and `win-volume-sys` -> > `WV-M2` -> `WV-2.1`; and durable-ioring's `ErrorCode` helpers are -> [DI-9.7.11](../durable-ioring/CHECKLIST.md). See [the order across crates](../durable-ioring/CHECKLIST.md#where-the-open-surface-work-goes-next-read-this-first-when-picking-the-work-back-up). +> [DI-9.7.11](../durable-ioring/CHECKLIST.md). See [the order across crates](../durable-ioring/COMPLETED-CHECKLIST.md#open-surface-order). diff --git a/crates/win-etw-sys/Cargo.toml b/crates/win-etw-sys/Cargo.toml new file mode 100644 index 000000000..31ac98881 --- /dev/null +++ b/crates/win-etw-sys/Cargo.toml @@ -0,0 +1,52 @@ +# Copyright (c) 2026 Mike Grier + +[package] +name = "win-etw-sys" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +readme = "README.md" +description = "Memory-safe Rust over the Win32 Event Tracing provider functions: register a provider, ask whether a session is listening, write an event from typed fields, and unregister when dropped. Thin over Win32, adding no policy." +keywords = ["windows", "etw", "tracing", "events", "ffi"] +categories = ["api-bindings", "os::windows-apis", "development-tools::debugging"] + +# Named by the engineer, 2026-10-20 ("win-etw-sys is good"), under the workspace's `win-` prefix decision +# (DESIGN-NOTES.md at the repository root), and placed as a safe layer more or less directly over a Win32 API +# area, the one Microsoft Learn documents the functions in: Event Tracing, under Windows Events (WE-D-1). + +[dependencies] +# The failure a call reports, as the one type the workspace's crates give a Win32 code. +win-error-codes = { version = "0.0.2", path = "../win-error-codes" } + +# `default-features = false` matches every other crate in the workspace. +# +# `Win32_System_Diagnostics_Etw` is `EventRegister`, `EventUnregister`, `EventWrite`, `EventEnabled`, +# `EventProviderEnabled` and the two structures they take. +[dependencies.windows-sys] +version = "0.61.2" +default-features = false +features = ["Win32_System_Diagnostics_Etw"] + +[features] +# A trace session a test starts, enables a provider in, and reads back (WE-D-3), for a test to see what a provider's +# events are when they reach a session. Off by default: it is a test facility and not a wrapper of the session functions, +# and starting a session needs privilege. +# +# `Win32_Foundation` is `FILETIME`, which `ProcessTrace` takes, and the error codes. +# `Win32_System_Time` is `TIME_ZONE_INFORMATION`, which the log file's header structure holds. +# The session functions and structures are `Win32_System_Diagnostics_Etw`, which the crate has already. +test-util = ["windows-sys/Win32_Foundation", "windows-sys/Win32_System_Time"] + +[dev-dependencies] +# The crate's own tests use its `test-util` feature, as `win-durable-ioring` does for `fault-injection`. +win-etw-sys = { path = ".", features = ["test-util"] } + +[package.metadata.docs.rs] +# The crate is Windows-only, so docs.rs must build on a Windows target or it +# would render an almost-empty crate. +default-target = "x86_64-pc-windows-msvc" +targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"] diff --git a/crates/win-etw-sys/DESIGN-NOTES.md b/crates/win-etw-sys/DESIGN-NOTES.md new file mode 100644 index 000000000..dc256ec33 --- /dev/null +++ b/crates/win-etw-sys/DESIGN-NOTES.md @@ -0,0 +1,205 @@ +# Design notes: win-etw-sys + +## WE-D-1: the crate is named for Event Tracing, and the provider functions are filed in it although the snapshot does not list them + +**The name is the engineer's, 2026-10-20.** The assistant proposed `win-etw-sys` for the wrapper of the event-tracing +provider functions that durable-ioring's ETW sink needs, and the engineer answered: "win-etw-sys is good". The workspace's +`win-` prefix rule applies, as the crate is a safe layer more or less directly over Win32 +([DESIGN-NOTES.md](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix)). The decision that led to it is +[DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76). + +**Where the functions are documented, which [DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76) required this step to +find before naming a section.** Read 2026-10-20. The page of each function the crate wraps (`EventRegister`, +`EventUnregister`, `EventEnabled`, `EventProviderEnabled`, `EventWrite`) and of each structure (`EVENT_DESCRIPTOR`, +`EVENT_DATA_DESCRIPTOR`) is in the `evntprov` header's reference, with `tech.root: ETW` and `toc_rel: ../_etw/toc.json`, +and the overview page for the technology, *Event Tracing* (`api/_etw`), lists them among its functions. So the section is +*Windows Events > Event Tracing*. + +**The snapshot does not list them.** [WIN32-LEARN-MAP-DATA.txt](../../WIN32-LEARN-MAP-DATA.txt) (2026-10-17) files under +*Desktop App Technologies > Diagnostics > Windows Events > Event Tracing > Event Tracing Reference* only the macros of +`evntprov.h` (`EventDataDescCreate`, `EventDescCreate` and the `EventDesc*` family, under *Event Tracing Macros*) and, under +*Event Tracing Functions*, one function, `CveEventWrite`. The provider functions themselves, and `EVENT_DESCRIPTOR` and +`EVENT_DATA_DESCRIPTOR`, appear nowhere in it, though the overview page lists the functions and the pages exist. By the +[placement rule](../../DESIGN-NOTES.md#safe-wrappers-are-placed-by-the-learn-section-they-are-documented-under) the section +is read from the snapshot, which here is silent, so this note records the section from the pages and the overview, and +leaves the snapshot as it is: a regeneration is `tools/snapshot-learn-toc.ps1`'s, and what the snapshot leaves out of the +functions listing is the snapshot's to explain. + +**What a `-sys` crate here is.** It is memory-safe over the call and adds no policy: it passes the arguments as given, +reports what the call reports, and refuses ahead of the call only what the call's page makes impossible to pass +([WE-D-2](#we-d-2)). + +**Scheduled work.** None for this crate beyond what [DI-12.1.5](../durable-ioring/CHECKLIST.md) and +[DI-12.1.6](../durable-ioring/CHECKLIST.md) ask of it as their caller. + +## WE-D-2: a registration owns the handle, an event's data is typed fields, and the call's failures and the crate's refusals are kept apart + +**Amended by [WE-D-4](#we-d-4): `EventWriteTransfer` is wrapped, as `Registration::write_transfer`, and the call's failure no longer names `EventWrite` in its text.** **Amended by [WE-D-5](#we-d-5): `EventActivityIdControl` is wrapped for creating an id, as `create_activity_id`.** + +Read 2026-10-20 from the pages for `EventRegister`, `EventUnregister`, `EventEnabled`, `EventProviderEnabled` and +`EventWrite` (each `ms.date` 2018-12-05, `updated_at` 2024-02-22), and for `EVENT_DESCRIPTOR` and `EVENT_DATA_DESCRIPTOR`. + +**What is wrapped, and what is not.** The step's four: register, unregister, whether a session is listening +(`EventEnabled` for a whole descriptor and `EventProviderEnabled` for a level and a keyword, which the page pairs as +"when a provider has access to an event's complete EVENT_DESCRIPTOR, the provider should use EventEnabled. When a provider +has access only to the event's Level and Keyword, the provider should use EventProviderEnabled"), and write an event. Not +wrapped: the enable callback `EventRegister` takes, `EventWriteTransfer` and `EventWriteEx` (activity ids and filters), +`EventSetInformation`, `EventActivityIdControl` and `EventDataDescCreate`, which the crate's own field type replaces. The +step does not call for them and no design here does; no work is scheduled for them. + +**A registration owns the handle.** `EventRegister`'s page says the handle is "a process-scope operation (the handle is +valid only within the process)" and must be freed "before your provider unloads or exits". `Registration` is the handle, +unregistered when it is dropped, or by `unregister`, which reports what the call returned. A registration is made only +from a successful call, so the zero handle the page says a failed one sets is never a value of the type, and nothing here +relies on the page's remark that the calls "safely accept a zero-valued RegHandle". It is `Send` and `Sync`: the handle is +an integer and the calls take it by value. + +**Registering is not installing a manifest.** The page: "It is not necessary to install a manifest to write events or +capture traces (though installing the manifest may be necessary for decoding the provider's events or for the provider to +work with Event Log)." So the crate has no manifest and no install; this is the same fact the engineer ruled on +([DI-D-76](../durable-ioring/DESIGN-NOTES.md#di-d-76)) and the page confirms. + +**An event's data is typed fields, and the layout is theirs.** `EventWrite` takes descriptors that each "describe one block +of memory", "concatenated in order with no padding or alignment to form the event content". So `Field` is the bytes of a +value (every integer width, signed or not, the two floats, a pointer-width integer, a GUID laid out as the structure, raw +bytes, raw UTF-16 units) and two terminated strings, which are their units and then a zero as two descriptors, the second +pointing at a static. A four-byte `boolean` is a constructor and not a type, because a template's `Boolean` is a four-byte +integer. The crate does not know a template: what a decoder reads the blocks back as is the manifest's, and the crate +neither adds a length nor a type tag. A zero already inside a terminated string ends it for a decoder, which is the +decoder's rule and not the call's, so the units are written as given. + +**The descriptors are built without `unsafe` for the caller, and the call is made under one.** The descriptors hold +addresses, so a descriptor list borrows the fields it points into and the call is made while it does; the `unsafe` is +the one call, with its argument each valid for as long as the borrow. + +**The crate's two refusals, and what they rest on.** A list of fields of more blocks than the call's count, a `ULONG`, can +hold is `WriteError::TooManyBlocks`, and a block of more bytes than a descriptor's `Size`, a `ULONG`, can hold is +`WriteError::BlockTooLong`; in each the call is not made. The citation is the structures' types: `Size` is a `ULONG` and +`UserDataCount` is a `ULONG`. They are limits of the call's signature and not conditions the pages call invalid, so they are +the crate's own refusals, kept apart from `WriteError::Call`, the call's failure with its code. No number a page gives is +checked ahead of the call: "The maximum number is 128" for `UserDataCount`, and "up to 1,024 providers" for a process, are +passed to the call to judge. + +**What the platform did with those two numbers, which is evidence and not contract.** On the machine these were written +on, a process was allowed 2,022 registrations and then refused with 14, `ERROR_OUTOFMEMORY`, where the page says 1,024; +`tests/limit.rs` asserts no number, only that the refusal is reached as the call's code and that the process recovers when +its registrations are dropped. This is a discrepancy beside the page's sentence, and the crate's behavior does not depend +on either figure. A write of 129 blocks is the call's to judge and is in the session tests, which recorded that the call refused it with 87. + +**Failures are the call's code, and most callers ignore them.** The pages say the codes are "primarily intended for use in +debugging and diagnostic scenarios" and that production code "should usually ignore" them. The crate returns them and +decides nothing about what a caller does with one. + +**The tests.** The pure parts are in memory: a GUID's layout and text, each field's bytes as read through the very +descriptors that would be passed to the call, the order and count of blocks, and the two refusals. What the pages say of a +registration no session listens to is observed: nothing is enabled and a write succeeds, as `EventWrite`'s page says ("If +no trace sessions are recording this event, this function will do nothing and return **ERROR_SUCCESS**"). What reaches a +session is [WE-D-3](#we-d-3). + +**Scheduled work.** None. + +## WE-D-3: a `test-util` feature starts a trace session, reads it back, and the tests that use it need privilege + +**Why a session, and why here.** The way to see that an event reached ETW is to start a session, enable the provider in it, +write, stop the session and read what it recorded, all by documented functions. The session is a test facility and not a +wrapper of the controller's functions, so it is behind the off-by-default `test-util` feature, as `win-debugging-sys`' +debugger is ([WD-D-3](../win-debugging-sys/DESIGN-NOTES.md#wd-d-3)), and it is one copy here that the crate that writes the +provider's events can use too ([DI-12.1.5](../durable-ioring/CHECKLIST.md)): the crate that wraps the calls owns the unsafe +code around them. + +**What it is.** `Session::start` starts a session that logs to a file in the temporary directory, under a name no other +session of the process has (`StartTraceW`); `enable` enables a provider in it for a level and a keyword +(`EnableTraceEx2`); `stop` stops it (`ControlTraceW`), reads the file back with the event-record callback (`OpenTraceW`, +`ProcessTrace`, `CloseTrace`) and returns the events of the providers enabled, with each one's provider, descriptor, data, +process id, activity id and related activity id ([WE-D-4](#we-d-4)). A session that is dropped without being stopped is stopped, and its file removed, because a session is +system-wide and outlives the process that started it. + +**It needs privilege, so its tests are ignored by default.** Starting a session needs an elevated process or a user in the +Performance Log Users group. The development machine this was written on is neither, and `StartTraceW` was refused there +with `ERROR_ACCESS_DENIED`, so `tests/session.rs` is `#[ignore]` as this workspace's other environment-dependent tests are, +with a step in CI that runs it with `--include-ignored` on the runner, which is an administrator; a failure there is a +result. The error `Session::start` returns says what was missing. + +**What it observed, run elevated on 2026-10-20, which is evidence and not contract.** All thirteen tests pass, twelve runs +in a row. A session recorded each event written to a provider enabled in it with the descriptor as given, the data as the +concatenated blocks, the writing process's id, and in the order written, from four threads sharing one registration as from +one; and it recorded nothing for a provider it had not enabled. A session enabled at a level did not enable events above it, +and one with a keyword enabled only events that share a bit with it; both are as the page for the structure says. Two +sessions each recorded the same event. A write of 128 blocks was recorded as one event of those bytes, and 129 was refused +by the call with 87, `ERROR_INVALID_PARAMETER`, which is the page's maximum of 128 for `UserDataCount`. An event of 70,000 +bytes was refused with 234, `ERROR_MORE_DATA`, one of the two codes the page lists for an event too large, in a session of +64 KB buffers, and nothing was recorded for it. + +**Two things the first run corrected.** An event with no fields, passed as a count of zero and the pointer an empty vector +holds, was refused with 87: the page says "UserData may be NULL if UserDataCount is zero", and the crate now passes null +for no fields, with a test of the array it builds. And a provider that was registered before a session enabled it was not +enabled at the moment `enable` returned: it followed within the bound a test waits for it, and followed a stopped session +the same way, so the tests wait for the state and do not read it at once. A provider registered after the session enabled it +was enabled from the first call. + +**Privilege.** The development machine is not elevated, so a session cannot be started there; the tests were run from a +process elevated by a prompt the engineer approved, and CI runs them on the runner, which is an administrator. + +**Scheduled work.** None. + +## WE-D-4: `write_transfer` writes an event with the activity ids its caller gives, and a session reads them back + +Made by the assistant for [DI-19.3](../durable-ioring/CHECKLIST.md), and open to a ruling. + +**Read 2026-10-21** from the page for `EventWriteTransfer` (`ms.date` 2018-12-05, `updated_at` 2024-02-22), and for +`EVENT_HEADER_EXTENDED_DATA_ITEM`. `EventWrite`'s page says it "is equivalent to EventWriteEx with 0 for Filter, 0 for Flags, +NULL for ActivityId, and NULL for RelatedActivityId", and `EventWriteTransfer`'s that it "is equivalent to EventWriteEx with 0 +for Filter and 0 for Flags". + +**What it is.** `Registration::write_transfer(descriptor, activity, related, fields)` is the call, with each id an +`Option<&Guid>` as the page makes each optional: an activity given is the event's whatever thread writes it, and `None` +is "the current thread's activity ID"; a related id given is recorded, and `None` records none. So a begin and its end written +on two threads carry one activity, which is what [DI-19.4](../durable-ioring/CHECKLIST.md) needs, and the call's every +combination stays open to a caller. The fields, and the crate's two refusals of them, are `write`'s, by the one builder. +`write` keeps its own call; the pages make the two equivalent with no ids, and nothing was gained by moving it. + +**Nothing about the ids is refused.** The page calls no value of either invalid. That "The related activity ID is usually set +on the activity's START event" is advice, and is documented and not enforced. + +**The call's failure says "the write call failed"**, and the refusal of too many blocks says "the write call's count", since +either call can be the one. This changes `Display` text a caller may have matched; the variants are unchanged. + +**A session reads both ids.** `Recorded` gains `activity`, the record header's `ActivityId`, and `related_activity`, from the +extended data item of type `EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID`, whose `DataPtr` the page says "points to an +EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID structure that contains the related activity identifier if you called +EventWriteTransfer". The item is read only if its `DataSize` covers the structure, and unaligned, since the page promises no +alignment. `Recorded` is now `#[non_exhaustive]`, so a later field does not break a caller again; adding fields to a struct +with public fields was breaking already, so the two go in one change. + +**The tests.** Unelevated, a transfer with each combination of ids, every kind of field, no fields, and one activity from +several threads succeed with no session. The session tests, ignored without privilege as [WE-D-3](#we-d-3)'s are, hold that a +begin written on one thread and its end on another carry the activity given and only the begin the related id; that an +activity given leaves the thread's own as it was for the next write; that no activity is the thread's, as `write` takes it; +that a related id is recorded with no activity given; and that four threads with activities of their own each record theirs. +What only a session can catch is swept by `sabotage-session.json`, whose `testArgs` run those tests with +`--include-ignored`, from an elevated process or CI's runner. Run elevated on 2026-10-21, the session tests all pass, and the sweep's +baseline ran them all green: each of its five sabotages was caught by the session tests that name an id, and its control +survived. That is evidence and not contract. + +**Scheduled work.** None beyond [DI-19.4](../durable-ioring/CHECKLIST.md), its first caller. + +## WE-D-5: `create_activity_id` creates an activity id with `EventActivityIdControl`, and wraps none of its other codes + +Made for [DI-19.4](../durable-ioring/COMPLETED-CHECKLIST.md#di-194), on the engineer's choice of the platform's ids for the +provider's pairs ([DI-D-93](../durable-ioring/DESIGN-NOTES.md#di-d-93)). + +**Read 2026-10-21** from the call's page (`ms.date` 2018-12-05). `EVENT_ACTIVITY_CTRL_CREATE_ID` "Sets the ActivityId parameter +to the value of a newly-generated locally-unique activity ID", and the remarks say the call "generates locally-unique IDs that +are guaranteed to be unique across all processes on the local system until the system reboots", and that "All events with an +activity ID of zero (i.e. GUID_NULL) are assumed to not be a part of any activity". The page names only success as a return. + +**What it is.** `create_activity_id()` makes the call with that code and returns the id, or the call's failure with its code. +It takes no argument, since the code only writes the parameter, and it leaves the calling thread's activity id as it was. The +codes that get, set or swap the thread's id are not wrapped: a writer that names each event's activity on the write, as +`write_transfer` does, has no use for them, and no design here asks for them. + +**The tests.** Unelevated: an id is created, is not zero, and no id is created twice in a row, among a thousand, or across +eight threads. A session test shows that creating an id leaves the thread's id as a plain write records it. Uniqueness across +processes and until a restart is the page's guarantee and is not something a test here can show. + +**Scheduled work.** None. diff --git a/crates/win-etw-sys/PLANS.md b/crates/win-etw-sys/PLANS.md new file mode 100644 index 000000000..baca46cbf --- /dev/null +++ b/crates/win-etw-sys/PLANS.md @@ -0,0 +1,8 @@ +# Plans: win-etw-sys + +The crate has no checklist of its own. Its functions are `DI-12.1.4` in the durable-ioring +[CHECKLIST.md](../durable-ioring/CHECKLIST.md), and its first caller is `DI-12.1.5` in that component. Add a row here when +new work is planned against a `CHECKLIST.md` in this directory. + +| Path to CHECKLIST.md | Status | Brief description | Design Notes | +|---|---|---|---| diff --git a/crates/win-etw-sys/README.md b/crates/win-etw-sys/README.md new file mode 100644 index 000000000..a26fb3e1d --- /dev/null +++ b/crates/win-etw-sys/README.md @@ -0,0 +1,40 @@ +# win-etw-sys + +Memory-safe Rust over the Win32 Event Tracing provider functions, the ones Microsoft Learn documents under *Event +Tracing*, in *Windows Events*. Thin over Win32, adding no policy. + +- `Registration::register` is `EventRegister`: it registers a provider by its GUID and owns the handle, which is + unregistered when the value is dropped, or by `Registration::unregister`, which says what the call returned. The handle + is valid in this process only. No enable callback is made. +- `Registration::is_enabled` and `is_level_enabled` are `EventEnabled` and `EventProviderEnabled`: whether ETW can + quickly tell that no session is listening. `write` makes the same test itself, so they are for a caller that wants to + skip the work of preparing an event's data. +- `Registration::write` is `EventWrite`: an `EventDescriptor` and a list of typed `Field`s, written in order with no + padding, as the page says the blocks are. A field is the bytes of a value, and a terminated string is its units and + then a zero. What a decoder reads them back as is the template's, and the crate has none. +- `Registration::write_transfer` is `EventWriteTransfer`: the same, with an activity id and a related activity id the + caller gives, each optional, so events written on different threads can be one activity. An activity of `None` is the + writing thread's, as `write` takes it. See [WE-D-4](DESIGN-NOTES.md#we-d-4). +- `create_activity_id` is `EventActivityIdControl` creating an id, which the page guarantees no process of the system is + given again until it restarts, for a writer that names its own activities. See [WE-D-5](DESIGN-NOTES.md#we-d-5). +- The call's failure is a `WriteError::Call` with the code. A list of fields that cannot be given to the call, because + a count or a size does not fit a `ULONG`, is refused as `WriteError::TooManyBlocks` or `BlockTooLong` and the call is not + made: those are this crate's refusals and not the call's. See [WE-D-2](DESIGN-NOTES.md#we-d-2). +- With no session listening the write "will do nothing and return **ERROR_SUCCESS**". A manifest is not needed to record + events: it decodes them for a reader. + +```rust +use win_etw_sys::{EventDescriptor, Field, Guid, Registration, level}; + +let provider = Guid::from_u128(0x6e2f4b1a_0c3d_4a55_9d4e_1f2a3b4c5d6e); +let registration = Registration::register(provider)?; + +let event = EventDescriptor { id: 1, level: level::INFO, keyword: 1, ..EventDescriptor::default() }; +if registration.is_enabled(&event) { + registration.write(&event, &[Field::U64(7), Field::Utf16Terminated(&[0x68, 0x69])])?; +} +# Ok::<(), Box>(()) +``` + +The crate was made so that the ETW sink of durable-ioring, in its own crate, has a safe wrapper to write through. See +[WE-D-1](DESIGN-NOTES.md#we-d-1) for the name and where the functions are filed. diff --git a/crates/win-etw-sys/sabotage-session.json b/crates/win-etw-sys/sabotage-session.json new file mode 100644 index 000000000..a0596128e --- /dev/null +++ b/crates/win-etw-sys/sabotage-session.json @@ -0,0 +1,90 @@ +{ + "package": "win-etw-sys", + "description": "Sabotages of win-etw-sys that only a trace session can catch: tests/session.rs, run with --include-ignored. Run with tools/run-sabotage.ps1 from an elevated process or one in the Performance Log Users group, as CI's runners are (WE-D-3); unelevated, every session test fails to start and the baseline is red, so the sweep stops before any entry.", + "testArgs": [ + "-p", + "win-etw-sys", + "--test", + "session", + "--", + "--include-ignored" + ], + "notCoveredHere": "What a unit test can see is sabotage.json.", + "sabotages": [ + { + "name": "WE-D-4: the activity given is not passed to the call", + "file": "src/registration.rs", + "expect": "caught", + "why": "The activity id the caller gives is what lets a begin and its end written on two threads be one activity; passed as null, the call takes each thread's own.", + "find": [ + " let activity: *const GUID = activity.map_or(null(), |id| id.raw());" + ], + "replace": [ + " let activity: *const GUID = null();" + ] + }, + { + "name": "WE-D-4: the activity and the related activity are passed in each other's places", + "file": "src/registration.rs", + "expect": "caught", + "why": "The call takes the activity before the related one; swapped, a start event is recorded as the activity it was related to.", + "find": [ + " unsafe { EventWriteTransfer(self.handle, &raw, activity, related, count, blocks) };" + ], + "replace": [ + " unsafe { EventWriteTransfer(self.handle, &raw, related, activity, count, blocks) };" + ] + }, + { + "name": "WE-D-4: the related activity given is not passed to the call", + "file": "src/registration.rs", + "expect": "caught", + "why": "A related id the caller gave and the call never saw is a parent the trace does not record.", + "find": [ + " let related: *const GUID = related.map_or(null(), |id| id.raw());" + ], + "replace": [ + " let related: *const GUID = null();" + ] + }, + { + "name": "WE-D-4: a session reads no related activity", + "file": "src/session.rs", + "expect": "caught", + "why": "The reader is how the tests see what the call wrote; one that drops the related id would let the call drop it unseen.", + "find": [ + " related_activity: unsafe { related_activity(extended) }," + ], + "replace": [ + " related_activity: {", + " let _ = extended;", + " None", + " }," + ] + }, + { + "name": "WE-D-4: a session takes another extended item for the related activity", + "file": "src/session.rs", + "expect": "caught", + "why": "The related id is the item of its own type, and no other item is read as one.", + "find": [ + " u32::from(item.ExtType) == EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID" + ], + "replace": [ + " u32::from(item.ExtType) != EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID" + ] + }, + { + "name": "WE-D-4 control: no related activity is passed by another spelling of null", + "file": "src/registration.rs", + "expect": "survives", + "why": "A control: the same null by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " let related: *const GUID = related.map_or(null(), |id| id.raw());" + ], + "replace": [ + " let related: *const GUID = related.map_or(std::ptr::null::(), |id| id.raw());" + ] + } + ] +} diff --git a/crates/win-etw-sys/sabotage.json b/crates/win-etw-sys/sabotage.json new file mode 100644 index 000000000..ceb034df8 --- /dev/null +++ b/crates/win-etw-sys/sabotage.json @@ -0,0 +1,111 @@ +{ + "package": "win-etw-sys", + "description": "Sabotages for win-etw-sys. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", + "notCoveredHere": "What only a trace session can show is not swept here: whether an event is enabled while a session listens, that the level and keyword filters hold, and that the descriptor and data reach the session as written are tests/session.rs, which are ignored without elevation (WE-D-3), so a sweep on a machine that cannot start a session would report a sabotage of those as surviving for a reason that is not a hole. They, and the activity ids an event is written with (WE-D-4), are sabotage-session.json, swept on a machine that can. A handle that is unregistered twice is not injected: the call would be made on a handle that is gone, and the sweep would be measuring a crash and not a test.", + "sabotages": [ + { + "name": "WE-D-2: a GUID's second group is read back one bit off", + "file": "src/guid.rs", + "expect": "caught", + "why": "A GUID's value is one big-endian number, so each group is read from its own place; a shifted group reads back a value that is not the one given.", + "find": [" | (data2 as u128) << 80\n"], + "replace": [" | (data2 as u128) << 79\n"] + }, + { + "name": "WE-D-2: a terminated UTF-16 string has no terminator", + "file": "src/field.rs", + "expect": "caught", + "why": "A decoder reads a UnicodeString to a zero unit, so a string without one runs on into the next field.", + "find": [" Self::Utf16Terminated(v) => [Some(slice(v)), Some(of(&NUL_UNIT))],\n"], + "replace": [" Self::Utf16Terminated(v) => [Some(slice(v)), None],\n"] + }, + { + "name": "WE-D-2: a terminated UTF-16 string is ended with one byte", + "file": "src/field.rs", + "expect": "caught", + "why": "The terminator is a zero unit, two bytes; a one-byte terminator leaves the event's layout wrong for every field after it.", + "find": [" Self::Utf16Terminated(v) => [Some(slice(v)), Some(of(&NUL_UNIT))],\n"], + "replace": [" Self::Utf16Terminated(v) => [Some(slice(v)), Some(of(&NUL_BYTE))],\n"] + }, + { + "name": "WE-D-2: a terminated byte string has no terminator", + "file": "src/field.rs", + "expect": "caught", + "why": "As for UTF-16: an AnsiString is read to a zero byte.", + "find": [" Self::BytesTerminated(v) => [Some(slice(v)), Some(of(&NUL_BYTE))],\n"], + "replace": [" Self::BytesTerminated(v) => [Some(slice(v)), None],\n"] + }, + { + "name": "WE-D-2: a boolean is true as zero", + "file": "src/field.rs", + "expect": "caught", + "why": "A template's Boolean is four bytes, one for true and zero for false.", + "find": [" Self::U32(value as u32)\n"], + "replace": [" Self::U32(!value as u32)\n"] + }, + { + "name": "WE-D-2: a block's size is cut to a ULONG and not refused", + "file": "src/field.rs", + "expect": "caught", + "why": "A block longer than a descriptor's size can hold would be written as a short one; it is refused instead, and the call is not made.", + "find": [" u32::try_from(len).ok()\n"], + "replace": [" Some(len as u32)\n"] + }, + { + "name": "WE-D-2: a terminated string's blocks are written in reverse", + "file": "src/field.rs", + "expect": "caught", + "why": "The blocks are concatenated in order, so a terminator before its string is a different event.", + "find": [" for block in field.blocks().into_iter().flatten() {\n"], + "replace": [" for block in field.blocks().into_iter().rev().flatten() {\n"] + }, + { + "name": "WE-D-2: a registration is not unregistered when dropped", + "file": "src/registration.rs", + "expect": "caught", + "why": "A registration that is dropped must give its handle back, or a process that registers and drops runs out of its registrations; the limit test is the one that takes them all.", + "find": [" unregister(self.handle);\n"], + "replace": [" let _ = self.handle;\n"] + }, + { + "name": "WE-D-2: no fields are passed as a dangling pointer and not null", + "file": "src/field.rs", + "expect": "caught", + "why": "The page allows UserData to be NULL when UserDataCount is zero, and the call refused an event with no fields that was given the pointer an empty vector holds.", + "find": [" let blocks = if self.descriptors.is_empty() {\n"], + "replace": [" let blocks = if false {\n"] + }, + { + "name": "WE-D-2 control: a boolean is built by another spelling of the same conversion", + "file": "src/field.rs", + "expect": "survives", + "why": "A control: the same value by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" Self::U32(value as u32)\n"], + "replace": [" Self::U32(if value { 1 } else { 0 })\n"] + }, + { + "name": "WE-D-5: the thread's activity id is read in place of a new one", + "file": "src/activity.rs", + "expect": "caught", + "why": "The code that creates an id and the one that reads the thread's take the same arguments; read in its place, every pair would share the thread's id, which is zero, no activity at all.", + "find": [ + " let code = unsafe { EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &raw mut id) };" + ], + "replace": [ + " let code = unsafe { EventActivityIdControl(windows_sys::Win32::System::Diagnostics::Etw::EVENT_ACTIVITY_CTRL_GET_ID, &raw mut id) };" + ] + }, + { + "name": "WE-D-5 control: the id is zeroed by another spelling before the call", + "file": "src/activity.rs", + "expect": "survives", + "why": "A control: the same value by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [ + " data4: [0; 8]," + ], + "replace": [ + " data4: [0_u8; 8]," + ] + } + ] +} diff --git a/crates/win-etw-sys/src/activity.rs b/crates/win-etw-sys/src/activity.rs new file mode 100644 index 000000000..a5e76f811 --- /dev/null +++ b/crates/win-etw-sys/src/activity.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2026 Mike Grier +//! `EventActivityIdControl` with `EVENT_ACTIVITY_CTRL_CREATE_ID`: a new activity id (WE-D-5). +//! +//! Read 2026-10-21 from the call's page (`ms.date` 2018-12-05). The code "Sets the ActivityId parameter to the value of a +//! newly-generated locally-unique activity ID", and the remarks say the call "generates locally-unique IDs that are +//! guaranteed to be unique across all processes on the local system until the system reboots". The call's other codes get +//! and set the calling thread's activity id, which a writer that names its activities on every write does not need, and they +//! are not wrapped. + +use win_error_codes::Win32Error; +use windows_sys::Win32::System::Diagnostics::Etw::{ + EVENT_ACTIVITY_CTRL_CREATE_ID, EventActivityIdControl, +}; +use windows_sys::core::GUID; + +use crate::guid::Guid; + +#[cfg(test)] +mod tests; + +/// A newly-generated activity id, unique across every process of this system until it restarts, as the page guarantees. +/// The calling thread's own activity id is left as it was. +/// +/// # Errors +/// +/// The call's failure, with its code. The page names only success, `ERROR_SUCCESS`, and lists no failure. +pub fn create_activity_id() -> Result { + let mut id = GUID { + data1: 0, + data2: 0, + data3: 0, + data4: [0; 8], + }; + // SAFETY: the code is one the page lists, and `id` is a valid GUID the call writes the new id into. + let code = unsafe { EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &raw mut id) }; + Win32Error::from_code(code).into_result()?; + Ok(Guid::from_raw(id)) +} diff --git a/crates/win-etw-sys/src/activity/tests.rs b/crates/win-etw-sys/src/activity/tests.rs new file mode 100644 index 000000000..579e04a4d --- /dev/null +++ b/crates/win-etw-sys/src/activity/tests.rs @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Mike Grier +//! A created activity id: never zero, never the same twice, from one thread or several. + +use std::collections::HashSet; +use std::sync::Mutex; +use std::thread; + +use super::create_activity_id; +use crate::Guid; + +const ZERO: Guid = Guid::from_u128(0); + +#[test] +fn an_id_is_created() { + assert!(create_activity_id().is_ok()); +} + +#[test] +fn a_created_id_is_not_zero_which_the_page_says_is_no_activity() { + assert_ne!(create_activity_id().expect("created"), ZERO); +} + +#[test] +fn two_ids_in_a_row_differ() { + let first = create_activity_id().expect("created"); + let second = create_activity_id().expect("created"); + assert_ne!(first, second); +} + +#[test] +fn a_thousand_ids_are_all_different() { + let ids: HashSet = (0..1000) + .map(|_| create_activity_id().expect("created")) + .collect(); + assert_eq!(ids.len(), 1000); +} + +#[test] +fn ids_created_on_several_threads_are_all_different() { + let ids = Mutex::new(HashSet::new()); + thread::scope(|scope| { + for _ in 0..8 { + let ids = &ids; + scope.spawn(move || { + for _ in 0..100 { + let id = create_activity_id().expect("created"); + assert!(ids.lock().expect("the set").insert(id), "{id} twice"); + } + }); + } + }); + assert_eq!(ids.lock().expect("the set").len(), 800); +} diff --git a/crates/win-etw-sys/src/descriptor.rs b/crates/win-etw-sys/src/descriptor.rs new file mode 100644 index 000000000..45b3306c1 --- /dev/null +++ b/crates/win-etw-sys/src/descriptor.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2026 Mike Grier +//! `EVENT_DESCRIPTOR`: the metadata of an event. +//! +//! Read 2026-10-20 from the structure's page (`ms.date` 2018-12-05, `updated_at` 2024-02-22). + +use windows_sys::Win32::System::Diagnostics::Etw::EVENT_DESCRIPTOR; + +/// The level values the page for the structure defines, and the ranges around them. +/// +/// The page: "Level values 0 through 5 are defined by Microsoft (see `evntrace.h` and `winmeta.h`). Level values 6 +/// through 15 are reserved. Level values 16 through 255 can be defined by the event provider." A session that sets a +/// level filter "will only accept events where `eventDescriptor.Level <= session.LevelFilter`". +pub mod level { + /// "Event bypasses level-based event filtering. Events should not use this level." + pub const LOG_ALWAYS: u8 = 0; + /// "Critical error". + pub const CRITICAL: u8 = 1; + /// "Error". + pub const ERROR: u8 = 2; + /// "Warning". + pub const WARNING: u8 = 3; + /// "Informational". + pub const INFO: u8 = 4; + /// "Verbose". + pub const VERBOSE: u8 = 5; +} + +/// The information (metadata) about an event that `EventWrite` and `EventEnabled` take. +/// +/// The page for `EventWrite` says that "ProviderId, Level and Keyword are the primary means for filtering events" and +/// "Always assign a nonzero level and keyword to every event"; this crate passes whatever it is given. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct EventDescriptor { + /// "A 16-bit number used to identify manifest-based events." With the provider's decode GUID and the version it + /// "should uniquely identify an event". + pub id: u16, + /// "An 8-bit number used to specify the version of a manifest-based event." + pub version: u8, + /// "An 8-bit number used to enable special event processing." Manifest-based events normally use 0; values below + /// 16 are reserved for Microsoft. + pub channel: u8, + /// "An 8-bit number used to describe an event's severity or importance": see [`level`]. + pub level: u8, + /// "An 8-bit number used to mark events with special semantics." Most events use 0, which is INFO. + pub opcode: u8, + /// "A 16-bit number used to annotate an event or related group of events." 0 is the default. + pub task: u16, + /// "A 64-bit bitmask used to indicate an event's membership in a set of event categories." The top 16 bits are + /// Microsoft's and the low 48 are the provider's. + pub keyword: u64, +} + +impl EventDescriptor { + /// The structure the calls take. + pub(crate) const fn raw(&self) -> EVENT_DESCRIPTOR { + EVENT_DESCRIPTOR { + Id: self.id, + Version: self.version, + Channel: self.channel, + Level: self.level, + Opcode: self.opcode, + Task: self.task, + Keyword: self.keyword, + } + } +} diff --git a/crates/win-etw-sys/src/field.rs b/crates/win-etw-sys/src/field.rs new file mode 100644 index 000000000..92329a369 --- /dev/null +++ b/crates/win-etw-sys/src/field.rs @@ -0,0 +1,193 @@ +// Copyright (c) 2026 Mike Grier +//! `EVENT_DATA_DESCRIPTOR`: the blocks of data an event is made of, built from typed fields. +//! +//! Read 2026-10-20 from the structure's page (`ms.date` 2018-12-05, `updated_at` 2024-02-22) and from `EventWrite`'s. +//! `EventWrite` takes an array of descriptors, each "one block of memory to be included in the event", and the +//! blocks "will be concatenated in order with no padding or alignment to form the event content". So an event's +//! layout is the order and the bytes of its fields, and this crate adds none of its own. + +use std::marker::PhantomData; + +use windows_sys::Win32::System::Diagnostics::Etw::{ + EVENT_DATA_DESCRIPTOR, EVENT_DATA_DESCRIPTOR_0, +}; + +use crate::guid::Guid; +use crate::registration::WriteError; + +/// The unit that ends a UTF-16 string a decoder reads to a terminator. +static NUL_UNIT: u16 = 0; +/// The byte that ends an ANSI or UTF-8 string a decoder reads to a terminator. +static NUL_BYTE: u8 = 0; + +/// One field of an event's data: a value, written to the event as the bytes it has in memory. +/// +/// The fields of an event are written in order and with no padding, so what a decoder reads them back as is what its +/// template says (a manifest's, for a manifest-based event) and not something this type knows: an `I32` is four bytes +/// whichever name the template gives it. Scalars are in the machine's own byte order, which is what a decoder +/// expects. +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum Field<'a> { + /// One byte. + U8(u8), + /// One byte, signed. + I8(i8), + /// Two bytes. + U16(u16), + /// Two bytes, signed. + I16(i16), + /// Four bytes. + U32(u32), + /// Four bytes, signed. + I32(i32), + /// Eight bytes. + U64(u64), + /// Eight bytes, signed. + I64(i64), + /// Four bytes, an IEEE 754 single. + F32(f32), + /// Eight bytes, an IEEE 754 double. + F64(f64), + /// As many bytes as a pointer has in this process: four or eight. + Pointer(usize), + /// Sixteen bytes, laid out as the `GUID` structure. + Guid(Guid), + /// These bytes, and nothing else: no length, and no terminator. A template that carries a length does so in a + /// field of its own, before this one. + Bytes(&'a [u8]), + /// These UTF-16 units, and nothing else: no length, and no terminator. + Utf16(&'a [u16]), + /// These UTF-16 units and then a zero unit, as a decoder reads a string of a template's `UnicodeString` type. + /// + /// A zero unit already in the slice ends the string for such a decoder at that unit. That is the decoder's rule + /// and not the call's, so the units are written as they are. + Utf16Terminated(&'a [u16]), + /// These bytes and then a zero byte, as a decoder reads a string of a template's `AnsiString` type. + /// + /// As [`Utf16Terminated`](Self::Utf16Terminated): a zero byte already in the slice ends the string for the + /// decoder. + BytesTerminated(&'a [u8]), +} + +impl Field<'_> { + /// A four-byte boolean, which is what a template's `Boolean` type is: 1 for true and 0 for false. + #[must_use] + pub const fn boolean(value: bool) -> Self { + Self::U32(value as u32) + } + + /// The blocks of memory this field is: one, or two for a terminated string, whose second is the terminator. + fn blocks(&self) -> [Option; 2] { + fn of(value: &T) -> Block { + Block { + ptr: std::ptr::from_ref(value).cast(), + len: size_of::(), + } + } + fn slice(values: &[T]) -> Block { + Block { + ptr: values.as_ptr().cast(), + len: size_of_val(values), + } + } + let one = |block| [Some(block), None]; + match self { + Self::U8(v) => one(of(v)), + Self::I8(v) => one(of(v)), + Self::U16(v) => one(of(v)), + Self::I16(v) => one(of(v)), + Self::U32(v) => one(of(v)), + Self::I32(v) => one(of(v)), + Self::U64(v) => one(of(v)), + Self::I64(v) => one(of(v)), + Self::F32(v) => one(of(v)), + Self::F64(v) => one(of(v)), + Self::Pointer(v) => one(of(v)), + Self::Guid(v) => one(of(v.raw())), + Self::Bytes(v) => one(slice(v)), + Self::Utf16(v) => one(slice(v)), + Self::Utf16Terminated(v) => [Some(slice(v)), Some(of(&NUL_UNIT))], + Self::BytesTerminated(v) => [Some(slice(v)), Some(of(&NUL_BYTE))], + } + } +} + +/// A block of memory, by address and length. +#[derive(Clone, Copy)] +struct Block { + ptr: *const u8, + len: usize, +} + +/// The descriptors for a list of fields, valid for as long as the fields are borrowed. +pub(crate) struct Built<'a> { + descriptors: Vec, + /// What the descriptors point into: the fields, and the statics for terminators. + fields: PhantomData<&'a ()>, +} + +impl Built<'_> { + /// The array and its count, as `EventWrite` takes them. + pub(crate) fn as_call(&self) -> (u32, *const EVENT_DATA_DESCRIPTOR) { + // The count was checked to fit when this was built. + let count = u32::try_from(self.descriptors.len()).expect("checked in `build`"); + // The page for `EventWrite`: "UserData may be NULL if UserDataCount is zero". An empty vector's pointer is not + // null but dangling, and the call refused an event given it with `ERROR_INVALID_PARAMETER`, so no fields are + // passed as the null the page names. + let blocks = if self.descriptors.is_empty() { + std::ptr::null() + } else { + self.descriptors.as_ptr() + }; + (count, blocks) + } + + /// The descriptors, for a test to read the blocks they describe. + #[cfg(test)] + pub(crate) fn descriptors(&self) -> &[EVENT_DATA_DESCRIPTOR] { + &self.descriptors + } +} + +/// The descriptors for `fields`, or the first thing that cannot be passed as the call takes it. +pub(crate) fn build<'a>(fields: &'a [Field<'a>]) -> Result, WriteError> { + let mut descriptors = Vec::with_capacity(fields.len()); + for (index, field) in fields.iter().enumerate() { + for block in field.blocks().into_iter().flatten() { + descriptors.push(descriptor(block, index)?); + } + } + if u32::try_from(descriptors.len()).is_err() { + return Err(WriteError::TooManyBlocks { + count: descriptors.len(), + }); + } + Ok(Built { + descriptors, + fields: PhantomData, + }) +} + +/// The descriptor of one block, which `EventDataDescCreate` would make: the address as a 64-bit integer, "in both +/// 32-bit and 64-bit architectures", and the size in bytes, as a `ULONG`. +fn descriptor(block: Block, field: usize) -> Result { + let size = size_of_block(block.len).ok_or(WriteError::BlockTooLong { + field, + len: block.len, + })?; + Ok(EVENT_DATA_DESCRIPTOR { + Ptr: block.ptr as usize as u64, + Size: size, + // The type is zero, "Normal event data", and the rest is reserved and zero. + Anonymous: EVENT_DATA_DESCRIPTOR_0 { Reserved: 0 }, + }) +} + +/// A block's length as the descriptor's `ULONG` size, or `None` if it does not fit. +pub(crate) fn size_of_block(len: usize) -> Option { + u32::try_from(len).ok() +} + +#[cfg(test)] +mod tests; diff --git a/crates/win-etw-sys/src/field/tests.rs b/crates/win-etw-sys/src/field/tests.rs new file mode 100644 index 000000000..287557b79 --- /dev/null +++ b/crates/win-etw-sys/src/field/tests.rs @@ -0,0 +1,281 @@ +// Copyright (c) 2026 Mike Grier +//! The descriptors built from fields (WE-D-2): each field is the bytes it has in memory, a terminated string is its +//! units and then a zero, and the blocks are in the order of the fields. The tests read through the very descriptors +//! that would be passed to `EventWrite`, so they say what an event's data would be. + +use super::{Built, Field, build, size_of_block}; +use crate::guid::Guid; +use crate::registration::WriteError; + +/// The bytes the descriptors describe, concatenated as the page says `EventWrite` does. +fn data(built: &Built<'_>) -> Vec { + let mut out = Vec::new(); + for descriptor in built.descriptors() { + // SAFETY: each descriptor describes memory held by the fields the test borrows, or a static, valid for + // reads of its size. + let block = unsafe { + std::slice::from_raw_parts( + descriptor.Ptr as usize as *const u8, + descriptor.Size as usize, + ) + }; + out.extend_from_slice(block); + } + out +} + +fn of(fields: &[Field<'_>]) -> Vec { + data(&build(fields).expect("fields that can be built")) +} + +fn blocks(fields: &[Field<'_>]) -> usize { + build(fields) + .expect("fields that can be built") + .descriptors() + .len() +} + +#[test] +fn no_fields_are_passed_as_a_count_of_zero_and_a_null_array() { + // The page: "UserData may be NULL if UserDataCount is zero". A dangling pointer is what an empty vector holds, and + // the call refused an event given one. + let built = build(&[]).expect("built"); + let (count, blocks) = built.as_call(); + assert_eq!(count, 0); + assert!(blocks.is_null()); +} + +#[test] +fn fields_are_passed_as_their_count_and_the_array() { + let built = build(&[Field::U8(1), Field::Utf16Terminated(&[1])]).expect("built"); + let (count, blocks) = built.as_call(); + assert_eq!(count, 3); + assert_eq!(blocks, built.descriptors().as_ptr()); +} + +#[test] +fn no_fields_are_no_blocks_and_no_data() { + assert_eq!(blocks(&[]), 0); + assert!(of(&[]).is_empty()); +} + +#[test] +fn the_integers_are_their_bytes_in_the_machines_order() { + assert_eq!(of(&[Field::U8(0xab)]), [0xab]); + assert_eq!(of(&[Field::I8(-2)]), [0xfe]); + assert_eq!(of(&[Field::U16(0x1234)]), 0x1234_u16.to_ne_bytes()); + assert_eq!(of(&[Field::I16(-2)]), (-2_i16).to_ne_bytes()); + assert_eq!( + of(&[Field::U32(0x1234_5678)]), + 0x1234_5678_u32.to_ne_bytes() + ); + assert_eq!(of(&[Field::I32(-3)]), (-3_i32).to_ne_bytes()); + assert_eq!( + of(&[Field::U64(u64::MAX - 1)]), + (u64::MAX - 1).to_ne_bytes() + ); + assert_eq!(of(&[Field::I64(i64::MIN)]), i64::MIN.to_ne_bytes()); +} + +#[test] +fn each_integer_is_one_block_of_its_size() { + for (field, size) in [ + (Field::U8(0), 1), + (Field::I8(0), 1), + (Field::U16(0), 2), + (Field::I16(0), 2), + (Field::U32(0), 4), + (Field::I32(0), 4), + (Field::U64(0), 8), + (Field::I64(0), 8), + (Field::F32(0.0), 4), + (Field::F64(0.0), 8), + (Field::Pointer(0), size_of::()), + (Field::Guid(Guid::from_u128(0)), 16), + ] { + let built = build(std::slice::from_ref(&field)).expect("built"); + assert_eq!(built.descriptors().len(), 1, "{field:?}"); + assert_eq!(built.descriptors()[0].Size as usize, size, "{field:?}"); + } +} + +#[test] +fn the_floats_are_their_ieee_bits() { + assert_eq!(of(&[Field::F32(1.5)]), 1.5_f32.to_ne_bytes()); + assert_eq!(of(&[Field::F64(-2.25)]), (-2.25_f64).to_ne_bytes()); +} + +#[test] +fn a_boolean_is_four_bytes() { + assert_eq!(of(&[Field::boolean(true)]), 1_u32.to_ne_bytes()); + assert_eq!(of(&[Field::boolean(false)]), 0_u32.to_ne_bytes()); +} + +#[test] +fn a_pointer_is_as_wide_as_a_pointer() { + assert_eq!(of(&[Field::Pointer(0x1000)]), 0x1000_usize.to_ne_bytes()); +} + +#[test] +fn a_guid_is_the_structure_with_the_first_three_groups_in_the_machines_order() { + let guid = Guid::from_u128(0x1234_5678_9abc_def0_1122_3344_5566_7788); + let mut expected = Vec::new(); + expected.extend_from_slice(&0x1234_5678_u32.to_ne_bytes()); + expected.extend_from_slice(&0x9abc_u16.to_ne_bytes()); + expected.extend_from_slice(&0xdef0_u16.to_ne_bytes()); + expected.extend_from_slice(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + assert_eq!(of(&[Field::Guid(guid)]), expected); +} + +#[test] +fn bytes_are_written_as_given_with_no_length_and_no_terminator() { + assert_eq!(of(&[Field::Bytes(&[1, 2, 3])]), [1, 2, 3]); + assert_eq!(blocks(&[Field::Bytes(&[1, 2, 3])]), 1); +} + +#[test] +fn empty_bytes_are_a_block_of_no_size() { + assert_eq!(blocks(&[Field::Bytes(&[])]), 1); + assert!(of(&[Field::Bytes(&[])]).is_empty()); +} + +#[test] +fn utf16_units_are_written_as_given_with_no_terminator() { + assert_eq!( + of(&[Field::Utf16(&[0x68, 0x69])]), + [0x68_u16.to_ne_bytes(), 0x69_u16.to_ne_bytes()].concat() + ); + assert_eq!(blocks(&[Field::Utf16(&[0x68, 0x69])]), 1); +} + +#[test] +fn a_terminated_utf16_string_is_its_units_then_a_zero_unit_in_two_blocks() { + let fields = [Field::Utf16Terminated(&[0x68, 0x69])]; + assert_eq!( + of(&fields), + [0x68_u16.to_ne_bytes(), 0x69_u16.to_ne_bytes(), [0, 0]].concat() + ); + assert_eq!(blocks(&fields), 2); +} + +#[test] +fn an_empty_terminated_utf16_string_is_the_zero_unit_alone() { + assert_eq!(of(&[Field::Utf16Terminated(&[])]), [0, 0]); +} + +#[test] +fn a_terminated_byte_string_is_its_bytes_then_a_zero_byte() { + assert_eq!(of(&[Field::BytesTerminated(b"hi")]), [b'h', b'i', 0]); + assert_eq!(blocks(&[Field::BytesTerminated(b"hi")]), 2); + assert_eq!(of(&[Field::BytesTerminated(b"")]), [0]); +} + +#[test] +fn a_zero_already_in_a_terminated_string_is_written_as_it_is() { + assert_eq!( + of(&[Field::Utf16Terminated(&[0x61, 0, 0x62])]), + [ + 0x61_u16.to_ne_bytes(), + [0, 0], + 0x62_u16.to_ne_bytes(), + [0, 0] + ] + .concat() + ); + assert_eq!( + of(&[Field::BytesTerminated(&[b'a', 0, b'b'])]), + [b'a', 0, b'b', 0] + ); +} + +#[test] +fn an_unpaired_surrogate_is_a_unit_like_any_other() { + assert_eq!(of(&[Field::Utf16(&[0xd800])]), 0xd800_u16.to_ne_bytes()); +} + +#[test] +fn fields_are_concatenated_in_order_with_no_padding() { + let fields = [ + Field::U8(1), + Field::U64(2), + Field::U16(3), + Field::Utf16Terminated(&[0x41]), + Field::U32(4), + ]; + let expected = [ + vec![1], + 2_u64.to_ne_bytes().to_vec(), + 3_u16.to_ne_bytes().to_vec(), + 0x41_u16.to_ne_bytes().to_vec(), + vec![0, 0], + 4_u32.to_ne_bytes().to_vec(), + ] + .concat(); + assert_eq!(of(&fields), expected); + assert_eq!(blocks(&fields), 6); +} + +#[test] +fn the_blocks_are_the_events_in_the_order_the_fields_were_given() { + let built = build(&[Field::U8(1), Field::U8(2), Field::U8(3)]).expect("built"); + let sizes: Vec = built.descriptors().iter().map(|d| d.Size).collect(); + assert_eq!(sizes, [1, 1, 1]); + assert_eq!(data(&built), [1, 2, 3]); +} + +#[test] +fn a_descriptors_address_is_that_of_the_field_and_its_type_is_zero() { + let value = 0x55_u32; + let fields = [Field::U32(value)]; + let built = build(&fields).expect("built"); + let descriptor = &built.descriptors()[0]; + let Field::U32(inside) = &fields[0] else { + unreachable!("built as a U32") + }; + assert_eq!(descriptor.Ptr, std::ptr::from_ref(inside) as usize as u64); + // The reserved word is the type and the two reserved fields, all zero: "Normal event data". + // SAFETY: both members are plain integers over the same four bytes. + assert_eq!(unsafe { descriptor.Anonymous.Reserved }, 0); +} + +#[test] +fn a_terminated_strings_second_block_is_the_same_static_each_time() { + let a = build(&[Field::Utf16Terminated(&[1])]).expect("built"); + let b = build(&[Field::Utf16Terminated(&[2, 3])]).expect("built"); + assert_eq!(a.descriptors()[1].Ptr, b.descriptors()[1].Ptr); + assert_eq!(a.descriptors()[1].Size, 2); +} + +#[test] +fn a_large_list_is_every_field() { + let fields: Vec> = (0..1000_u32).map(Field::U32).collect(); + assert_eq!(blocks(&fields), 1000); + assert_eq!(of(&fields).len(), 4000); +} + +#[test] +fn a_size_that_fits_a_ulong_is_kept() { + assert_eq!(size_of_block(0), Some(0)); + assert_eq!(size_of_block(u32::MAX as usize), Some(u32::MAX)); +} + +#[test] +fn a_size_that_does_not_fit_a_ulong_has_none() { + assert_eq!(size_of_block(u32::MAX as usize + 1), None); + assert_eq!(size_of_block(usize::MAX), None); +} + +#[test] +fn a_block_too_long_is_refused_naming_its_field() { + let refused = super::descriptor( + super::Block { + ptr: std::ptr::null(), + len: u32::MAX as usize + 1, + }, + 3, + ); + assert!(matches!( + refused, + Err(WriteError::BlockTooLong { field: 3, len }) if len == u32::MAX as usize + 1 + )); +} diff --git a/crates/win-etw-sys/src/guid.rs b/crates/win-etw-sys/src/guid.rs new file mode 100644 index 000000000..f42847ad3 --- /dev/null +++ b/crates/win-etw-sys/src/guid.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2026 Mike Grier +//! A GUID, the identifier of a provider. + +use std::fmt; +use std::hash::{Hash, Hasher}; + +use windows_sys::core::GUID; + +#[cfg(test)] +mod tests; + +/// A GUID: what identifies a provider, and what a data field of that type holds. +/// +/// It is built from, and read back as, the GUID's value as one big-endian number in the order its text is +/// written, so that `{12345678-9abc-def0-1234-56789abcdef0}` is `0x12345678_9abc_def0_1234_56789abcdef0`. That is +/// `windows_sys::core::GUID::from_u128`'s order and the one `windows-namespace-request-sys` takes an object id in. It +/// is not the order of the bytes of the structure, which holds its first three groups in the machine's own order; +/// a value of this type is laid out as that structure where one is written to an event. +#[derive(Clone, Copy)] +pub struct Guid(GUID); + +impl Guid { + /// The GUID whose value, read as one big-endian number, is `value`. + #[must_use] + pub const fn from_u128(value: u128) -> Self { + Self(GUID::from_u128(value)) + } + + /// The GUID's value as one big-endian number, as [`from_u128`](Self::from_u128) takes it. + #[must_use] + pub const fn as_u128(self) -> u128 { + let GUID { + data1, + data2, + data3, + data4, + } = self.0; + (data1 as u128) << 96 + | (data2 as u128) << 80 + | (data3 as u128) << 64 + | u64::from_be_bytes(data4) as u128 + } + + /// The structure the calls take. + pub(crate) const fn raw(&self) -> &GUID { + &self.0 + } + + /// The GUID a call gave. + pub(crate) const fn from_raw(raw: GUID) -> Self { + Self(raw) + } +} + +impl PartialEq for Guid { + fn eq(&self, other: &Self) -> bool { + self.as_u128() == other.as_u128() + } +} + +impl Eq for Guid {} + +impl Hash for Guid { + fn hash(&self, state: &mut H) { + self.as_u128().hash(state); + } +} + +impl fmt::Display for Guid { + /// As the registry and the trace tools write one: braces, lowercase hexadecimal, groups of 8, 4, 4, 4 and 12 digits. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let v = self.as_u128(); + write!( + f, + "{{{:08x}-{:04x}-{:04x}-{:04x}-{:012x}}}", + (v >> 96) as u32, + (v >> 80) as u16, + (v >> 64) as u16, + (v >> 48) as u16, + v & 0xffff_ffff_ffff + ) + } +} + +impl fmt::Debug for Guid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Guid({self})") + } +} diff --git a/crates/win-etw-sys/src/guid/tests.rs b/crates/win-etw-sys/src/guid/tests.rs new file mode 100644 index 000000000..bfee0ada5 --- /dev/null +++ b/crates/win-etw-sys/src/guid/tests.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2026 Mike Grier +//! A GUID's value, text and layout. + +use super::Guid; + +const VALUE: u128 = 0x1234_5678_9abc_def0_1122_3344_5566_7788; + +#[test] +fn a_value_reads_back_as_it_was_given() { + assert_eq!(Guid::from_u128(VALUE).as_u128(), VALUE); +} + +#[test] +fn the_zero_and_the_all_ones_values_read_back() { + assert_eq!(Guid::from_u128(0).as_u128(), 0); + assert_eq!(Guid::from_u128(u128::MAX).as_u128(), u128::MAX); +} + +#[test] +fn each_group_of_the_text_is_its_place_in_the_number() { + let raw = *Guid::from_u128(VALUE).raw(); + assert_eq!(raw.data1, 0x1234_5678); + assert_eq!(raw.data2, 0x9abc); + assert_eq!(raw.data3, 0xdef0); + assert_eq!(raw.data4, [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); +} + +#[test] +fn it_displays_as_the_registry_writes_one() { + assert_eq!( + Guid::from_u128(VALUE).to_string(), + "{12345678-9abc-def0-1122-334455667788}" + ); +} + +#[test] +fn it_displays_the_zero_value_with_every_digit() { + assert_eq!( + Guid::from_u128(0).to_string(), + "{00000000-0000-0000-0000-000000000000}" + ); +} + +#[test] +fn a_group_with_leading_zeros_keeps_them() { + assert_eq!( + Guid::from_u128(0x0000_0001_0002_0003_0004_0000_0000_0005).to_string(), + "{00000001-0002-0003-0004-000000000005}" + ); +} + +#[test] +fn it_debugs_as_its_text() { + assert_eq!( + format!("{:?}", Guid::from_u128(1)), + "Guid({00000000-0000-0000-0000-000000000001})" + ); +} + +#[test] +fn the_structure_is_laid_out_as_the_machine_holds_the_first_three_groups() { + let guid = Guid::from_u128(VALUE); + let raw = guid.raw(); + // SAFETY: the structure is plain data, so its bytes are valid to read. + let bytes: [u8; 16] = unsafe { std::mem::transmute_copy(raw) }; + assert_eq!(&bytes[..4], &0x1234_5678_u32.to_ne_bytes()); + assert_eq!(&bytes[4..6], &0x9abc_u16.to_ne_bytes()); + assert_eq!(&bytes[6..8], &0xdef0_u16.to_ne_bytes()); + assert_eq!( + &bytes[8..], + &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] + ); +} + +#[test] +fn equal_values_are_equal_and_others_are_not() { + assert_eq!(Guid::from_u128(VALUE), Guid::from_u128(VALUE)); + assert_ne!(Guid::from_u128(VALUE), Guid::from_u128(VALUE + 1)); +} + +#[test] +fn it_is_usable_in_a_const() { + const ID: Guid = Guid::from_u128(VALUE); + const BACK: u128 = ID.as_u128(); + assert_eq!(BACK, VALUE); +} diff --git a/crates/win-etw-sys/src/lib.rs b/crates/win-etw-sys/src/lib.rs new file mode 100644 index 000000000..797dcc628 --- /dev/null +++ b/crates/win-etw-sys/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2026 Mike Grier +#![doc = include_str!("../README.md")] +#![cfg(windows)] +#![deny(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] + +mod activity; +mod descriptor; +mod field; +mod guid; +mod registration; +#[cfg(feature = "test-util")] +mod session; + +pub use activity::create_activity_id; +pub use descriptor::{EventDescriptor, level}; +pub use field::Field; +pub use guid::Guid; +pub use registration::{Registration, WriteError}; +#[cfg(feature = "test-util")] +pub use session::{Recorded, Session}; diff --git a/crates/win-etw-sys/src/registration.rs b/crates/win-etw-sys/src/registration.rs new file mode 100644 index 000000000..025d0edfe --- /dev/null +++ b/crates/win-etw-sys/src/registration.rs @@ -0,0 +1,228 @@ +// Copyright (c) 2026 Mike Grier +//! `EventRegister`, `EventUnregister`, `EventEnabled`, `EventProviderEnabled`, `EventWrite` and `EventWriteTransfer`: a +//! provider's registration, and what is done through it. +//! +//! Read 2026-10-20 from each call's page (all `ms.date` 2018-12-05, `updated_at` 2024-02-22). The registration is +//! "a process-scope operation (the handle is valid only within the process)", it must be undone "before your +//! provider unloads or exits", and "It is not necessary to install a manifest to write events or capture traces", +//! which is the manifest's part in decoding and not in recording. So a registration here is a value that owns the +//! handle and gives it back when dropped. + +use std::fmt; +use std::ptr::null; + +use win_error_codes::Win32Error; +use windows_sys::Win32::System::Diagnostics::Etw::{ + EventEnabled, EventProviderEnabled, EventRegister, EventUnregister, EventWrite, + EventWriteTransfer, +}; +use windows_sys::core::GUID; + +use crate::descriptor::EventDescriptor; +use crate::field::{Field, build}; +use crate::guid::Guid; + +/// A registered provider: the handle `EventRegister` made, unregistered when this is dropped. +/// +/// The handle is valid in this process only. Registering the same GUID twice makes two registrations, each with its own +/// handle, as the page allows (a process may register up to 1,024 providers, "you should limit the number of providers +/// that your component registers to one or two"). +#[derive(Debug)] +pub struct Registration { + /// Never zero for a value that has not been unregistered: `EventRegister` sets zero only on failure, and a failure + /// is not a value of this type. + handle: i64, +} + +impl Registration { + /// Registers the provider `provider`, by `EventRegister`, with no enable callback. + /// + /// The page: "GUID that uniquely identifies the provider, sometimes called a control GUID. This must be a stable + /// identifier so that trace controllers can use the GUID to subscribe to events from this provider." + /// + /// # Errors + /// + /// The call's failure, as the code it returns. The page says that code "is primarily intended for use in debugging + /// and diagnostic scenarios" and that "Most production code should continue to run even if an ETW provider failed + /// to register": a caller that does not want the events handles the error by writing none. + pub fn register(provider: Guid) -> Result { + let mut handle: i64 = 0; + // SAFETY: the provider id is a valid GUID for the call to read, the callback and its context are null, which + // the page allows, and the handle is a valid `i64` for the call to write. + let code = unsafe { EventRegister(provider.raw(), None, std::ptr::null(), &mut handle) }; + Win32Error::from_code(code).into_result()?; + Ok(Self { handle }) + } + + /// Unregisters the provider, by `EventUnregister`, and reports what the call returned. + /// + /// A registration that is dropped unregisters just as this does, and drops what the call returned, which the page + /// says is `ERROR_SUCCESS` when it succeeds and gives no failure for. + /// + /// # Errors + /// + /// The call's return, if it was not `ERROR_SUCCESS`. The registration is gone either way. + pub fn unregister(mut self) -> Result<(), Win32Error> { + let handle = std::mem::take(&mut self.handle); + Win32Error::from_code(unregister(handle)).into_result() + } + + /// Whether a session is listening for this event, by `EventEnabled`: `false` "if ETW can quickly determine that no + /// event collection session would record an event from the given provider with the specified descriptor". + /// + /// The page says the test is "conservative": `true` can be returned where "subsequent in-depth filtering would + /// determine that no sessions need to record the event". [`write`](Self::write) makes this test itself and returns + /// at once if the event is not enabled, so a caller asks it to avoid the work of preparing the data. + #[must_use] + pub fn is_enabled(&self, descriptor: &EventDescriptor) -> bool { + let raw = descriptor.raw(); + // SAFETY: the handle is one `EventRegister` returned and has not been unregistered, and `raw` is a valid + // descriptor for the call to read. + unsafe { EventEnabled(self.handle, &raw) } + } + + /// As [`is_enabled`](Self::is_enabled), for a caller that has only an event's level and keyword, by + /// `EventProviderEnabled`. The page: "When a provider has access only to the event's Level and Keyword, the + /// provider should use" this. + #[must_use] + pub fn is_level_enabled(&self, level: u8, keyword: u64) -> bool { + // SAFETY: the handle is one `EventRegister` returned and has not been unregistered. + unsafe { EventProviderEnabled(self.handle, level, keyword) } + } + + /// Writes an event whose data is `fields` in order, by `EventWrite`, with the current thread's activity id. + /// + /// "If no trace sessions are recording this event, this function will do nothing and return **ERROR_SUCCESS**." + /// The fields are written with no padding, as the page says the blocks are. + /// + /// # Errors + /// + /// [`WriteError::TooManyBlocks`] or [`WriteError::BlockTooLong`] if the fields cannot be given to the call, without + /// calling it. Otherwise [`WriteError::Call`], the call's failure, which the page lists as `ERROR_INVALID_PARAMETER` + /// among others, `ERROR_ARITHMETIC_OVERFLOW` for "larger than the allowed maximum (64KB - header)", and + /// `ERROR_NOT_ENOUGH_MEMORY` when the sessions' buffers cannot keep up. The page says the code "is primarily intended + /// for use in debugging and diagnostic scenarios" and that most callers ignore it. + /// + /// The page gives the number of data descriptors, the call's `UserDataCount`, as "The maximum number is 128". A + /// terminated string is two descriptors. This function passes however many there are, and the call reports what it + /// makes of that. + pub fn write( + &self, + descriptor: &EventDescriptor, + fields: &[Field<'_>], + ) -> Result<(), WriteError> { + let built = build(fields)?; + let (count, blocks) = built.as_call(); + let raw = descriptor.raw(); + // SAFETY: the handle is one `EventRegister` returned and has not been unregistered; `raw` is a valid descriptor; + // `blocks` points at `count` descriptors that `built` owns, each of which describes memory that the borrowed + // `fields` hold or a static, all valid for reads of the size given, and `built` and `fields` outlive the call. + let code = unsafe { EventWrite(self.handle, &raw, count, blocks) }; + Win32Error::from_code(code) + .into_result() + .map_err(WriteError::Call) + } + + /// Writes an event whose data is `fields` in order, by `EventWriteTransfer`, with the activity id and the related + /// activity id the caller gives (WE-D-4). + /// + /// Read 2026-10-21 from the call's page (`ms.date` 2018-12-05, `updated_at` 2024-02-22). Both ids are optional there: + /// "If this is non-NULL, **EventWriteTransfer** will use the specified value for the event's activity ID. If this is + /// NULL, **EventWriteTransfer** will use the current thread's activity ID", and for the related id, "If this is NULL, + /// the event will not have a related activity ID. The related activity ID is usually set on the activity's START + /// event". So `activity` names the event's activity whatever thread writes it, which lets a begin and its end written + /// on two threads share one, and `None` is the thread's, as [`write`](Self::write) takes it. The page calls no value + /// of either invalid, so none is refused here; "usually" on a START event is advice and is not enforced. + /// + /// # Errors + /// + /// As [`write`](Self::write): the crate's refusals of fields that cannot be given to the call, without calling it, + /// or [`WriteError::Call`], the call's failure, whose codes the page lists as `write`'s are listed, with + /// `ERROR_MORE_DATA` when "The session buffer size is too small for the event" and `STATUS_LOG_FILE_FULL` for a + /// real-time playback file that is full. + pub fn write_transfer( + &self, + descriptor: &EventDescriptor, + activity: Option<&Guid>, + related: Option<&Guid>, + fields: &[Field<'_>], + ) -> Result<(), WriteError> { + let built = build(fields)?; + let (count, blocks) = built.as_call(); + let raw = descriptor.raw(); + let activity: *const GUID = activity.map_or(null(), |id| id.raw()); + let related: *const GUID = related.map_or(null(), |id| id.raw()); + // SAFETY: as `write`'s, and each id is null or points at a GUID the caller's borrow keeps alive for the call. + let code = + unsafe { EventWriteTransfer(self.handle, &raw, activity, related, count, blocks) }; + Win32Error::from_code(code) + .into_result() + .map_err(WriteError::Call) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + unregister(self.handle); + } +} + +/// The call, which does nothing for the zero handle a registration that has been unregistered holds. +fn unregister(handle: i64) -> u32 { + if handle == 0 { + return 0; + } + // SAFETY: the handle is one `EventRegister` returned and is unregistered once: the caller takes it out of its + // registration, or the registration is being dropped. + unsafe { EventUnregister(handle) } +} + +/// Why an event was not written. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum WriteError { + /// The fields are more blocks than the call's count, a `ULONG`, can hold, so the call was not made. This is the + /// crate's refusal and not the call's, which would have refused far fewer. + TooManyBlocks { + /// How many blocks the fields come to. + count: usize, + }, + /// A field's block is longer than a descriptor's size, a `ULONG`, can hold, so the call was not made. This is the + /// crate's refusal and not the call's, which would have refused far less. + BlockTooLong { + /// The place of the field in the list given. + field: usize, + /// The block's length in bytes. + len: usize, + }, + /// The call, `EventWrite` or `EventWriteTransfer`, failed and gave this code. + Call(Win32Error), +} + +impl fmt::Display for WriteError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TooManyBlocks { count } => write!( + f, + "the fields are {count} blocks, more than the write call's count, a ULONG, can hold" + ), + Self::BlockTooLong { field, len } => write!( + f, + "field {field} is {len} bytes, more than an EVENT_DATA_DESCRIPTOR's size, a ULONG, can hold" + ), + Self::Call(error) => write!(f, "the write call failed: {error}"), + } + } +} + +impl std::error::Error for WriteError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Call(error) => Some(error), + Self::TooManyBlocks { .. } | Self::BlockTooLong { .. } => None, + } + } +} + +#[cfg(test)] +mod tests; diff --git a/crates/win-etw-sys/src/registration/tests.rs b/crates/win-etw-sys/src/registration/tests.rs new file mode 100644 index 000000000..7eafc7a57 --- /dev/null +++ b/crates/win-etw-sys/src/registration/tests.rs @@ -0,0 +1,262 @@ +// Copyright (c) 2026 Mike Grier +//! A registration with no session listening (WE-D-2): what the pages say happens, which is what a process that no one +//! traces sees. Events that reach a session are `tests/session.rs`, and the call's failure is `tests/limit.rs`, which +//! needs a process of its own. + +use std::thread; + +use super::{Registration, WriteError}; +use crate::{EventDescriptor, Field, Guid, level}; +use win_error_codes::Win32Error; + +/// A provider no session has been told of, one for each test, so that tests in one process do not share an id. +const fn provider(n: u128) -> Guid { + Guid::from_u128(0x7a3c_91e0_5d24_4b8f_a1c6_0000_0000_0000 | n) +} + +fn event() -> EventDescriptor { + EventDescriptor { + id: 1, + level: level::INFO, + keyword: 1, + ..EventDescriptor::default() + } +} + +#[test] +fn a_provider_is_registered_and_unregistered() { + let registration = Registration::register(provider(1)).expect("registered"); + assert_eq!(registration.unregister(), Ok(())); +} + +#[test] +fn dropping_a_registration_unregisters_it_so_the_provider_can_be_registered_again() { + for _ in 0..20 { + let registration = Registration::register(provider(2)).expect("registered"); + drop(registration); + } +} + +#[test] +fn a_provider_registered_twice_is_two_registrations() { + let a = Registration::register(provider(3)).expect("registered"); + let b = Registration::register(provider(3)).expect("registered again"); + assert_ne!(a.handle, b.handle); + drop(a); + assert_eq!(b.write(&event(), &[]), Ok(())); +} + +#[test] +fn several_providers_are_registered_at_once() { + let all: Vec = (0..40) + .map(|n| Registration::register(provider(100 + n)).expect("registered")) + .collect(); + let mut handles: Vec = all.iter().map(|r| r.handle).collect(); + handles.sort_unstable(); + handles.dedup(); + assert_eq!(handles.len(), 40); +} + +#[test] +fn a_registration_is_a_nonzero_handle() { + let registration = Registration::register(provider(4)).expect("registered"); + assert_ne!(registration.handle, 0); +} + +#[test] +fn with_no_session_nothing_is_enabled() { + // Observed: the page says false "if ETW can quickly determine that no session is listening", and that true can be + // returned where it cannot, so this is evidence of what a provider no session has enabled reports. + let registration = Registration::register(provider(5)).expect("registered"); + assert!(!registration.is_enabled(&event())); + assert!(!registration.is_level_enabled(level::INFO, 1)); + assert!(!registration.is_level_enabled(level::LOG_ALWAYS, u64::MAX)); + assert!(!registration.is_level_enabled(level::VERBOSE, 0)); +} + +#[test] +fn with_no_session_a_write_does_nothing_and_succeeds() { + let registration = Registration::register(provider(6)).expect("registered"); + assert_eq!(registration.write(&event(), &[]), Ok(())); + assert_eq!( + registration.write( + &event(), + &[Field::U64(1), Field::Utf16Terminated(&[0x68, 0x69])] + ), + Ok(()) + ); +} + +#[test] +fn every_kind_of_field_is_written_with_no_failure() { + let registration = Registration::register(provider(7)).expect("registered"); + let text = [0x68_u16, 0x69]; + let fields = [ + Field::U8(1), + Field::I8(-1), + Field::U16(2), + Field::I16(-2), + Field::U32(3), + Field::I32(-3), + Field::U64(4), + Field::I64(-4), + Field::F32(1.5), + Field::F64(2.5), + Field::boolean(true), + Field::Pointer(0x10), + Field::Guid(Guid::from_u128(9)), + Field::Bytes(&[1, 2]), + Field::Utf16(&text), + Field::Utf16Terminated(&text), + Field::BytesTerminated(b"ok"), + ]; + assert_eq!(registration.write(&event(), &fields), Ok(())); +} + +#[test] +fn a_registration_is_shared_between_threads_that_write_through_it() { + fn shares() {} + shares::(); + let registration = Registration::register(provider(8)).expect("registered"); + thread::scope(|scope| { + for n in 0..8_u32 { + let registration = ®istration; + scope.spawn(move || { + for i in 0..50_u32 { + registration + .write(&event(), &[Field::U32(n), Field::U32(i)]) + .expect("written"); + let _ = registration.is_enabled(&event()); + } + }); + } + }); +} + +#[test] +fn a_registration_moves_between_threads_and_is_dropped_there() { + let registration = Registration::register(provider(9)).expect("registered"); + thread::spawn(move || { + assert_eq!(registration.write(&event(), &[]), Ok(())); + }) + .join() + .expect("the thread finished"); +} + +#[test] +fn a_write_with_a_zero_handle_would_do_nothing_but_the_type_never_holds_one() { + // A registration is only made from a successful call, whose handle the page says is nonzero, and `unregister` + // takes the handle out of the value it consumes. + let registration = Registration::register(provider(10)).expect("registered"); + assert_ne!(registration.handle, 0); +} + +#[test] +fn a_refusal_names_the_field_and_is_not_the_calls() { + let error = WriteError::BlockTooLong { field: 2, len: 5 }; + assert_eq!( + error.to_string(), + "field 2 is 5 bytes, more than an EVENT_DATA_DESCRIPTOR's size, a ULONG, can hold" + ); + assert!(std::error::Error::source(&error).is_none()); +} + +#[test] +fn too_many_blocks_reads_as_a_sentence_with_no_source() { + let error = WriteError::TooManyBlocks { count: 7 }; + assert_eq!( + error.to_string(), + "the fields are 7 blocks, more than the write call's count, a ULONG, can hold" + ); + assert!(std::error::Error::source(&error).is_none()); +} + +#[test] +fn a_calls_failure_is_its_code_and_is_the_errors_source() { + let error = WriteError::Call(Win32Error::from_code(87)); + assert!(error.to_string().starts_with("the write call failed: ")); + let source = std::error::Error::source(&error).expect("the call's code"); + assert_eq!(source.to_string(), Win32Error::from_code(87).to_string()); +} + +/// An activity id and a related one, as a caller would give them. +const ACTIVITY: Guid = Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0001); +const RELATED: Guid = Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0002); + +#[test] +fn with_no_session_a_transfer_does_nothing_and_succeeds_whichever_ids_are_given() { + let registration = Registration::register(provider(20)).expect("registered"); + for (activity, related) in [ + (None, None), + (Some(&ACTIVITY), None), + (None, Some(&RELATED)), + (Some(&ACTIVITY), Some(&RELATED)), + ] { + assert_eq!( + registration.write_transfer(&event(), activity, related, &[Field::U32(7)]), + Ok(()), + "{activity:?} {related:?}" + ); + } +} + +#[test] +fn a_transfer_takes_every_kind_of_field() { + let registration = Registration::register(provider(21)).expect("registered"); + let text: Vec = "seal".encode_utf16().collect(); + let fields = [ + Field::U8(1), + Field::U32(2), + Field::U64(3), + Field::boolean(true), + Field::Utf16Terminated(&text), + Field::Bytes(&[1, 2, 3]), + ]; + assert_eq!( + registration.write_transfer(&event(), Some(&ACTIVITY), None, &fields), + Ok(()) + ); +} + +#[test] +fn a_transfer_with_no_fields_succeeds() { + let registration = Registration::register(provider(22)).expect("registered"); + assert_eq!( + registration.write_transfer(&event(), Some(&ACTIVITY), Some(&RELATED), &[]), + Ok(()) + ); +} + +#[test] +fn one_activity_is_written_from_several_threads_through_one_registration() { + let registration = Registration::register(provider(23)).expect("registered"); + thread::scope(|scope| { + for n in 0..4_u32 { + let registration = ®istration; + scope.spawn(move || { + for i in 0..25_u32 { + registration + .write_transfer( + &event(), + Some(&ACTIVITY), + None, + &[Field::U32(n), Field::U32(i)], + ) + .expect("written"); + } + }); + } + }); +} + +#[test] +fn an_id_borrowed_for_the_call_alone_is_enough() { + let registration = Registration::register(provider(24)).expect("registered"); + for n in 0..16_u128 { + let activity = Guid::from_u128(n + 1); + assert_eq!( + registration.write_transfer(&event(), Some(&activity), None, &[]), + Ok(()) + ); + } +} diff --git a/crates/win-etw-sys/src/session.rs b/crates/win-etw-sys/src/session.rs new file mode 100644 index 000000000..a1808450a --- /dev/null +++ b/crates/win-etw-sys/src/session.rs @@ -0,0 +1,359 @@ +// Copyright (c) 2026 Mike Grier +//! A trace session a test starts, enables a provider in, and reads back (WE-D-3), so that a test, here or in another +//! crate, can see what a provider's events are when they reach a session by the documented route and not by a protocol +//! no page describes. +//! +//! This is a test facility and not a wrapper of the session functions: it is behind the `test-util` feature, it starts +//! a system-wide object, and the policy it carries (a unique name, a file in the temporary directory, stopped when +//! dropped) is a test's. It uses `StartTraceW` to start a session that logs to a file, `EnableTraceEx2` to enable a +//! provider in it, `ControlTraceW` to stop it, and then `OpenTraceW`, `ProcessTrace` and `CloseTrace` to read the file +//! back with the event-record callback. +//! +//! Starting a session is a privileged act: the calling process must be elevated, or its user in the Performance Log Users +//! group. [`Session::start`] says so in its error when it is not, and does not hide it. + +use std::ffi::c_void; +use std::io; +use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::OsStrExt; +use std::path::{Path, PathBuf}; +use std::ptr::null; +use std::sync::atomic::{AtomicU32, Ordering}; + +use windows_sys::Win32::System::Diagnostics::Etw::{ + CONTROLTRACE_HANDLE, CloseTrace, ControlTraceW, EVENT_CONTROL_CODE_ENABLE_PROVIDER, + EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID, EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID, + EVENT_HEADER_EXTENDED_DATA_ITEM, EVENT_RECORD, EVENT_TRACE_CONTROL_STOP, + EVENT_TRACE_FILE_MODE_SEQUENTIAL, EVENT_TRACE_LOGFILEW, EVENT_TRACE_PROPERTIES, EnableTraceEx2, + OpenTraceW, PROCESS_TRACE_MODE_EVENT_RECORD, PROCESSTRACE_HANDLE, ProcessTrace, StartTraceW, + WNODE_FLAG_TRACED_GUID, +}; + +use crate::{EventDescriptor, Guid}; + +/// `OpenTraceW`'s failure: "INVALID_PROCESSTRACE_HANDLE", which is all ones. +const INVALID_PROCESSTRACE: u64 = u64::MAX; + +/// A session's buffers, in kilobytes: small, and a few, since a test writes a handful of events. +const BUFFER_KB: u32 = 64; + +/// One event that a session recorded. +/// +/// Read only: a field may be added, which is why it cannot be built outside the crate. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub struct Recorded { + /// The provider's id. + pub provider: Guid, + /// The event's descriptor, as the provider gave it. + pub descriptor: EventDescriptor, + /// The event's user data: the blocks the provider wrote, concatenated. + pub data: Vec, + /// The id of the process that wrote it. + pub process_id: u32, + /// The event's activity id: the one the writer gave `EventWriteTransfer`, or the writing thread's. + pub activity: Guid, + /// The event's related activity id, if the writer gave one: the record's extended data item of type + /// `EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID`, which "contains the related activity identifier if you called + /// EventWriteTransfer to write the event". + pub related_activity: Option, +} + +/// A running session logging to a file. It is stopped, and its file removed, when this is dropped. +pub struct Session { + handle: CONTROLTRACE_HANDLE, + name: Vec, + file: PathBuf, + enabled: Vec, + stopped: bool, +} + +/// What distinguishes the sessions one process starts. +static NEXT: AtomicU32 = AtomicU32::new(0); + +impl Session { + /// Starts a session logging to a file in the temporary directory, with a name no other session of this process has. + /// + /// # Errors + /// + /// The operating system's. `ERROR_ACCESS_DENIED` is the usual one, and means this process is neither elevated nor + /// run by a member of the Performance Log Users group, which starting a session requires; the error's text says so. + pub fn start() -> io::Result { + let n = NEXT.fetch_add(1, Ordering::Relaxed); + let name = format!("win-etw-sys-test-{}-{n}", std::process::id()); + let file = std::env::temp_dir().join(format!("{name}.etl")); + let name: Vec = name.encode_utf16().chain([0]).collect(); + let file_units: Vec = file.as_os_str().encode_wide().chain([0]).collect(); + + // The properties are followed by the session's name and the log file's name, in one buffer, which the call + // fills in. It is 8-aligned: the structure holds 64-bit fields. + let names = (name.len() + file_units.len()) * size_of::(); + let total = size_of::() + names; + let mut buffer = vec![0_u64; total.div_ceil(size_of::())]; + let properties = buffer.as_mut_ptr().cast::(); + // SAFETY: the buffer is 8-aligned, zeroed, and at least the structure's size, so the structure is valid in it; + // the offsets are within the buffer and the names are copied to where they say, each with its terminator. + unsafe { + (*properties).Wnode.BufferSize = u32::try_from(total).expect("a small buffer"); + (*properties).Wnode.Flags = WNODE_FLAG_TRACED_GUID; + // The clock the events are stamped with: 1 is the performance counter. + (*properties).Wnode.ClientContext = 1; + (*properties).BufferSize = BUFFER_KB; + (*properties).MinimumBuffers = 2; + (*properties).LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; + let name_offset = size_of::(); + let file_offset = name_offset + name.len() * size_of::(); + (*properties).LoggerNameOffset = u32::try_from(name_offset).expect("small"); + (*properties).LogFileNameOffset = u32::try_from(file_offset).expect("small"); + let base = buffer.as_mut_ptr().cast::(); + std::ptr::copy_nonoverlapping( + name.as_ptr().cast::(), + base.add(name_offset), + name.len() * size_of::(), + ); + std::ptr::copy_nonoverlapping( + file_units.as_ptr().cast::(), + base.add(file_offset), + file_units.len() * size_of::(), + ); + } + let mut handle = CONTROLTRACE_HANDLE { Value: 0 }; + // SAFETY: the handle is valid for the call to write, the name is NUL-terminated and outlives the call, and the + // properties are the buffer built above, which the call may write to. + let code = unsafe { StartTraceW(&mut handle, name.as_ptr(), properties) }; + if code != 0 { + return Err(denied(code, "StartTraceW")); + } + Ok(Self { + handle, + name, + file, + enabled: Vec::new(), + stopped: false, + }) + } + + /// Enables `provider` in the session for events whose level is at most `level` and whose keyword shares a bit with + /// `match_any_keyword`, by `EnableTraceEx2`. + /// + /// # Errors + /// + /// The operating system's. + pub fn enable(&mut self, provider: Guid, level: u8, match_any_keyword: u64) -> io::Result<()> { + // SAFETY: the handle is the session's, and the GUID is valid for the call to read; the filter and parameters are + // null, which the call allows. + let code = unsafe { + EnableTraceEx2( + self.handle, + provider.raw(), + EVENT_CONTROL_CODE_ENABLE_PROVIDER, + level, + match_any_keyword, + 0, + 0, + null(), + ) + }; + if code != 0 { + return Err(denied(code, "EnableTraceEx2")); + } + self.enabled.push(provider); + Ok(()) + } + + /// Stops the session, reads its file back, and returns the events of the providers enabled in it, in the order they + /// were recorded. + /// + /// # Errors + /// + /// The operating system's, for the stop or for reading the file. The session is stopped and its file removed either + /// way. + pub fn stop(mut self) -> io::Result> { + self.stop_session()?; + let read = read_file(&self.file, &self.enabled); + let _ = std::fs::remove_file(&self.file); + read + } + + fn stop_session(&mut self) -> io::Result<()> { + if std::mem::replace(&mut self.stopped, true) { + return Ok(()); + } + let total = size_of::() + 2 * 1024; + let mut buffer = vec![0_u64; total.div_ceil(size_of::())]; + let properties = buffer.as_mut_ptr().cast::(); + // SAFETY: as in `start`: an aligned, zeroed buffer, large enough for the structure and the two names the call + // writes back, whose offsets are set so that it knows where. + let code = unsafe { + (*properties).Wnode.BufferSize = u32::try_from(total).expect("a small buffer"); + (*properties).LoggerNameOffset = + u32::try_from(size_of::()).expect("small"); + (*properties).LogFileNameOffset = + u32::try_from(size_of::() + 1024).expect("small"); + ControlTraceW( + self.handle, + self.name.as_ptr(), + properties, + EVENT_TRACE_CONTROL_STOP, + ) + }; + if code != 0 { + return Err(denied(code, "ControlTraceW")); + } + Ok(()) + } +} + +impl std::fmt::Debug for Session { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Session") + .field("file", &self.file) + .field("enabled", &self.enabled) + .field("stopped", &self.stopped) + .finish_non_exhaustive() + } +} + +impl Drop for Session { + fn drop(&mut self) { + if !self.stopped { + let _ = self.stop_session(); + let _ = std::fs::remove_file(&self.file); + } + } +} + +/// The error for a failed call, with what to do about the one that is usually the cause. +fn denied(code: u32, call: &str) -> io::Error { + const ACCESS_DENIED: u32 = 5; + let error = io::Error::from_raw_os_error(code.cast_signed()); + if code == ACCESS_DENIED { + io::Error::new( + error.kind(), + format!( + "{call} was denied: starting a trace session needs an elevated process, or a user in the Performance Log Users group ({error})" + ), + ) + } else { + io::Error::new(error.kind(), format!("{call} failed: {error}")) + } +} + +/// What the callback collects, behind the context pointer the log file carries. +struct Collected { + wanted: Vec, + events: Vec, +} + +/// Called by `ProcessTrace` for each event record. +/// +/// # Safety +/// +/// `record` is the record `ProcessTrace` hands to its callback, whose context is the `Collected` that `read_file` +/// gave `OpenTraceW` and which is not otherwise in use. +unsafe extern "system" fn on_event(record: *mut EVENT_RECORD) { + // SAFETY: the caller's. + let (header, user, length, extended, collected) = unsafe { + let record = &*record; + ( + record.EventHeader, + record.UserData.cast::(), + usize::from(record.UserDataLength), + extended_items(record), + &mut *record.UserContext.cast::(), + ) + }; + let provider = Guid::from_raw(header.ProviderId); + if !collected.wanted.contains(&provider) { + return; + } + let data = if length == 0 || user.is_null() { + Vec::new() + } else { + // SAFETY: the record's user data is `UserDataLength` bytes at `UserData`, valid for the callback. + unsafe { std::slice::from_raw_parts(user, length) }.to_vec() + }; + let d = header.EventDescriptor; + collected.events.push(Recorded { + provider, + descriptor: EventDescriptor { + id: d.Id, + version: d.Version, + channel: d.Channel, + level: d.Level, + opcode: d.Opcode, + task: d.Task, + keyword: d.Keyword, + }, + data, + process_id: header.ProcessId, + activity: Guid::from_raw(header.ActivityId), + // SAFETY: the items are the record's, valid for the callback. + related_activity: unsafe { related_activity(extended) }, + }); +} + +/// The record's extended data items. +/// +/// # Safety +/// +/// `record` is a record `ProcessTrace` handed to its callback, and the slice is used only while the callback runs. +unsafe fn extended_items(record: &EVENT_RECORD) -> &[EVENT_HEADER_EXTENDED_DATA_ITEM] { + let count = usize::from(record.ExtendedDataCount); + if count == 0 || record.ExtendedData.is_null() { + return &[]; + } + // SAFETY: the record's `ExtendedData` is `ExtendedDataCount` items, valid for the callback. + unsafe { std::slice::from_raw_parts(record.ExtendedData, count) } +} + +/// The related activity id among a record's extended data items, if it has one. +/// +/// # Safety +/// +/// The items are a record's, and each `DataPtr` is valid for the `DataSize` bytes it gives while the callback runs. +unsafe fn related_activity(items: &[EVENT_HEADER_EXTENDED_DATA_ITEM]) -> Option { + let item = items.iter().find(|item| { + u32::from(item.ExtType) == EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID + && usize::from(item.DataSize) >= size_of::() + })?; + // SAFETY: the page says `DataPtr` points to an `EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID` for this type, and its size + // was checked; the read is unaligned because the page promises no alignment. + let value = unsafe { + std::ptr::read_unaligned(item.DataPtr as *const EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID) + }; + Some(Guid::from_raw(value.RelatedActivityId)) +} + +/// Reads the events of `wanted` providers out of a session's log file. +fn read_file(path: &Path, wanted: &[Guid]) -> io::Result> { + let mut file: Vec = path.as_os_str().encode_wide().chain([0]).collect(); + let mut collected = Collected { + wanted: wanted.to_vec(), + events: Vec::new(), + }; + // SAFETY: all-zero is a valid `EVENT_TRACE_LOGFILEW`, which the call and the fields set below describe. + let mut log: EVENT_TRACE_LOGFILEW = unsafe { zeroed() }; + log.LogFileName = file.as_mut_ptr(); + log.Anonymous1.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; + log.Anonymous2.EventRecordCallback = Some(on_event); + log.Context = (&raw mut collected).cast::(); + // SAFETY: `log` is valid for the call, its file name is NUL-terminated and outlives it, and its context is the + // collection, which is not touched but by the callback until the trace is closed. + let handle = unsafe { OpenTraceW(&mut log) }; + if handle.Value == INVALID_PROCESSTRACE { + return Err(io::Error::last_os_error()); + } + // SAFETY: the handle is the one just opened, and the times are null, which the call allows. + let code = unsafe { ProcessTrace(&handle, 1, null(), null()) }; + // SAFETY: the handle is the one opened above, closed once. + unsafe { + CloseTrace(PROCESSTRACE_HANDLE { + Value: handle.Value, + }) + }; + if code != 0 { + return Err(denied(code, "ProcessTrace")); + } + Ok(collected.events) +} diff --git a/crates/win-etw-sys/tests/limit.rs b/crates/win-etw-sys/tests/limit.rs new file mode 100644 index 000000000..46890fee0 --- /dev/null +++ b/crates/win-etw-sys/tests/limit.rs @@ -0,0 +1,51 @@ +// Copyright (c) 2026 Mike Grier +//! The call's failure to register (WE-D-2): a process that keeps registering is eventually refused, and the refusal is the +//! call's code. +//! +//! The page says "Each process can register up to 1,024 providers". On the machine this was written on the call allowed +//! about twice that and then refused with `ERROR_OUTOFMEMORY`, so this test asserts no number: the page's limit is not a +//! contract this crate can check ahead of the call, and a measurement is documentation and never a check. What is asserted +//! is that the failure is reached, that it arrives as the call's code, that what was registered still works, and that +//! letting it go makes room again. +//! +//! # Why this is an integration test +//! +//! It takes every registration a process may have, which another test in the same process would find gone, so it needs a +//! process of its own, which an integration test file is. + +#![cfg(windows)] + +use win_etw_sys::{EventDescriptor, Guid, Registration}; + +/// Far more than the page's limit or what was observed, so that a process that is refused at all is refused here. +const ATTEMPTS: u128 = 200_000; + +#[test] +fn a_process_that_keeps_registering_is_refused_with_the_calls_code_and_recovers() { + let mut held = Vec::new(); + let mut refused = None; + for n in 0..ATTEMPTS { + match Registration::register(Guid::from_u128( + 0x5b21_c0de_0001_4e8a_8f10_0000_0000_0000 | n, + )) { + Ok(registration) => held.push(registration), + Err(code) => { + refused = Some(code); + break; + } + } + } + let code = refused.expect("the process was refused within the attempts"); + assert!(!code.is_success()); + assert!( + !held.is_empty(), + "nothing was registered before the refusal" + ); + + // What was held is still good, and letting it go makes room again. + let event = EventDescriptor::default(); + assert_eq!(held[0].write(&event, &[]), Ok(())); + assert_eq!(held[held.len() - 1].write(&event, &[]), Ok(())); + drop(held); + Registration::register(Guid::from_u128(1)).expect("room again once they are dropped"); +} diff --git a/crates/win-etw-sys/tests/session.rs b/crates/win-etw-sys/tests/session.rs new file mode 100644 index 000000000..5c6fc6f18 --- /dev/null +++ b/crates/win-etw-sys/tests/session.rs @@ -0,0 +1,565 @@ +// Copyright (c) 2026 Mike Grier +//! What a provider's events are when a session records them (WE-D-3): this test starts a real trace session logging to a +//! file, enables a provider in it, writes events through a `Registration`, stops the session, and reads the file back, +//! with the crate's `test-util` session, which uses only documented functions. +//! +//! # Why this is an integration test +//! +//! It starts a system-wide trace session and reads a file the operating system wrote: it crosses the boundary into the +//! operating system's tracing, which a unit test in memory cannot. +//! +//! # Privilege, and why these are ignored +//! +//! Starting a session needs an elevated process, or a user in the Performance Log Users group, which an ordinary +//! developer account is not. So these tests are ignored by default and a plain `cargo test` reports the environment +//! limitation as ignored and not as a defect, and CI runs them with `--include-ignored` on a runner that is an +//! administrator, where a failure is a result. Run them by hand from an elevated shell: +//! `cargo test -p win-etw-sys --test session -- --include-ignored`. + +#![cfg(windows)] + +use win_error_codes::Win32Error; +use win_etw_sys::{ + EventDescriptor, Field, Guid, Recorded, Registration, Session, WriteError, create_activity_id, + level, +}; + +/// A provider no other test of this binary uses: tests run in parallel, and a session records every event of a provider it +/// has enabled, so providers are not shared. +const fn provider(n: u128) -> Guid { + Guid::from_u128(0x3c0d_5f12_8a44_49b7_b2e1_0000_0000_0000 | n) +} + +const INFO_EVENT: EventDescriptor = EventDescriptor { + id: 10, + version: 2, + channel: 0, + level: level::INFO, + opcode: 0, + task: 3, + keyword: 0x4, +}; + +fn session() -> Session { + Session::start().expect("a trace session (see the privilege note in this file's documentation)") +} + +/// Whether `condition` becomes true within a bound: the provider's state follows a session's change, and is not set by the +/// time the call that made the change returns. +fn becomes(condition: impl Fn() -> bool) -> bool { + let started = std::time::Instant::now(); + while started.elapsed() < std::time::Duration::from_secs(10) { + if condition() { + return true; + } + std::thread::sleep(std::time::Duration::from_millis(5)); + } + false +} + +fn only(events: Vec) -> Recorded { + assert_eq!(events.len(), 1, "{events:?}"); + events.into_iter().next().expect("one") +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_event_written_to_an_enabled_provider_is_recorded_with_its_descriptor_and_data() { + let id = provider(1); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration + .write(&INFO_EVENT, &[Field::U32(0xdead_beef), Field::U8(7)]) + .expect("written"); + drop(registration); + let event = only(session.stop().expect("stopped and read")); + assert_eq!(event.provider, id); + assert_eq!(event.descriptor, INFO_EVENT); + assert_eq!( + event.data, + [0xdead_beef_u32.to_ne_bytes().as_slice(), &[7]].concat() + ); + assert_eq!(event.process_id, std::process::id()); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_provider_is_enabled_while_a_session_listens_and_not_before_or_after() { + let id = provider(2); + let registration = Registration::register(id).expect("registered"); + assert!(!registration.is_enabled(&INFO_EVENT), "before"); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + // Observed: a provider that was registered before a session enabled it is told so afterwards, not at the moment the + // call returns, so the state is waited for. + assert!( + becomes(|| registration.is_enabled(&INFO_EVENT)), + "never enabled while" + ); + assert!(registration.is_level_enabled(level::INFO, 0x4)); + drop(session.stop().expect("stopped")); + assert!( + becomes(|| !registration.is_enabled(&INFO_EVENT)), + "never disabled after" + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_session_at_a_level_does_not_enable_events_above_it() { + let id = provider(3); + let mut session = session(); + session.enable(id, level::WARNING, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + assert!(registration.is_level_enabled(level::ERROR, 0x4)); + assert!(registration.is_level_enabled(level::WARNING, 0x4)); + assert!(!registration.is_level_enabled(level::INFO, 0x4)); + assert!(!registration.is_level_enabled(level::VERBOSE, 0x4)); + let warning = EventDescriptor { + level: level::WARNING, + ..INFO_EVENT + }; + registration + .write(&INFO_EVENT, &[Field::U8(1)]) + .expect("written"); + registration + .write(&warning, &[Field::U8(2)]) + .expect("written"); + drop(registration); + let event = only(session.stop().expect("stopped")); + assert_eq!(event.data, [2]); + assert_eq!(event.descriptor.level, level::WARNING); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_session_with_a_keyword_records_only_events_that_share_a_bit_with_it() { + let id = provider(4); + let mut session = session(); + session.enable(id, level::VERBOSE, 0x2).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let wanted = EventDescriptor { + keyword: 0x3, + ..INFO_EVENT + }; + let other = EventDescriptor { + keyword: 0x4, + ..INFO_EVENT + }; + assert!(registration.is_enabled(&wanted)); + assert!(!registration.is_enabled(&other)); + registration + .write(&other, &[Field::U8(1)]) + .expect("written"); + registration + .write(&wanted, &[Field::U8(2)]) + .expect("written"); + drop(registration); + assert_eq!(only(session.stop().expect("stopped")).data, [2]); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_provider_that_was_not_enabled_is_not_recorded() { + let (enabled, other) = (provider(5), provider(6)); + let mut session = session(); + session + .enable(enabled, level::VERBOSE, 0xff) + .expect("enabled"); + let a = Registration::register(enabled).expect("registered"); + let b = Registration::register(other).expect("registered"); + assert!(!b.is_enabled(&INFO_EVENT)); + b.write(&INFO_EVENT, &[Field::U8(9)]).expect("written"); + a.write(&INFO_EVENT, &[Field::U8(1)]).expect("written"); + drop((a, b)); + let event = only(session.stop().expect("stopped")); + assert_eq!(event.provider, enabled); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn events_are_recorded_in_the_order_they_were_written() { + let id = provider(7); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + for n in 0..50_u32 { + registration + .write(&INFO_EVENT, &[Field::U32(n)]) + .expect("written"); + } + drop(registration); + let events = session.stop().expect("stopped"); + let seen: Vec = events + .iter() + .map(|e| u32::from_ne_bytes(e.data[..4].try_into().expect("four bytes"))) + .collect(); + assert_eq!(seen, (0..50).collect::>()); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn every_kind_of_field_is_recorded_as_the_bytes_it_was_written_as() { + let id = provider(8); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let guid = Guid::from_u128(0x1234_5678_9abc_def0_1122_3344_5566_7788); + let text = [0x68_u16, 0x69]; + let fields = [ + Field::U8(1), + Field::I8(-1), + Field::U16(2), + Field::I16(-2), + Field::U32(3), + Field::I32(-3), + Field::U64(4), + Field::I64(-4), + Field::F32(1.5), + Field::F64(2.5), + Field::boolean(true), + Field::Pointer(0x10), + Field::Guid(guid), + Field::Bytes(&[9, 8]), + Field::Utf16(&text), + Field::Utf16Terminated(&text), + Field::BytesTerminated(b"ok"), + ]; + registration.write(&INFO_EVENT, &fields).expect("written"); + drop(registration); + let mut expected = vec![1, 0xff]; + expected.extend(2_u16.to_ne_bytes()); + expected.extend((-2_i16).to_ne_bytes()); + expected.extend(3_u32.to_ne_bytes()); + expected.extend((-3_i32).to_ne_bytes()); + expected.extend(4_u64.to_ne_bytes()); + expected.extend((-4_i64).to_ne_bytes()); + expected.extend(1.5_f32.to_ne_bytes()); + expected.extend(2.5_f64.to_ne_bytes()); + expected.extend(1_u32.to_ne_bytes()); + expected.extend(0x10_usize.to_ne_bytes()); + expected.extend(0x1234_5678_u32.to_ne_bytes()); + expected.extend(0x9abc_u16.to_ne_bytes()); + expected.extend(0xdef0_u16.to_ne_bytes()); + expected.extend([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + expected.extend([9, 8]); + for unit in text { + expected.extend(unit.to_ne_bytes()); + } + for unit in text { + expected.extend(unit.to_ne_bytes()); + } + expected.extend([0, 0]); + expected.extend([b'o', b'k', 0]); + assert_eq!(only(session.stop().expect("stopped")).data, expected); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_event_with_no_fields_is_recorded_with_no_data() { + let id = provider(9); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration.write(&INFO_EVENT, &[]).expect("written"); + drop(registration); + assert!(only(session.stop().expect("stopped")).data.is_empty()); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn two_sessions_each_record_the_same_event() { + let id = provider(10); + let (mut a, mut b) = (session(), session()); + a.enable(id, level::VERBOSE, 0xff).expect("enabled"); + b.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration + .write(&INFO_EVENT, &[Field::U8(5)]) + .expect("written"); + drop(registration); + assert_eq!(only(a.stop().expect("stopped")).data, [5]); + assert_eq!(only(b.stop().expect("stopped")).data, [5]); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn threads_write_through_one_registration_and_every_event_is_recorded() { + let id = provider(11); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + std::thread::scope(|scope| { + for t in 0..4_u8 { + let registration = ®istration; + scope.spawn(move || { + for i in 0..25_u8 { + registration + .write(&INFO_EVENT, &[Field::U8(t), Field::U8(i)]) + .expect("written"); + } + }); + } + }); + drop(registration); + let mut seen: Vec<(u8, u8)> = session + .stop() + .expect("stopped") + .iter() + .map(|e| (e.data[0], e.data[1])) + .collect(); + seen.sort_unstable(); + let expected: Vec<(u8, u8)> = (0..4).flat_map(|t| (0..25).map(move |i| (t, i))).collect(); + assert_eq!(seen, expected); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_event_larger_than_the_page_allows_is_the_calls_failure_and_is_not_recorded() { + // The page lists `ERROR_ARITHMETIC_OVERFLOW` for "The event size is larger than the allowed maximum (64KB - header)" and + // `ERROR_MORE_DATA` for "The session buffer size is too small for the event". The session's buffers are 64 KB, so + // either fits; which the call gave is what was observed. + let id = provider(12); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let big = vec![0_u8; 70_000]; + let result = registration.write(&INFO_EVENT, &[Field::Bytes(&big)]); + assert!(matches!(result, Err(WriteError::Call(_))), "{result:?}"); + if let Err(WriteError::Call(code)) = result { + const ERROR_MORE_DATA: u32 = 234; + const ERROR_ARITHMETIC_OVERFLOW: u32 = 534; + assert!( + [ERROR_MORE_DATA, ERROR_ARITHMETIC_OVERFLOW].contains(&code.code()), + "{code}" + ); + } + drop(registration); + assert!(session.stop().expect("stopped").is_empty()); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn more_blocks_than_the_page_gives_as_the_maximum_are_the_calls_to_judge() { + // The page gives `UserDataCount`'s maximum as 128 and this crate passes however many there are. What the call + // made of 129 blocks on the machine this ran on is what is asserted: evidence, and not a contract. + let id = provider(13); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let fields = vec![Field::U8(1); 129]; + let result = registration.write(&INFO_EVENT, &fields); + // `ERROR_INVALID_PARAMETER` is 87, which the page lists for "One or more of the parameters is not valid". + assert_eq!(result, Err(WriteError::Call(Win32Error::from_code(87)))); + drop(registration); + assert!(session.stop().expect("stopped").is_empty()); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_hundred_and_twenty_eight_blocks_are_one_event_of_that_many_bytes() { + let id = provider(14); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let fields: Vec> = (0..128_u8).map(Field::U8).collect(); + registration.write(&INFO_EVENT, &fields).expect("written"); + drop(registration); + assert_eq!( + only(session.stop().expect("stopped")).data, + (0..128).collect::>() + ); +} + +/// A pair's two events, as a provider that pairs them writes them: a start and a stop, opcodes 1 and 2. +const START_EVENT: EventDescriptor = EventDescriptor { + opcode: 1, + ..INFO_EVENT +}; +const STOP_EVENT: EventDescriptor = EventDescriptor { + opcode: 2, + id: 11, + ..INFO_EVENT +}; + +const ACTIVITY: Guid = Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0001); +const RELATED: Guid = Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0002); + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_begin_and_its_end_written_on_two_threads_carry_the_activity_the_caller_gave() { + let id = provider(30); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + let (began, ended) = std::thread::scope(|scope| { + let began = scope + .spawn(|| { + registration + .write_transfer( + &START_EVENT, + Some(&ACTIVITY), + Some(&RELATED), + &[Field::U8(1)], + ) + .expect("the begin"); + std::thread::current().id() + }) + .join() + .expect("the first thread"); + let ended = scope + .spawn(|| { + registration + .write_transfer(&STOP_EVENT, Some(&ACTIVITY), None, &[Field::U8(2)]) + .expect("the end"); + std::thread::current().id() + }) + .join() + .expect("the second thread"); + (began, ended) + }); + assert_ne!(began, ended, "two threads"); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 2, "{events:?}"); + assert_eq!( + events + .iter() + .map(|e| ( + e.descriptor.opcode, + e.activity, + e.related_activity, + e.data[0] + )) + .collect::>(), + [(1, ACTIVITY, Some(RELATED), 1), (2, ACTIVITY, None, 2)] + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn an_activity_given_is_recorded_and_the_writing_threads_own_is_left_as_it_was() { + let id = provider(31); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration + .write(&INFO_EVENT, &[Field::U8(1)]) + .expect("before"); + registration + .write_transfer(&INFO_EVENT, Some(&ACTIVITY), None, &[Field::U8(2)]) + .expect("given"); + registration + .write(&INFO_EVENT, &[Field::U8(3)]) + .expect("after"); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 3, "{events:?}"); + let thread_own = events[0].activity; + assert_ne!(thread_own, ACTIVITY); + assert_eq!( + events.iter().map(|e| e.activity).collect::>(), + [thread_own, ACTIVITY, thread_own] + ); + assert!( + events.iter().all(|e| e.related_activity.is_none()), + "{events:?}" + ); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn no_activity_given_is_the_threads_as_a_plain_write_takes_it() { + let id = provider(32); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration.write(&INFO_EVENT, &[]).expect("plain"); + registration + .write_transfer(&INFO_EVENT, None, None, &[]) + .expect("transfer"); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 2, "{events:?}"); + assert_eq!(events[0].activity, events[1].activity); + assert_eq!(events[1].related_activity, None); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn a_related_activity_is_recorded_without_an_activity_given() { + let id = provider(33); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration.write(&INFO_EVENT, &[]).expect("plain"); + registration + .write_transfer(&INFO_EVENT, None, Some(&RELATED), &[]) + .expect("transfer"); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 2, "{events:?}"); + assert_eq!(events[1].activity, events[0].activity, "the thread's"); + assert_eq!(events[1].related_activity, Some(RELATED)); +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn threads_writing_activities_of_their_own_each_record_theirs() { + let id = provider(34); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + std::thread::scope(|scope| { + for t in 0..4_u8 { + let registration = ®istration; + scope.spawn(move || { + let activity = + Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0100 + u128::from(t)); + for i in 0..25_u8 { + registration + .write_transfer( + &INFO_EVENT, + Some(&activity), + None, + &[Field::U8(t), Field::U8(i)], + ) + .expect("written"); + } + }); + } + }); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 100, "every event"); + for event in &events { + let expected = + Guid::from_u128(0x0a11_0000_0000_4000_8000_0000_0000_0100 + u128::from(event.data[0])); + assert_eq!(event.activity, expected, "{event:?}"); + } +} + +#[test] +#[ignore = "starts a trace session, which needs an elevated process or the Performance Log Users group: run with --include-ignored, as CI does"] +fn creating_an_activity_id_leaves_the_threads_own_as_a_plain_write_records_it() { + let id = provider(35); + let mut session = session(); + session.enable(id, level::VERBOSE, 0xff).expect("enabled"); + let registration = Registration::register(id).expect("registered"); + registration.write(&INFO_EVENT, &[]).expect("before"); + let created = create_activity_id().expect("created"); + registration.write(&INFO_EVENT, &[]).expect("after"); + registration + .write_transfer(&INFO_EVENT, Some(&created), None, &[]) + .expect("in the created activity"); + drop(registration); + let events = session.stop().expect("stopped and read"); + assert_eq!(events.len(), 3, "{events:?}"); + assert_eq!( + events[0].activity, events[1].activity, + "the thread's, unchanged" + ); + assert_ne!(events[1].activity, created); + assert_eq!(events[2].activity, created); +} diff --git a/crates/win-handles-sys/CHECKLIST.md b/crates/win-handles-sys/CHECKLIST.md index 18c54a65d..ae3a051a6 100644 --- a/crates/win-handles-sys/CHECKLIST.md +++ b/crates/win-handles-sys/CHECKLIST.md @@ -3,16 +3,7 @@ Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md). The crate was made by `durable-ioring`'s `DI-14.8.3`, moving `win-sync-sys`'s `duplicate_handle_with_access` and `Access` here. The area is the five functions Microsoft Learn lists under *Handle and Object Functions* -in the Win32 reference; one is built, and the rest are queued. +in the Win32 reference. The items below say which are built. -- [x] **WH-1.1** -- `GetHandleInformation` as a safe query. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wh-11) - -- [ ] **WH-1.2** -- `SetHandleInformation`, the write half of the query above, taking the same typed flags and - a mask of which are changed. It is documented under the same Learn section, so it belongs here. - -- [ ] **WH-1.3** -- `CompareObjectHandles`: whether two handles name the same kernel object. It is listed in the - same section, and the tests of this crate already call it raw (`same_object` in `duplicate/tests.rs`), so a - safe form lets them stop. - -- [ ] **WH-1.4** -- `CloseHandle`, taking an `OwnedHandle` and reporting the error the call can return, which - dropping one cannot. It is the last of the five the section lists. +Nothing is pending. `WH-1.1` to `WH-1.4`, the five functions, are complete and archived in +[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). diff --git a/crates/win-handles-sys/COMPLETED-CHECKLIST.md b/crates/win-handles-sys/COMPLETED-CHECKLIST.md index d8b85642d..69709dd0d 100644 --- a/crates/win-handles-sys/COMPLETED-CHECKLIST.md +++ b/crates/win-handles-sys/COMPLETED-CHECKLIST.md @@ -8,3 +8,44 @@ Append-only. `get_handle_information` and `HandleFlags`, built as `durable-ioring`'s `DI-14.8.4`. What was measured is in [DESIGN-NOTES.md](DESIGN-NOTES.md) `WH-D-2`. + +## Moved 2026-10-21 22:06:12 -04:00 -- the write half of the query + +### WH-1.2 -- `SetHandleInformation`, the write half of the query, taking the same typed flags and a mask of which are changed. *(completed 2026-10-21 22:06:12 -04:00)* + +The item as written: + +**WH-1.2** -- `SetHandleInformation`, the write half of the query above, taking the same typed flags and + a mask of which are changed. It is documented under the same Learn section, so it belongs here. + +`set_handle_information(handle, mask, flags)`, with nothing refused ahead of the call, read from the page and measured on a real +handle. The reading, the measurements, and what the sweep does and cannot pin are [WH-D-3](DESIGN-NOTES.md#wh-d-3). + +## Moved 2026-10-21 22:19:12 -04:00 -- the comparison of two handles + +### WH-1.3 -- `CompareObjectHandles`: whether two handles name the same kernel object, as `compare_object_handles`, and this crate's tests no longer call it raw. *(completed 2026-10-21 22:19:12 -04:00)* + +The item as written: + +**WH-1.3** -- `CompareObjectHandles`: whether two handles name the same kernel object. It is listed in the + same section, and the tests of this crate already call it raw (`same_object` in `duplicate/tests.rs`), so a + safe form lets them stop. + +`compare_object_handles(first, second) -> bool`, read from the page and measured on real handles. The duplicate's tests, which +called the raw function through a helper of their own, call it now. Why the answer is a `bool` and not a `Result`, and what was +measured, are [WH-D-4](DESIGN-NOTES.md#wh-d-4). + +## Moved 2026-10-21 22:27:07 -04:00 -- the close of a handle, and the checklist is complete + +### WH-1.4 -- `CloseHandle`, taking an `OwnedHandle` and reporting the error the call can return, which dropping one cannot. *(completed 2026-10-21 22:27:07 -04:00)* + +The item as written: + +**WH-1.4** -- `CloseHandle`, taking an `OwnedHandle` and reporting the error the call can return, which + dropping one cannot. It is the last of the five the section lists. + +`close_handle(handle: OwnedHandle) -> io::Result<()>`, read from the page and measured on real handles. It takes the handle whether or +not the call succeeds and does not give it back, which is [WH-D-5](DESIGN-NOTES.md#wh-d-5)'s ground and its cost. + +The checklist is complete: [WH-1.1](#wh-11), [WH-1.2](#wh-12), [WH-1.3](#wh-13) and WH-1.4 are the five functions the Learn section +lists, and nothing is parked. diff --git a/crates/win-handles-sys/COMPLETED-PLANS.md b/crates/win-handles-sys/COMPLETED-PLANS.md new file mode 100644 index 000000000..45e119259 --- /dev/null +++ b/crates/win-handles-sys/COMPLETED-PLANS.md @@ -0,0 +1,9 @@ +# Completed plans: win-handles-sys + +Checklists whose planned work is complete. A checklist reappears in +[PLANS.md](PLANS.md) if new work is planned against it; the row here stays as the record of the work that was +finished. Individual items are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + +| Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | +|---|---|---|---| +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-21 | Memory-safe Rust over the Win32 handle and object API, the Learn section *System Services > Windows System Information > Handles and Objects*: duplicating a handle with a chosen access (moved here from `win-sync-sys`), reading and setting a handle's flags, asking whether two handles name one object, and closing a handle with its result. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-handles-sys/Cargo.toml b/crates/win-handles-sys/Cargo.toml index 6cf347d2f..414f337f1 100644 --- a/crates/win-handles-sys/Cargo.toml +++ b/crates/win-handles-sys/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true repository.workspace = true homepage.workspace = true readme = "README.md" -description = "Memory-safe Rust over the Win32 handle and object API: duplicating a handle with a chosen access, with a typed access mask, and reading a handle's flags. Thin over Win32, adding no policy." +description = "Memory-safe Rust over the Win32 handle and object API: duplicating a handle with the access kept or chosen and its inheritance chosen, with a typed access mask, and reading and setting a handle's flags, asking whether two handles name one object, and closing a handle with its result. Thin over Win32, adding no policy." keywords = ["windows", "handle", "duplicatehandle", "ffi"] categories = ["api-bindings", "os::windows-apis"] diff --git a/crates/win-handles-sys/DESIGN-NOTES.md b/crates/win-handles-sys/DESIGN-NOTES.md index 8fd22ad20..c7a8c460e 100644 --- a/crates/win-handles-sys/DESIGN-NOTES.md +++ b/crates/win-handles-sys/DESIGN-NOTES.md @@ -37,3 +37,155 @@ not inheritable even when its source is; and protecting a handle from close read and clears when the protection is removed. The tests set the flags through raw `SetHandleInformation`, so the query is never checked against itself, and the protection is cleared by a guard before the handle is dropped, since closing a protected handle is refused. + +## WH-D-3: `set_handle_information` takes the page's mask and flags, and refuses nothing + +**Read 2026-10-21** from the page for `SetHandleInformation` (`ms.date` 2018-12-05, `updated_at` 2024-02-22), beside +`GetHandleInformation`'s (`ms.date` 2018-12-05). The call takes a handle, `dwMask` ("A mask that specifies the bit flags to be +changed") and `dwFlags` ("This parameter can be 0 or one or more of the following values"), and its one remark is "To set or +clear the associated bit flag in dwFlags, you must set a change mask bit flag in dwMask". + +**The function is the call.** `set_handle_information(handle, mask, flags)` takes the same `HandleFlags` the query returns, in +the call's order, and returns `io::Result<()>`; a flag in the mask is set if `flags` has it and cleared if it does not. The handle +is `impl AsHandle`, as the query's is. The crate adds no convenience over it: a caller who wants to set one flag passes it as the +mask and as the flags, which the example shows. + +**Nothing is refused ahead of the call.** The page calls no value of either argument invalid, so none is refused. A flag in +`flags` and not in `mask` is the page's "you must set a change mask bit": the page says what changes, and a flag that is passed +and not applied is not an invalid argument, so it is passed through and documented. A bit the page does not name is the call's +to judge, as it is for the query's result ([WH-D-2](#wh-d-2)), and is not narrowed. + +**Measured on Windows 11 build 26300, 2026-10-21, none of it a ruling.** Through raw calls on an event handle, over every start +state, mask and flags of the two named flags (sixty-four), the result was `(start & !mask) | (flags & mask)` each time. A bit the +page does not name, in the mask, in the flags, or in both and with every bit set, was accepted and changed no named flag. The +flags belong to the handle: setting one on a duplicate left the source unchanged, and the other way round. The current +process's pseudo-handle, which `std` lets a `BorrowedHandle` hold, was refused by the set with `ERROR_ACCESS_DENIED` (5), where +the query on it succeeded and read no flag; so the set's error path has a test, which the query's has not. `CloseHandle` on a +handle protected from close, with no debugger attached, returned `FALSE` with `ERROR_INVALID_HANDLE` (6) and left the handle +open with the flag still set, which is what the page says of the flag ("calling the CloseHandle function will not close the +object handle"); so an `OwnedHandle` dropped while protected stays open, which the function's documentation says. No test closes a +protected handle, because a debugger makes that raise an exception, which was not measured. + +**The tests** read what the setter did through raw `GetHandleInformation`, as the query's set it through the raw setter, so +neither is checked against the other, and a protection is cleared by a guard before its handle is dropped. The sweep +([sabotage.json](sabotage.json)) catches a mask not passed, flags not passed, the two passed in each other's places, and a refused +call read as success. Its control passes `flags & mask`, which the platform applies anyway. Its declared blind spot is narrowing a +bit the platform does not name before the call: the platform gives such a bit no effect, so the narrowing cannot be seen, and +the claim that the crate does not narrow is the documentation's. + +**Scheduled work.** None; `WH-1.3` and `WH-1.4` are the checklist's. + +## WH-D-4: `compare_object_handles` returns the call's answer as a `bool` + +**Read 2026-10-21** from the page for `CompareObjectHandles` (`ms.date` 2018-12-05, `updated_at` 2024-08-01). It returns "A +Boolean value that indicates if the two handles refer to the same underlying kernel object. TRUE if the same, otherwise FALSE." +and lists no failure and no use of `GetLastError`. Its remarks say it works "without imposing a requirement that specific access +rights be granted to either handle", give "whether a process handle is a handle to the current process" as the use, and its example +says of two objects of different types that the comparison "is legal to make, though the function will always return FALSE and +indicate a last error status of ERROR_NOT_SAME_OBJECT". The page gives Windows 10 and Windows Server 2016 as the first that have +the call, and the binding reaches it through the `api-ms-win-core-handle-l1-1-0` API set, not `kernel32`. + +**The function is the call, and it returns a `bool`.** `compare_object_handles(first, second)` takes two `impl AsHandle` and is +`#[must_use]`. The answer is the page's: the page documents a `TRUE` and a `FALSE` and no failure, and both handles are open for +their borrows, so the one way a comparison could fail, a handle that is not open, cannot be made through the types. + +**Why not `io::Result`.** The last error does separate the two kinds of `FALSE`, as measured below, and a function that +read it would return an error for a handle that is not open. That would put this crate's classification of a code on top of a page +that ties `ERROR_NOT_SAME_OBJECT` to different types only, to guard a case the borrows have excluded, and a caller would meet a +`Result` that never fails. The page's own contract is a `bool`. Changing it later to a `Result` would be a breaking change, which +is the price of this choice. + +**Measured on Windows 11 build 26300, 2026-10-21, none of it a ruling.** A handle against itself, a duplicate against its source +in either order, a duplicate that grants no access, a named event made twice, and the current process's pseudo-handle against a real +handle to it in either order were the same object. Two anonymous events, an event and a mutex, an event and the process +pseudo-handle, and the process's pseudo-handle against the thread's were not. A file and its duplicate were the same object, and +one file opened twice was two, since each open makes a file object of its own. The `FALSE` for two valid handles of different +objects, of one type or two, set `ERROR_NOT_SAME_OBJECT` (1656). The `FALSE` for a null handle, a value that was never a handle, +and the value of a handle just closed set `ERROR_INVALID_HANDLE` (6), and the pseudo-handle `INVALID_HANDLE_VALUE` against itself +was the same object. These are the unit tests' cases, except the invalid handles, which the types do not let a test make. + +**The tests** make their objects with raw calls and their duplicates with `std`'s `try_clone`, and not with this crate's +`duplicate_handle_with_access`, so the two are not checked against each other; the duplicate's own tests now ask +`compare_object_handles` what they used to ask the raw call. A test of all seven pairs in three groups of duplicates holds the +answer, its symmetry and its transitivity at once. The sweep ([sabotage.json](sabotage.json)) catches an inverted answer, one that +is always yes, one that is always no, and the first handle compared with itself; its control writes the test of the result +against a literal zero. + +**Scheduled work.** None; `WH-1.4` is the checklist's. + +## WH-D-5: `close_handle` takes the handle and does not give it back, and reports what `CloseHandle` returned + +**Read 2026-10-21** from the page for `CloseHandle` (`ms.date` 2018-12-05, `updated_at` 2024-02-22). The parameter is "A valid handle +to an open object"; a failure is a zero return with extended error information from `GetLastError`; "If the application is running +under a debugger, the function will throw an exception if it receives either a handle value that is not valid or a pseudo-handle +value." The page lists the kinds of object the call closes, and says not to use it for a socket, for a handle from `FindFirstFile`, +or for a registry key, of which it says "CloseHandle does not close the handle to the registry key, but does not return an error to +indicate this failure". It says nothing of what state a handle is in after a failed close. + +**The function.** `close_handle(handle: OwnedHandle) -> io::Result<()>` takes the handle out of its owner with `into_raw_handle`, so +the owner's drop cannot close it again, and makes the call. It is what dropping an `OwnedHandle` does, with the result kept: a drop +cannot report a failure, which is the one thing this adds. A `File` and the other handle owners convert to an `OwnedHandle` by +`From`, so a caller writes `close_handle(file.into())`. + +**The handle is taken whether or not the call succeeds, and is not given back.** The page is silent on the state after a failure, so +a failure cannot be turned into a handle the caller may keep: one that was not open, dropped, would close a value that may by then be +another handle's, which is the double close the type exists to prevent. That is an analytic ground and not a measurement, and it +forecloses a caller retrying a close of a handle protected from close without the flag cleared. The way to meet that case is to clear +the flag with `set_handle_information` before the close, which the function's documentation says. A handle that did fail to close and +stayed open stays open for the life of the process. + +**Measured on Windows 11 build 26300 with no debugger attached, 2026-10-21, none of it a ruling.** Closing an open event succeeded. +Closing the last handle to a named event removed the object, and closing one of two left it, as the page says of "the last handle". +Closing a value twice, a null handle and a value that was never a handle failed with `ERROR_INVALID_HANDLE` (6), and a handle +protected from close failed with the same code and was still open, with its flag set, afterward; clearing the flag let it close. +The pseudo-handles `-1` and `-2` "succeeded" with no error, which the page does not describe and no `OwnedHandle` holds. A +debugger's exception, which the page describes for an invalid or a pseudo handle, was not measured, nor whether one is raised for a +protected handle; the test of a protected handle is for a run with none attached. + +**The tests** never ask a closed handle's value anything, since another test's thread may have been given it: what a close did is +read from named events, whose object is removed with its last handle, and a protected handle is reclaimed by its raw value only +because the close failed and left it open. The error path is the protected handle, the one failure a safe caller can reach. The +sweep ([sabotage.json](sabotage.json)) catches a failed close read as success, a close that does nothing, a success read as failure, +and an error that is not the call's; its control writes the test of the result against a literal zero. A handle closed twice is not +injected: the second close would act on a value another thread may have been given. + +**Scheduled work.** None; the checklist is complete. + +## WH-D-6: `duplicate_handle` chooses the access and the inheritance, and is the one `DuplicateHandle` call + +**Read 2026-10-21** from the page for `DuplicateHandle` (`ms.date` 2018-12-05, `updated_at` 2025-05-21). `dwDesiredAccess` "is ignored +if the *dwOptions* parameter specifies the DUPLICATE_SAME_ACCESS flag"; `bInheritHandle`, if `TRUE`, makes the duplicate one "that can +be inherited by new processes created by the target process", and if `FALSE` one that cannot; and a process "can use DuplicateHandle +to create a noninheritable duplicate of an inheritable handle, or a handle with different access than the original handle". Both +parameters are on the page for a same-process duplicate, and nothing on it calls either value invalid. + +**The function.** `duplicate_handle(handle, DuplicateAccess, Inheritance) -> io::Result`. `DuplicateAccess::Same` is +`DUPLICATE_SAME_ACCESS`; `DuplicateAccess::Chosen(Access)` is a mask, as `duplicate_handle_with_access` always was. `Inheritance` is +`NotInheritable` or `Inheritable`. Each is an enum and not a `bool` or a pair of flags, so a call reads at the site without the +reader looking up which argument is which, and `Same` is a case of its own and not a mask of nothing, which would be a handle that +can do nothing. Both stay in the process: a duplicate into another process is not offered here. + +**Why the crate grew it.** Three crates each wrote their own same-process `DuplicateHandle` -- `windows-file-watcher`'s queue and +`windows-waitable-queues`' doorbell, both with the source's access and not inheritable, and `win-job-launcher`'s redirected standard +handles, with the source's access and inheritable. None could use `duplicate_handle_with_access`, which took a mask and was never +inheritable. Two of the three do what `BorrowedHandle::try_clone_to_owned` does; they use this function anyway, so that the +repository's duplicate is one call with one set of tests and one place to read what its attributes are, and not a fact about a +standard library's internals that three crates happen to share. + +**`duplicate_handle_with_access` is the same call.** It passes `Chosen` and `NotInheritable` to `duplicate_handle`, so the process has +one `DuplicateHandle` site and the two cannot disagree. Its signature and behaviour are unchanged, and the entries of +[sabotage.json](sabotage.json) that named its body name the general function's. + +**Nothing is refused ahead of the call.** A mask Windows refuses (every bit set) is the call's error, `ERROR_ACCESS_DENIED`, as it +was. The test of that goes through both inheritance values. There is no failing `Same` case a safe caller can build, since the handle +is an open `AsHandle`. + +**The tests and the sweep** read a duplicate's access by what it can do to an event (wait, set, reset), and its inheritance by +`get_handle_information`, never by anything this function reports. A same-access duplicate is made of a full, a wait-only and a +set-only source, and an inheritable source is duplicated to a plain handle and back. The sweep catches a same-access duplicate with +no access, an inheritable one that is not, and the with-access form passing the wrong access or inheritance; its control writes a +nonzero mask beside the same-access flag, which the page says is ignored. Whether an inheritable duplicate is received by a child +process is the crate that creates one's to test, which `win-job-launcher` does. + +**Scheduled work.** None. The three crates' use of it was `M44.2` to `M44.4`, done and archived in +[COMPLETED-CHECKLIST.md](../../COMPLETED-CHECKLIST.md). diff --git a/crates/win-handles-sys/PLANS.md b/crates/win-handles-sys/PLANS.md index 3ddf1186c..91dd63b79 100644 --- a/crates/win-handles-sys/PLANS.md +++ b/crates/win-handles-sys/PLANS.md @@ -1,5 +1,7 @@ # Plans: win-handles-sys +Completed checklists are recorded in [COMPLETED-PLANS.md](COMPLETED-PLANS.md), and the items they +contained are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 handle and object API, the Learn section *System Services > Windows System Information > Handles and Objects*: duplicating a handle with a chosen access, moved here from `win-sync-sys`, and the handle-information queries. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-handles-sys/README.md b/crates/win-handles-sys/README.md index 4f51f8310..dffd3a3a8 100644 --- a/crates/win-handles-sys/README.md +++ b/crates/win-handles-sys/README.md @@ -3,10 +3,20 @@ Memory-safe Rust over the Win32 handle and object API, the area Microsoft Learn files under *System Services > Windows System Information > Handles and Objects*. Thin over Win32, adding no policy. -- `duplicate_handle_with_access` is `DuplicateHandle` over any handle, in this process, with the access mask - chosen. It returns an `OwnedHandle`, so the duplicate is closed when it is dropped and nothing else has to. +- `duplicate_handle` is `DuplicateHandle` over any handle, in this process, with the access either the source's or a mask chosen, + and whether a child process can inherit the duplicate chosen. `duplicate_handle_with_access` is the same call with a mask and no + inheritance, and the rest of this bullet is true of both. It returns an `OwnedHandle`, so the duplicate is closed when it is dropped and nothing else has to. - `get_handle_information` is `GetHandleInformation`: the `HandleFlags` a handle carries, whether it is inheritable and whether closing it is refused. A bit Windows defines that is not named is kept, not refused. +- `set_handle_information` is `SetHandleInformation`: the same `HandleFlags`, with a mask of which are changed. A flag in the + mask is set if the flags have it and cleared if they do not, and one outside the mask is left as it was. Nothing is refused + before the call. A handle protected from close is not closed by dropping it, so clear the flag first. +- `compare_object_handles` is `CompareObjectHandles`: whether two handles name the same kernel object, which needs no access + right on either. It returns the call's `TRUE` or `FALSE` as a `bool`. A duplicate is the same object; two opens of one file are + not. It needs Windows 10 or Windows Server 2016. +- `close_handle` is `CloseHandle` over an `OwnedHandle`: what dropping one does, with the result kept, for a caller that needs to + know the close worked. The handle is taken whether or not it succeeds and is not given back; one protected from close fails to + close and stays open, so clear the flag first. - `Access` is the mask, a newtype over the Win32 bits combined with `|`. `SYNCHRONIZE` and `EVENT_MODIFY_STATE` are named; any other right is `Access::from_bits`. @@ -30,6 +40,23 @@ drop(waiter); # Ok::<(), std::io::Error>(()) ``` +A duplicate that keeps the source's access, and that a child process can inherit: + +```rust +use std::fs::File; + +use win_handles_sys::{DuplicateAccess, Inheritance, duplicate_handle}; + +let path = std::env::temp_dir().join(format!("wh-doc-inherit-{}.tmp", std::process::id())); +let file = File::create(&path)?; + +let for_a_child = duplicate_handle(&file, DuplicateAccess::Same, Inheritance::Inheritable)?; +drop(for_a_child); +# drop(file); +# std::fs::remove_file(&path)?; +# Ok::<(), std::io::Error>(()) +``` + The crate was made by moving this code out of `win-sync-sys`. From that crate's release that depends on this one, it re-exports this crate at the paths it had; a release from before then has its own copy, whose types are distinct from these. diff --git a/crates/win-handles-sys/sabotage.json b/crates/win-handles-sys/sabotage.json index 7bb58e74a..c26a3a451 100644 --- a/crates/win-handles-sys/sabotage.json +++ b/crates/win-handles-sys/sabotage.json @@ -1,7 +1,7 @@ { "package": "win-handles-sys", "description": "Sabotages for win-handles-sys's duplicate_handle_with_access and Access, which were win-sync-sys's duplicate module until DI-14.8.3 moved them. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format.", - "notCoveredHere": "DuplicateHandle's failure other than for a mask Windows refuses is absent: it fails only when the process cannot get another handle, which no test can produce on demand. duplicate.rs says so at the definition.", + "notCoveredHere": "DuplicateHandle's failure other than for a mask Windows refuses is absent: it fails only when the process cannot get another handle, which no test can produce on demand. duplicate.rs says so at the definition. A handle closed twice is not injected for close_handle: the second close would act on a value another thread may have been given, and the sweep would be measuring that and not a test.", "testArgs": ["-p", "win-handles-sys", "--lib", "--locked"], "sabotages": [ { @@ -9,24 +9,24 @@ "file": "src/duplicate.rs", "expect": "caught", "why": "The access is the whole point. A duplicate that gets none cannot be waited on, which the wait tests see, and cannot set, which the modify-state test sees.", - "find": [" access.bits(),\n"], - "replace": [" 0,\n"] + "find": [" DuplicateAccess::Chosen(mask) => (mask.bits(), 0),\n"], + "replace": [" DuplicateAccess::Chosen(_) => (0, 0),\n"] }, { "name": "WS-1+.5: the duplicate keeps the source's access", "file": "src/duplicate.rs", "expect": "caught", "why": "DUPLICATE_SAME_ACCESS (2) makes the call ignore the mask, so every duplicate would be a full-access clone: the narrowing, which is the reason for the function, silently not happening. The synchronize-only tests see it as a set that succeeds.", - "find": [" FALSE,\n 0,\n )\n"], - "replace": [" FALSE,\n 2,\n )\n"] + "find": [" DuplicateAccess::Chosen(mask) => (mask.bits(), 0),\n"], + "replace": [" DuplicateAccess::Chosen(mask) => (mask.bits(), DUPLICATE_SAME_ACCESS),\n"] }, { "name": "WS-1+.5: the duplicate is inheritable", "file": "src/duplicate.rs", "expect": "caught", "why": "A handle that a child process inherits is one the caller did not decide to hand over; the documented contract is that it is not inheritable.", - "find": [" &mut duplicate,\n access.bits(),\n FALSE,\n"], - "replace": [" &mut duplicate,\n access.bits(),\n 1,\n"] + "find": [" Inheritance::NotInheritable => FALSE,\n"], + "replace": [" Inheritance::NotInheritable => TRUE,\n"] }, { "name": "WS-1+.5: a refused duplicate is reported as success", @@ -107,6 +107,174 @@ "why": "GetHandleInformation fails only for a handle that is not open, which AsHandle rules out, so no test can make it fail. The definition says so.", "find": [" if ok == FALSE {\n return Err(io::Error::last_os_error());\n }\n Ok(HandleFlags(flags))\n"], "replace": [" if ok == 12345 {\n return Err(io::Error::last_os_error());\n }\n Ok(HandleFlags(flags))\n"] + }, + { + "name": "WH-1.2: the mask is not passed to the call", + "file": "src/handle_information.rs", + "expect": "caught", + "why": "The mask is what names the flags to change: passed as zero, nothing is ever set or cleared.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), 0, flags.0) };"] + }, + { + "name": "WH-1.2: the flags are not passed to the call", + "file": "src/handle_information.rs", + "expect": "caught", + "why": "The flags give each flag in the mask its new value: passed as zero, a flag can only be cleared, never set.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, 0) };"] + }, + { + "name": "WH-1.2: the mask and the flags are passed in each other's places", + "file": "src/handle_information.rs", + "expect": "caught", + "why": "The call takes the mask before the flags; swapped, a flag is set only if it is also what is to be cleared, and a clear changes nothing.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), flags.0, mask.0) };"] + }, + { + "name": "WH-1.2: a refused call is reported as success", + "file": "src/handle_information.rs", + "expect": "caught", + "why": "A failure of the call is its error: a handle the platform refuses, which a test can hold as the current process's pseudo-handle, must not read as set.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };", " if ok == FALSE {"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };", " if false {"] + }, + { + "name": "WH-1.2 control: only the flags in the mask are passed as flags", + "file": "src/handle_information.rs", + "expect": "survives", + "why": "A control: the platform applies flags & mask, so passing that is the same observable by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0 & mask.0) };"] + }, + { + "name": "WH-1.2: BLIND SPOT: a bit Windows does not name is narrowed away before the call", + "file": "src/handle_information.rs", + "expect": "survives", + "why": "The crate does not narrow what the call is given (HandleFlags), but the platform accepts a bit it does not name and gives it no effect, so a narrowing before the call looks the same from outside. The claim is documented and measured (WH-D-3) and cannot be pinned by a test.", + "find": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) };"], + "replace": [" let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0 & 3, flags.0 & 3) };"] + }, + { + "name": "WH-1.3: the answer is inverted", + "file": "src/compare.rs", + "expect": "caught", + "why": "TRUE is that the two handles name one object; read the other way, every duplicate is a different object and every pair of different ones is the same.", + "find": [" same != FALSE"], + "replace": [" same == FALSE"] + }, + { + "name": "WH-1.3: every pair is the same object", + "file": "src/compare.rs", + "expect": "caught", + "why": "A comparison that cannot say no cannot tell a handle from another object's.", + "find": [" same != FALSE"], + "replace": [" let _ = same;", " true"] + }, + { + "name": "WH-1.3: no pair is the same object", + "file": "src/compare.rs", + "expect": "caught", + "why": "A comparison that cannot say yes cannot find a duplicate, nor a handle to the current process.", + "find": [" same != FALSE"], + "replace": [" let _ = same;", " false"] + }, + { + "name": "WH-1.3: the first handle is compared with itself", + "file": "src/compare.rs", + "expect": "caught", + "why": "The second handle is the other half of the question: with it dropped, every handle is the same object as whatever it is asked about.", + "find": [" second.as_handle().as_raw_handle(),"], + "replace": [" first.as_handle().as_raw_handle(),"] + }, + { + "name": "WH-1.3 control: the call's result is tested against a literal zero", + "file": "src/compare.rs", + "expect": "survives", + "why": "A control: FALSE is zero, so the same test by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" same != FALSE"], + "replace": [" same != 0"] + }, + { + "name": "WH-1.4: a failed close is reported as success", + "file": "src/close.rs", + "expect": "caught", + "why": "The point of the function is the result a drop throws away: a close the platform refused, which a handle protected from close is, must be an error.", + "find": [" if unsafe { CloseHandle(raw) } == FALSE {"], + "replace": [" if unsafe { CloseHandle(raw) } == FALSE && false {"] + }, + { + "name": "WH-1.4: the handle is not closed", + "file": "src/close.rs", + "expect": "caught", + "why": "A close that does nothing and says it worked leaks every handle it is given; the last handle to a named object is what shows it, since the object is not removed.", + "find": [" if unsafe { CloseHandle(raw) } == FALSE {"], + "replace": [" if { let _ = raw; 1 } == FALSE {"] + }, + { + "name": "WH-1.4: every close is reported as a failure", + "file": "src/close.rs", + "expect": "caught", + "why": "A close that succeeded is Ok: reported as failed, a caller cannot tell a handle that closed from one that did not.", + "find": [" if unsafe { CloseHandle(raw) } == FALSE {"], + "replace": [" if unsafe { CloseHandle(raw) } == FALSE || true {"] + }, + { + "name": "WH-1.4: the error is not the call's", + "file": "src/close.rs", + "expect": "caught", + "why": "The code the call set is the whole of what a caller can learn of why it failed.", + "find": [" return Err(io::Error::last_os_error());"], + "replace": [" return Err(io::Error::from(io::ErrorKind::Other));"] + }, + { + "name": "WH-1.4 control: the call's result is tested against a literal zero", + "file": "src/close.rs", + "expect": "survives", + "why": "A control: FALSE is zero, so the same test by another spelling, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" if unsafe { CloseHandle(raw) } == FALSE {"], + "replace": [" if unsafe { CloseHandle(raw) } == 0 {"] + }, + { + "name": "WH-2.1: a same-access duplicate gets no access", + "file": "src/duplicate.rs", + "expect": "caught", + "why": "DUPLICATE_SAME_ACCESS with the flag dropped and the mask zero is a handle that can do nothing: a same-access duplicate that cannot wait, which every same-access test sees.", + "find": [" DuplicateAccess::Same => (0, DUPLICATE_SAME_ACCESS),\n"], + "replace": [" DuplicateAccess::Same => (0, 0),\n"] + }, + { + "name": "WH-2.1: an inheritable duplicate is not", + "file": "src/duplicate.rs", + "expect": "caught", + "why": "Asking for Inheritable and getting a handle a child does not receive hands the child nothing, silently: the flag read back from the duplicate is how it is seen.", + "find": [" Inheritance::Inheritable => TRUE,\n"], + "replace": [" Inheritance::Inheritable => FALSE,\n"] + }, + { + "name": "WH-2.1: the with-access form keeps the source's access", + "file": "src/duplicate.rs", + "expect": "caught", + "why": "The wrapper that names a mask must pass it on. Passing Same instead makes every narrowed duplicate a full-access clone, which the synchronize-only tests see as a set that succeeds.", + "find": [" DuplicateAccess::Chosen(access),\n Inheritance::NotInheritable,\n"], + "replace": [" DuplicateAccess::Same,\n Inheritance::NotInheritable,\n"] + }, + { + "name": "WH-2.1: the with-access form is inheritable", + "file": "src/duplicate.rs", + "expect": "caught", + "why": "The wrapper's documented contract is a duplicate that is not inheritable; the not-inheritable test reads the flag back through it.", + "find": [" DuplicateAccess::Chosen(access),\n Inheritance::NotInheritable,\n"], + "replace": [" DuplicateAccess::Chosen(access),\n Inheritance::Inheritable,\n"] + }, + { + "name": "CONTROL: the mask given with the same-access flag is not zero", + "file": "src/duplicate.rs", + "expect": "survives", + "why": "DUPLICATE_SAME_ACCESS makes DuplicateHandle ignore the mask (the page for the call says so), so a nonzero mask beside the flag is the same observable by another spelling. If it comes back caught, something reacts to the text and not to the behaviour.", + "find": [" DuplicateAccess::Same => (0, DUPLICATE_SAME_ACCESS),\n"], + "replace": [" DuplicateAccess::Same => (1, DUPLICATE_SAME_ACCESS),\n"] } ] } diff --git a/crates/win-handles-sys/src/close.rs b/crates/win-handles-sys/src/close.rs new file mode 100644 index 000000000..590da7168 --- /dev/null +++ b/crates/win-handles-sys/src/close.rs @@ -0,0 +1,59 @@ +// Copyright (c) 2026 Mike Grier +//! Closing a handle, and learning whether it closed: `CloseHandle`. + +use std::io; +use std::os::windows::io::{IntoRawHandle, OwnedHandle}; + +use windows_sys::Win32::Foundation::{CloseHandle, FALSE}; + +/// Closes `handle`, and reports what `CloseHandle` returned. +/// +/// Dropping an `OwnedHandle` calls `CloseHandle` and has no way to say whether it worked. This is the same call with +/// the result kept, for a caller that needs to know: the page says only that a failure is a zero return with extended +/// error information from `GetLastError`. +/// +/// The handle is taken whether or not the call succeeds, and is not given back. The page does not say what state a +/// handle is in after a failed close, and a handle that is not open cannot be given to a caller to drop, since dropping +/// it would close a value that may by then be another handle's. A handle protected from close +/// ([`HandleFlags::PROTECT_FROM_CLOSE`](crate::HandleFlags::PROTECT_FROM_CLOSE)), measured with no debugger attached, +/// failed to close and stayed open (WH-D-5), so it stays open for the life of the process: clear the flag with +/// [`set_handle_information`](crate::set_handle_information) first if the close is meant to succeed. +/// +/// The page lists what `CloseHandle` closes, and says not to use it for a socket, for a handle from `FindFirstFile`, or +/// for a registry key: of the last it says that the call "does not close the handle to the registry key, but does not +/// return an error to indicate this failure", so a handle that is not one of the listed kinds is not made one by being +/// held in an `OwnedHandle`. Under a debugger the page says the call raises an exception for "a handle value that is not +/// valid or a pseudo-handle value", which an `OwnedHandle` does not hold. +/// +/// # Example +/// +/// ``` +/// use std::fs::File; +/// +/// use win_handles_sys::close_handle; +/// +/// let path = std::env::temp_dir().join(format!("wh-close-{}.tmp", std::process::id())); +/// let file = File::create(&path)?; +/// +/// // A `File` is an `OwnedHandle` by `From`; the close is reported and not ignored. +/// close_handle(file.into())?; +/// # std::fs::remove_file(&path)?; +/// # Ok::<(), std::io::Error>(()) +/// ``` +/// +/// # Errors +/// +/// The error from `CloseHandle`. A handle protected from close was refused where it was measured, with +/// `ERROR_INVALID_HANDLE` (WH-D-5). No other failure is reachable from here: the handle is open, since an `OwnedHandle` +/// is. +pub fn close_handle(handle: OwnedHandle) -> io::Result<()> { + let raw = handle.into_raw_handle(); + // SAFETY: `raw` is the open handle `handle` owned, which nothing else will now close. + if unsafe { CloseHandle(raw) } == FALSE { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +#[cfg(test)] +mod tests; diff --git a/crates/win-handles-sys/src/close/tests.rs b/crates/win-handles-sys/src/close/tests.rs new file mode 100644 index 000000000..fd7536317 --- /dev/null +++ b/crates/win-handles-sys/src/close/tests.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2026 Mike Grier +//! Unit tests for `close_handle`. +//! +//! What a close did is observed through named events, whose object is removed when the last handle to it is closed, so a test +//! never asks a closed handle's value anything: another test's thread may have been given it. A handle is protected through the raw +//! `SetHandleInformation`, not through this crate's own. + +use std::fs::File; +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; + +use windows_sys::Win32::Foundation::{ + ERROR_ALREADY_EXISTS, FALSE, HANDLE_FLAG_PROTECT_FROM_CLOSE, SetHandleInformation, +}; +use windows_sys::Win32::System::Threading::CreateEventW; + +use super::close_handle; + +/// A manual-reset event, anonymous or named, and whether a named one already existed. +fn event(name: Option<&str>) -> (OwnedHandle, bool) { + let wide: Option> = name.map(|n| n.encode_utf16().chain([0]).collect()); + let name_ptr = wide.as_ref().map_or(std::ptr::null(), |w| w.as_ptr()); + // SAFETY: no attributes are given and the name, if any, is NUL-terminated and outlives the call. + let raw = unsafe { CreateEventW(std::ptr::null(), 1, 0, name_ptr) }; + // The error is read at once, before anything else can change it. + let existed = + std::io::Error::last_os_error().raw_os_error() == Some(ERROR_ALREADY_EXISTS as i32); + assert!(!raw.is_null(), "create an event"); + // SAFETY: the call returned a new handle nothing else owns. + (unsafe { OwnedHandle::from_raw_handle(raw) }, existed) +} + +/// A name for an event that is this test's alone. +fn name(test: &str) -> String { + format!("wh-close-{test}-{}", std::process::id()) +} + +#[test] +fn closing_an_open_handle_succeeds() { + let (handle, _) = event(None); + close_handle(handle).expect("closed"); +} + +#[test] +fn closing_the_last_handle_to_an_object_removes_it() { + // The page: "After the last handle to an object is closed, the object is removed from the system." + let name = name("last"); + let (only, existed) = event(Some(&name)); + assert!(!existed, "a new object"); + + close_handle(only).expect("closed"); + + let (again, existed) = event(Some(&name)); + assert!(!existed, "the object was removed with its last handle"); + close_handle(again).expect("closed"); +} + +#[test] +fn closing_one_of_two_handles_leaves_the_object() { + let name = name("two"); + let (first, _) = event(Some(&name)); + let (second, existed) = event(Some(&name)); + assert!(existed, "the same object"); + + close_handle(first).expect("closed"); + + let (third, existed) = event(Some(&name)); + assert!(existed, "the second handle keeps the object"); + close_handle(second).expect("closed"); + close_handle(third).expect("closed"); +} + +#[test] +fn a_file_is_closed_through_its_owned_handle() { + let path = std::env::temp_dir().join(format!("wh-close-file-{}.tmp", std::process::id())); + let file = File::create(&path).expect("create"); + + let closed = close_handle(file.into()); + + std::fs::remove_file(&path).expect("remove"); + closed.expect("closed"); +} + +#[test] +fn a_thousand_handles_closed_in_a_row_all_close() { + for _ in 0..1000 { + let (handle, _) = event(None); + close_handle(handle).expect("closed"); + } +} + +#[test] +fn a_handle_is_closed_from_the_thread_it_was_moved_to() { + let name = name("thread"); + let (handle, _) = event(Some(&name)); + + std::thread::spawn(move || close_handle(handle)) + .join() + .expect("the thread") + .expect("closed"); + + let (again, existed) = event(Some(&name)); + assert!(!existed, "the close on the other thread removed the object"); + close_handle(again).expect("closed"); +} + +#[test] +fn a_handle_protected_from_close_is_an_error_and_stays_open() { + // Measured with no debugger attached, 2026-10-21 (WH-D-5): the call returned FALSE with `ERROR_INVALID_HANDLE`, and the + // handle was still open. The page says only that the flag means "calling the CloseHandle function will not close the object + // handle"; under a debugger the call raises an exception, which was not measured. + let (handle, _) = event(None); + let raw = handle.as_raw_handle(); + // SAFETY: the handle is open. + assert_ne!( + unsafe { + SetHandleInformation( + raw, + HANDLE_FLAG_PROTECT_FROM_CLOSE, + HANDLE_FLAG_PROTECT_FROM_CLOSE, + ) + }, + FALSE, + "protect it" + ); + + let error = close_handle(handle).expect_err("a protected handle is not closed"); + assert!(error.raw_os_error().is_some(), "{error}"); + + // Still open: the flag can be cleared on it, and then it closes. The handle was taken, so this reclaims it. + // SAFETY: the handle was not closed, so it is open and nothing else owns it. + let reclaimed = unsafe { + assert_ne!( + SetHandleInformation(raw, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0), + FALSE, + "it is still open" + ); + OwnedHandle::from_raw_handle(raw) + }; + close_handle(reclaimed).expect("closes once the protection is cleared"); +} diff --git a/crates/win-handles-sys/src/compare.rs b/crates/win-handles-sys/src/compare.rs new file mode 100644 index 000000000..f6dbf45d4 --- /dev/null +++ b/crates/win-handles-sys/src/compare.rs @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Mike Grier +//! Whether two handles name the same kernel object: `CompareObjectHandles`. + +use std::os::windows::io::{AsHandle, AsRawHandle}; + +use windows_sys::Win32::Foundation::{CompareObjectHandles, FALSE}; + +/// Whether `first` and `second` name the same kernel object: `CompareObjectHandles`. +/// +/// The page says the call is for deciding this "without imposing a requirement that specific access rights be granted to either +/// handle in order to perform the comparison", so a handle that grants nothing can still be compared. Its example is whether a +/// process handle is the current process, which the current process's pseudo-handle answers. The comparison of two handles of +/// different types of object is "legal to make, though the function will always return FALSE". +/// +/// The result is the call's `TRUE` or `FALSE` and no more: the page documents no failure, and the handles are open for the +/// borrows, so there is none to report. A `FALSE` is that the two do not name one object. Each handle is its own: two duplicates +/// of one object are the same object, and two opens of one file are not (WH-D-4). +/// +/// Needs Windows 10 or Windows Server 2016, which the page gives as the first that has the call. +/// +/// # Example +/// +/// ``` +/// use std::fs::File; +/// +/// use win_handles_sys::compare_object_handles; +/// +/// let path = std::env::temp_dir().join(format!("wh-compare-{}.tmp", std::process::id())); +/// let file = File::create(&path)?; +/// let duplicate = file.try_clone()?; +/// let another_open = File::open(&path)?; +/// +/// assert!(compare_object_handles(&file, &duplicate)); +/// // Opening a file again makes another file object, whatever the file. +/// assert!(!compare_object_handles(&file, &another_open)); +/// # drop((file, duplicate, another_open)); +/// # std::fs::remove_file(&path)?; +/// # Ok::<(), std::io::Error>(()) +/// ``` +#[must_use] +pub fn compare_object_handles(first: impl AsHandle, second: impl AsHandle) -> bool { + // SAFETY: both handles are open for the borrows. + let same = unsafe { + CompareObjectHandles( + first.as_handle().as_raw_handle(), + second.as_handle().as_raw_handle(), + ) + }; + same != FALSE +} + +#[cfg(test)] +mod tests; diff --git a/crates/win-handles-sys/src/compare/tests.rs b/crates/win-handles-sys/src/compare/tests.rs new file mode 100644 index 000000000..e0435466e --- /dev/null +++ b/crates/win-handles-sys/src/compare/tests.rs @@ -0,0 +1,222 @@ +// Copyright (c) 2026 Mike Grier +//! Unit tests for `compare_object_handles`. +//! +//! The objects are made with raw calls and the duplicates by `std`'s `try_clone`, never by this crate's own duplicate, so the +//! comparison and the duplication are not checked against each other. + +use std::fs::File; +use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle}; + +use windows_sys::Win32::Foundation::{DuplicateHandle, FALSE, HANDLE, INVALID_HANDLE_VALUE}; +use windows_sys::Win32::System::Threading::{ + CreateEventW, CreateMutexW, GetCurrentProcess, GetCurrentProcessId, OpenProcess, + PROCESS_QUERY_LIMITED_INFORMATION, +}; + +use super::compare_object_handles; + +/// A manual-reset event, anonymous or named. +fn event(name: Option<&str>) -> OwnedHandle { + let wide: Option> = name.map(|n| n.encode_utf16().chain([0]).collect()); + let name_ptr = wide.as_ref().map_or(std::ptr::null(), |w| w.as_ptr()); + // SAFETY: no attributes are given and the name, if any, is NUL-terminated and outlives the call. + let raw = unsafe { CreateEventW(std::ptr::null(), 1, 0, name_ptr) }; + assert!(!raw.is_null(), "create an event"); + // SAFETY: the call returned a new handle nothing else owns. + unsafe { OwnedHandle::from_raw_handle(raw) } +} + +fn mutex() -> OwnedHandle { + // SAFETY: no attributes and no name are given. + let raw = unsafe { CreateMutexW(std::ptr::null(), 0, std::ptr::null()) }; + assert!(!raw.is_null(), "create a mutex"); + // SAFETY: the call returned a new handle nothing else owns. + unsafe { OwnedHandle::from_raw_handle(raw) } +} + +fn duplicate(handle: &OwnedHandle) -> OwnedHandle { + handle.try_clone().expect("duplicate") +} + +/// A temporary file's path, unique to this test. +fn path(test: &str) -> std::path::PathBuf { + std::env::temp_dir().join(format!("wh-compare-{test}-{}.tmp", std::process::id())) +} + +#[test] +fn a_handle_is_the_same_object_as_itself() { + let handle = event(None); + assert!(compare_object_handles(&handle, &handle)); +} + +#[test] +fn a_duplicate_is_the_same_object_whichever_is_first() { + let handle = event(None); + let copy = duplicate(&handle); + assert_ne!( + copy.as_raw_handle(), + handle.as_raw_handle(), + "a handle of its own" + ); + assert!(compare_object_handles(&handle, ©)); + assert!(compare_object_handles(©, &handle)); +} + +#[test] +fn two_events_made_separately_are_not_the_same_object() { + let (a, b) = (event(None), event(None)); + assert!(!compare_object_handles(&a, &b)); + assert!(!compare_object_handles(&b, &a)); +} + +#[test] +fn a_named_event_made_twice_is_one_object() { + // The page's own example. + let name = format!("wh-compare-named-{}", std::process::id()); + let (first, second) = (event(Some(&name)), event(Some(&name))); + assert_ne!(first.as_raw_handle(), second.as_raw_handle()); + assert!(compare_object_handles(&first, &second)); + assert!(compare_object_handles(&second, &first)); +} + +#[test] +fn objects_of_different_types_are_never_the_same() { + // The page: "legal to make, though the function will always return FALSE". + let (event, mutex) = (event(None), mutex()); + // SAFETY: `INVALID_HANDLE_VALUE` is the one value besides an open handle that `BorrowedHandle::borrow_raw` allows, and + // names the current process. + let process = unsafe { BorrowedHandle::borrow_raw(INVALID_HANDLE_VALUE) }; + assert!(!compare_object_handles(&event, &mutex)); + assert!(!compare_object_handles(&mutex, &event)); + assert!(!compare_object_handles(&event, process)); + assert!(!compare_object_handles(process, &mutex)); +} + +#[test] +fn the_current_process_pseudo_handle_is_the_current_process() { + // The page's use: "whether a process handle is a handle to the current process". + // SAFETY: the process id is this one's. + let raw = unsafe { + OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION, + FALSE, + GetCurrentProcessId(), + ) + }; + assert!(!raw.is_null(), "open this process"); + // SAFETY: the call returned a new handle nothing else owns. + let real = unsafe { OwnedHandle::from_raw_handle(raw) }; + // SAFETY: as above: `INVALID_HANDLE_VALUE` is the pseudo-handle `GetCurrentProcess` returns. + let pseudo = unsafe { BorrowedHandle::borrow_raw(INVALID_HANDLE_VALUE) }; + // SAFETY: the call takes no arguments. + assert_eq!(unsafe { GetCurrentProcess() }, INVALID_HANDLE_VALUE); + + assert!(compare_object_handles(pseudo, &real)); + assert!(compare_object_handles(&real, pseudo)); + assert!(compare_object_handles(pseudo, pseudo)); +} + +#[test] +fn a_handle_that_grants_no_access_is_compared_all_the_same() { + // The page: "without imposing a requirement that specific access rights be granted to either handle". + let source = event(None); + let mut bare: HANDLE = std::ptr::null_mut(); + // SAFETY: both handles are this process's, the source is open, and `bare` is a valid out pointer; no access is asked for + // and none is copied, so the new handle grants nothing. + let ok = unsafe { + DuplicateHandle( + GetCurrentProcess(), + source.as_raw_handle(), + GetCurrentProcess(), + &mut bare, + 0, + FALSE, + 0, + ) + }; + assert_ne!(ok, FALSE, "duplicate with no access"); + // SAFETY: the call returned a new handle nothing else owns. + let bare = unsafe { OwnedHandle::from_raw_handle(bare) }; + + assert!(compare_object_handles(&source, &bare)); + assert!(compare_object_handles(&bare, &source)); +} + +#[test] +fn a_file_and_its_duplicate_are_one_object_and_two_opens_of_it_are_two() { + // Measured, 2026-10-21 (WH-D-4): each open of a file makes a file object of its own. + let path = path("opens"); + let file = File::create(&path).expect("create"); + let duplicate = file.try_clone().expect("duplicate"); + let another_open = File::open(&path).expect("open again"); + + let same = compare_object_handles(&file, &duplicate); + let different = compare_object_handles(&file, &another_open); + + drop((file, duplicate, another_open)); + std::fs::remove_file(&path).expect("remove"); + assert!(same); + assert!(!different); +} + +#[test] +fn each_handle_may_be_borrowed_a_reference_or_owned() { + let handle = event(None); + + assert!(compare_object_handles(&handle, &handle)); + assert!(compare_object_handles(handle.as_handle(), &handle)); + assert!(compare_object_handles(&handle, handle.as_handle())); + // An owned handle is closed by the call, so the other is a duplicate of it. + assert!(compare_object_handles(duplicate(&handle), &handle)); + assert!(compare_object_handles(&handle, duplicate(&handle))); + assert!(compare_object_handles( + duplicate(&handle), + duplicate(&handle) + )); +} + +#[test] +fn comparing_is_an_equivalence_over_duplicates_of_three_objects() { + // Seven handles in three groups: duplicates of one object, of a second, and of a mutex. Every pair is the same object + // exactly when it is in one group, which is symmetry and transitivity as well as the answer. + let a0 = event(None); + let a1 = duplicate(&a0); + let a2 = duplicate(&a1); + let b0 = event(None); + let b1 = duplicate(&b0); + let c0 = mutex(); + let c1 = duplicate(&c0); + let handles = [ + (&a0, 'a'), + (&a1, 'a'), + (&a2, 'a'), + (&b0, 'b'), + (&b1, 'b'), + (&c0, 'c'), + (&c1, 'c'), + ]; + + for (first, first_group) in handles { + for (second, second_group) in handles { + assert_eq!( + compare_object_handles(first, second), + first_group == second_group, + "{first_group} against {second_group}" + ); + } + } +} + +#[test] +fn a_comparison_leaves_both_handles_open_and_usable() { + let (a, b) = (event(None), event(None)); + + for _ in 0..3 { + assert!(!compare_object_handles(&a, &b)); + assert!(compare_object_handles(&a, &a)); + } + + // A handle the comparison closed would no longer be duplicable. + duplicate(&a); + duplicate(&b); +} diff --git a/crates/win-handles-sys/src/duplicate.rs b/crates/win-handles-sys/src/duplicate.rs index 07b7ff050..6e1f617fd 100644 --- a/crates/win-handles-sys/src/duplicate.rs +++ b/crates/win-handles-sys/src/duplicate.rs @@ -1,13 +1,13 @@ // Copyright (c) 2026 Mike Grier // Split from crates/win-sync-sys/src/duplicate.rs at a9a97bf6. -//! Duplicating a handle with a chosen access: `DuplicateHandle`. +//! Duplicating a handle, with the access and the inheritance chosen: `DuplicateHandle`. use std::io; use std::ops::BitOr; use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle}; use std::ptr; -use windows_sys::Win32::Foundation::{DuplicateHandle, FALSE, HANDLE}; +use windows_sys::Win32::Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle, FALSE, HANDLE, TRUE}; use windows_sys::Win32::System::Threading::{ EVENT_MODIFY_STATE, GetCurrentProcess, SYNCHRONIZATION_SYNCHRONIZE, }; @@ -51,11 +51,37 @@ impl BitOr for Access { } } -/// A second handle to the object `handle` refers to, in this process, carrying exactly `access`. +/// The access a duplicate carries: the source's own, or a mask chosen for it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DuplicateAccess { + /// Whatever access the source handle has, as `DUPLICATE_SAME_ACCESS` asks. Not a mask of + /// nothing: the duplicate can do what the source can. + Same, + /// Exactly this mask, whatever the source holds. + Chosen(Access), +} + +/// Whether a child process can inherit a duplicate. +/// +/// This is the duplicate's own attribute, set by the call and independent of the source's: an +/// inheritable source can be duplicated to a handle that is not, and the reverse. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Inheritance { + /// Not inheritable: a process this one creates does not receive the handle. + NotInheritable, + /// Inheritable: a process this one creates receives the handle when it is created asking to + /// inherit handles. + Inheritable, +} + +/// A second handle to the object `handle` refers to, in this process, with the access and +/// inheritance chosen. /// -/// The result can do no more than `access` allows, which is the use: a handle with `SYNCHRONIZE` -/// alone can wait on an object and change nothing about it. The object lives until every handle to -/// it is closed, and closing the result does not touch the source. +/// With [`DuplicateAccess::Chosen`] the result can do no more than the mask allows, which is the +/// use: a handle with `SYNCHRONIZE` alone can wait on an object and change nothing about it. With +/// [`DuplicateAccess::Same`] it can do what the source can, so a caller that only needs another +/// handle to the object does not have to name the rights. The object lives until every handle to it +/// is closed, and closing the result does not touch the source. /// /// **Windows does not confine the result to the source's access.** A duplicate may ask for rights /// the source handle lacks and get them, so a holder of a narrow handle can make a wider one with @@ -63,18 +89,26 @@ impl BitOr for Access { /// was not handed out for, not a boundary against code in the process that sets out to cross it -- /// no such boundary exists inside a process, and raw `DuplicateHandle` crosses it just as well. /// -/// Not inheritable by child processes. An access of nothing is accepted and gives a handle that -/// can do nothing. +/// A chosen access of nothing is accepted and gives a handle that can do nothing. /// /// # Errors /// -/// The error from `DuplicateHandle`: access denied for a mask Windows refuses, such as one with -/// every bit set, and otherwise only when the process cannot get another handle, which no test +/// The error from `DuplicateHandle`: access denied for a chosen mask Windows refuses, such as one +/// with every bit set, and otherwise only when the process cannot get another handle, which no test /// reaches. -pub fn duplicate_handle_with_access( +pub fn duplicate_handle( handle: impl AsHandle, - access: Access, + access: DuplicateAccess, + inheritance: Inheritance, ) -> io::Result { + let (desired_access, options) = match access { + DuplicateAccess::Same => (0, DUPLICATE_SAME_ACCESS), + DuplicateAccess::Chosen(mask) => (mask.bits(), 0), + }; + let inherit = match inheritance { + Inheritance::NotInheritable => FALSE, + Inheritance::Inheritable => TRUE, + }; let mut duplicate: HANDLE = ptr::null_mut(); // SAFETY: both process handles are the current-process pseudo-handle, the source is open for // the borrow, and `duplicate` is a valid out pointer. @@ -84,9 +118,9 @@ pub fn duplicate_handle_with_access( handle.as_handle().as_raw_handle(), GetCurrentProcess(), &mut duplicate, - access.bits(), - FALSE, - 0, + desired_access, + inherit, + options, ) }; if ok == FALSE { @@ -96,5 +130,25 @@ pub fn duplicate_handle_with_access( Ok(unsafe { OwnedHandle::from_raw_handle(duplicate) }) } +/// A second handle to the object `handle` refers to, in this process, carrying exactly `access`. +/// +/// This is [`duplicate_handle`] with [`DuplicateAccess::Chosen`] and [`Inheritance::NotInheritable`], +/// which is what its documentation says of the access, the narrowing and the errors. Not +/// inheritable by child processes. +/// +/// # Errors +/// +/// The errors of [`duplicate_handle`]. +pub fn duplicate_handle_with_access( + handle: impl AsHandle, + access: Access, +) -> io::Result { + duplicate_handle( + handle, + DuplicateAccess::Chosen(access), + Inheritance::NotInheritable, + ) +} + #[cfg(test)] mod tests; diff --git a/crates/win-handles-sys/src/duplicate/tests.rs b/crates/win-handles-sys/src/duplicate/tests.rs index aebad2981..7a2fa5854 100644 --- a/crates/win-handles-sys/src/duplicate/tests.rs +++ b/crates/win-handles-sys/src/duplicate/tests.rs @@ -1,24 +1,26 @@ // Copyright (c) 2026 Mike Grier // Split from crates/win-sync-sys/src/duplicate/tests.rs at a9a97bf6. -//! Unit tests for `duplicate_handle_with_access` and `Access`. +//! Unit tests for `duplicate_handle`, `duplicate_handle_with_access` and `Access`. //! //! What a duplicate can and cannot do is observed through raw Win32 calls, never through anything //! this crate offers to set, reset or wait, so a defect in the duplication cannot hide behind the //! thing it duplicated. The objects are events made with `CreateEventW`, since the events of -//! `win-sync-sys` are a layer above this crate. +//! `win-sync-sys` are a layer above this crate. Whether a duplicate is the same object is this +//! crate's `compare_object_handles`, whose own tests make their duplicates by `std`'s `try_clone`, +//! so the two are not checked against each other. use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle}; use windows_sys::Win32::Foundation::{ - CompareObjectHandles, ERROR_ACCESS_DENIED, FALSE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, + ERROR_ACCESS_DENIED, FALSE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, }; use windows_sys::Win32::System::Threading::{ CreateEventW, CreateMutexW, EVENT_MODIFY_STATE, ResetEvent, SYNCHRONIZATION_SYNCHRONIZE, SetEvent, WaitForSingleObject, }; -use super::{Access, duplicate_handle_with_access}; -use crate::{HandleFlags, get_handle_information}; +use super::{Access, DuplicateAccess, Inheritance, duplicate_handle, duplicate_handle_with_access}; +use crate::{HandleFlags, compare_object_handles, get_handle_information}; /// A manual-reset event with no name and default security. fn manual_event(signalled: bool) -> OwnedHandle { @@ -57,11 +59,6 @@ fn reset_error(handle: &impl AsRawHandle) -> Option { None } -fn same_object(a: &impl AsRawHandle, b: &impl AsRawHandle) -> bool { - // SAFETY: both handles are open for the duration of the borrows. - unsafe { CompareObjectHandles(a.as_raw_handle(), b.as_raw_handle()) != FALSE } -} - const DENIED: Option = Some(ERROR_ACCESS_DENIED as i32); #[test] @@ -69,8 +66,8 @@ fn a_duplicate_is_the_same_object_and_a_new_event_is_not() { let event = manual_event(false); let other = manual_event(false); let copy = duplicate(&event, Access::SYNCHRONIZE); - assert!(same_object(&event, ©)); - assert!(!same_object(&other, ©)); + assert!(compare_object_handles(&event, ©)); + assert!(!compare_object_handles(&other, ©)); assert_ne!(copy.as_raw_handle(), event.as_raw_handle()); } @@ -140,7 +137,7 @@ fn a_duplicate_may_hold_rights_its_source_does_not() { assert_eq!(set_error(&waiter), DENIED, "the source cannot set"); let widened = duplicate(&waiter, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE); - assert!(same_object(&widened, &event)); + assert!(compare_object_handles(&widened, &event)); assert_eq!(set_error(&widened), None, "the duplicate can"); assert_eq!(poll(&event), WAIT_OBJECT_0); } @@ -153,7 +150,7 @@ fn narrowing_and_asking_for_what_the_source_holds_both_work() { let same = duplicate(&narrowed, Access::SYNCHRONIZE); let nothing = duplicate(&same, Access::from_bits(0)); for copy in [&both, &narrowed, &same, ¬hing] { - assert!(same_object(copy, &event)); + assert!(compare_object_handles(copy, &event)); } assert_eq!(set_error(&narrowed), DENIED); assert_eq!(set_error(&same), DENIED); @@ -223,7 +220,7 @@ fn any_kind_of_object_can_be_duplicated_not_only_an_event() { let mutex = unsafe { OwnedHandle::from_raw_handle(raw) }; let copy = duplicate(&mutex, Access::SYNCHRONIZE); - assert!(same_object(&mutex, ©)); + assert!(compare_object_handles(&mutex, ©)); assert_eq!(poll(©), WAIT_OBJECT_0, "an unowned mutex is signalled"); } @@ -237,7 +234,7 @@ fn the_source_may_be_borrowed_or_owned_or_a_duplicate() { let from_borrowed = duplicate(event.as_handle(), Access::SYNCHRONIZE); let from_duplicate = duplicate(&duplicated, Access::SYNCHRONIZE); for copy in [&from_owned, &from_borrowed, &from_duplicate] { - assert!(same_object(copy, &event)); + assert!(compare_object_handles(copy, &event)); } } @@ -270,3 +267,190 @@ fn access_is_copy_send_and_sync() { fn assert_traits() {} assert_traits::(); } + +fn duplicate_with( + handle: impl AsHandle, + access: DuplicateAccess, + inheritance: Inheritance, +) -> OwnedHandle { + duplicate_handle(handle, access, inheritance).expect("duplicate") +} + +/// A same-access duplicate that is not inheritable, the common case. +fn same(handle: impl AsHandle) -> OwnedHandle { + duplicate_with(handle, DuplicateAccess::Same, Inheritance::NotInheritable) +} + +/// Whether `handle` carries the inherit flag. +fn is_inheritable(handle: &OwnedHandle) -> bool { + get_handle_information(handle) + .expect("query the handle") + .contains(HandleFlags::INHERIT) +} + +/// Every pairing of the two choices a duplicate makes. +fn every_choice() -> [(DuplicateAccess, Inheritance); 4] { + [ + (DuplicateAccess::Same, Inheritance::NotInheritable), + (DuplicateAccess::Same, Inheritance::Inheritable), + ( + DuplicateAccess::Chosen(Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE), + Inheritance::NotInheritable, + ), + ( + DuplicateAccess::Chosen(Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE), + Inheritance::Inheritable, + ), + ] +} + +#[test] +fn a_same_access_duplicate_of_a_full_handle_can_wait_set_and_reset() { + let event = manual_event(false); + let copy = same(&event); + assert!(compare_object_handles(©, &event)); + assert_eq!(poll(©), WAIT_TIMEOUT, "it can wait"); + assert_eq!(set_error(©), None, "it can set"); + assert_eq!(poll(&event), WAIT_OBJECT_0, "the source sees the signal"); + assert_eq!(reset_error(©), None, "it can reset"); + assert_eq!(poll(&event), WAIT_TIMEOUT); +} + +#[test] +fn a_same_access_duplicate_of_a_wait_only_handle_stays_wait_only() { + let event = manual_event(true); + let waiter = duplicate(&event, Access::SYNCHRONIZE); + let copy = same(&waiter); + assert!(compare_object_handles(©, &event)); + assert_eq!(poll(©), WAIT_OBJECT_0, "it can wait"); + assert_eq!(set_error(©), DENIED, "it cannot set"); + assert_eq!(reset_error(©), DENIED, "it cannot reset"); +} + +#[test] +fn a_same_access_duplicate_of_a_set_only_handle_cannot_wait() { + let event = manual_event(false); + let setter = duplicate(&event, Access::EVENT_MODIFY_STATE); + let copy = same(&setter); + assert_eq!(set_error(©), None, "it can set"); + assert_eq!(poll(&event), WAIT_OBJECT_0); + assert_eq!(poll(©), WAIT_FAILED, "it holds no right to wait"); +} + +#[test] +fn a_same_access_duplicate_outlives_its_source_and_closing_it_leaves_the_source() { + let event = manual_event(false); + let copy = same(&event); + let second = same(&event); + drop(copy); + assert_eq!(set_error(&second), None, "the other duplicate still works"); + assert_eq!(poll(&event), WAIT_OBJECT_0, "and so does the source"); + + drop(event); + assert_eq!( + poll(&second), + WAIT_OBJECT_0, + "the object outlives its source" + ); +} + +#[test] +fn a_same_access_duplicate_works_for_any_kind_of_object() { + // SAFETY: an unnamed, unowned mutex with default security; null pointers are accepted. + let raw = unsafe { CreateMutexW(std::ptr::null(), FALSE, std::ptr::null()) }; + assert!(!raw.is_null()); + // SAFETY: the call returned a new handle nothing else owns. + let mutex = unsafe { OwnedHandle::from_raw_handle(raw) }; + let copy = same(&mutex); + assert!(compare_object_handles(&mutex, ©)); + assert_eq!(poll(©), WAIT_OBJECT_0, "an unowned mutex is signalled"); +} + +#[test] +fn the_inheritance_asked_for_is_the_inheritance_the_duplicate_has() { + let event = manual_event(false); + for (access, inheritance) in every_choice() { + let copy = duplicate_with(&event, access, inheritance); + assert_eq!( + is_inheritable(©), + inheritance == Inheritance::Inheritable, + "{access:?} {inheritance:?}" + ); + assert!(compare_object_handles(©, &event)); + } +} + +#[test] +fn a_duplicates_inheritance_does_not_follow_its_source() { + let event = manual_event(false); + let inheritable = duplicate_with(&event, DuplicateAccess::Same, Inheritance::Inheritable); + assert!(is_inheritable(&inheritable)); + + for access in [ + DuplicateAccess::Same, + DuplicateAccess::Chosen(Access::SYNCHRONIZE), + ] { + let plain = duplicate_with(&inheritable, access, Inheritance::NotInheritable); + assert!(!is_inheritable(&plain), "an inheritable source, {access:?}"); + } + + let plain = same(&inheritable); + let again = duplicate_with(plain, DuplicateAccess::Same, Inheritance::Inheritable); + assert!(is_inheritable(&again), "a plain source"); +} + +#[test] +fn an_inheritable_duplicate_with_a_chosen_access_is_still_narrowed() { + let event = manual_event(false); + let copy = duplicate_with( + &event, + DuplicateAccess::Chosen(Access::SYNCHRONIZE), + Inheritance::Inheritable, + ); + assert!(is_inheritable(©)); + assert_eq!(set_error(©), DENIED, "inheritance does not widen it"); + assert_eq!(poll(©), WAIT_TIMEOUT); +} + +#[test] +fn a_chosen_mask_windows_refuses_is_reported_whatever_the_inheritance() { + let event = manual_event(false); + for inheritance in [Inheritance::NotInheritable, Inheritance::Inheritable] { + let refused = duplicate_handle( + &event, + DuplicateAccess::Chosen(Access::from_bits(u32::MAX)), + inheritance, + ) + .expect_err("every bit set, reserved ones included"); + assert_eq!(refused.raw_os_error(), DENIED, "{inheritance:?}"); + } + assert_eq!(set_error(&event), None, "the source is untouched"); +} + +#[test] +fn the_with_access_form_is_a_chosen_access_that_is_not_inheritable() { + let event = manual_event(false); + let through_wrapper = duplicate(&event, Access::SYNCHRONIZE); + let through_general = duplicate_with( + &event, + DuplicateAccess::Chosen(Access::SYNCHRONIZE), + Inheritance::NotInheritable, + ); + for copy in [&through_wrapper, &through_general] { + assert_eq!(set_error(copy), DENIED); + assert_eq!(poll(copy), WAIT_TIMEOUT); + assert!(!is_inheritable(copy)); + } +} + +#[test] +fn the_duplicate_choices_are_plain_comparable_values() { + fn assert_traits() {} + assert_traits::(); + assert_traits::(); + assert_ne!( + DuplicateAccess::Same, + DuplicateAccess::Chosen(Access::from_bits(0)) + ); + assert_ne!(Inheritance::NotInheritable, Inheritance::Inheritable); +} diff --git a/crates/win-handles-sys/src/handle_information.rs b/crates/win-handles-sys/src/handle_information.rs index bbd479201..30d4a3494 100644 --- a/crates/win-handles-sys/src/handle_information.rs +++ b/crates/win-handles-sys/src/handle_information.rs @@ -1,5 +1,6 @@ // Copyright (c) 2026 Mike Grier -//! What a handle can be asked about itself: `GetHandleInformation`. +//! What a handle can be asked about itself, and told: `GetHandleInformation` and +//! `SetHandleInformation`. use std::io; use std::ops::BitOr; @@ -7,6 +8,7 @@ use std::os::windows::io::{AsHandle, AsRawHandle}; use windows_sys::Win32::Foundation::{ FALSE, GetHandleInformation, HANDLE_FLAG_INHERIT, HANDLE_FLAG_PROTECT_FROM_CLOSE, + SetHandleInformation, }; /// The flags a handle carries: whether a child process created with handle inheritance gets a copy of @@ -101,5 +103,64 @@ pub fn get_handle_information(handle: impl AsHandle) -> io::Result Ok(HandleFlags(flags)) } +/// Changes the flags `handle` carries: `SetHandleInformation`. +/// +/// `mask` names the flags to change, and `flags` gives each of them its new value: a flag in `mask` is set if it is +/// in `flags` and cleared if it is not. The page says "To set or clear the associated bit flag in dwFlags, you must +/// set a change mask bit flag in dwMask", so a flag in `flags` that is not in `mask` is left as it was, and so is +/// every flag the handle carries that is not in `mask`. The flags are the handle's and not its object's: a duplicate +/// of it has its own. +/// +/// Nothing is refused before the call: the page calls no value of either argument invalid, and a bit it does not +/// name is the call's to judge ([`HandleFlags`]). +/// +/// A handle given [`PROTECT_FROM_CLOSE`](HandleFlags::PROTECT_FROM_CLOSE) is not closed by `CloseHandle`, which is +/// what dropping an `OwnedHandle` calls, so one dropped while protected stays open for the life of the process. +/// Clear the flag first. +/// +/// # Example +/// +/// ``` +/// use std::fs::File; +/// +/// use win_handles_sys::{HandleFlags, get_handle_information, set_handle_information}; +/// +/// let path = std::env::temp_dir().join(format!("wh-set-{}.tmp", std::process::id())); +/// let file = File::create(&path)?; +/// +/// // Make the handle inheritable, and leave every other flag as it was. +/// set_handle_information(&file, HandleFlags::INHERIT, HandleFlags::INHERIT)?; +/// assert!(get_handle_information(&file)?.contains(HandleFlags::INHERIT)); +/// +/// // A flag in `flags` but not in the mask is not changed. +/// set_handle_information(&file, HandleFlags::NONE, HandleFlags::PROTECT_FROM_CLOSE)?; +/// assert!(!get_handle_information(&file)?.contains(HandleFlags::PROTECT_FROM_CLOSE)); +/// +/// // A flag in the mask and not in `flags` is cleared. +/// set_handle_information(&file, HandleFlags::INHERIT, HandleFlags::NONE)?; +/// assert_eq!(get_handle_information(&file)?, HandleFlags::NONE); +/// # drop(file); +/// # std::fs::remove_file(&path)?; +/// # Ok::<(), std::io::Error>(()) +/// ``` +/// +/// # Errors +/// +/// The error from `SetHandleInformation`, which the page does not list. The current process's pseudo-handle, which +/// `std` allows a `BorrowedHandle` to hold, was refused with `ERROR_ACCESS_DENIED` where it was measured +/// (WH-D-3). +pub fn set_handle_information( + handle: impl AsHandle, + mask: HandleFlags, + flags: HandleFlags, +) -> io::Result<()> { + // SAFETY: the handle is open for the borrow. + let ok = unsafe { SetHandleInformation(handle.as_handle().as_raw_handle(), mask.0, flags.0) }; + if ok == FALSE { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + #[cfg(test)] mod tests; diff --git a/crates/win-handles-sys/src/handle_information/tests.rs b/crates/win-handles-sys/src/handle_information/tests.rs index d88a05159..45d7b5791 100644 --- a/crates/win-handles-sys/src/handle_information/tests.rs +++ b/crates/win-handles-sys/src/handle_information/tests.rs @@ -1,19 +1,20 @@ // Copyright (c) 2026 Mike Grier -//! Unit tests for `get_handle_information` and `HandleFlags`. +//! Unit tests for `get_handle_information`, `set_handle_information` and `HandleFlags`. //! -//! The flags a handle carries are set and cleared through raw `SetHandleInformation`, never through -//! anything this crate offers, so a defect in the query cannot hide behind the thing that set them. +//! The query's tests set and clear the flags through raw `SetHandleInformation`, and the setter's read them back through +//! raw `GetHandleInformation`, never through anything this crate offers, so a defect in either cannot hide behind the other. use std::fs::File; -use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle}; use windows_sys::Win32::Foundation::{ - FALSE, HANDLE_FLAG_INHERIT, HANDLE_FLAG_PROTECT_FROM_CLOSE, SetHandleInformation, TRUE, + FALSE, GetHandleInformation, HANDLE_FLAG_INHERIT, HANDLE_FLAG_PROTECT_FROM_CLOSE, + INVALID_HANDLE_VALUE, SetHandleInformation, TRUE, }; use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; use windows_sys::Win32::System::Threading::CreateEventW; -use super::{HandleFlags, get_handle_information}; +use super::{HandleFlags, get_handle_information, set_handle_information}; use crate::{Access, duplicate_handle_with_access}; /// A manual-reset event, inheritable or not. @@ -211,3 +212,222 @@ fn flags_are_copy_send_and_sync() { fn assert_traits() {} assert_traits::(); } + +/// The flags `handle` carries, through the raw call. +fn raw_flags(handle: &impl AsRawHandle) -> u32 { + let mut flags = 0u32; + // SAFETY: the handle is open for the duration of the borrow and `flags` is a valid out pointer. + let ok = unsafe { GetHandleInformation(handle.as_raw_handle(), &mut flags) }; + assert_ne!(ok, FALSE, "read the flags"); + flags +} + +/// Clears the protection when dropped, so a failing assertion does not leave a handle whose close is refused. +struct ClearsProtection<'a>(&'a OwnedHandle); + +impl Drop for ClearsProtection<'_> { + fn drop(&mut self) { + set_flags(self.0, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); + } +} + +const BOTH: u32 = HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE; + +#[test] +fn a_flag_in_the_mask_and_in_flags_is_set() { + let handle = event(false); + + set_handle_information(&handle, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("set"); + + assert_eq!(raw_flags(&handle), HANDLE_FLAG_INHERIT); +} + +#[test] +fn a_flag_in_the_mask_and_not_in_flags_is_cleared() { + let handle = event(true); + + set_handle_information(&handle, HandleFlags::INHERIT, HandleFlags::NONE).expect("cleared"); + + assert_eq!(raw_flags(&handle), 0); +} + +#[test] +fn a_flag_in_flags_and_not_in_the_mask_is_left_as_it_was() { + let handle = event(false); + let _clears = ClearsProtection(&handle); + let both = HandleFlags::INHERIT | HandleFlags::PROTECT_FROM_CLOSE; + + set_handle_information(&handle, HandleFlags::NONE, both).expect("an empty mask"); + assert_eq!(raw_flags(&handle), 0, "nothing changes with an empty mask"); + + set_handle_information(&handle, HandleFlags::INHERIT, both).expect("one flag in the mask"); + assert_eq!( + raw_flags(&handle), + HANDLE_FLAG_INHERIT, + "only the flag in the mask is set" + ); +} + +#[test] +fn a_flag_the_handle_carries_that_is_not_in_the_mask_is_left_alone() { + let handle = event(true); + let _clears = ClearsProtection(&handle); + set_flags( + &handle, + HANDLE_FLAG_PROTECT_FROM_CLOSE, + HANDLE_FLAG_PROTECT_FROM_CLOSE, + ); + assert_eq!(raw_flags(&handle), BOTH); + + set_handle_information(&handle, HandleFlags::INHERIT, HandleFlags::NONE).expect("clear one"); + + assert_eq!( + raw_flags(&handle), + HANDLE_FLAG_PROTECT_FROM_CLOSE, + "the protection stays" + ); +} + +#[test] +fn every_start_mask_and_flags_give_the_result_the_pages_mask_rule_says() { + // The page: "To set or clear the associated bit flag in dwFlags, you must set a change mask bit flag in dwMask". + // The result is what stays of the start, outside the mask, with what `flags` says inside it. + for start in 0..=3u32 { + for mask in 0..=3u32 { + for flags in 0..=3u32 { + let handle = event(false); + let _clears = ClearsProtection(&handle); + set_flags(&handle, BOTH, start); + + set_handle_information( + &handle, + HandleFlags::from_bits(mask), + HandleFlags::from_bits(flags), + ) + .expect("set"); + + let expected = (start & !mask) | (flags & mask); + assert_eq!( + raw_flags(&handle) & BOTH, + expected, + "start {start}, mask {mask}, flags {flags}" + ); + } + } + } +} + +#[test] +fn a_set_flag_is_read_back_by_the_query() { + let handle = event(false); + let _clears = ClearsProtection(&handle); + + set_handle_information(&handle, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("set"); + assert_eq!( + get_handle_information(&handle).expect("query"), + HandleFlags::INHERIT + ); + + set_handle_information(&handle, BOTH_FLAGS, BOTH_FLAGS).expect("both"); + assert_eq!(get_handle_information(&handle).expect("query"), BOTH_FLAGS); + + set_handle_information(&handle, BOTH_FLAGS, HandleFlags::NONE).expect("neither"); + assert_eq!( + get_handle_information(&handle).expect("query"), + HandleFlags::NONE + ); +} + +const BOTH_FLAGS: HandleFlags = HandleFlags::from_bits(BOTH); + +#[test] +fn a_handle_std_opens_can_be_made_inheritable_and_made_not_so_again() { + let path = std::env::temp_dir().join(format!("wh-set-test-{}.tmp", std::process::id())); + let file = File::create(&path).expect("create"); + assert_eq!(raw_flags(&file), 0); + + set_handle_information(&file, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("set"); + let inheritable = raw_flags(&file); + set_handle_information(&file, HandleFlags::INHERIT, HandleFlags::NONE).expect("cleared"); + let cleared = raw_flags(&file); + + drop(file); + std::fs::remove_file(&path).expect("remove"); + assert_eq!((inheritable, cleared), (HANDLE_FLAG_INHERIT, 0)); +} + +#[test] +fn the_handle_may_be_borrowed_owned_or_a_reference() { + let handle = event(false); + + set_handle_information(&handle, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("reference"); + assert_eq!(raw_flags(&handle), HANDLE_FLAG_INHERIT); + + set_handle_information(handle.as_handle(), HandleFlags::INHERIT, HandleFlags::NONE) + .expect("borrow"); + assert_eq!(raw_flags(&handle), 0); + + // An owned handle is dropped by the call, so what is shown is that it is accepted. + set_handle_information(handle, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("value"); +} + +#[test] +fn flags_belong_to_the_handle_and_not_to_its_object() { + // Measured, 2026-10-21 (WH-D-3), and not a promise of the page, which speaks of "the object handle". + let source = event(false); + let copy = duplicate_handle_with_access(&source, Access::SYNCHRONIZE).expect("duplicate"); + + set_handle_information(©, HandleFlags::INHERIT, HandleFlags::INHERIT).expect("the copy"); + assert_eq!(raw_flags(©), HANDLE_FLAG_INHERIT); + assert_eq!(raw_flags(&source), 0, "the source is not changed"); + + set_handle_information(&source, HandleFlags::INHERIT, HandleFlags::INHERIT) + .expect("the source"); + set_handle_information(©, HandleFlags::INHERIT, HandleFlags::NONE).expect("clear the copy"); + assert_eq!( + raw_flags(&source), + HANDLE_FLAG_INHERIT, + "nor is it by the copy" + ); +} + +#[test] +fn a_bit_windows_does_not_name_is_not_refused_and_changes_no_named_flag() { + // Measured, 2026-10-21 (WH-D-3), and the call's to judge: the page names two flags and calls no other bit invalid. + let handle = event(true); + let _clears = ClearsProtection(&handle); + let unnamed = HandleFlags::from_bits(0x8000_0000); + + set_handle_information(&handle, unnamed, unnamed) + .expect("an unnamed bit in the mask and flags"); + assert_eq!( + raw_flags(&handle), + HANDLE_FLAG_INHERIT, + "nothing named changed" + ); + + set_handle_information(&handle, HandleFlags::INHERIT, unnamed) + .expect("an unnamed bit in flags"); + assert_eq!( + raw_flags(&handle), + 0, + "the named flag took its value from the flags" + ); + + let every_bit = HandleFlags::from_bits(u32::MAX); + set_handle_information(&handle, every_bit, every_bit).expect("every bit"); + assert_eq!(raw_flags(&handle) & BOTH, BOTH); +} + +#[test] +fn a_handle_the_call_refuses_is_its_error() { + // The current process's pseudo-handle, which `std` allows a `BorrowedHandle` to hold. Measured, 2026-10-21 (WH-D-3): + // refused with `ERROR_ACCESS_DENIED`. The page does not say it is. + // SAFETY: `INVALID_HANDLE_VALUE` is the one value besides an open handle that `BorrowedHandle::borrow_raw` allows. + let pseudo = unsafe { BorrowedHandle::borrow_raw(INVALID_HANDLE_VALUE) }; + + let error = set_handle_information(pseudo, HandleFlags::INHERIT, HandleFlags::INHERIT) + .expect_err("a pseudo-handle is refused"); + + assert!(error.raw_os_error().is_some(), "{error}"); +} diff --git a/crates/win-handles-sys/src/lib.rs b/crates/win-handles-sys/src/lib.rs index 69a7dbcfe..24ef0bd54 100644 --- a/crates/win-handles-sys/src/lib.rs +++ b/crates/win-handles-sys/src/lib.rs @@ -4,8 +4,14 @@ #![deny(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] +mod close; +mod compare; mod duplicate; mod handle_information; -pub use duplicate::{Access, duplicate_handle_with_access}; -pub use handle_information::{HandleFlags, get_handle_information}; +pub use close::close_handle; +pub use compare::compare_object_handles; +pub use duplicate::{ + Access, DuplicateAccess, Inheritance, duplicate_handle, duplicate_handle_with_access, +}; +pub use handle_information::{HandleFlags, get_handle_information, set_handle_information}; diff --git a/crates/win-job-launcher/Cargo.toml b/crates/win-job-launcher/Cargo.toml index 98991cbac..2bc86b509 100644 --- a/crates/win-job-launcher/Cargo.toml +++ b/crates/win-job-launcher/Cargo.toml @@ -17,6 +17,8 @@ description = "Runs one command inside a fresh Windows job object under a wall-c publish = false [dependencies] +# Duplicating a handle: this crate's `DuplicateHandle` call is `win-handles-sys`'s, the repository's one. +win-handles-sys = { path = "../win-handles-sys" } windows-sys = { version = "0.61.2", default-features = false, features = [ "Win32_Foundation", # `CreateJobObjectW` takes a `SECURITY_ATTRIBUTES`, so windows-sys gates it diff --git a/crates/win-job-launcher/src/process.rs b/crates/win-job-launcher/src/process.rs index 364f1bca7..f1aafdb0b 100644 --- a/crates/win-job-launcher/src/process.rs +++ b/crates/win-job-launcher/src/process.rs @@ -40,13 +40,13 @@ use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; use std::path::{Path, PathBuf}; use std::ptr; +use win_handles_sys::{DuplicateAccess, Inheritance, duplicate_handle}; use windows_sys::Win32::Foundation::{ - CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_FILE_NOT_FOUND, - ERROR_INSUFFICIENT_BUFFER, HANDLE, TRUE, + CloseHandle, ERROR_FILE_NOT_FOUND, ERROR_INSUFFICIENT_BUFFER, HANDLE, TRUE, }; use windows_sys::Win32::System::SystemInformation::{GetSystemDirectoryW, GetWindowsDirectoryW}; use windows_sys::Win32::System::Threading::{ - CreateProcessW, DeleteProcThreadAttributeList, EXTENDED_STARTUPINFO_PRESENT, GetCurrentProcess, + CreateProcessW, DeleteProcThreadAttributeList, EXTENDED_STARTUPINFO_PRESENT, GetExitCodeProcess, InitializeProcThreadAttributeList, LPPROC_THREAD_ATTRIBUTE_LIST, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, PROC_THREAD_ATTRIBUTE_JOB_LIST, PROCESS_INFORMATION, STARTF_USESTDHANDLES, STARTUPINFOEXW, STARTUPINFOW, UpdateProcThreadAttribute, @@ -135,9 +135,9 @@ pub fn spawn_in_job( // a later or concurrent process creation, by anything, would inherit them. let stdin = File::open("NUL")?; let owned = [ - duplicate_inheritable(stdin.as_raw_handle())?, - duplicate_inheritable(stdout.as_raw_handle())?, - duplicate_inheritable(stderr.as_raw_handle())?, + duplicate_inheritable(&stdin)?, + duplicate_inheritable(stdout)?, + duplicate_inheritable(stderr)?, ]; let stdio: [HANDLE; 3] = owned.each_ref().map(AsRawHandle::as_raw_handle); let jobs: [HANDLE; 1] = [job.as_raw_handle()]; @@ -453,28 +453,9 @@ fn cmd_exe() -> io::Result { } } -/// A new handle to the same object as `handle`, inheritable, owned by the caller. -fn duplicate_inheritable(handle: HANDLE) -> io::Result { - let mut duplicate: HANDLE = ptr::null_mut(); - // SAFETY: `handle` is live for the call; both process arguments are this - // process's own pseudo-handle, and `duplicate` is writable. - let ok = unsafe { - DuplicateHandle( - GetCurrentProcess(), - handle, - GetCurrentProcess(), - &raw mut duplicate, - 0, - TRUE, - DUPLICATE_SAME_ACCESS, - ) - }; - if ok == 0 { - return Err(io::Error::last_os_error()); - } - // SAFETY: a successful DuplicateHandle returns a new handle this process - // owns. - Ok(unsafe { OwnedHandle::from_raw_handle(duplicate) }) +/// A new handle to the same object as `handle`, with its access and inheritable, owned by the caller. +fn duplicate_inheritable(handle: &File) -> io::Result { + duplicate_handle(handle, DuplicateAccess::Same, Inheritance::Inheritable) } /// A `PROC_THREAD_ATTRIBUTE_LIST`, deleted on drop. diff --git a/crates/win-multi-file-tail-watcher/CHECKLIST.md b/crates/win-multi-file-tail-watcher/CHECKLIST.md index 8c150d7b7..72f56e807 100644 --- a/crates/win-multi-file-tail-watcher/CHECKLIST.md +++ b/crates/win-multi-file-tail-watcher/CHECKLIST.md @@ -7,197 +7,5 @@ Reports length changes across a large population of files, on a beat the caller Each milestone ends in a deliverable exercised by integration tests: every one of them crosses a real file system, process, or thread-pool boundary. Unit tests accompany each item as usual. -## M2 -- The poll engine: three passes and a stop point, driven synchronously - -Deliverable: the engine, called directly with no thread pool, over hundreds of real files and an SQ -far smaller than the number of changes, reports every change exactly once across stops and -continuations, then a poll-done entry. The engine comes before the thread pool because the stop and -the continuation are part of the third pass, and a synchronous engine can be tested through every -one of them deterministically ([TW-D-7](DESIGN-NOTES.md#tw-d-7)). - -- [x] **TW-2.1** -- `Watcher` registers files into a roster and returns an RAII `Registration` whose `Drop` unregisters. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-21) - -- [x] **TW-2.2** -- The watcher owns a bounded submission queue of attention and poll-done entries, set at `Watcher::new` and drained by the caller. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-22) - -- [x] **TW-2.3** -- The watcher owns two manual-reset doorbell events, one each way, each exposed as a waitable handle. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-23) - -- [x] **TW-2.4** -- `Chunked`, an append-only sequence in chunks of at most 64 KiB that keeps its chunks across a clear, for the poll's copies. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-24) - -- [x] **TW-2.5** -- Each roster entry holds a last known length starting at its starting offset, and the snapshot pass copies id, last known length and an observer into a chunked buffer one batch per lock. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-25) - -- [x] **TW-2.6.1** -- One predicate classifies a probe failure as transient or terminal from its Win32 code; the terminal list is `ERROR_INVALID_HANDLE` and `ERROR_FILE_INVALID`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-261) - -- [x] **TW-2.6.2** -- The watcher reports typed diagnostic events to a sink the caller supplies through `Watcher::with_diagnostics`, in the shape of durable-ioring's `DiagnosticSink`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-262) - -- [x] **TW-2.6.3** -- `SqEntry::Terminal` is a third entry kind carrying a clone of `T`, the Win32 code and the last length reported, and `SqEntry` is `#[non_exhaustive]`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-263) - -- [x] **TW-2.6.4** -- A roster entry has a terminal state, holding the Win32 code, which the snapshot skips and which `mark_terminal` sets once. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-264) - -- [x] **TW-2.6** -- The probe pass reads each snapshotted observer's length with no lock held, drops each observer as read, and keeps the changes and terminal failures in a second chunked buffer. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-26) - -- [x] **TW-2.7** -- The report pass writes the changed list to the SQ a batch at a time under the roster lock, a refused write stops the pass without waiting, and the engine continues from the kept index. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-27) - -- [x] **TW-2.8** -- Timings of each pass against roster sizes from 1,000 to 50,000 are captured by ignored tests, with the roster map measured alone; no constant is changed by it. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-28) - -- [x] **TW-2.8.1** -- The batch sizes, the chunk size and the roster map are chosen from the captures, and sharding the probe is recorded as an available remedy that is not built. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-281) - -- [x] **TW-2.9** -- The engine is tested over a few hundred real files, with child processes appending through stdout handles their parent opened, against an SQ far smaller than the number of changes. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-29) - -## M2+ -- Decisions to revisit once M2 is done - -Parked, not pending: the milestone that is next after M2 pulls these in. - -- [ ] **TW-2+.1** -- **Decide whether the diagnostic sink's machinery moves to a crate both this one - and durable-ioring use**, generic over the event type, or whether this crate's mirror stays. The - mirror is deliberate duplication, so that the sink could be built without touching another - crate's layering; the decision is to merge it or to delete the question, and is made when there - is more than one event to compare. durable-ioring's `StderrDiagnostics` and `FanOutDiagnostics` - were not mirrored, so shared machinery would bring them here. ([TW-D-13](DESIGN-NOTES.md#tw-d-13)) - -- [ ] **TW-2+.2** -- **Decide whether the buffers give chunks back after an unusually large poll**, and how many they keep. Needs a measurement the captures do not have: a roster that grows and then shrinks, polled across the change. ([TW-D-14](DESIGN-NOTES.md#tw-d-14)) - -## M3 -- The poll on the thread pool - -Deliverable: `poll` runs the engine on the thread pool, one at a time. When the SQ fills it parks, -and resumes when the caller rings back. A watcher over hundreds of files with child-process writers -is polled repeatedly while registrations are dropped and added, with no hang and no crate callback -running between polls. - -- [x] **TW-3.1** -- `Watcher::poll` runs the engine on the thread pool and returns, one poll at a time, with the doorbell rung after each full batch and at the end. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-31) - -- [x] **TW-3.2** -- A poll that stops on a full SQ is parked on a thread-pool wait on the resume doorbell, and the resumed callback resets it, retries from the kept index, and only then arms again. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-32) - -- [x] **TW-3.3** -- Registering and dropping registrations during a running or parked poll is tested on the thread pool and needed no change: neither waits for the poll, attentions already written keep their value, and a registration made mid-poll is seen by the next poll. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-33) - -- [x] **TW-3.4** -- Polls over real files that child processes write run through a queue of two slots in tests of the public API, reporting every change across parks and resumes while registrations come and go. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-34) - -## M4 -- Shutdown - -Deliverable: shutdown requested from inside the caller's own doorbell callback stops the watcher -from any state without blocking, and the subsequent `Drop` waits at most for a running poll to reach -its next step, which is one probe or one report. - -- [x] **TW-4.1** -- `Watcher::shutdown` sets a stop flag and returns, may be repeated, and `poll` is refused with `PollError::Shutdown` after it. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-41) - -- [x] **TW-4.2** -- A shutdown stops a running or parked poll at its next step (a batch of the copy, a file probed, a change reported) and the poll goes on to poll-done, which is always delivered. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#tw-42) - -- [x] **TW-4.3** -- Dropping a `Watcher` stops it as `shutdown` does, waits for a running poll to reach its next step and return, and never waits for poll-done or for a parked poll. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-43) - -- [x] **TW-4.4** -- Shutdown requested from the caller's own callback on a thread-pool wait is tested from a parked poll, a poll inside its probe and a poll held inside the report, with repeated requests, drops bounded by a step, and nothing running after `Drop`. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-44) - -## M5 -- Reading the announced bytes through an I/O ring, driven synchronously - -Deliverable: the reading layer, driven directly with no thread pool, over hundreds of files that -child processes write and a pool far smaller than the bytes announced, delivers every announced byte -exactly once across chunks labelled with their ranges, and then poll-done. Driven synchronously first -for the same reason as M2: every stop and continuation can then be tested deterministically -([TW-D-15](DESIGN-NOTES.md#tw-d-15)). These items replace TW-4+.1 and TW-4+.2, which planned the reads -through `ThreadpoolIo`. - -- [x] **TW-5.1** -- `HeldFile` holds its endpoint as an `EndpointHandle`, consumed from the `UnassociatedEndpoint` when the file is opened, and lends it to the crate for the reading engine. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-51) - -- [x] **TW-5.2** -- A pool of owned buffers of one size, all allocated and zeroed when it is made, lends each as a buffer a ring read can take and a chunk of the bytes read, both given back on drop, with a doorbell for a starved reader. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-52) - -- [x] **TW-5.3** -- The reading layer's SQ, made by the same function and capacity rule as the watcher's, carries data entries whose range is computed from their chunk, shrink, terminal and poll-done entries, with its own doorbell pair. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-53) - -- [x] **TW-5.4** -- Each of the watcher's entries is planned without a ring: growth as reads of at most one buffer each, computed as they are taken, a shrink as a shrink entry, a terminal entry passed through, and poll-done kept for the engine. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-54) - -- [x] **TW-5.5** -- The reading engine, driven synchronously against a ring, drains the watcher's SQ, reads each attention's announced bytes into pool buffers with the handle addressed raw and an observer in the sidecar, delivers a data entry per read as it completes, and stops without waiting on an empty pool, a full ring or a full client SQ and continues from where it stopped. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-55) - -- [x] **TW-5.6** -- The reading engine is tested over real files that child processes write through inherited handles, through a pool of a few buffers and a client SQ of two: every byte delivered once, chunks the client keeps stopping the reads and dropping them resuming them, and registrations dropped with reads in flight. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-56) - -## M6 -- The reading layer on the thread pool - -Coarse on purpose: its scope is set when M5 is done. - -- [x] **TW-6.1** -- The reading engine runs on the thread pool with no thread of the caller's: the watcher's doorbell, a completed read, a chunk returned to an empty pool and the client's resume doorbell each drive it through a wait of their own, which stays armed and idle between polls. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-61) - -- [x] **TW-6.2** -- The layer is tested on the thread pool over files that child processes write, through a pool of four small buffers and a client SQ of two: polls repeated while the children write, every byte delivered once, a stall on kept chunks that dropping them continues, and a drop that releases every file. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-62) - -- [x] **TW-6.3** -- A failed read is classified as a failed probe is: a terminal one is delivered on the client's SQ as a terminal entry and reaches the diagnostic sink, a transient one reaches the sink alone. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-63) - -## M7 -- Shutdown with reads in flight - -Coarse on purpose: its scope is set when M6 is done. - -- [x] **TW-7.1** -- Shutdown and `Drop` with reads in flight: shutdown is a non-blocking, repeatable request that stops reads being planned or submitted, reads in flight complete and are delivered, every poll-done is still delivered, and `Drop` waits only for the reads already in flight. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-71) - -- [x] **TW-7.2** -- Integration tests over children's files: shutdown with reads in flight, while the children still write, while stalled on kept chunks, and of an idle layer, each returning and releasing every file. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-72) - -## M8 -- Findings of the pre-merge review - -A review of the branch against origin/main, 2026-10-16, found three defects in M5 to M7. - -- [x] **TW-8.1** -- A stop request that lands while a plan's reads are being pushed ends the pushing at the next read, and the reads left are abandoned. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-81) - -- [x] **TW-8.2** -- A poller that cannot be made leaves the watcher usable: dropping the callbacks' view of the shared state no longer stops it, so a poll after the failure is retried rather than refused with `Shutdown`. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-82) - -- [x] **TW-8.3** -- A terminal read failure makes its registration terminal once: a later terminal failure of it, one after a probe reported it, and one of a registration since dropped reach the diagnostic sink alone. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-83) - -- [x] **TW-8.4** -- A `T` whose `Drop` uses the watcher is dropped outside the roster lock, tested, and the sabotage that dropped it under the lock is declared caught rather than a control. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-84) - -- [x] **TW-8.5** -- The chunk directory is bound as the chunks are: no allocation of a `Chunked` is larger than 64 KiB, at the price of a ceiling of 2,730 x 2,730 chunks. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-85) - -- [x] **TW-8.6** -- The root `PLANS.md` row for this crate no longer carries a milestone status that the crate's checklist owns. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-86) -- [x] **TW-8.7** -- CI sweeps every sabotage manifest a crate carries, not only `sabotage.json`, so this crate's integration and doctest manifests are run. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-87) - -- [x] **TW-8.8** -- The crate is seeded at `0.0.1` in the release-please manifest, with `Release-As: 0.1.0` on the commit that records it, so its first release is `0.1.0`. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-88) - -- [x] **TW-8.9** -- Error edges that had no test or no answer: a push the ring refuses is a failed read and abandons the rest of its plan, a poll whose poller cannot be made releases the gate, the layer keeps the first error a run returns, and a failed construction drains the waits it made. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-89) - -- [x] **TW-8.10** -- Documentation and housekeeping from the third review: `PollError::Io` and the poll docs, the callback hazards (a panic aborts, a `Drop` must not release the last owner of the watcher), `FAN` stated as derived, a control's `why`, stale lint reasons and comments, and the plans and README claims. -> [completed 2026-10-16](COMPLETED-CHECKLIST.md#tw-810) - -## M9 -- The reading layer is public, and the client chooses its sizes - -Deliverable: a client outside the crate, using only public items, creates the reading layer with a -buffer size, a buffer count, a client SQ capacity and the ring's two queue sizes of its own choosing, -registers files that child processes write, polls, and receives every announced byte exactly once as -chunks; and a size that cannot work is refused at construction, naming the field and its bound -([TW-D-16](DESIGN-NOTES.md#tw-d-16)). The layer is a module of this crate, not a new one. - -The module is `reader`, and its types are `TailReader` (the layer), `TailReaderOptions` (the five -sizes), `ReadEntry` (the SQ entry) and `Chunk` (the bytes): the engineer, 2026-10-16, "the names are -fine". - -- [x] **TW-9.1** -- The ring's two queue sizes are measured and asserted: how it rounds and pairs them, its largest sizes and the conditions it refuses with, and how its completion queue bounds the reads in flight against the buffer count. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-91) - -- [x] **TW-9.2** -- An options type holds the five sizes, checks them before anything is allocated, and refuses each size that cannot work with an error that names the option, the value and the cause, including every ring size the ring would round, replace or raise. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-92) - -- [x] **TW-9.3** -- The reading layer's entries are public in the `reader` module: the entry type and its data, shrink, terminal and poll-done kinds, the chunk, the drain and the SQ with its two doorbells, each with rustdoc stating what it promises and what holding it too long does. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-93) - -- [x] **TW-9.4** -- The reading layer itself is public as `reader::TailReader`: made from the options and the watcher's own arguments, it registers a file with an offset and a `T`, polls, exposes its SQ and doorbells, shuts down without blocking and reports the first error a run returned, with nothing it needs from the watcher made public. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-94) - -- [x] **TW-9.5** -- The layer is exercised from `tests/` through the public API only, over files that child processes write, under the default options, a set well above them and two small sets, with the near-term and worst-case loads, every invalid size refused, and a held chunk and an undrained client SQ each stopping the reads; the README names what each size limits and the three stops. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-95) - -- [x] **TW-9.6** -- A queue capacity that is valid but more than memory can hold is an error and not an abort: the queue crate's allocation refusal is mapped to a new `CreateError::Allocation` for the client queue and the watcher's own queue, and tested through the public constructor. -> [completed 2026-10-17](COMPLETED-CHECKLIST.md#tw-96) - -## M10 -- Findings of the review of M9 - -A review of the branch against origin/main, 2026-10-17, found four defects in M9. The engineer: "polls -initiated before the previous one completes should fail. fix all". - -- [x] **TW-10.1** -- A sabotage entry made stale by TW-9.6 is repaired, and every sabotage manifest of the crate is swept in full, so that no other entry is stale. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-101) - -- [x] **TW-10.2** -- A failure to read the ring's limits is an I/O ring failure and not a refused size: `TailReaderError::Ring`, not `Options`, with the documentation of both variants saying so. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-102) - -- [x] **TW-10.3** -- `TailReader::poll` is refused while the previous poll is not complete, and a poll is complete when its poll-done has been queued for the client, after every read of it is delivered, and not when the watcher has finished announcing; a client that has read poll-done may poll again at once. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-103) - -- [x] **TW-10.4** -- The integration tests do not use a fixed sleep as proof that asynchronous work has finished: the undrained-client-queue test asserts what the public API can observe, that the poll cannot complete and nothing is lost, and leaves the exact stop at capacity to the synchronous engine test that already asserts it, and the quiet check after a poll tolerates the one empty ring that may land late instead of sleeping past it. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-104) - -- [x] **TW-10.5** -- A poll refused as in flight wakes the reading engine, so that a poll held up by an error a run returned, which leaves nothing in flight and rings no doorbell, is not refused for ever now that the reader's gate is in front of the watcher's. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-105) - -- [x] **TW-10.6** -- The tests of the reading layer that count how many times the engine ran no longer take their baseline while the ring's setup signal is still driving it, and wait for those drives instead of sleeping past them. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-106) - -- [x] **TW-10.7** -- Two public documentation claims are corrected to what the code does: `Terminal` says its `last_known` and `win32_code` mean something different for a failed read than for a failed probe, and `TailReader` no longer promises that no wake follows a poll-done. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-107) - -- [x] **TW-10.8** -- `ReadSq::capacity` no longer takes the lock a `ReadDrain` holds, so asking for the capacity while draining does not wait for the drain, on the thread that holds it or on another. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-108) - -- [x] **TW-10.9** -- A design-notes bullet that an earlier commit joined onto the end of the one before it is on its own line again. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-109) - -- [x] **TW-10.10** -- The public `TailReader` example rings `ring_resume` after it drains, as a reader stopped on a full queue needs, instead of teaching a loop that stalls on a poll longer than the queue. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-1010) - -- [x] **TW-10.11** -- `CreateError`, which is now the error of the watcher's queue and of the reading layer's client queue, words its messages for a submission queue and not for the watcher. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-1011) - -- [x] **TW-10.12** -- The README says a data entry carries the value, range and bytes, and what the other entries carry, instead of saying every entry does. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-1012) - -- [x] **TW-10.13** -- The tiny option set is no longer documented as reaching the ring-full stop, which it cannot, and the README says which small set reaches which stop. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#tw-1013) +Nothing is pending. `M1` to `M10` are complete and archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md); the two +decisions of `M2+` are parked in [PARKED.md](PARKED.md). diff --git a/crates/win-multi-file-tail-watcher/COMPLETED-CHECKLIST.md b/crates/win-multi-file-tail-watcher/COMPLETED-CHECKLIST.md index ee80871f8..118af27ae 100644 --- a/crates/win-multi-file-tail-watcher/COMPLETED-CHECKLIST.md +++ b/crates/win-multi-file-tail-watcher/COMPLETED-CHECKLIST.md @@ -1192,6 +1192,7 @@ fan's group never exceeds a chunk, a small fan's allocations stay within it, ind ### TW-8.6 -- The root `PLANS.md` row for this crate no longer carries a milestone status that the crate's checklist owns. *(completed 2026-10-16 19:20:00 -04:00)* The row said reads were parked behind M4. It now says what the crate is and links its checklist. + ## Moved 2026-10-16 20:58:29 -04:00 -- TW-8.7 to TW-8.10: the third pre-merge review ### TW-8.7 -- CI sweeps every sabotage manifest a crate carries, not only `sabotage.json`, so this crate's integration and doctest manifests are run. *(completed 2026-10-16 20:58:29 -04:00)* @@ -1642,3 +1643,82 @@ completion queue, and a batch never pushes more reads than the ring's submission client-full stops, and the ring-full stop is reached by the test whose pool is larger than the ring. The comment in `option_sets.rs` and the README said otherwise, the README as "every stop is reached all the time", which no test observes. Both now say which set reaches which stop. No test asserts which stop was reached, and the engine's own tests are where that is asserted. + +## Moved 2026-10-21 21:45:55 -04:00 -- win-multi-file-tail-watcher: milestones M2, M3, M4, M5, M6, M7, M8, M9 and M10 are complete + +### M2 -- The poll engine: three passes and a stop point, driven synchronously + +Deliverable: the engine, called directly with no thread pool, over hundreds of real files and an SQ +far smaller than the number of changes, reports every change exactly once across stops and +continuations, then a poll-done entry. The engine comes before the thread pool because the stop and +the continuation are part of the third pass, and a synchronous engine can be tested through every +one of them deterministically ([TW-D-7](DESIGN-NOTES.md#tw-d-7)). + +Items, each archived above under its own heading: [TW-2.1](#tw-21), [TW-2.2](#tw-22), [TW-2.3](#tw-23), [TW-2.4](#tw-24), [TW-2.5](#tw-25), [TW-2.6.1](#tw-261), [TW-2.6.2](#tw-262), [TW-2.6.3](#tw-263), [TW-2.6.4](#tw-264), [TW-2.6](#tw-26), [TW-2.7](#tw-27), [TW-2.8](#tw-28), [TW-2.8.1](#tw-281), [TW-2.9](#tw-29). + +### M3 -- The poll on the thread pool + +Deliverable: `poll` runs the engine on the thread pool, one at a time. When the SQ fills it parks, +and resumes when the caller rings back. A watcher over hundreds of files with child-process writers +is polled repeatedly while registrations are dropped and added, with no hang and no crate callback +running between polls. + +Items, each archived above under its own heading: [TW-3.1](#tw-31), [TW-3.2](#tw-32), [TW-3.3](#tw-33), [TW-3.4](#tw-34). + +### M4 -- Shutdown + +Deliverable: shutdown requested from inside the caller's own doorbell callback stops the watcher +from any state without blocking, and the subsequent `Drop` waits at most for a running poll to reach +its next step, which is one probe or one report. + +Items, each archived above under its own heading: [TW-4.1](#tw-41), [TW-4.2](#tw-42), [TW-4.3](#tw-43), [TW-4.4](#tw-44). + +### M5 -- Reading the announced bytes through an I/O ring, driven synchronously + +Deliverable: the reading layer, driven directly with no thread pool, over hundreds of files that +child processes write and a pool far smaller than the bytes announced, delivers every announced byte +exactly once across chunks labelled with their ranges, and then poll-done. Driven synchronously first +for the same reason as M2: every stop and continuation can then be tested deterministically +([TW-D-15](DESIGN-NOTES.md#tw-d-15)). These items replace TW-4+.1 and TW-4+.2, which planned the reads +through `ThreadpoolIo`. + +Items, each archived above under its own heading: [TW-5.1](#tw-51), [TW-5.2](#tw-52), [TW-5.3](#tw-53), [TW-5.4](#tw-54), [TW-5.5](#tw-55), [TW-5.6](#tw-56). + +### M6 -- The reading layer on the thread pool + +Coarse on purpose: its scope is set when M5 is done. + +Items, each archived above under its own heading: [TW-6.1](#tw-61), [TW-6.2](#tw-62), [TW-6.3](#tw-63). + +### M7 -- Shutdown with reads in flight + +Coarse on purpose: its scope is set when M6 is done. + +Items, each archived above under its own heading: [TW-7.1](#tw-71), [TW-7.2](#tw-72). + +### M8 -- Findings of the pre-merge review + +A review of the branch against origin/main, 2026-10-16, found three defects in M5 to M7. + +Items, each archived above under its own heading: [TW-8.1](#tw-81), [TW-8.2](#tw-82), [TW-8.3](#tw-83), [TW-8.4](#tw-84), [TW-8.5](#tw-85), [TW-8.6](#tw-86), [TW-8.7](#tw-87), [TW-8.8](#tw-88), [TW-8.9](#tw-89), [TW-8.10](#tw-810). + +### M9 -- The reading layer is public, and the client chooses its sizes + +Deliverable: a client outside the crate, using only public items, creates the reading layer with a +buffer size, a buffer count, a client SQ capacity and the ring's two queue sizes of its own choosing, +registers files that child processes write, polls, and receives every announced byte exactly once as +chunks; and a size that cannot work is refused at construction, naming the field and its bound +([TW-D-16](DESIGN-NOTES.md#tw-d-16)). The layer is a module of this crate, not a new one. + +The module is `reader`, and its types are `TailReader` (the layer), `TailReaderOptions` (the five +sizes), `ReadEntry` (the SQ entry) and `Chunk` (the bytes): the engineer, 2026-10-16, "the names are +fine". + +Items, each archived above under its own heading: [TW-9.1](#tw-91), [TW-9.2](#tw-92), [TW-9.3](#tw-93), [TW-9.4](#tw-94), [TW-9.5](#tw-95), [TW-9.6](#tw-96). + +### M10 -- Findings of the review of M9 + +A review of the branch against origin/main, 2026-10-17, found four defects in M9. The engineer: "polls +initiated before the previous one completes should fail. fix all". + +Items, each archived above under its own heading: [TW-10.1](#tw-101), [TW-10.2](#tw-102), [TW-10.3](#tw-103), [TW-10.4](#tw-104), [TW-10.5](#tw-105), [TW-10.6](#tw-106), [TW-10.7](#tw-107), [TW-10.8](#tw-108), [TW-10.9](#tw-109), [TW-10.10](#tw-1010), [TW-10.11](#tw-1011), [TW-10.12](#tw-1012), [TW-10.13](#tw-1013). diff --git a/crates/win-multi-file-tail-watcher/COMPLETED-PLANS.md b/crates/win-multi-file-tail-watcher/COMPLETED-PLANS.md new file mode 100644 index 000000000..b1b609399 --- /dev/null +++ b/crates/win-multi-file-tail-watcher/COMPLETED-PLANS.md @@ -0,0 +1,9 @@ +# Completed plans: win-multi-file-tail-watcher + +Checklists whose planned work is complete. A checklist reappears in +[PLANS.md](PLANS.md) if new work is planned against it; the row here stays as the record of the work that was +finished. Individual items are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + +| Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | +|---|---|---|---| +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-18 | Report length changes across many files on a caller-owned beat, and read the bytes they gain through an I/O ring: M1 to M10, from the roster and the poll engine through the thread pool, shutdown, the reading layer and its public sizes, to the findings of their reviews. The two decisions of `M2+` are parked in [PARKED.md](PARKED.md). | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) | diff --git a/crates/win-multi-file-tail-watcher/PARKED.md b/crates/win-multi-file-tail-watcher/PARKED.md new file mode 100644 index 000000000..8ff40f3d0 --- /dev/null +++ b/crates/win-multi-file-tail-watcher/PARKED.md @@ -0,0 +1,17 @@ +# Parked: win-multi-file-tail-watcher + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M2+ -- Decisions to revisit once M2 is done + +Parked, not pending: the milestone that is next after M2 pulls these in. + +- [ ] **TW-2+.1** -- **Decide whether the diagnostic sink's machinery moves to a crate both this one + and durable-ioring use**, generic over the event type, or whether this crate's mirror stays. The + mirror is deliberate duplication, so that the sink could be built without touching another + crate's layering; the decision is to merge it or to delete the question, and is made when there + is more than one event to compare. durable-ioring's `StderrDiagnostics` and `FanOutDiagnostics` + were not mirrored, so shared machinery would bring them here. ([TW-D-13](DESIGN-NOTES.md#tw-d-13)) + +- [ ] **TW-2+.2** -- **Decide whether the buffers give chunks back after an unusually large poll**, and how many they keep. Needs a measurement the captures do not have: a roster that grows and then shrinks, polled across the change. ([TW-D-14](DESIGN-NOTES.md#tw-d-14)) diff --git a/crates/win-multi-file-tail-watcher/PLANS.md b/crates/win-multi-file-tail-watcher/PLANS.md index bbac34378..e82ce29c5 100644 --- a/crates/win-multi-file-tail-watcher/PLANS.md +++ b/crates/win-multi-file-tail-watcher/PLANS.md @@ -1,5 +1,8 @@ # Plans: win-multi-file-tail-watcher +Completed checklists are recorded in [COMPLETED-PLANS.md](COMPLETED-PLANS.md), and the items they +contained are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | Report length changes across many files on a caller-owned beat, and read the bytes they gain through an I/O ring. The milestones are in the checklist. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) | +| [PARKED.md](PARKED.md) | parked | `M2+`: whether the diagnostic sink's machinery moves to a crate this one and durable-ioring share, and whether the buffers give chunks back after an unusually large poll. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) | diff --git a/crates/win-numa-sys/PLANS.md b/crates/win-numa-sys/PLANS.md index f2ab79504..9e832b641 100644 --- a/crates/win-numa-sys/PLANS.md +++ b/crates/win-numa-sys/PLANS.md @@ -2,4 +2,4 @@ | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | M1 finishes what creating the crate started: `windows-placement-probe` still has its own `VirtualAllocExNuma`, so the duplication is currently **relocated rather than removed**, and `N-1.1` is where that is closed. `N-1.2` decides whether `QueryWorkingSetEx` observation moves here with it -- the capability that would let a caller ask whether a placement request was honoured, rather than being told by the documentation that success proves nothing. `M2+.1` is publishing, which is not optional indefinitely: the crate is a path dependency of the published `windows-ioring-sys`. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | +| [CHECKLIST.md](CHECKLIST.md) | not started | M1 finishes what creating the crate started: `windows-placement-probe` still has its own `VirtualAllocExNuma`, so the duplication is currently **relocated rather than removed**, and `N-1.1` is where that is closed. `N-1.2` decides whether `QueryWorkingSetEx` observation moves here with it -- the capability that would let a caller ask whether a placement request was honoured, rather than being told by the documentation that success proves nothing. `M2+.1` is publishing, which is not optional indefinitely: the crate is a path dependency of the published `windows-ioring-sys`. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-pipes-sys/CHECKLIST.md b/crates/win-pipes-sys/CHECKLIST.md index f63e0185f..4f9ead27d 100644 --- a/crates/win-pipes-sys/CHECKLIST.md +++ b/crates/win-pipes-sys/CHECKLIST.md @@ -5,18 +5,5 @@ Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md). The crate was made b Functions* Microsoft Learn lists under *System Services > Interprocess Communications > Pipes* in the Win32 reference. -- [x] **WP-1.1** -- `create_named_pipe`, `CreateNamedPipeW` with typed open and pipe modes. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-11) - -- [x] **WP-1.2** -- The workspace's other sites that call `CreateNamedPipeW` with an `unsafe` block of their own make their pipes with this crate. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-12) - -- [x] **WP-1.3** -- `ConnectNamedPipe` in its two forms and `DisconnectNamedPipe`, with `ERROR_PIPE_CONNECTED` and `ERROR_IO_PENDING` named as results. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-13) - -- [x] **WP-1.3.1** -- `windows-overlapped-io-sys`'s `tests/overlapped_cancellation.rs` issues its three `ConnectNamedPipe` calls through `connect_named_pipe_overlapped`. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-131) - -- [x] **WP-1.4** -- `CreatePipe`, the anonymous pipe, returning the two ends as owned handles. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-14) - -- [x] **WP-1.5** -- `GetNamedPipeInfo`, `GetNamedPipeHandleStateW` and `SetNamedPipeHandleState`, taking and returning the typed modes. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-15) - -- [x] **WP-1.6** -- `PeekNamedPipe`, `TransactNamedPipe`, `CallNamedPipeW` and `WaitNamedPipeW`, the operations on a connected pipe and on a name. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-16) - -- [x] **WP-1.7** -- `GetNamedPipeClientComputerNameW`, `GetNamedPipeClientProcessId`, `GetNamedPipeClientSessionId`, `GetNamedPipeServerProcessId` and `GetNamedPipeServerSessionId`, the queries of who is at each end. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wp-17) +Nothing is pending. `WP-1.1` to `WP-1.7` are complete and archived in +[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). diff --git a/crates/win-shared-os-owned-handle/CHECKLIST.md b/crates/win-shared-os-owned-handle/CHECKLIST.md index b1b59a02a..b8525846b 100644 --- a/crates/win-shared-os-owned-handle/CHECKLIST.md +++ b/crates/win-shared-os-owned-handle/CHECKLIST.md @@ -3,23 +3,5 @@ A shared owner of a Windows handle. See [README.md](README.md) for what the crate is, and [DESIGN-NOTES.md](DESIGN-NOTES.md) for why it exists. -## M1+ -- Adoption and publication (parked behind this crate's first merge) - -- [ ] **SH-1+.1** -- **Adoption by the crates that wrap `Arc` themselves.** The crate - exists so that they stop. Each adoption is that crate's own checklist item: - - [x] `windows-ioring-sys`: its own `SharedFile` is removed, and a safe push takes `SharedHandle` - (its [D-82](../windows-ioring-sys/DESIGN-NOTES.md#d-82)), landed by #119 -- not the alias first - planned; its `M31.3`, archived in - [its COMPLETED-CHECKLIST.md](../windows-ioring-sys/COMPLETED-CHECKLIST.md#m313). - - [ ] `durable-ioring`: its design names `SharedHandle` as the contract's file type (its - `DI-D-29`), replacing the crate-local `DurableFile` it had first designed; it is built on it - from `DI-3.1`. - - Done when both have landed. Two further crates wrap a handle the same way and were parked by the - engineer on 2026-10-07, each in its own checklist and outside this item: - `windows-namespace-request-sys`' `NR-inf.1` ([its CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md)), - which is not a drop-in replacement, and `windows-impersonation-token-sys`' `IT-inf.1` - ([its CHECKLIST.md](../windows-impersonation-token-sys/CHECKLIST.md)). - -- [x] **SH-1+.2** -- **Publish to crates.io.** Done: 0.1.0, by the publish workflow on the - `win-shared-os-owned-handle-v0.1.0` tag, 2026-10-07. +Nothing is pending. `SH-1+.1`, the adoption by the crates that wrapped `Arc` themselves, and `SH-1+.2`, +publication to crates.io, are complete and archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). diff --git a/crates/win-shared-os-owned-handle/COMPLETED-CHECKLIST.md b/crates/win-shared-os-owned-handle/COMPLETED-CHECKLIST.md new file mode 100644 index 000000000..9fb6bf579 --- /dev/null +++ b/crates/win-shared-os-owned-handle/COMPLETED-CHECKLIST.md @@ -0,0 +1,46 @@ +# Completed checklist: win-shared-os-owned-handle + +## Moved 2026-10-21 21:40:08 -04:00 -- SH-1+.1 and SH-1+.2: adoption and publication; the checklist is complete + +### SH-1+.1 -- Every crate that wrapped `Arc` itself now holds a `SharedHandle`, and none is left: the ring crate and, in the Windows driver, `durable-ioring`'s file type. *(completed 2026-10-21 21:40:08 -04:00)* + +The item as written, with its last sub-item checked: + +**SH-1+.1** -- **Adoption by the crates that wrap `Arc` themselves.** The crate + exists so that they stop. Each adoption is that crate's own checklist item: + - [x] `windows-ioring-sys`: its own `SharedFile` is removed, and a safe push takes `SharedHandle` + (its [D-82](../windows-ioring-sys/DESIGN-NOTES.md#d-82)), landed by #119 -- not the alias first + planned; its `M31.3`, archived in + [its COMPLETED-CHECKLIST.md](../windows-ioring-sys/COMPLETED-CHECKLIST.md#m313). + - [x] `durable-ioring`: its design names `SharedHandle` as the contract's file type (its + `DI-D-29`), replacing the crate-local `DurableFile` it had first designed; it is built on it + from `DI-3.1`. + + Done when both have landed. Two further crates wrap a handle the same way and were parked by the + engineer on 2026-10-07, each in its own checklist and outside this item: + `windows-namespace-request-sys`' `NR-inf.1` ([its CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md)), + which is not a drop-in replacement and which graduated on 2026-10-18 to that crate's `NR-6` + ([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32)), and `windows-impersonation-token-sys`' `IT-inf.1`, done 2026-10-18 + ([its COMPLETED-CHECKLIST.md](../windows-impersonation-token-sys/COMPLETED-CHECKLIST.md#it-inf1)). + +**What was found when it was closed.** Both of its sub-items had landed, and the second's wording had become wrong. +`windows-ioring-sys` was done by #119, as the item says. `durable-ioring` is built on the type in a different place than the +item expects: its design first named `SharedHandle` as the contract's file type ([DI-D-29](../durable-ioring/DESIGN-NOTES.md#di-d-29)), +and the target design ([DI-D-50](../durable-ioring/DESIGN-NOTES.md#di-d-50)) then made the portable core name a target by +`IoTargetKey`, so that no handle crosses the contract and the core has no Windows dependency. The type is held by the Windows +driver, `win-durable-ioring`: it depends on this crate, re-exports `SharedHandle`, and its `OpenedFile` is a `SharedHandle` +with the way it was opened recorded beside it. The portable crate depends on nothing here, by design. + +**The sweep.** A search of every crate's code for `Arc` finds it only inside this crate, and the crates that depend on +this one are the ring crate, the driver, and the crates the item names as outside it: the thread pool, the overlapped-I/O, +namespace-request, file-enumeration, impersonation-token, platform-probes and sync crates. The two the item records as parked +by the engineer on 2026-10-07 are done in their own checklists: `windows-namespace-request-sys` as `NR-6` +([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32)) and `windows-impersonation-token-sys` as +[`IT-inf.1`](../windows-impersonation-token-sys/COMPLETED-CHECKLIST.md#it-inf1). + +### SH-1+.2 -- Published to crates.io as 0.1.0, by the publish workflow on the `win-shared-os-owned-handle-v0.1.0` tag. *(completed 2026-10-07)* + +The item as written: + +**SH-1+.2** -- **Publish to crates.io.** Done: 0.1.0, by the publish workflow on the + `win-shared-os-owned-handle-v0.1.0` tag, 2026-10-07. diff --git a/crates/win-shared-os-owned-handle/COMPLETED-PLANS.md b/crates/win-shared-os-owned-handle/COMPLETED-PLANS.md new file mode 100644 index 000000000..df96c3361 --- /dev/null +++ b/crates/win-shared-os-owned-handle/COMPLETED-PLANS.md @@ -0,0 +1,9 @@ +# Completed plans: win-shared-os-owned-handle + +Checklists whose planned work is complete. A checklist reappears in +[PLANS.md](PLANS.md) if new work is planned against it; the row here stays as the record of the work that was +finished. Individual items are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + +| Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | +|---|---|---|---| +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-21 | A shared owner of a Windows handle: std's `OwnedHandle` behind a reference count, which std lacks and crates here had each wrapped under their own name. Published as 0.1.0 on 2026-10-07; the ring crate, `win-durable-ioring` and the other crates that wrapped `Arc` themselves hold a `SharedHandle`, and none is left. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-shared-os-owned-handle/PLANS.md b/crates/win-shared-os-owned-handle/PLANS.md index 106e6dea6..74fb7743a 100644 --- a/crates/win-shared-os-owned-handle/PLANS.md +++ b/crates/win-shared-os-owned-handle/PLANS.md @@ -1,5 +1,7 @@ # Plans: win-shared-os-owned-handle +Completed checklists are recorded in [COMPLETED-PLANS.md](COMPLETED-PLANS.md), and the items they +contained are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | Adoption by the crates that wrap `Arc` themselves; publication is done. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-sync-sys/CHECKLIST.md b/crates/win-sync-sys/CHECKLIST.md index 97907d543..a9386a36b 100644 --- a/crates/win-sync-sys/CHECKLIST.md +++ b/crates/win-sync-sys/CHECKLIST.md @@ -36,6 +36,15 @@ milestones when the next of these is taken up. dependency-version check ([check-dependency-versions.cjs](../../.github/release/check-dependency-versions.cjs)) does not cover this, since it asks only that a version exist. Gated on this crate's next release. + **Amended 2026-10-20: no `Release-As` footer is needed for the release that carries `WS-2.2`.** That release + includes a breaking commit (`feat(win-sync)!`), and with `bump-minor-pre-major` a breaking change takes 0.0.2 + to 0.1.0 on its own. A pin there would also be refused by + [check-commit-scope.ps1](../../tools/check-commit-scope.ps1), which rejects a pin followed by a break in the + same release. What is left of this item is the dependents' re-pin after the release. + +WS-M2 -- an event's clones share one handle -- is complete and archived in +[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#ws-22). + ## M-inf -- Parked - [ ] **WS-inf.1** -- **The in-process primitives**: SRW locks with condition variables (including diff --git a/crates/win-sync-sys/COMPLETED-CHECKLIST.md b/crates/win-sync-sys/COMPLETED-CHECKLIST.md index 707f878e0..5d73ad7a6 100644 --- a/crates/win-sync-sys/COMPLETED-CHECKLIST.md +++ b/crates/win-sync-sys/COMPLETED-CHECKLIST.md @@ -144,3 +144,22 @@ The item as it stood at completion: implements too. The outcome as an enum: signalled or abandoned with an index, timed out, or ended by an APC in an alertable wait. `QueueUserAPC` belongs with it, as what ends an alertable wait. `WaitForSingleObject` itself was taken up first, as [WS-2.1](COMPLETED-CHECKLIST.md#ws-21). + +## Moved 2026-10-18 23:18:38 -04:00 -- WS-M2: an event's clones share one handle + +### WS-2.2 -- `Event` and `WaitOnlyEvent` hold a `SharedHandle` and their clones share it; `try_clone` is gone, and the one operation that makes a handle is `duplicate_wait_only`. *(completed 2026-10-18 23:18:38 -04:00)* + +Planned and implemented as `WS-2.1`, an ID the single-object wait above already had; renumbered `WS-2.2` on +2026-10-20 so the two have distinct anchors. Records written before then that mean this item say `WS-2.1`. + +Proposed by the assistant as the engineer's ruling of 2026-10-18 applied here, and started on "implement ws-2.1". +The decision is [WS-D-10](DESIGN-NOTES.md#ws-d-10); [WS-D-5](DESIGN-NOTES.md#ws-d-5) and +[WS-D-7](DESIGN-NOTES.md#ws-d-7) are marked amended. Implementing it found the plan backwards: the thread pool's +`From` took the event as an `OwnedHandle`, so its shared wait target +([M-T15.1](../windows-threadpool-sys/COMPLETED-CHECKLIST.md#m-t151)) had to land first, and the item was +restructured. It landed in single-crate steps that each built: the additive change here with the `Release-As: +0.1.0` footer `WS-1+.6` asks for; the thread pool's `From` moving to the shared target; `win-durable-ioring` +cloning where it duplicated; the deletion here. The sweep found the `WS-1+.1` APC sabotages surviving, a +pre-existing gap since `WS-D-9`'s same-process check came to precede `QueueUserAPC`'s own refusal; one test's +handle was given the query right so that the refusal is the call's again, in a commit of its own. All 47 of the +crate's sabotages behave as declared. diff --git a/crates/win-sync-sys/Cargo.toml b/crates/win-sync-sys/Cargo.toml index 910c670be..5b681781f 100644 --- a/crates/win-sync-sys/Cargo.toml +++ b/crates/win-sync-sys/Cargo.toml @@ -18,6 +18,13 @@ categories = ["api-bindings", "os::windows-apis", "concurrency"] # safe layer more or less directly over a Win32 API area, synchronization. [dependencies] +# `DuplicateHandle` and the `Access` mask, which were this crate's `duplicate` module until they were +# moved to where Microsoft Learn files them. Re-exported at the paths they had. +win-handles-sys = { version = "0.1.0", path = "../win-handles-sys" } + +# An event's clones share one handle (WS-D-10), held as the workspace's one shared owner of a handle. +win-shared-os-owned-handle = { version = "0.1.0", path = "../win-shared-os-owned-handle" } + # `default-features = false` matches every other crate in the workspace. # # `Win32_System_Threading` is `CreateEventW`, `SetEvent` and `ResetEvent`. diff --git a/crates/win-sync-sys/DESIGN-NOTES.md b/crates/win-sync-sys/DESIGN-NOTES.md index c8be00ba2..810b8c9db 100644 --- a/crates/win-sync-sys/DESIGN-NOTES.md +++ b/crates/win-sync-sys/DESIGN-NOTES.md @@ -18,8 +18,18 @@ which exists to wrap the thread pool and nothing else. | WS-D-2 | **`Event` comes first, alone.** **Amended by [WS-D-6](#ws-d-6): the single-object wait was the first of the wait functions taken up.** The engineer's sequencing, 2026-10-07. The other kernel objects and the wait functions follow as their own items in [CHECKLIST.md](CHECKLIST.md) -- sequenced after, not dropped. `Event` has no `wait` of its own in the first item for the same reason: waiting is the wait functions' item, and an event's wait is one case of it. | | WS-D-3 | **`PulseEvent` is not offered, because it gives a wrong answer rather than a slow one.** Its own documentation says it "is unreliable and should not be used" and "exists mainly for backward compatibility" ([PulseEvent](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-pulseevent)). The mechanism it gives: a thread waiting on the event can be momentarily removed from the wait by a kernel-mode APC, and a pulse that lands in that window releases nothing, because `PulseEvent` releases only the threads waiting at the instant it is called. A waiter can therefore miss a signal it was waiting for, and nothing reports that it did. The same page notes that a wait-for-all on several objects can see the pulse come and go without returning. Microsoft's replacement is a condition variable. This is a removal on analytic grounds -- an operation that silently does the wrong thing -- not on a measurement, which is the ground the repository accepts for foreclosing an option. | | WS-D-4 | **An `Event` owns a handle to an event object, and the load-bearing half of that is "an event", because a safe conversion into the thread pool's wait target depends on it.** Ownership alone -- what std's `OwnedHandle` already gives -- is what makes `set` and `reset` memory-safe: the handle is valid, and `SetEvent` on a valid handle to some other kind of object simply fails. Event-ness makes them *correct* rather than safe. It becomes a safety property one layer up: the thread pool treats a wait on a **mutex** as undefined behaviour, so `windows-threadpool-sys` can accept an `Event` without `unsafe` only because an `Event` is never a mutex ([WS-D-5](#ws-d-5)). Therefore **adopting an existing handle is `unsafe`** (`Event::from_owned_handle`): the caller vouches that it is an event, and that it carries `SYNCHRONIZE` access. The second clause is conservative: what the pool does with a handle it cannot wait on is not documented, and an `Event` must be safe to hand it. A handle lacking `EVENT_MODIFY_STATE` is permitted -- `set` and `reset` then fail with an error, which is the error path their tests traverse. No checked adoption is offered: telling whether a handle is an event means asking the kernel for the object's type, a native-API question this crate has not taken up; the option stays open. | -| WS-D-5 | **`windows-threadpool-sys` depends on this crate, never the reverse; the conversion into the pool's wait target lives there.** The pool waits on synchronization objects, so the arrow runs from the pool to the objects. `windows-threadpool-sys` provides `From for WaitableHandle`, and `WaitableHandle::event` is built on `Event`, so the crate creates no event of its own. A pool-owned event is signalled through a clone the caller keeps (`Event::try_clone`, a duplicate handle to the same object), which needs no `unsafe` -- unlike signalling through the borrowed handle the wait hands back, which is what its examples did before. When `Mutex` is added it gets **no** such conversion, so the pool's one undefined case stays unrepresentable in safe code. | +| WS-D-5 | **Amended by [WS-D-10](#ws-d-10): the clone a caller keeps shares the event's handle rather than duplicating it.** **`windows-threadpool-sys` depends on this crate, never the reverse; the conversion into the pool's wait target lives there.** The pool waits on synchronization objects, so the arrow runs from the pool to the objects. `windows-threadpool-sys` provides `From for WaitableHandle`, and `WaitableHandle::event` is built on `Event`, so the crate creates no event of its own. A pool-owned event is signalled through a clone the caller keeps (`Event::try_clone`, a duplicate handle to the same object), which needs no `unsafe` -- unlike signalling through the borrowed handle the wait hands back, which is what its examples did before. When `Mutex` is added it gets **no** such conversion, so the pool's one undefined case stays unrepresentable in safe code. | | WS-D-6 | **The wait functions begin with `wait_for_single_object`, over any `AsHandle`, and `Event` still has no `wait` method of its own.** **Extended by [WS-D-8](#ws-d-8): the rest of the wait functions, the alertable ending and `QueueUserAPC` are built.** The assistant's choice, 2026-10-12, confirmed by the engineer on 2026-10-13 under durable-ioring's `DI-9.7.7`. Raised by that crate's `DI-7.3`, whose pumped delivery has the consumer's own thread wait on a backend's notification, a handle, in a crate that forbids `unsafe`; [WS-D-2](#ws-d-2) sequenced the wait functions after `Event`, and this is the first of them taken up, the rest of `WS-1+.1` staying where the engineer parked it. **Over any handle**, because the wait is what `WaitForSingleObject` is, and the ring's completion event reaches durable-ioring as an `OwnedHandle`, not an `Event`; an `Event::wait` would be one more door onto the same call, and a `Waitable` trait over every object is the parked item's design, which one function does not pre-empt. **`WaitOutcome` has `Abandoned`**, though no event is ever abandoned: the function is over any handle, and answering `Signalled` for a mutex whose owner died would be a wrong answer, not a slow one. It is non-exhaustive so the alertable wait's third ending can join it. **A satisfied wait changes the object** -- an auto-reset event is consumed -- and the documentation says so, since a poll with `Duration::ZERO` is also a wait. **A timeout rounds up** to whole milliseconds so a nonzero wait is never a poll, and **one the call cannot express is refused**: `INFINITE` is `u32::MAX`, so a finite wait may not be that many milliseconds, and shortening it would end a wait early without saying so. **Not alertable**, so a queued APC does not end it; `WaitForSingleObjectEx` is the parked item's. | -| WS-D-7 | **A handle with a chosen access is `duplicate_handle_with_access` over any handle, with a typed `Access` mask; `Event::try_clone_wait_only` gives a `WaitOnlyEvent`, a type with no `set` and no `reset`.** The assistant's choice, 2026-10-12, confirmed by the engineer on 2026-10-13 under durable-ioring's `DI-9.7.9`. Raised by that crate's `DI-7.5`, whose read-only handle on the ring's completion event ([DI-D-55](../durable-ioring/DESIGN-NOTES.md#di-d-55)) is `DI-7+.1`; the item here is [WS-1+.5](COMPLETED-CHECKLIST.md#ws-1+5). **Over any handle**, returning an `OwnedHandle`, because that consumer holds the ring's event as an `OwnedHandle` and not an `Event`, and whether a handle is an event cannot be asked here ([WS-D-4](#ws-d-4)); the function is memory-safe on any open handle, so it is not `unsafe`. **`Access`** is a newtype over the Win32 bits with `SYNCHRONIZE` and `EVENT_MODIFY_STATE` named, combined with `|`, and `from_bits` for every other right, so a consumer that forbids `unsafe` and does not depend on `windows-sys` need not take a constant from it. **`WaitOnlyEvent` is a type, not an `Event` that fails when set.** An `Event` adopted without `EVENT_MODIFY_STATE` is permitted ([WS-D-4](#ws-d-4)) and reports access denied from `set`; that is an `Event` whose methods fail, where this has nothing to fail, and the failure is not something a caller can discover too late. It keeps what [WS-D-4](#ws-d-4) and [WS-D-5](#ws-d-5) rely on, being known to be an event, and nothing adopts a handle as one, so that holds without `unsafe`. It lends its handle, so `wait_for_single_object` waits on it. **What the guarantee is, measured on this host and written as a test:** Windows does **not** confine a duplicate to the source handle's access. A duplicate of a `SYNCHRONIZE`-only handle asking for `EVENT_MODIFY_STATE` succeeds and can set the event. So a wait-only handle cannot be used to signal, by this crate or by a raw call, and a holder that sets out to can duplicate it into one that can; the narrowing guards against a handle used for what it was not handed out for, and is not a boundary within the process, which no process has. The item as first written said the function "can only narrow"; a test expecting the refusal found it false, and the documentation and the item's consumers were corrected before anything bound to it. A mask Windows refuses, every bit set, is reported as access denied, which gives the error path a deterministic test. **Rejected:** making the function narrow-only by reading the source's granted access, which is a native-API query this crate has not taken up and would not stop a raw `DuplicateHandle`. **Same process only**: the two process arguments are the current process, and duplicating into another process crosses the boundary [WS-D-1](#ws-d-1) leaves undecided; it belongs with [WS-inf.2](CHECKLIST.md). | +| WS-D-7 | **Amended by [WS-D-10](#ws-d-10): `try_clone_wait_only` is renamed `duplicate_wait_only`, since `clone` now means sharing a handle.** **A handle with a chosen access is `duplicate_handle_with_access` over any handle, with a typed `Access` mask; `Event::try_clone_wait_only` gives a `WaitOnlyEvent`, a type with no `set` and no `reset`.** The assistant's choice, 2026-10-12, confirmed by the engineer on 2026-10-13 under durable-ioring's `DI-9.7.9`. Raised by that crate's `DI-7.5`, whose read-only handle on the ring's completion event ([DI-D-55](../durable-ioring/DESIGN-NOTES.md#di-d-55)) is `DI-7+.1`; the item here is [WS-1+.5](COMPLETED-CHECKLIST.md#ws-1+5). **Over any handle**, returning an `OwnedHandle`, because that consumer holds the ring's event as an `OwnedHandle` and not an `Event`, and whether a handle is an event cannot be asked here ([WS-D-4](#ws-d-4)); the function is memory-safe on any open handle, so it is not `unsafe`. **`Access`** is a newtype over the Win32 bits with `SYNCHRONIZE` and `EVENT_MODIFY_STATE` named, combined with `|`, and `from_bits` for every other right, so a consumer that forbids `unsafe` and does not depend on `windows-sys` need not take a constant from it. **`WaitOnlyEvent` is a type, not an `Event` that fails when set.** An `Event` adopted without `EVENT_MODIFY_STATE` is permitted ([WS-D-4](#ws-d-4)) and reports access denied from `set`; that is an `Event` whose methods fail, where this has nothing to fail, and the failure is not something a caller can discover too late. It keeps what [WS-D-4](#ws-d-4) and [WS-D-5](#ws-d-5) rely on, being known to be an event, and nothing adopts a handle as one, so that holds without `unsafe`. It lends its handle, so `wait_for_single_object` waits on it. **What the guarantee is, measured on this host and written as a test:** Windows does **not** confine a duplicate to the source handle's access. A duplicate of a `SYNCHRONIZE`-only handle asking for `EVENT_MODIFY_STATE` succeeds and can set the event. So a wait-only handle cannot be used to signal, by this crate or by a raw call, and a holder that sets out to can duplicate it into one that can; the narrowing guards against a handle used for what it was not handed out for, and is not a boundary within the process, which no process has. The item as first written said the function "can only narrow"; a test expecting the refusal found it false, and the documentation and the item's consumers were corrected before anything bound to it. A mask Windows refuses, every bit set, is reported as access denied, which gives the error path a deterministic test. **Rejected:** making the function narrow-only by reading the source's granted access, which is a native-API query this crate has not taken up and would not stop a raw `DuplicateHandle`. **Same process only**: the two process arguments are the current process, and duplicating into another process crosses the boundary [WS-D-1](#ws-d-1) leaves undecided; it belongs with [WS-inf.2](CHECKLIST.md). | | WS-D-8 | **The rest of the wait functions take alertability as an `Alertable` argument, wait for any and wait for all are two functions, `SignalObjectAndWait` takes a `Signalable`, and `QueueUserAPC` takes a Rust closure.** The assistant's choices, 2026-10-12, confirmed by the engineer on 2026-10-13 under durable-ioring's `DI-9.7.9`; the item is [WS-1+.1](COMPLETED-CHECKLIST.md#ws-1+1). **Alertability** is a two-state enum on `wait_for_single_object_ex`, `wait_for_any`, `wait_for_all` and `signal_object_and_wait`, not a `bool`, so a call site reads `Alertable::Yes`; `wait_for_single_object` stays as the non-alertable form, being what Win32 names `WaitForSingleObject`, and `WaitForMultipleObjects` is the `Alertable::No` case of the `Ex` form, not a second function. **`WaitOutcome::Alerted`** is the ending [WS-D-6](#ws-d-6) left room for by making the enum non-exhaustive. **The wait stays over `AsHandle`, and there is no `Waitable` trait**, which the item proposed: every handle can be passed to a wait, so a trait would restrict nothing and would exclude the `OwnedHandle` and `BorrowedHandle` a consumer such as durable-ioring holds; the option stays open, since a trait with a blanket implementation over `AsHandle` could be added without breaking a caller. **Any and all are two functions** because their results differ: a wait for any reports which object, as `WaitAnyOutcome::{Signalled(i), Abandoned(i)}`, and a wait for all reports `WaitOutcome`, since the index that Win32 returns with it is not one a caller can use. **`MAXIMUM_OBJECTS`** (64) is public and a larger set is refused, not split, because a wait on the first sixty-four is not a wait on all of them; the zero and over-maximum checks also keep the handle array from being read past its length, which the kernel's own refusal makes unobservable, so their two sabotages are declared `survives`. **`Signalable`** is the trait for what `SignalObjectAndWait` can signal, so that an object which cannot be signalled is refused when it is passed. `Event` implements it, the semaphore and mutex items will, and `WaitOnlyEvent` does not, which a `compile_fail` doctest states; it is not sealed, since a handle claimed as signalable that is not gets the call's error and no undefined behaviour. **`queue_user_apc`** takes any handle that lends itself and a `FnOnce() + Send + 'static`, boxed twice because a fat pointer does not fit the call's one word, with a form for the calling thread, which is what makes an alertable wait reachable in a test without a second thread. A failed call drops the routine; a call that succeeds for a thread that never waits alertably leaks it, which is documented and tested. **Observed on this host, and each written as a test so a Windows that changed it would show:** a pending APC ends an alertable wait and runs even when an object is already signalled, leaving the object for the next wait; a wait for any tolerates the same handle twice and reports the first index, and a wait for all refuses it with `ERROR_INVALID_PARAMETER`; and **an APC queued to a thread that has not finished starting runs as the thread starts, before the Rust runtime has set the thread up, and a routine that calls `std::thread::current` there aborts the process**. The last was found when the first run of the tests aborted, and is on the function's documentation. **Not built, and not decided:** `QueueUserAPC2`, whose special user-mode APC runs without the target being in an alertable wait; it is queued as [WS-inf.4](CHECKLIST.md). | | WS-D-9 | **`queue_user_apc` refuses a thread that is not this process's, because it is safe and the routine is an address in this process.** Found 2026-10-14 by the review of PR 126: the function took any `AsHandle`, and a thread handle of another process with `THREAD_SET_CONTEXT` -- one `OpenThread` can make -- would have had this process's callback address and boxed closure delivered where they mean nothing, running whatever is at that address. **The check** is `GetProcessIdOfThread` against `GetCurrentProcessId` before anything is queued, as [`InvalidInput`] for another process, and the call's own error for a handle that is not a thread or that lacks access; it asks of the handle `THREAD_QUERY_LIMITED_INFORMATION` besides `THREAD_SET_CONTEXT`, which the handle of a `std::thread::JoinHandle` carries. A thread's process cannot change, so the answer holds for as long as the handle is borrowed. **Rejected**: making the function `unsafe` with a same-process requirement, which burdens every caller with an obligation the crate can discharge itself. **Tested** against a suspended child process's main thread handle, which carries every access and belongs to another process; the sabotage that removes the check is caught. **No work is queued behind this**: nothing about it is deferred. | +| WS-D-10 | **An `Event`'s clones share one handle, held as a `SharedHandle`; `try_clone` goes without a replacement, and the one operation that makes a new handle is `duplicate_wait_only`, because narrowing access needs one.** The engineer's ruling of 2026-10-18, recorded in the request crate's [D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32) ("I want us to move forward as much as possible towards better interfaces and this counts"), applied here by the assistant as [WS-2.2](COMPLETED-CHECKLIST.md#ws-22) (then numbered `WS-2.1`) and started on the engineer's "implement ws-2.1" the same day. **No names of its own for duplicating.** Asked whether duplication needs them, it has them already, over any handle: std's `try_clone_to_owned` at the same access, and `duplicate_handle_with_access` at a chosen one. A same-access duplicate gives nothing sharing does not. **`duplicate_wait_only` stays** because its result is the typed `WaitOnlyEvent` ([WS-D-7](#ws-d-7)), which the general operations cannot make without `unsafe`, and it loses `clone` from its name. `WaitOnlyEvent`'s clones share as well. **Out of an event:** `From for SharedHandle`, for a consumer that holds handles shared, such as the thread pool's wait target (the workspace's [A wait may hold its handle shared](../../DESIGN-NOTES.md#a-wait-may-share-its-handle)); and `try_into_owned`, which gives an `OwnedHandle` to the last holder and returns the event otherwise. **Deleted:** `try_clone`, `try_clone_wait_only`, `WaitOnlyEvent::try_clone`, and the two `From` conversions into `OwnedHandle`, which could not be infallible once a clone might hold the handle; for a while they remained and duplicated while a clone was held, so that the workspace's callers could move in commits of their own. | + +## `duplicate_handle_with_access` and `Access` moved to win-handles-sys + +The engineer, 2026-10-18. The `duplicate` module of [WS-D-7](#ws-d-7) is in +[win-handles-sys](../win-handles-sys/DESIGN-NOTES.md), where Microsoft Learn files `DuplicateHandle` +([the rule](../../DESIGN-NOTES.md#safe-wrappers-are-placed-by-the-learn-section-they-are-documented-under)). +This crate re-exports both at the paths it had, so none changed. `WS-D-7`'s reasoning about `Access`, the +duplicate and `WaitOnlyEvent` stands; only where the function lives changed, and the tests that observe a +duplicate through raw calls went with it. diff --git a/crates/win-sync-sys/README.md b/crates/win-sync-sys/README.md index b5c5ea5f8..1b6c98b58 100644 --- a/crates/win-sync-sys/README.md +++ b/crates/win-sync-sys/README.md @@ -7,24 +7,26 @@ suffix is that promise, as the workspace's naming decision defines it. ## What is here - **`Event`** -- an owned event object, created with `CreateEventW`, that can be set and reset - without `unsafe`. A clone (`try_clone`) is a second handle to the same object, so one part of a - program can keep an event to signal while another hands it to something that waits on it. + without `unsafe`. A clone shares the one handle, so one part of a program can keep an event to + signal while another hands it to something that waits on it, and the handle closes when the last + clone drops. ```rust -use std::os::windows::io::OwnedHandle; +use win_shared_os_owned_handle::SharedHandle; use win_sync_sys::{Event, ResetMode}; let ready = Event::new(ResetMode::Auto, false)?; -// A second handle to the same event, for another thread to signal through. -let signaller = ready.try_clone()?; +// A second holder of the same handle, for another thread to signal through. +let signaller = ready.clone(); std::thread::spawn(move || signaller.set()) .join() .expect("the signalling thread")?; // `ready` is now signalled. Anything that waits on a handle can take it from -// here; an `Event` converts into an `OwnedHandle` and lends one as well. -let handle: OwnedHandle = ready.into(); +// here: an `Event` lends its handle, converts into a `SharedHandle`, and gives +// an `OwnedHandle` to its last holder through `try_into_owned`. +let handle: SharedHandle = ready.into(); # drop(handle); # Ok::<(), std::io::Error>(()) ``` @@ -99,7 +101,7 @@ signal_object_and_wait(&ping, &pong, poll, Alertable::No)?; ``` - **`WaitOnlyEvent`** and **`duplicate_handle_with_access`** -- a handle to the same object that can - do less. `Event::try_clone_wait_only` gives a handle to the event that can be waited on and can + do less. `Event::duplicate_wait_only` makes a new handle to the event that can be waited on and can neither set nor reset it: the kernel refuses a `SetEvent` or `ResetEvent` on it. `duplicate_handle_with_access` is `DuplicateHandle` with the access mask chosen, over any handle and returning an `OwnedHandle`. **Windows does not confine a duplicate to the source's access**, @@ -114,7 +116,7 @@ use win_sync_sys::{Access, Event, ResetMode, WaitOutcome, duplicate_handle_with_ let event = Event::new(ResetMode::Manual, false)?; // Hand this to something that should wait for the event and never signal it. -let waiter = event.try_clone_wait_only()?; +let waiter = event.duplicate_wait_only()?; assert_eq!(wait_for_single_object(&waiter, Some(Duration::ZERO))?, WaitOutcome::TimedOut); event.set()?; assert_eq!(wait_for_single_object(&waiter, None)?, WaitOutcome::Signalled); diff --git a/crates/win-sync-sys/sabotage.json b/crates/win-sync-sys/sabotage.json index 1c7fa0db0..75fda876f 100644 --- a/crates/win-sync-sys/sabotage.json +++ b/crates/win-sync-sys/sabotage.json @@ -1,7 +1,7 @@ { "package": "win-sync-sys", "description": "Sabotages for win-sync-sys. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", - "notCoveredHere": "Event::new's and Event::try_clone's error paths are absent: CreateEventW and DuplicateHandle fail only when the process cannot get another handle or the kernel cannot allocate the object, which no test can produce on demand. event.rs says so at each definition. The APC routine's panic is absent: a panic out of an extern \"system\" function aborts the process, which no in-process test survives. So is an APC delivered during a thread's start, which aborts when the routine touches the Rust runtime and is documented on queue_user_apc rather than tested. QueueUserAPC's failure for the current thread (the kernel cannot allocate the APC) is not reachable on demand.", + "notCoveredHere": "Event::new's and Event::duplicate_wait_only's error paths are absent: CreateEventW and DuplicateHandle fail only when the process cannot get another handle or the kernel cannot allocate the object, which no test can produce on demand. event.rs says so at each definition. The APC routine's panic is absent: a panic out of an extern \"system\" function aborts the process, which no in-process test survives. So is an APC delivered during a thread's start, which aborts when the routine touches the Rust runtime and is documented on queue_user_apc rather than tested. QueueUserAPC's failure for the current thread (the kernel cannot allocate the APC) is not reachable on demand.", "sabotages": [ { "name": "WS-1.1: set resets instead", @@ -60,12 +60,12 @@ "replace": [" if unsafe { ResetEvent(self.handle.as_raw_handle()) } == FALSE {\n return Ok(());\n"] }, { - "name": "WS-1.1: try_clone creates a new event", + "name": "WS-2.2: a clone makes a new event instead of sharing the handle", "file": "src/event.rs", "expect": "caught", - "why": "A clone that is a different event looks like a working event in isolation; it fails only where the two are meant to be one, which is the whole use -- keeping one handle to signal while another is waited on.", - "find": [" /// the process cannot get another handle.\n pub fn try_clone(&self) -> io::Result {\n Ok(Self {\n handle: self.handle.try_clone()?,\n"], - "replace": [" /// the process cannot get another handle.\n pub fn try_clone(&self) -> io::Result {\n Ok(Self {\n handle: Event::new(ResetMode::Manual, false)?.handle,\n"] + "why": "A clone that is a different event looks like a working event in isolation; it fails only where the two are meant to be one, which is the whole use -- keeping one holder to signal while another is waited on. Since WS-D-10 a clone is a second holder of the one handle.", + "find": ["#[derive(Clone, Debug)]\npub struct Event {\n handle: SharedHandle,\n}\n"], + "replace": ["#[derive(Debug)]\npub struct Event {\n handle: SharedHandle,\n}\n\nimpl Clone for Event {\n fn clone(&self) -> Self {\n Self::new(ResetMode::Manual, false).expect(\"a new event\")\n }\n}\n"] }, { "name": "WS-2.1: a timeout is reported as a signal", @@ -147,77 +147,29 @@ "find": [" let code = unsafe { WaitForSingleObject(object.as_handle().as_raw_handle(), milliseconds) };\n"], "replace": [" let code = unsafe { WaitForSingleObject(std::ptr::null_mut(), milliseconds) };\n"] }, - { - "name": "WS-1+.5: the duplicate ignores the access asked for", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "The access is the whole point. A duplicate that gets none cannot be waited on, which the wait tests see, and cannot set, which the modify-state test sees.", - "find": [" access.bits(),\n"], - "replace": [" 0,\n"] - }, - { - "name": "WS-1+.5: the duplicate keeps the source's access", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "DUPLICATE_SAME_ACCESS (2) makes the call ignore the mask, so every duplicate would be a full-access clone: the narrowing, which is the reason for the function, silently not happening. The synchronize-only tests see it as a set that succeeds.", - "find": [" FALSE,\n 0,\n )\n"], - "replace": [" FALSE,\n 2,\n )\n"] - }, - { - "name": "WS-1+.5: the duplicate is inheritable", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "A handle that a child process inherits is one the caller did not decide to hand over; the documented contract is that it is not inheritable.", - "find": [" &mut duplicate,\n access.bits(),\n FALSE,\n"], - "replace": [" &mut duplicate,\n access.bits(),\n 1,\n"] - }, - { - "name": "WS-1+.5: a refused duplicate is reported as success", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "The error path is reached by a mask Windows refuses. Swallowing it would hand back a handle that is not one.", - "find": [" if ok == FALSE {\n return Err(io::Error::last_os_error());\n"], - "replace": [" if false {\n return Err(io::Error::last_os_error());\n"] - }, - { - "name": "WS-1+.5: combining access masks intersects them", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "A mask built with | that comes out as the common bits asks for nothing: the modify-state-and-synchronize duplicates cannot set or cannot wait.", - "find": [" Self(self.0 | other.0)\n"], - "replace": [" Self(self.0 & other.0)\n"] - }, - { - "name": "WS-1+.5: the SYNCHRONIZE constant is the modify-state right", - "file": "src/duplicate.rs", - "expect": "caught", - "why": "The named right the wait-only handle is built from. Wrong, the handle can set and cannot wait.", - "find": [" pub const SYNCHRONIZE: Self = Self(SYNCHRONIZATION_SYNCHRONIZE);\n"], - "replace": [" pub const SYNCHRONIZE: Self = Self(EVENT_MODIFY_STATE);\n"] - }, { "name": "WS-1+.5: a wait-only clone is made with the modify-state right too", "file": "src/event.rs", "expect": "caught", "why": "The guarantee of the type. A wait-only clone that can set is a typed promise the kernel does not back; every cannot-set test sees a set that succeeds.", - "find": [" handle: duplicate_handle_with_access(self, Access::SYNCHRONIZE)?,\n"], - "replace": [" handle: duplicate_handle_with_access(self, Access::SYNCHRONIZE | Access::EVENT_MODIFY_STATE)?,\n"] + "find": [" handle: SharedHandle::new(duplicate_handle_with_access(self, Access::SYNCHRONIZE)?),\n"], + "replace": [" handle: SharedHandle::new(duplicate_handle_with_access(self, Access::SYNCHRONIZE | Access::EVENT_MODIFY_STATE)?),\n"] }, { - "name": "WS-1+.5: a clone of a wait-only clone can set", + "name": "WS-2.2: a clone of a wait-only event can set", "file": "src/event.rs", "expect": "caught", - "why": "try_clone on the wait-only type must keep it wait-only; a clone made with more access is the narrowing undone by the one operation the type offers.", - "find": [" /// [`Event::try_clone`].\n pub fn try_clone(&self) -> io::Result {\n Ok(Self {\n handle: self.handle.try_clone()?,\n"], - "replace": [" /// [`Event::try_clone`].\n pub fn try_clone(&self) -> io::Result {\n Ok(Self {\n handle: duplicate_handle_with_access(&self.handle, Access::SYNCHRONIZE | Access::EVENT_MODIFY_STATE)?,\n"] + "why": "A clone of the wait-only type must keep it wait-only; a clone made with more access is the narrowing undone by the one operation the type offers. Since WS-D-10 a clone shares the handle, and so its access.", + "find": ["#[derive(Clone, Debug)]\npub struct WaitOnlyEvent {\n handle: SharedHandle,\n}\n"], + "replace": ["#[derive(Debug)]\npub struct WaitOnlyEvent {\n handle: SharedHandle,\n}\n\nimpl Clone for WaitOnlyEvent {\n fn clone(&self) -> Self {\n Self {\n handle: SharedHandle::new(\n duplicate_handle_with_access(&self.handle, Access::SYNCHRONIZE | Access::EVENT_MODIFY_STATE)\n .expect(\"a duplicate\"),\n ),\n }\n }\n}\n"] }, { "name": "WS-1+.5: a wait-only clone is a different event", "file": "src/event.rs", "expect": "caught", "why": "A clone that waits on some other event looks right until the original is set.", - "find": [" handle: duplicate_handle_with_access(self, Access::SYNCHRONIZE)?,\n"], - "replace": [" handle: duplicate_handle_with_access(Event::new(ResetMode::Manual, false)?, Access::SYNCHRONIZE)?,\n"] + "find": [" handle: SharedHandle::new(duplicate_handle_with_access(self, Access::SYNCHRONIZE)?),\n"], + "replace": [" handle: SharedHandle::new(duplicate_handle_with_access(Event::new(ResetMode::Manual, false)?, Access::SYNCHRONIZE)?),\n"] }, { "name": "WS-1+.1: an alerted wait is reported as a timeout", @@ -379,14 +331,6 @@ "find": [" if wait_all { TRUE } else { FALSE },\n"], "replace": [" if wait_all { 1 } else { 0 },\n"] }, - { - "name": "CONTROL: the duplicate's failure is tested against 0 rather than FALSE", - "file": "src/duplicate.rs", - "expect": "survives", - "why": "The manifest checking itself on the new module: FALSE is 0, so this is an equivalent change on code every duplicate runs. If it comes back caught, something is reacting to text rather than behaviour.", - "find": [" if ok == FALSE {\n return Err(io::Error::last_os_error());\n"], - "replace": [" if ok == 0 {\n return Err(io::Error::last_os_error());\n"] - }, { "name": "CONTROL: set compares against 0 rather than FALSE", "file": "src/event.rs", @@ -410,6 +354,30 @@ "why": "The manifest checking itself on the new module: rounding up by addition is the same value for every timeout a Duration can hold, so this must survive; if it comes back caught, something is matching incidental text.", "find": [" let rounded_up = timeout.as_nanos().div_ceil(1_000_000);\n"], "replace": [" let rounded_up = (timeout.as_nanos() + 999_999) / 1_000_000;\n"] + }, + { + "name": "WS-2.2: an event never gives its handle back", + "file": "src/event.rs", + "expect": "caught", + "why": "try_into_owned gives the handle to its last holder; refusing always would leave a caller unable to recover an OwnedHandle at all.", + "find": [" /// lost and the caller can try again once the clones have dropped.\n pub fn try_into_owned(self) -> Result {\n self.handle\n .try_into_owned()\n"], + "replace": [" /// lost and the caller can try again once the clones have dropped.\n pub fn try_into_owned(self) -> Result {\n Err::(self.handle)\n"] + }, + { + "name": "WS-2.2: the wait-only operation shares the handle instead of narrowing a new one", + "file": "src/event.rs", + "expect": "caught", + "why": "Narrowing access needs a new handle, which is why this one operation still duplicates; sharing would hand out a handle that can set the event.", + "find": [" handle: SharedHandle::new(duplicate_handle_with_access(self, Access::SYNCHRONIZE)?),\n"], + "replace": [" handle: self.handle.clone(),\n"] + }, + { + "name": "CONTROL: WS-2.2: a wait-only event's try_into_owned names its handle differently", + "file": "src/event.rs", + "expect": "survives", + "why": "The manifest checking itself. Renaming a closure's parameter changes nothing a test can see.", + "find": [" /// If a clone still holds the handle, returns `self` unchanged.\n pub fn try_into_owned(self) -> Result {\n self.handle\n .try_into_owned()\n .map_err(|handle| Self { handle })\n"], + "replace": [" /// If a clone still holds the handle, returns `self` unchanged.\n pub fn try_into_owned(self) -> Result {\n self.handle\n .try_into_owned()\n .map_err(|shared| Self { handle: shared })\n"] } ] } diff --git a/crates/win-sync-sys/src/duplicate.rs b/crates/win-sync-sys/src/duplicate.rs deleted file mode 100644 index 108a2a3f9..000000000 --- a/crates/win-sync-sys/src/duplicate.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! Duplicating a handle with a chosen access: `DuplicateHandle`. - -use std::io; -use std::ops::BitOr; -use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle}; -use std::ptr; - -use windows_sys::Win32::Foundation::{DuplicateHandle, FALSE, HANDLE}; -use windows_sys::Win32::System::Threading::{ - EVENT_MODIFY_STATE, GetCurrentProcess, SYNCHRONIZATION_SYNCHRONIZE, -}; - -/// An access mask for a duplicated handle: what the holder of the handle may do with the object. -/// -/// A mask is a set of rights, combined with `|`. The rights an object type defines are named by -/// Win32 (`EVENT_MODIFY_STATE` for an event, `SEMAPHORE_MODIFY_STATE` for a semaphore, and so on), -/// and the ones this crate's own objects need are constants here; any other is -/// [`from_bits`](Self::from_bits). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Access(u32); - -impl Access { - /// `SYNCHRONIZE`: the right to wait on the object, and nothing else. A handle with only this - /// can be waited on and cannot change the object. - pub const SYNCHRONIZE: Self = Self(SYNCHRONIZATION_SYNCHRONIZE); - - /// `EVENT_MODIFY_STATE`: the right to set and reset an event. - pub const EVENT_MODIFY_STATE: Self = Self(EVENT_MODIFY_STATE); - - /// A mask from its Win32 bits. - #[must_use] - pub const fn from_bits(bits: u32) -> Self { - Self(bits) - } - - /// The mask's Win32 bits. - #[must_use] - pub const fn bits(self) -> u32 { - self.0 - } -} - -impl BitOr for Access { - type Output = Self; - - fn bitor(self, other: Self) -> Self { - Self(self.0 | other.0) - } -} - -/// A second handle to the object `handle` refers to, in this process, carrying exactly `access`. -/// -/// The result can do no more than `access` allows, which is the use: a handle with `SYNCHRONIZE` -/// alone can wait on an object and change nothing about it. The object lives until every handle to -/// it is closed, and closing the result does not touch the source. -/// -/// **Windows does not confine the result to the source's access.** A duplicate may ask for rights -/// the source handle lacks and get them, so a holder of a narrow handle can make a wider one with -/// this very function. The narrowing is therefore a guard against a handle being used for what it -/// was not handed out for, not a boundary against code in the process that sets out to cross it -- -/// no such boundary exists inside a process, and raw `DuplicateHandle` crosses it just as well. -/// -/// Not inheritable by child processes. An access of nothing is accepted and gives a handle that -/// can do nothing. -/// -/// # Errors -/// -/// The error from `DuplicateHandle`: access denied for a mask Windows refuses, such as one with -/// every bit set, and otherwise only when the process cannot get another handle, which no test -/// reaches. -pub fn duplicate_handle_with_access( - handle: impl AsHandle, - access: Access, -) -> io::Result { - let mut duplicate: HANDLE = ptr::null_mut(); - // SAFETY: both process handles are the current-process pseudo-handle, the source is open for - // the borrow, and `duplicate` is a valid out pointer. - let ok = unsafe { - DuplicateHandle( - GetCurrentProcess(), - handle.as_handle().as_raw_handle(), - GetCurrentProcess(), - &mut duplicate, - access.bits(), - FALSE, - 0, - ) - }; - if ok == FALSE { - return Err(io::Error::last_os_error()); - } - // SAFETY: the call succeeded, so `duplicate` is a new handle nothing else owns. - Ok(unsafe { OwnedHandle::from_raw_handle(duplicate) }) -} - -#[cfg(test)] -mod tests; diff --git a/crates/win-sync-sys/src/duplicate/tests.rs b/crates/win-sync-sys/src/duplicate/tests.rs deleted file mode 100644 index 4e2093a9f..000000000 --- a/crates/win-sync-sys/src/duplicate/tests.rs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! Unit tests for `duplicate_handle_with_access` and `Access`. -//! -//! What a duplicate can and cannot do is observed through raw Win32 calls, never through this -//! crate's own `set`, `reset` or wait, so a defect in the duplication cannot hide behind the -//! thing it duplicated. - -use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle}; - -use windows_sys::Win32::Foundation::{ - CompareObjectHandles, ERROR_ACCESS_DENIED, FALSE, GetHandleInformation, HANDLE_FLAG_INHERIT, - WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, -}; -use windows_sys::Win32::System::Threading::{ - CreateMutexW, EVENT_MODIFY_STATE, ResetEvent, SYNCHRONIZATION_SYNCHRONIZE, SetEvent, - WaitForSingleObject, -}; - -use super::{Access, duplicate_handle_with_access}; -use crate::{Event, ResetMode}; - -fn manual_event(signalled: bool) -> Event { - Event::new(ResetMode::Manual, signalled).expect("create an event") -} - -fn duplicate(handle: impl AsHandle, access: Access) -> OwnedHandle { - duplicate_handle_with_access(handle, access).expect("duplicate") -} - -/// What a zero-timeout wait on `handle` returned. -fn poll(handle: &impl AsRawHandle) -> u32 { - // SAFETY: the handle is open for the duration of the borrow. - unsafe { WaitForSingleObject(handle.as_raw_handle(), 0) } -} - -/// The error `SetEvent` reports on `handle`, `None` if it succeeded. -fn set_error(handle: &impl AsRawHandle) -> Option { - // SAFETY: the handle is open for the duration of the borrow. - if unsafe { SetEvent(handle.as_raw_handle()) } == FALSE { - return std::io::Error::last_os_error().raw_os_error(); - } - None -} - -/// The error `ResetEvent` reports on `handle`, `None` if it succeeded. -fn reset_error(handle: &impl AsRawHandle) -> Option { - // SAFETY: the handle is open for the duration of the borrow. - if unsafe { ResetEvent(handle.as_raw_handle()) } == FALSE { - return std::io::Error::last_os_error().raw_os_error(); - } - None -} - -fn same_object(a: &impl AsRawHandle, b: &impl AsRawHandle) -> bool { - // SAFETY: both handles are open for the duration of the borrows. - unsafe { CompareObjectHandles(a.as_raw_handle(), b.as_raw_handle()) != FALSE } -} - -const DENIED: Option = Some(ERROR_ACCESS_DENIED as i32); - -#[test] -fn a_duplicate_is_the_same_object_and_a_new_event_is_not() { - let event = manual_event(false); - let other = manual_event(false); - let copy = duplicate(&event, Access::SYNCHRONIZE); - assert!(same_object(&event, ©)); - assert!(!same_object(&other, ©)); - assert_ne!(copy.as_raw_handle(), event.as_raw_handle()); -} - -#[test] -fn a_synchronize_only_duplicate_can_wait_and_sees_the_signal() { - let event = manual_event(false); - let copy = duplicate(&event, Access::SYNCHRONIZE); - assert_eq!(poll(©), WAIT_TIMEOUT); - event.set().expect("set the original"); - assert_eq!(poll(©), WAIT_OBJECT_0); -} - -#[test] -fn a_synchronize_only_duplicate_can_neither_set_nor_reset() { - let event = manual_event(false); - let copy = duplicate(&event, Access::SYNCHRONIZE); - - assert_eq!(set_error(©), DENIED); - assert_eq!( - poll(&event), - WAIT_TIMEOUT, - "the refused set changed nothing" - ); - - event.set().expect("set the original"); - assert_eq!(reset_error(©), DENIED); - assert_eq!( - poll(&event), - WAIT_OBJECT_0, - "the refused reset changed nothing" - ); -} - -#[test] -fn a_duplicate_with_modify_state_changes_the_original() { - let event = manual_event(false); - let copy = duplicate(&event, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE); - assert_eq!(set_error(©), None); - assert_eq!(poll(&event), WAIT_OBJECT_0, "set through the duplicate"); - assert_eq!(reset_error(©), None); - assert_eq!(poll(&event), WAIT_TIMEOUT, "reset through the duplicate"); -} - -#[test] -fn a_duplicate_with_modify_state_alone_can_change_the_event_and_not_wait_on_it() { - let event = manual_event(true); - let copy = duplicate(&event, Access::EVENT_MODIFY_STATE); - assert_eq!(set_error(©), None); - assert_eq!(reset_error(©), None); - assert_eq!(poll(&event), WAIT_TIMEOUT, "the duplicate reset it"); - assert_eq!(poll(©), WAIT_FAILED, "no SYNCHRONIZE, no wait"); - assert_eq!( - std::io::Error::last_os_error().raw_os_error(), - DENIED, - "the wait is refused for access" - ); -} - -/// **Windows does not confine a duplicate to the source handle's access**, which is why the -/// documentation of a wait-only handle says what it does and does not guarantee. This test is the -/// evidence for that sentence: it observes the platform, and a Windows that began refusing would -/// fail it, which is the cue to tighten the documentation. -#[test] -fn a_duplicate_may_hold_rights_its_source_does_not() { - let event = manual_event(false); - let waiter = duplicate(&event, Access::SYNCHRONIZE); - assert_eq!(set_error(&waiter), DENIED, "the source cannot set"); - - let widened = duplicate(&waiter, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE); - assert!(same_object(&widened, &event)); - assert_eq!(set_error(&widened), None, "the duplicate can"); - assert_eq!(poll(&event), WAIT_OBJECT_0); -} - -#[test] -fn narrowing_and_asking_for_what_the_source_holds_both_work() { - let event = manual_event(false); - let both = duplicate(&event, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE); - let narrowed = duplicate(&both, Access::SYNCHRONIZE); - let same = duplicate(&narrowed, Access::SYNCHRONIZE); - let nothing = duplicate(&same, Access::from_bits(0)); - for copy in [&both, &narrowed, &same, ¬hing] { - assert!(same_object(copy, &event)); - } - assert_eq!(set_error(&narrowed), DENIED); - assert_eq!(set_error(&same), DENIED); -} - -/// The error path: a mask Windows refuses. Reserved bits are refused with access denied on the -/// Windows this was observed on, which gives the error a deterministic way to be reached; what a -/// mask means is the object type's, so this says nothing about any mask but this one. -#[test] -fn a_mask_windows_refuses_is_reported_and_makes_no_handle() { - let event = manual_event(false); - let refused = duplicate_handle_with_access(&event, Access::from_bits(u32::MAX)) - .expect_err("every bit set, reserved ones included"); - assert_eq!(refused.raw_os_error(), DENIED); - event.set().expect("the source is untouched by the refusal"); - assert_eq!(poll(&event), WAIT_OBJECT_0); -} - -#[test] -fn a_duplicate_with_no_rights_can_do_nothing() { - let event = manual_event(true); - let nothing = duplicate(&event, Access::from_bits(0)); - assert_eq!(poll(¬hing), WAIT_FAILED); - assert_eq!(set_error(¬hing), DENIED); - assert_eq!(poll(&event), WAIT_OBJECT_0, "the original is untouched"); -} - -#[test] -fn a_duplicate_outlives_its_source() { - let event = manual_event(false); - let keeper = event.try_clone().expect("clone"); - let copy = duplicate(&event, Access::SYNCHRONIZE); - drop(event); - keeper.set().expect("set through the kept clone"); - assert_eq!(poll(©), WAIT_OBJECT_0); - drop(keeper); - // Every other handle is closed; the duplicate alone keeps the object, and it still answers. - assert_eq!(poll(©), WAIT_OBJECT_0); -} - -#[test] -fn closing_a_duplicate_leaves_its_source_working() { - let event = manual_event(false); - drop(duplicate(&event, Access::SYNCHRONIZE)); - event.set().expect("set after the duplicate closed"); - assert_eq!(poll(&event), WAIT_OBJECT_0); -} - -#[test] -fn a_duplicate_is_not_inheritable() { - let event = manual_event(false); - let copy = duplicate(&event, Access::SYNCHRONIZE); - let mut flags = 0u32; - // SAFETY: the handle is open and `flags` is a valid out pointer. - let ok = unsafe { GetHandleInformation(copy.as_raw_handle(), &mut flags) }; - assert_ne!(ok, FALSE); - assert_eq!(flags & HANDLE_FLAG_INHERIT, 0); -} - -#[test] -fn any_kind_of_object_can_be_duplicated_not_only_an_event() { - // SAFETY: an unnamed, unowned mutex with default security; null pointers are accepted. - let raw = unsafe { CreateMutexW(std::ptr::null(), FALSE, std::ptr::null()) }; - assert!(!raw.is_null()); - // SAFETY: the call returned a new handle nothing else owns. - let mutex = unsafe { OwnedHandle::from_raw_handle(raw) }; - - let copy = duplicate(&mutex, Access::SYNCHRONIZE); - assert!(same_object(&mutex, ©)); - assert_eq!(poll(©), WAIT_OBJECT_0, "an unowned mutex is signalled"); -} - -#[test] -fn the_source_may_be_borrowed_owned_or_an_event() { - let event = manual_event(false); - let owned: OwnedHandle = event.try_clone().expect("clone").into(); - - let from_event = duplicate(&event, Access::SYNCHRONIZE); - let from_owned = duplicate(&owned, Access::SYNCHRONIZE); - let from_borrowed = duplicate(owned.as_handle(), Access::SYNCHRONIZE); - for copy in [&from_event, &from_owned, &from_borrowed] { - assert!(same_object(copy, &event)); - } -} - -#[test] -fn the_named_rights_are_the_win32_ones() { - assert_eq!(Access::SYNCHRONIZE.bits(), SYNCHRONIZATION_SYNCHRONIZE); - assert_eq!(Access::EVENT_MODIFY_STATE.bits(), EVENT_MODIFY_STATE); - assert_eq!( - Access::SYNCHRONIZE.bits() & Access::EVENT_MODIFY_STATE.bits(), - 0, - "the two rights are distinct bits" - ); -} - -#[test] -fn access_masks_combine_and_round_trip() { - let both = Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE; - assert_eq!( - both.bits(), - EVENT_MODIFY_STATE | SYNCHRONIZATION_SYNCHRONIZE - ); - assert_eq!(Access::from_bits(both.bits()), both); - assert_eq!(Access::from_bits(0).bits(), 0); - assert_eq!(both | both, both, "combining is a union"); - assert_ne!(both, Access::SYNCHRONIZE); -} - -#[test] -fn access_is_copy_send_and_sync() { - fn assert_traits() {} - assert_traits::(); -} diff --git a/crates/win-sync-sys/src/event.rs b/crates/win-sync-sys/src/event.rs index 9cec32c14..5668583f4 100644 --- a/crates/win-sync-sys/src/event.rs +++ b/crates/win-sync-sys/src/event.rs @@ -10,7 +10,8 @@ use std::ptr; use windows_sys::Win32::Foundation::{FALSE, TRUE}; use windows_sys::Win32::System::Threading::{CreateEventW, ResetEvent, SetEvent}; -use crate::duplicate::{Access, duplicate_handle_with_access}; +use win_handles_sys::{Access, duplicate_handle_with_access}; +use win_shared_os_owned_handle::SharedHandle; /// How an event returns to unsignalled after it is set. /// @@ -29,9 +30,12 @@ pub enum ResetMode { /// An owned event object. /// -/// Holds a handle to an event and closes it on drop. Because the handle is -/// owned and is known to be an event, [`set`](Self::set) and -/// [`reset`](Self::reset) need no `unsafe`. +/// Holds a handle to an event, shared by every clone, and closes it when the +/// last clone drops. A clone is a second holder of the same handle, not a new +/// handle: setting through one is seen through every other +/// ([WS-D-10](../DESIGN-NOTES.md#ws-d-10)). Because the handle is owned and is +/// known to be an event, [`set`](Self::set) and [`reset`](Self::reset) need no +/// `unsafe`. /// /// **The handle being an event is what other crates may rely on** /// ([WS-D-4](../DESIGN-NOTES.md#ws-d-4)). @@ -46,9 +50,9 @@ pub enum ResetMode { /// how often it was set. /// /// There is no `pulse`; see the crate documentation for why. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Event { - handle: OwnedHandle, + handle: SharedHandle, } impl Event { @@ -74,7 +78,9 @@ impl Event { } // SAFETY: the call returned a new handle that nothing else owns. let handle = unsafe { OwnedHandle::from_raw_handle(raw) }; - Ok(Self { handle }) + Ok(Self { + handle: SharedHandle::new(handle), + }) } /// Adopt a handle the caller already owns as an event. @@ -98,7 +104,9 @@ impl Event { /// it. #[must_use] pub unsafe fn from_owned_handle(handle: OwnedHandle) -> Self { - Self { handle } + Self { + handle: SharedHandle::new(handle), + } } /// Set the event to signalled. @@ -137,27 +145,25 @@ impl Event { Ok(()) } - /// A second handle to the same event, with the same access. - /// - /// Setting or resetting either affects both, and the event lives until - /// every handle to it is closed, so a clone may outlive the original. + /// The handle, as an [`OwnedHandle`], if `self` is its last holder. /// /// # Errors /// - /// Returns the error from `DuplicateHandle`. Like [`new`](Self::new)'s, no - /// test reaches it: duplicating a handle this process owns fails only when - /// the process cannot get another handle. - pub fn try_clone(&self) -> io::Result { - Ok(Self { - handle: self.handle.try_clone()?, - }) + /// If a clone still holds the handle, returns `self` unchanged, so nothing is + /// lost and the caller can try again once the clones have dropped. + pub fn try_into_owned(self) -> Result { + self.handle + .try_into_owned() + .map_err(|handle| Self { handle }) } - /// A second handle to the same event that can wait on it and do nothing else. + /// A new handle to the same event that can wait on it and do nothing else: the one operation + /// here that makes a handle, because access belongs to a handle and narrowing it needs a new + /// one ([WS-D-10](../DESIGN-NOTES.md#ws-d-10)). /// /// The handle carries `SYNCHRONIZE` access alone, so it can neither set nor reset the event: - /// the kernel refuses a `SetEvent` or `ResetEvent` on it. That is something [`try_clone`] cannot - /// give, since its clone holds whatever access the original does. See [`WaitOnlyEvent`] for + /// the kernel refuses a `SetEvent` or `ResetEvent` on it. A clone cannot give that, since it + /// shares the original's handle and so its access. See [`WaitOnlyEvent`] for /// what the result is, why it is not an `Event`, and what the guarantee does not cover. /// /// A wait through it takes what a signalled event gives, as any wait does: waiting through a @@ -166,21 +172,20 @@ impl Event { /// /// # Errors /// - /// Returns the error from `DuplicateHandle`. Like [`try_clone`](Self::try_clone)'s, no test - /// reaches it: duplicating a handle this process owns fails only when the process cannot get - /// another handle. - /// - /// [`try_clone`]: Self::try_clone - pub fn try_clone_wait_only(&self) -> io::Result { + /// Returns the error from `DuplicateHandle`. Like [`new`](Self::new)'s, no test reaches it: + /// duplicating a handle this process owns fails only when the process cannot get another + /// handle. + pub fn duplicate_wait_only(&self) -> io::Result { Ok(WaitOnlyEvent { - handle: duplicate_handle_with_access(self, Access::SYNCHRONIZE)?, + handle: SharedHandle::new(duplicate_handle_with_access(self, Access::SYNCHRONIZE)?), }) } } /// A handle to an event that can wait on it and cannot set or reset it. /// -/// Made by [`Event::try_clone_wait_only`], carrying `SYNCHRONIZE` access and no more. It is its own +/// Made by [`Event::duplicate_wait_only`], carrying `SYNCHRONIZE` access and no more. Its clones +/// share its handle, as an [`Event`]'s do. It is its own /// type, not an [`Event`] that fails when set, because what it cannot do is not an error to be /// discovered at run time: it has no `set` and no `reset`, and the kernel would refuse them if it /// were handed to code that tried ([WS-D-7](../DESIGN-NOTES.md#ws-d-7)). @@ -197,24 +202,21 @@ impl Event { /// /// It lends its handle, so [`wait_for_single_object`](crate::wait_for_single_object) waits on it. /// `Send` and `Sync`, as an event is. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct WaitOnlyEvent { - handle: OwnedHandle, + handle: SharedHandle, } impl WaitOnlyEvent { - /// A second wait-only handle to the same event. - /// - /// It holds the same access as this one, so the clone cannot signal either. + /// The handle, as an [`OwnedHandle`], if `self` is its last holder. /// /// # Errors /// - /// Returns the error from `DuplicateHandle`; no test reaches it, as for - /// [`Event::try_clone`]. - pub fn try_clone(&self) -> io::Result { - Ok(Self { - handle: self.handle.try_clone()?, - }) + /// If a clone still holds the handle, returns `self` unchanged. + pub fn try_into_owned(self) -> Result { + self.handle + .try_into_owned() + .map_err(|handle| Self { handle }) } } @@ -230,7 +232,8 @@ impl AsRawHandle for WaitOnlyEvent { } } -impl From for OwnedHandle { +/// The shared handle, given up by this holder; it is still an event's, with `SYNCHRONIZE` alone. +impl From for SharedHandle { fn from(event: WaitOnlyEvent) -> Self { event.handle } @@ -248,7 +251,9 @@ impl AsRawHandle for Event { } } -impl From for OwnedHandle { +/// The shared handle, given up by this holder: what a consumer that holds handles shared, such as +/// the thread pool's wait target, takes. +impl From for SharedHandle { fn from(event: Event) -> Self { event.handle } diff --git a/crates/win-sync-sys/src/event/tests.rs b/crates/win-sync-sys/src/event/tests.rs index cfa26c14b..883ed106e 100644 --- a/crates/win-sync-sys/src/event/tests.rs +++ b/crates/win-sync-sys/src/event/tests.rs @@ -164,24 +164,24 @@ fn reset_withdraws_an_auto_reset_signal_nobody_has_taken() { #[test] fn a_clone_is_the_same_object_and_a_new_event_is_not() { let original = event(ResetMode::Manual, false); - let clone = original.try_clone().expect("clone"); + let clone = original.clone(); let other = event(ResetMode::Manual, false); assert!(same_object(&original, &clone), "a clone is the same event"); assert!( !same_object(&original, &other), "and the comparison can tell two events apart" ); - assert_ne!( + assert_eq!( original.as_raw_handle(), clone.as_raw_handle(), - "a clone is a second handle, not the same handle twice" + "a clone is the same handle, held twice (WS-D-10)" ); } #[test] fn a_clone_and_its_original_signal_each_other() { let original = event(ResetMode::Manual, false); - let clone = original.try_clone().expect("clone"); + let clone = original.clone(); original.set().expect("set the original"); assert!(take(&clone), "the clone saw the original's set"); original.reset().expect("reset the original"); @@ -195,7 +195,7 @@ fn a_clone_and_its_original_signal_each_other() { #[test] fn a_clone_outlives_its_original() { let original = event(ResetMode::Manual, false); - let clone = original.try_clone().expect("clone"); + let clone = original.clone(); drop(original); clone.set().expect("set the surviving clone"); assert!(take(&clone)); @@ -204,7 +204,7 @@ fn a_clone_outlives_its_original() { #[test] fn a_set_on_another_thread_releases_a_waiter_on_this_one() { let event = event(ResetMode::Auto, false); - let signaller = event.try_clone().expect("clone"); + let signaller = event.clone(); let thread = std::thread::spawn(move || signaller.set()); assert_eq!(wait(event.as_handle(), BOUND_MS), WAIT_OBJECT_0); thread @@ -216,7 +216,7 @@ fn a_set_on_another_thread_releases_a_waiter_on_this_one() { #[test] fn a_reset_on_another_thread_is_seen_on_this_one() { let event = event(ResetMode::Manual, true); - let resetter = event.try_clone().expect("clone"); + let resetter = event.clone(); std::thread::spawn(move || resetter.reset()) .join() .expect("the resetting thread") @@ -229,7 +229,7 @@ fn a_reset_on_another_thread_is_seen_on_this_one() { fn waiters(event: &Event, count: usize) -> mpsc::Receiver<(usize, u32)> { let (tx, rx) = mpsc::channel(); for which in 0..count { - let waiter = event.try_clone().expect("clone"); + let waiter = event.clone(); let tx = tx.clone(); std::thread::spawn(move || { let result = wait(waiter.as_handle(), BOUND_MS); @@ -311,18 +311,6 @@ fn set_and_reset_succeed_on_an_adopted_handle_with_modify_state() { assert!(!take(&event), "the adopted handle reset the original"); } -#[test] -fn an_event_converted_into_an_owned_handle_is_the_same_object() { - let event = event(ResetMode::Manual, false); - let keeper = event.try_clone().expect("clone"); - let raw = event.as_raw_handle(); - let owned: OwnedHandle = event.into(); - assert_eq!(owned.as_raw_handle(), raw, "the handle itself moved out"); - assert!(same_object(&owned, &keeper)); - keeper.set().expect("set through the kept clone"); - assert!(take(&owned), "the owned handle sees the set"); -} - #[test] fn as_handle_lends_the_handle_as_raw_handle_names() { let event = event(ResetMode::Auto, false); @@ -336,7 +324,7 @@ fn an_event_is_send_and_sync() { } fn wait_only(event: &Event) -> WaitOnlyEvent { - event.try_clone_wait_only().expect("wait-only clone") + event.duplicate_wait_only().expect("a wait-only duplicate") } /// The error a raw `SetEvent` on `handle` reports, `None` if it succeeded. @@ -402,7 +390,7 @@ fn a_wait_only_clone_can_neither_set_nor_reset_the_event() { #[test] fn a_wait_only_clone_converted_to_a_handle_and_adopted_as_an_event_cannot_set_it() { let event = event(ResetMode::Manual, false); - let handle: OwnedHandle = wait_only(&event).into(); + let handle = wait_only(&event).try_into_owned().expect("the only holder"); // SAFETY: a duplicate of an event is an event, and this one carries SYNCHRONIZE. let adopted = unsafe { Event::from_owned_handle(handle) }; let refused = adopted.set().expect_err("the access was narrowed away"); @@ -414,7 +402,7 @@ fn a_wait_only_clone_converted_to_a_handle_and_adopted_as_an_event_cannot_set_it fn a_clone_of_a_wait_only_clone_is_wait_only_too() { let event = event(ResetMode::Manual, false); let first = wait_only(&event); - let second = first.try_clone().expect("clone the clone"); + let second = first.clone(); assert!(same_object(&first, &second)); assert!(same_object(&event, &second)); assert_eq!(raw_set_error(&second), Some(ERROR_ACCESS_DENIED as i32)); @@ -425,7 +413,7 @@ fn a_clone_of_a_wait_only_clone_is_wait_only_too() { #[test] fn a_wait_only_clone_outlives_the_event_it_came_from() { let event = event(ResetMode::Manual, false); - let keeper = event.try_clone().expect("clone"); + let keeper = event.clone(); let waiter = wait_only(&event); drop(event); keeper.set().expect("set through the kept clone"); @@ -452,7 +440,7 @@ fn a_wait_only_clone_is_waited_on_by_the_crates_wait() { fn a_set_on_another_thread_releases_a_wait_only_waiter_on_this_one() { let event = event(ResetMode::Auto, false); let waiter = wait_only(&event); - let signaller = event.try_clone().expect("clone"); + let signaller = event.clone(); let worker = std::thread::spawn(move || { std::thread::sleep(Duration::from_millis(10)); signaller.set() @@ -470,7 +458,7 @@ fn as_handle_lends_a_wait_only_clones_handle_as_raw_handle_names() { let waiter = wait_only(&event); assert_eq!(waiter.as_handle().as_raw_handle(), waiter.as_raw_handle()); let raw = waiter.as_raw_handle(); - let owned: OwnedHandle = waiter.into(); + let owned = waiter.try_into_owned().expect("the only holder"); assert_eq!(owned.as_raw_handle(), raw, "the handle itself moved out"); } @@ -479,3 +467,91 @@ fn a_wait_only_event_is_send_and_sync() { fn assert_send_and_sync() {} assert_send_and_sync::(); } + +// --- clones share one handle (WS-2.2, WS-D-10) --- + +#[test] +fn a_clone_shares_the_handle_rather_than_making_one() { + let event = event(ResetMode::Manual, false); + let clone = event.clone(); + assert_eq!( + clone.as_raw_handle(), + event.as_raw_handle(), + "one handle, held twice" + ); +} + +#[test] +fn a_set_through_a_clone_is_seen_through_the_original_and_a_reset_back() { + let event = event(ResetMode::Manual, false); + let clone = event.clone(); + clone.set().expect("set through the clone"); + assert!(take(&event), "the original sees the clone's set"); + event.reset().expect("reset through the original"); + assert!(!take(&clone), "the clone sees the original's reset"); +} + +#[test] +fn a_clone_keeps_the_handle_open_after_the_original_drops() { + let event = event(ResetMode::Manual, false); + let clone = event.clone(); + let raw = event.as_raw_handle(); + drop(event); + clone.set().expect("set after the original dropped"); + assert!(take(&clone)); + assert_eq!(clone.as_raw_handle(), raw, "the same handle is still open"); +} + +#[test] +fn an_event_gives_its_handle_back_only_to_its_last_holder() { + let event = event(ResetMode::Manual, false); + let clone = event.clone(); + let raw = event.as_raw_handle(); + let event = event.try_into_owned().expect_err("a clone is still held"); + drop(clone); + let owned = event.try_into_owned().expect("the last holder"); + assert_eq!( + owned.as_raw_handle(), + raw, + "the handle itself, not a duplicate" + ); +} + +#[test] +fn an_event_converted_into_a_shared_handle_is_its_handle() { + let event = event(ResetMode::Manual, false); + let clone = event.clone(); + let shared = win_shared_os_owned_handle::SharedHandle::from(event); + assert_eq!(shared.as_raw_handle(), clone.as_raw_handle()); + clone.set().expect("set through the clone"); + assert!(take(&shared), "the shared handle sees the set"); +} + +#[test] +fn duplicate_wait_only_makes_a_new_handle_that_cannot_signal() { + let event = event(ResetMode::Manual, false); + let waiter = event.duplicate_wait_only().expect("a wait-only duplicate"); + assert_ne!( + waiter.as_raw_handle(), + event.as_raw_handle(), + "a new handle" + ); + assert!(same_object(&waiter, &event), "to the same event"); + assert_eq!(raw_set_error(&waiter), Some(ERROR_ACCESS_DENIED as i32)); +} + +#[test] +fn a_clone_of_a_wait_only_event_shares_its_handle_and_its_access() { + let event = event(ResetMode::Manual, false); + let waiter = event.duplicate_wait_only().expect("a wait-only duplicate"); + let clone = waiter.clone(); + assert_eq!( + clone.as_raw_handle(), + waiter.as_raw_handle(), + "one handle, held twice" + ); + assert_eq!(raw_set_error(&clone), Some(ERROR_ACCESS_DENIED as i32)); + let waiter = waiter.try_into_owned().expect_err("a clone is still held"); + drop(clone); + assert!(waiter.try_into_owned().is_ok(), "the last holder"); +} diff --git a/crates/win-sync-sys/src/lib.rs b/crates/win-sync-sys/src/lib.rs index e3b978974..be6a65cd3 100644 --- a/crates/win-sync-sys/src/lib.rs +++ b/crates/win-sync-sys/src/lib.rs @@ -5,7 +5,6 @@ #![deny(unsafe_op_in_unsafe_fn)] mod apc; -mod duplicate; mod event; mod signal; #[cfg(test)] @@ -14,8 +13,10 @@ mod wait; mod wait_many; pub use apc::{queue_user_apc, queue_user_apc_to_current_thread}; -pub use duplicate::{Access, duplicate_handle_with_access}; pub use event::{Event, ResetMode, WaitOnlyEvent}; pub use signal::{Signalable, signal_object_and_wait}; pub use wait::{Alertable, WaitOutcome, wait_for_single_object, wait_for_single_object_ex}; pub use wait_many::{MAXIMUM_OBJECTS, WaitAnyOutcome, wait_for_all, wait_for_any}; +// `DuplicateHandle` is documented under Handles and Objects, so it lives in `win-handles-sys`; it is +// re-exported here at the paths it had. +pub use win_handles_sys::{Access, duplicate_handle_with_access}; diff --git a/crates/win-sync-sys/src/signal.rs b/crates/win-sync-sys/src/signal.rs index 17962f040..5e5dc1f59 100644 --- a/crates/win-sync-sys/src/signal.rs +++ b/crates/win-sync-sys/src/signal.rs @@ -21,7 +21,7 @@ use crate::wait::{Alertable, WaitOutcome, milliseconds, outcome}; /// use win_sync_sys::{Alertable, Event, ResetMode, signal_object_and_wait}; /// /// let ping = Event::new(ResetMode::Auto, false)?; -/// let waiter = ping.try_clone_wait_only()?; +/// let waiter = ping.duplicate_wait_only()?; /// let pong = Event::new(ResetMode::Auto, true)?; /// // A wait-only handle cannot be signalled. /// signal_object_and_wait(&waiter, &pong, None, Alertable::No)?; diff --git a/crates/win-sync-sys/src/signal/tests.rs b/crates/win-sync-sys/src/signal/tests.rs index 862e4e751..eda135f1a 100644 --- a/crates/win-sync-sys/src/signal/tests.rs +++ b/crates/win-sync-sys/src/signal/tests.rs @@ -55,7 +55,7 @@ fn the_signal_object_may_be_given_by_reference_or_by_value() { assert!(is_signalled(&by_reference)); let by_value = manual(false); - let keeper = by_value.try_clone().expect("clone"); + let keeper = by_value.clone(); signal_object_and_wait(by_value, &wait, POLL, NO).expect("by value"); assert!(is_signalled(&keeper)); } @@ -63,7 +63,7 @@ fn the_signal_object_may_be_given_by_reference_or_by_value() { #[test] fn the_wait_object_may_be_any_handle() { let signal = manual(false); - let owned: OwnedHandle = manual(true).into(); + let owned: OwnedHandle = manual(true).try_into_owned().expect("the only holder"); let outcome = signal_object_and_wait(&signal, &owned, POLL, NO).expect("the call"); assert_eq!(outcome, WaitOutcome::Signalled); } @@ -72,10 +72,7 @@ fn the_wait_object_may_be_any_handle() { fn two_threads_hand_a_turn_back_and_forth() { const ROUNDS: usize = 200; let (ping, pong) = (auto(false), auto(false)); - let (their_ping, their_pong) = ( - ping.try_clone().expect("clone"), - pong.try_clone().expect("clone"), - ); + let (their_ping, their_pong) = (ping.clone(), pong.clone()); // Each side signals only after the other's signal was taken, so an auto-reset signal is never // set twice before it is taken, which would merge two turns into one. diff --git a/crates/win-sync-sys/src/wait/tests.rs b/crates/win-sync-sys/src/wait/tests.rs index fb8130645..da7cde153 100644 --- a/crates/win-sync-sys/src/wait/tests.rs +++ b/crates/win-sync-sys/src/wait/tests.rs @@ -106,7 +106,7 @@ fn a_wait_for_a_manual_reset_event_leaves_it_signalled() { #[test] fn a_set_after_the_wait_began_is_seen() { let event = auto(false); - let signaller = event.try_clone().expect("a second handle"); + let signaller = event.clone(); let setter = std::thread::spawn(move || { std::thread::sleep(Duration::from_millis(50)); signaller.set().expect("set"); @@ -148,7 +148,7 @@ fn a_zero_timeout_polls_and_does_not_wait() { #[test] fn any_handle_that_lends_itself_can_be_waited_on() { let event = manual(true); - let owned: OwnedHandle = event.try_clone().expect("a clone").into(); + let owned: OwnedHandle = event.as_handle().try_clone_to_owned().expect("a duplicate"); assert_eq!(wait(&owned, Some(BOUND)), WaitOutcome::Signalled); assert_eq!(wait(owned.as_handle(), Some(BOUND)), WaitOutcome::Signalled); assert_eq!(wait(event, Some(BOUND)), WaitOutcome::Signalled); diff --git a/crates/win-sync-sys/src/wait_many/tests.rs b/crates/win-sync-sys/src/wait_many/tests.rs index d2d215566..66cd5ac45 100644 --- a/crates/win-sync-sys/src/wait_many/tests.rs +++ b/crates/win-sync-sys/src/wait_many/tests.rs @@ -88,7 +88,7 @@ fn any_times_out_when_nothing_is_signalled_and_takes_at_least_its_time() { #[test] fn any_is_released_by_a_set_on_another_thread() { let events = events(3); - let signaller = events[2].try_clone().expect("clone"); + let signaller = events[2].clone(); let worker = std::thread::spawn(move || { std::thread::sleep(Duration::from_millis(10)); signaller.set() @@ -110,7 +110,7 @@ fn any_reports_an_abandoned_mutex_by_its_index_and_owns_it_after() { #[test] fn any_takes_a_mixture_of_object_kinds_as_borrowed_handles() { let event = auto(false); - let owned: OwnedHandle = manual(true).into(); + let owned: OwnedHandle = manual(true).try_into_owned().expect("the only holder"); let mutex = abandoned_mutex(); let objects: [BorrowedHandle<'_>; 3] = [event.as_handle(), owned.as_handle(), mutex.as_handle()]; @@ -205,7 +205,7 @@ fn all_is_released_when_the_last_object_is_set_on_another_thread() { let events = events(3); events[0].set().expect("set"); events[1].set().expect("set"); - let signaller = events[2].try_clone().expect("clone"); + let signaller = events[2].clone(); let worker = std::thread::spawn(move || { std::thread::sleep(Duration::from_millis(10)); signaller.set() diff --git a/crates/win-text-sys/Cargo.toml b/crates/win-text-sys/Cargo.toml new file mode 100644 index 000000000..61e08c869 --- /dev/null +++ b/crates/win-text-sys/Cargo.toml @@ -0,0 +1,39 @@ +# Copyright (c) 2026 Mike Grier + +[package] +name = "win-text-sys" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +readme = "README.md" +description = "Memory-safe Rust over the Win32 text-handling API, starting with CompareStringOrdinal: an ordinal comparison of two counted UTF-16 strings, case-sensitive or not, as an Ordering. Thin over Win32, adding no policy." +keywords = ["windows", "string", "unicode", "ffi"] +categories = ["api-bindings", "os::windows-apis", "text-processing"] + +# Named under the workspace's `win-` prefix decision (DESIGN-NOTES.md at the repository root), and +# placed as a safe layer more or less directly over a Win32 API area, the one Microsoft Learn files +# the functions under: Internationalization for Windows Applications > Globalization Services > +# National Language Support. The name is the subject, text, and not the section's (WT-D-1). + +[dependencies] +# The failure a call reports, as the one type the workspace's crates give a Win32 code. +win-error-codes = { version = "0.0.2", path = "../win-error-codes" } + +# `default-features = false` matches every other crate in the workspace. +# +# `Win32_Foundation` is `TRUE` and `FALSE`, the values of the call's `bIgnoreCase`. +# `Win32_Globalization` is `CompareStringOrdinal` and the three results it returns. +[dependencies.windows-sys] +version = "0.61.2" +default-features = false +features = ["Win32_Foundation", "Win32_Globalization"] + +[package.metadata.docs.rs] +# The crate is Windows-only, so docs.rs must build on a Windows target or it +# would render an almost-empty crate. +default-target = "x86_64-pc-windows-msvc" +targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"] diff --git a/crates/win-text-sys/DESIGN-NOTES.md b/crates/win-text-sys/DESIGN-NOTES.md new file mode 100644 index 000000000..059f9356e --- /dev/null +++ b/crates/win-text-sys/DESIGN-NOTES.md @@ -0,0 +1,69 @@ +# Design notes: win-text-sys + +## WT-D-1: the crate is named for its subject, text, and `CompareStringOrdinal` is the first function filed in it + +**The name is the engineer's, 2026-10-18.** Asked which name the crate should take for the ordinal string +comparison, the engineer said "I lean towards win-text-sys for the crate name. locale implies that text handling +that was not specific to a locale would not belong", and then "win-text-sys is the name of the crate unless you +have a solid argument against it". None was found. The name was unclaimed on crates.io when it was checked that +day. The workspace's `win-` prefix rule applies, as the crate is a safe layer more or less directly over Win32 +([DESIGN-NOTES.md](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix)). + +**The section.** By [the placement rule](../../DESIGN-NOTES.md#safe-wrappers-are-placed-by-the-learn-section-they-are-documented-under), +the snapshot [WIN32-LEARN-MAP-DATA.txt](../../WIN32-LEARN-MAP-DATA.txt) (2026-10-17) files `CompareStringOrdinal` +once, under *Desktop App Technologies > Desktop App User Interface > Internationalization for Windows Applications > +Globalization Services > National Language Support > National Language Support Reference > National Language Support +Functions*. That is this crate's section. The snapshot files a second area under the same Internationalization +heading, *Unicode and Character Sets*, which holds functions of text that is not tied to a locale; it is not this +crate's by the rule, and a wrapper from it is judged by the crate's subject: it belongs here if it is a function of +text, whichever section lists it. The name is the subject whether or not the text is particular to a locale, and +neither Microsoft's section title nor its header appears in a name or an identifier, as the rule requires. + +**What a `-sys` crate here is.** It is memory-safe over the call and adds no policy: it passes the arguments as +given, reports what the call reports, and refuses ahead of the call only what the call's page makes +impossible to pass ([WT-D-2](#wt-d-2)). + +**Scheduled work, and none beyond it.** The crate has no checklist of its own: its one function is +[M43.1](../../COMPLETED-CHECKLIST.md#m431) in the workspace plan, and the two sites that call `CompareStringOrdinal` themselves +move to it in `SAFE-4` of [windows-file-enumeration-sys' COMPLETED-CHECKLIST.md](../windows-file-enumeration-sys/COMPLETED-CHECKLIST.md#safe-4) +and `M17.1` of [windows-file-watcher's COMPLETED-CHECKLIST.md](../windows-file-watcher/COMPLETED-CHECKLIST.md#m171). The other functions +the section lists, such as `CompareStringEx` and `FindStringOrdinal`, are outside the item that made the crate. + +## WT-D-2: `compare_string_ordinal` takes two slices and a case and gives an `Ordering`, with the call's failure and the crate's one refusal as distinct errors + +Read 2026-10-20 from the call's page (`ms.date` 2018-12-05, `updated_at` 2024-02-22). + +**The shape.** Two `&[u16]` and a `Case`, which is `bIgnoreCase`: `Sensitive` is `FALSE`, and `Insensitive` is `TRUE`, +which is 1, "the only numeric value that can be used to specify a true value for this boolean parameter that does not +result an invalid parameter error", so no other value can be passed and the type cannot make that invalid call. The +result is an `Ordering`, from the page's `CSTR_LESS_THAN`, `CSTR_EQUAL` and `CSTR_GREATER_THAN`. + +**The count is always passed.** The page gives `-1` for a NUL-terminated string, and a slice is not one, so every +call passes the slice's length. A NUL in the data is then a unit and the comparison of `"a\0b"` and `"a\0c"` is of +three units each, where the `-1` form would compare the one-unit string `"a"` with itself. The same holds for an +unpaired surrogate, which is a unit. + +**The one refusal ahead of the call, and what it rests on.** A slice of more than `i32::MAX` units is refused as +`CompareStringOrdinalError::TooLong`, naming the first operand when both are too long. The citation is the page's +syntax block, which types `cchCount1` and `cchCount2` as `int`, and its parameter text, "Length of the string indicated +by *lpString1*". It is on the arguments alone. It is a limit of the call's signature and not a condition the page calls +invalid, so it is this crate's refusal and is kept distinct from a failure the call reports, in an error of this +crate's own rather than `std::io::Error`: `ERROR_INVALID_PARAMETER`, which the page lists for the call, is +`ErrorKind::InvalidInput` in std, and a refusal given that kind could not be told from the call's failure. The +consumer decides what a string that long means for it; the two sites this was made for compare names, and each +chose a fallback of its own that this crate does not take on. + +**Nothing else is checked ahead of the call.** The page's one failure, `ERROR_INVALID_PARAMETER` "Any of the +parameter values was invalid", is the call's to give, and it reaches the caller as `CompareStringOrdinalError::Call` +with the code as a `Win32Error`. The code is read immediately after the call. The match arm for a result the +page does not list, neither of the three nor zero, reports a failure with code zero, which says Windows gave none, as +`windows-file-enumeration-sys`' [D-24](../windows-file-enumeration-sys/DESIGN-NOTES.md#d-24) does for its own +unreachable state; the mapping is a function of the result alone and is tested in memory over values the call +does not return. + +**What the tests are, and are not.** The call's failure is made by passing `bIgnoreCase` as 2, which the page calls an +invalid parameter, directly to the private call; the safe function cannot pass it. That the comparison is ordinal +and not linguistic (`"\u00df"` against `"ss"`, an `e` with a combining accent against the one-unit letter), and that +its order is that of the units and not of code points, are evidence of what was observed and not a contract this +crate states: the page says "binary equality" and gives no ordering rule, so the crate's documentation says the +same and no check ahead of the call depends on either. diff --git a/crates/win-text-sys/PLANS.md b/crates/win-text-sys/PLANS.md new file mode 100644 index 000000000..ff9bcc0e3 --- /dev/null +++ b/crates/win-text-sys/PLANS.md @@ -0,0 +1,9 @@ +# Plans: win-text-sys + +The crate has no checklist of its own. Its one function is `M43.1` in the workspace +[COMPLETED-CHECKLIST.md](../../COMPLETED-CHECKLIST.md#m431), and the work that moves the workspace's other `CompareStringOrdinal` call +sites onto it is in the checklists named in [DESIGN-NOTES.md](DESIGN-NOTES.md#wt-d-1). Add a row here when new work is +planned against a `CHECKLIST.md` in this directory. + +| Path to CHECKLIST.md | Status | Brief description | Design Notes | +|---|---|---|---| diff --git a/crates/win-text-sys/README.md b/crates/win-text-sys/README.md new file mode 100644 index 000000000..c0617e1ce --- /dev/null +++ b/crates/win-text-sys/README.md @@ -0,0 +1,32 @@ +# win-text-sys + +Memory-safe Rust over the Win32 text-handling API, the area Microsoft Learn files `CompareStringOrdinal` under: +*Internationalization for Windows Applications > Globalization Services > National Language Support > National +Language Support Reference > National Language Support Functions*. Thin over Win32, adding no policy. + +- `compare_string_ordinal` is `CompareStringOrdinal`: it compares two slices of UTF-16 code units, case-sensitively or + not as a `Case` says, and returns the first's place against the second's as an `Ordering`. Each slice is passed + with its length and never as the NUL-terminated string the page allows, so a NUL is a unit like any other and an + unpaired surrogate is a unit. The comparison is the call's, which "tests for binary equality, not linguistic + equality". +- A slice of more than `i32::MAX` units has no count to be passed with, because the page gives each count as an `int`, + so it is refused as `CompareStringOrdinalError::TooLong` and the call is not made. That is this crate's refusal and + not the call's. A failure the call reports is `CompareStringOrdinalError::Call`, with the code as a `Win32Error`. + See [WT-D-2](DESIGN-NOTES.md#wt-d-2). + +```rust +use std::cmp::Ordering; + +use win_text_sys::{Case, compare_string_ordinal}; + +let name: Vec = "README.md".encode_utf16().collect(); +let other: Vec = "readme.MD".encode_utf16().collect(); + +assert_eq!(compare_string_ordinal(&name, &other, Case::Insensitive)?, Ordering::Equal); +assert_eq!(compare_string_ordinal(&name, &other, Case::Sensitive)?, Ordering::Less); +# Ok::<(), win_text_sys::CompareStringOrdinalError>(()) +``` + +The crate was made so that the sites of this workspace that each call `CompareStringOrdinal` with an `unsafe` +block of their own have one safe wrapper to share. See [WT-D-1](DESIGN-NOTES.md#wt-d-1) for the name and where the +function is filed. diff --git a/crates/win-text-sys/sabotage.json b/crates/win-text-sys/sabotage.json new file mode 100644 index 000000000..e620b7c3a --- /dev/null +++ b/crates/win-text-sys/sabotage.json @@ -0,0 +1,103 @@ +{ + "package": "win-text-sys", + "description": "Sabotages for win-text-sys. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are.", + "notCoveredHere": "The counts are never passed as the page's -1. A sabotage that passed -1 for a count would make the call read each slice as a NUL-terminated string, which a slice is not, so it would read past the slice's end: the embedded-NUL test would catch it, but by whatever the memory past a Vec holds, which is not a result to record. A slice of more than i32::MAX units cannot be made by a test, so the refusal's limit is observed through the function that makes the counts and not through compare_string_ordinal.", + "sabotages": [ + { + "name": "WT-1: the less-than result is reported as greater", + "file": "src/compare.rs", + "expect": "caught", + "why": "The two orderings are the same shape in the match, so a swap compiles; every comparison of unequal strings then reads backwards.", + "find": [" CSTR_LESS_THAN => Ok(Ordering::Less),\n"], + "replace": [" CSTR_LESS_THAN => Ok(Ordering::Greater),\n"] + }, + { + "name": "WT-1: the equal result is reported as less", + "file": "src/compare.rs", + "expect": "caught", + "why": "Equality is what the workspace's name matching reads, so it has to be the one result that is reported as itself.", + "find": [" CSTR_EQUAL => Ok(Ordering::Equal),\n"], + "replace": [" CSTR_EQUAL => Ok(Ordering::Less),\n"] + }, + { + "name": "WT-1: a result the page does not list is reported as equal", + "file": "src/compare.rs", + "expect": "caught", + "why": "A value the call should never give must not be read as a successful match.", + "find": [" _ => Err(Win32Error::from_code(0)),\n"], + "replace": [" _ => Ok(Ordering::Equal),\n"] + }, + { + "name": "WT-1: a zero result does not report the call's last error", + "file": "src/compare.rs", + "expect": "caught", + "why": "The code is the one thing the failure carries; without it a caller cannot tell a call's refusal from no code at all.", + "find": [" 0 => Err(last_error()),\n"], + "replace": [" 0 => Err(Win32Error::from_code(0)),\n"] + }, + { + "name": "WT-1: a case-insensitive comparison is made case-sensitive", + "file": "src/compare.rs", + "expect": "caught", + "why": "The flag is the only thing that separates the two; folding case is observed by comparing strings that differ only in case.", + "find": [" Self::Insensitive => TRUE,\n"], + "replace": [" Self::Insensitive => FALSE,\n"] + }, + { + "name": "WT-1: a case-sensitive comparison is made case-insensitive", + "file": "src/compare.rs", + "expect": "caught", + "why": "The opposite direction: a comparison that was asked to take the units as they are folds them.", + "find": [" Self::Sensitive => FALSE,\n"], + "replace": [" Self::Sensitive => TRUE,\n"] + }, + { + "name": "WT-1: the two strings are passed in each other's place", + "file": "src/compare.rs", + "expect": "caught", + "why": "A comparison of unequal strings reads backwards, and swapping the arguments no longer reverses the result.", + "find": [" first.as_ptr(),\n first_count,\n second.as_ptr(),\n second_count,\n"], + "replace": [" second.as_ptr(),\n second_count,\n first.as_ptr(),\n first_count,\n"] + }, + { + "name": "WT-1: a length is truncated to the count instead of refused", + "file": "src/compare.rs", + "expect": "caught", + "why": "A length above i32::MAX would wrap to a small or negative count, and the call would compare a different string, or take a negative count as the page's -1.", + "find": [" i32::try_from(len).map_err(|_| CompareStringOrdinalError::TooLong { operand, len })\n"], + "replace": [" {\n let _ = operand;\n Ok(len as i32)\n }\n"] + }, + { + "name": "WT-1: the second string is named as the first when it is too long", + "file": "src/compare.rs", + "expect": "caught", + "why": "The refusal names the string; a caller that reports which one is too long reports the wrong one.", + "find": ["count(Operand::Second, second)?"], + "replace": ["count(Operand::First, second)?"] + }, + { + "name": "WT-1: the second length is checked before the first", + "file": "src/compare.rs", + "expect": "caught", + "why": "When both strings are too long the first is the one named; the order of the two checks is that guarantee.", + "find": [" Ok((\n count(Operand::First, first)?,\n count(Operand::Second, second)?,\n ))\n"], + "replace": [" let second = count(Operand::Second, second)?;\n Ok((count(Operand::First, first)?, second))\n"] + }, + { + "name": "WT-1: the call's failure has no source", + "file": "src/compare.rs", + "expect": "caught", + "why": "The code is the failure's cause; dropping it as the source leaves an error chain that stops at the wrapper.", + "find": [" Self::Call(error) => Some(error),\n"], + "replace": [" Self::Call(_) => None,\n"] + }, + { + "name": "WT-1 control: the equal result is reported through a binding", + "file": "src/compare.rs", + "expect": "survives", + "why": "A control: the same ordering through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" CSTR_EQUAL => Ok(Ordering::Equal),\n"], + "replace": [" CSTR_EQUAL => {\n let equal = Ordering::Equal;\n Ok(equal)\n }\n"] + } + ] +} diff --git a/crates/win-text-sys/src/compare.rs b/crates/win-text-sys/src/compare.rs new file mode 100644 index 000000000..85a43c459 --- /dev/null +++ b/crates/win-text-sys/src/compare.rs @@ -0,0 +1,178 @@ +// Copyright (c) 2026 Mike Grier +//! `CompareStringOrdinal`: the ordinal comparison of two counted wide strings. +//! +//! Read 2026-10-20 from the call's page (`ms.date` 2018-12-05, `updated_at` 2024-02-22). The call takes each +//! string as a pointer and an `int` count, and this crate always passes the count, never the `-1` the +//! page gives for a NUL-terminated string, so a NUL in the data is a unit like any other. The one argument +//! the crate cannot pass as given is a slice longer than the `int` holds ([`CompareStringOrdinalError::TooLong`]). + +use std::cmp::Ordering; +use std::fmt; + +use win_error_codes::Win32Error; +use windows_sys::Win32::Foundation::{FALSE, TRUE}; +use windows_sys::Win32::Globalization::{ + CSTR_EQUAL, CSTR_GREATER_THAN, CSTR_LESS_THAN, CompareStringOrdinal, +}; + +#[cfg(test)] +mod tests; + +/// Whether the comparison folds case, which is the call's `bIgnoreCase`. +/// +/// The page: "TRUE if the function is to perform a case-insensitive comparison, using the operating +/// system uppercase table information", and "FALSE if the function is to compare the strings exactly as +/// they are passed in". The page also says 1 is the only numeric value that is a true value that does not +/// give an invalid parameter error, so [`Insensitive`](Self::Insensitive) is passed as `TRUE`, which is 1. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Case { + /// Compare the strings exactly as they are passed in. + Sensitive, + /// Compare using the operating system's uppercase table. + Insensitive, +} + +impl Case { + /// The value of `bIgnoreCase`. + const fn ignore_case(self) -> i32 { + match self { + Self::Sensitive => FALSE, + Self::Insensitive => TRUE, + } + } +} + +/// Which of the two strings a [`CompareStringOrdinalError::TooLong`] names. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Operand { + /// `lpString1`. + First, + /// `lpString2`. + Second, +} + +/// Why [`compare_string_ordinal`] gave no ordering. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CompareStringOrdinalError { + /// A string is longer than the call's count can hold, so the call was not made. + /// + /// The page's syntax gives each count as an `int` (`cchCount1` and `cchCount2`: "Length of the string + /// indicated by *lpString1*"), so a slice of more than `i32::MAX` units has no count to be passed + /// with. This is the crate's own refusal and not the call's: the call was not made and Windows gave + /// no code. When both strings are too long, the first is named. + TooLong { + /// The string that is too long. + operand: Operand, + /// Its length in `u16` units. + len: usize, + }, + /// The call failed and gave this code. The page lists `ERROR_INVALID_PARAMETER`, "Any of the + /// parameter values was invalid"; a code of zero says the call gave a result the page does not list + /// and no code. + Call(Win32Error), +} + +impl fmt::Display for CompareStringOrdinalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TooLong { operand, len } => write!( + f, + "the {} string of {len} units is longer than CompareStringOrdinal's count, an int, can hold", + match operand { + Operand::First => "first", + Operand::Second => "second", + } + ), + Self::Call(error) => write!(f, "CompareStringOrdinal failed: {error}"), + } + } +} + +impl std::error::Error for CompareStringOrdinalError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::TooLong { .. } => None, + Self::Call(error) => Some(error), + } + } +} + +/// Compares two strings of UTF-16 code units by `CompareStringOrdinal`, which "tests for binary equality, +/// not linguistic equality". +/// +/// Each slice is passed with its length, so it is compared as exactly the units it holds: a NUL is a unit, +/// an unpaired surrogate is a unit, and nothing past the end is read. The result is the first string's +/// place against the second's, which the page gives as `CSTR_LESS_THAN`, `CSTR_EQUAL` and +/// `CSTR_GREATER_THAN`. +/// +/// # Errors +/// +/// [`CompareStringOrdinalError::TooLong`] for a slice of more than `i32::MAX` units, without calling +/// `CompareStringOrdinal`. Otherwise the call's own failure, [`CompareStringOrdinalError::Call`]. +pub fn compare_string_ordinal( + first: &[u16], + second: &[u16], + case: Case, +) -> Result { + let (first_count, second_count) = counts(first.len(), second.len())?; + // SAFETY: each pointer is that of a slice and is valid for reads of exactly the count passed with it, + // which is that slice's length, and neither count is -1. + unsafe { + call( + first.as_ptr(), + first_count, + second.as_ptr(), + second_count, + case.ignore_case(), + ) + } + .map_err(CompareStringOrdinalError::Call) +} + +/// The two lengths as the call's counts, or the first that does not fit. +fn counts(first: usize, second: usize) -> Result<(i32, i32), CompareStringOrdinalError> { + let count = |operand, len| { + i32::try_from(len).map_err(|_| CompareStringOrdinalError::TooLong { operand, len }) + }; + Ok(( + count(Operand::First, first)?, + count(Operand::Second, second)?, + )) +} + +/// The call, and the code it left if it failed. +/// +/// # Safety +/// +/// `first` and `second` are valid for reads of `first_count` and `second_count` units, as the page +/// requires, or the counts are -1 and the strings are NUL-terminated. +unsafe fn call( + first: *const u16, + first_count: i32, + second: *const u16, + second_count: i32, + ignore_case: i32, +) -> Result { + // SAFETY: the caller's. + let result = + unsafe { CompareStringOrdinal(first, first_count, second, second_count, ignore_case) }; + // Nothing runs between the call and the read of its last error. + ordering_of(result, Win32Error::last) +} + +/// The ordering the call's result says, or the failure it is. +/// +/// A zero is the failure the page gives, and `last_error` is read only then. A value the page does not +/// list is a failure with no code, which is how a code of zero is read: Windows gave none. +fn ordering_of( + result: i32, + last_error: impl FnOnce() -> Win32Error, +) -> Result { + match result { + CSTR_LESS_THAN => Ok(Ordering::Less), + CSTR_EQUAL => Ok(Ordering::Equal), + CSTR_GREATER_THAN => Ok(Ordering::Greater), + 0 => Err(last_error()), + _ => Err(Win32Error::from_code(0)), + } +} diff --git a/crates/win-text-sys/src/compare/tests.rs b/crates/win-text-sys/src/compare/tests.rs new file mode 100644 index 000000000..028d3ec3f --- /dev/null +++ b/crates/win-text-sys/src/compare/tests.rs @@ -0,0 +1,360 @@ +// Copyright (c) 2026 Mike Grier +//! Tests for `compare_string_ordinal` and what it is made of. + +use std::cmp::Ordering; + +use win_error_codes::Win32Error; +use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; +use windows_sys::Win32::Globalization::{CSTR_EQUAL, CSTR_GREATER_THAN, CSTR_LESS_THAN}; + +use super::{ + Case, CompareStringOrdinalError, Operand, call, compare_string_ordinal, counts, ordering_of, +}; + +fn wide(text: &str) -> Vec { + text.encode_utf16().collect() +} + +fn compare(first: &[u16], second: &[u16], case: Case) -> Ordering { + compare_string_ordinal(first, second, case).expect("a comparison") +} + +fn compare_text(first: &str, second: &str, case: Case) -> Ordering { + compare(&wide(first), &wide(second), case) +} + +#[test] +fn equal_strings_are_equal_in_both_cases() { + for case in [Case::Sensitive, Case::Insensitive] { + for text in ["", "a", "abc", "C:\\Users\\name", "caf\u{e9}", "\u{1f600}"] { + assert_eq!(compare_text(text, text, case), Ordering::Equal, "{text:?}"); + } + } +} + +#[test] +fn a_smaller_string_is_less_and_the_larger_is_greater() { + for (smaller, larger) in [ + ("a", "b"), + ("abc", "abd"), + ("A", "B"), + ("0", "9"), + ("x", "xy"), + ] { + assert_eq!( + compare_text(smaller, larger, Case::Sensitive), + Ordering::Less, + "{smaller:?} against {larger:?}" + ); + assert_eq!( + compare_text(larger, smaller, Case::Sensitive), + Ordering::Greater, + "{larger:?} against {smaller:?}" + ); + } +} + +#[test] +fn a_prefix_is_less_than_what_it_is_a_prefix_of() { + assert_eq!(compare_text("ab", "abc", Case::Sensitive), Ordering::Less); + assert_eq!( + compare_text("abc", "ab", Case::Sensitive), + Ordering::Greater + ); + assert_eq!(compare_text("ab", "abc", Case::Insensitive), Ordering::Less); +} + +#[test] +fn case_sensitive_comparison_takes_the_units_as_they_are() { + // 'a' is 0x61 and 'A' is 0x41. + assert_eq!( + compare_text("abc", "ABC", Case::Sensitive), + Ordering::Greater + ); + assert_eq!(compare_text("ABC", "abc", Case::Sensitive), Ordering::Less); +} + +#[test] +fn case_insensitive_comparison_folds_ascii_case() { + assert_eq!( + compare_text("abc", "ABC", Case::Insensitive), + Ordering::Equal + ); + assert_eq!( + compare_text("AbC", "aBc", Case::Insensitive), + Ordering::Equal + ); +} + +#[test] +fn case_insensitive_order_is_that_of_the_uppercase_table() { + // 'a' folds to 'A', which is before 'B', where the units 0x61 and 0x42 are the other way round. + assert_eq!(compare_text("a", "B", Case::Insensitive), Ordering::Less); + assert_eq!(compare_text("a", "B", Case::Sensitive), Ordering::Greater); +} + +#[test] +fn case_insensitive_comparison_folds_beyond_ascii() { + for (lower, upper) in [ + ("\u{e9}", "\u{c9}"), + ("\u{3c3}", "\u{3a3}"), + ("\u{44f}", "\u{42f}"), + ] { + assert_eq!( + compare_text(lower, upper, Case::Insensitive), + Ordering::Equal, + "{lower:?}" + ); + assert_ne!( + compare_text(lower, upper, Case::Sensitive), + Ordering::Equal, + "{lower:?}" + ); + } +} + +#[test] +fn the_comparison_is_ordinal_and_not_linguistic() { + // Evidence, not contract: a linguistic comparison could equate these two, and the page says this + // call tests "binary equality, not linguistic equality". + assert_ne!( + compare_text("\u{df}", "ss", Case::Insensitive), + Ordering::Equal + ); + assert_ne!( + compare_text("e\u{301}", "\u{e9}", Case::Insensitive), + Ordering::Equal + ); +} + +#[test] +fn empty_strings_compare_as_the_shortest() { + for case in [Case::Sensitive, Case::Insensitive] { + assert_eq!(compare(&[], &[], case), Ordering::Equal); + assert_eq!(compare(&[], &wide("a"), case), Ordering::Less); + assert_eq!(compare(&wide("a"), &[], case), Ordering::Greater); + } +} + +#[test] +fn an_embedded_nul_is_a_unit_and_not_the_end() { + // Passed with the page's -1 these would each be the one-unit string "a". + let with_nul_b = [u16::from(b'a'), 0, u16::from(b'b')]; + let with_nul_c = [u16::from(b'a'), 0, u16::from(b'c')]; + for case in [Case::Sensitive, Case::Insensitive] { + assert_eq!(compare(&with_nul_b, &with_nul_c, case), Ordering::Less); + assert_eq!(compare(&with_nul_c, &with_nul_b, case), Ordering::Greater); + assert_eq!(compare(&with_nul_b, &with_nul_b, case), Ordering::Equal); + assert_eq!( + compare(&[u16::from(b'a'), 0], &[u16::from(b'a')], case), + Ordering::Greater + ); + assert_eq!(compare(&[0], &[], case), Ordering::Greater); + } +} + +#[test] +fn a_slice_is_compared_as_the_units_it_holds_and_no_further() { + let first = wide("abcX"); + let second = wide("abcY"); + assert_eq!( + compare(&first[..3], &second[..3], Case::Sensitive), + Ordering::Equal + ); + assert_eq!(compare(&first, &second, Case::Sensitive), Ordering::Less); + assert_eq!( + compare(&first[1..3], &second[..3], Case::Sensitive), + Ordering::Greater + ); +} + +#[test] +fn unpaired_surrogates_are_units() { + for case in [Case::Sensitive, Case::Insensitive] { + assert_eq!(compare(&[0xD800], &[0xD800], case), Ordering::Equal); + assert_eq!(compare(&[0xD800], &[0xDC00], case), Ordering::Less); + assert_eq!(compare(&[0xDC00], &[0xD800], case), Ordering::Greater); + assert_eq!(compare(&[0xD800], &[], case), Ordering::Greater); + } +} + +#[test] +fn the_order_observed_is_that_of_the_units_and_not_of_code_points() { + // Evidence, not contract: the page says only "binary". U+1F600 is the units D83D DE00, and U+FF5E + // is the one unit FF5E, so by unit order the first is less and by code point order it is greater. + let emoji = wide("\u{1f600}"); + let fullwidth_tilde = wide("\u{ff5e}"); + assert_eq!(emoji, [0xD83D, 0xDE00]); + assert_eq!( + compare(&emoji, &fullwidth_tilde, Case::Sensitive), + Ordering::Less + ); +} + +#[test] +fn a_difference_in_the_last_unit_of_a_long_string_is_found() { + let mut first = vec![u16::from(b'x'); 1 << 20]; + let mut second = first.clone(); + assert_eq!(compare(&first, &second, Case::Sensitive), Ordering::Equal); + *first.last_mut().expect("not empty") = u16::from(b'a'); + *second.last_mut().expect("not empty") = u16::from(b'b'); + assert_eq!(compare(&first, &second, Case::Sensitive), Ordering::Less); +} + +#[test] +fn swapping_the_strings_reverses_the_ordering_and_equality_is_reflexive() { + let corpus = [ + "", + "a", + "A", + "b", + "ab", + "AB", + "abc", + "\u{e9}", + "\u{c9}", + "z", + "Z", + "\u{1f600}", + "a\0", + ]; + for case in [Case::Sensitive, Case::Insensitive] { + for first in corpus { + assert_eq!(compare_text(first, first, case), Ordering::Equal); + for second in corpus { + assert_eq!( + compare_text(first, second, case), + compare_text(second, first, case).reverse(), + "{first:?} and {second:?} under {case:?}" + ); + } + } + } +} + +#[test] +fn a_case_sensitive_comparison_is_equal_exactly_when_the_units_are() { + let corpus = ["", "a", "A", "ab", "ba", "\u{e9}", "\u{c9}", "a\0b", "a\0"]; + for first in corpus { + for second in corpus { + assert_eq!( + compare_text(first, second, Case::Sensitive) == Ordering::Equal, + first == second, + "{first:?} and {second:?}" + ); + } + } +} + +#[test] +fn case_values_are_the_calls_documented_booleans() { + // The page: 1 is the only numeric value for true that does not give an invalid parameter error. + assert_eq!(Case::Insensitive.ignore_case(), 1); + assert_eq!(Case::Sensitive.ignore_case(), 0); +} + +#[test] +fn a_length_the_count_holds_is_passed_as_it_is() { + assert_eq!(counts(0, 0).expect("counts"), (0, 0)); + assert_eq!(counts(3, 7).expect("counts"), (3, 7)); + let largest = i32::MAX as usize; + assert_eq!( + counts(largest, largest).expect("counts"), + (i32::MAX, i32::MAX) + ); +} + +#[test] +fn a_length_the_count_cannot_hold_is_refused_and_names_the_string() { + let too_long = i32::MAX as usize + 1; + assert_eq!( + counts(too_long, 0), + Err(CompareStringOrdinalError::TooLong { + operand: Operand::First, + len: too_long + }) + ); + assert_eq!( + counts(0, too_long), + Err(CompareStringOrdinalError::TooLong { + operand: Operand::Second, + len: too_long + }) + ); + assert_eq!( + counts(usize::MAX, 1), + Err(CompareStringOrdinalError::TooLong { + operand: Operand::First, + len: usize::MAX + }) + ); +} + +#[test] +fn when_both_lengths_are_too_long_the_first_is_named() { + let first = i32::MAX as usize + 1; + let second = i32::MAX as usize + 5; + assert_eq!( + counts(first, second), + Err(CompareStringOrdinalError::TooLong { + operand: Operand::First, + len: first + }) + ); +} + +#[test] +fn the_three_results_the_page_lists_are_the_three_orderings() { + let never = || -> Win32Error { panic!("the last error is read only for a failure") }; + assert_eq!(ordering_of(CSTR_LESS_THAN, never), Ok(Ordering::Less)); + assert_eq!(ordering_of(CSTR_EQUAL, never), Ok(Ordering::Equal)); + assert_eq!(ordering_of(CSTR_GREATER_THAN, never), Ok(Ordering::Greater)); +} + +#[test] +fn a_zero_result_is_the_failure_and_reads_the_last_error() { + let code = Win32Error::from_code(ERROR_INVALID_PARAMETER); + assert_eq!(ordering_of(0, || code), Err(code)); +} + +#[test] +fn a_result_the_page_does_not_list_is_a_failure_with_no_code() { + let never = || -> Win32Error { panic!("the call did not report a failure") }; + for result in [4, -1, 100, i32::MIN, i32::MAX] { + assert_eq!( + ordering_of(result, never), + Err(Win32Error::from_code(0)), + "{result}" + ); + } +} + +#[test] +fn the_call_failing_gives_its_code() { + // The failure the page documents: a value of `bIgnoreCase` other than 0 and 1 is "an invalid + // parameter error", and the page lists ERROR_INVALID_PARAMETER for it. The safe function cannot + // pass one, so the call is made here directly. + let text = wide("abc"); + // SAFETY: the pointers are those of `text`, valid for its three units, with those counts. + let result = unsafe { call(text.as_ptr(), 3, text.as_ptr(), 3, 2) }; + assert_eq!(result, Err(Win32Error::from_code(ERROR_INVALID_PARAMETER))); +} + +#[test] +fn a_failure_is_the_calls_and_a_refusal_is_the_crates() { + let call_failure = + CompareStringOrdinalError::Call(Win32Error::from_code(ERROR_INVALID_PARAMETER)); + let refusal = CompareStringOrdinalError::TooLong { + operand: Operand::Second, + len: 1 << 40, + }; + assert_ne!(call_failure, refusal); + assert!( + std::error::Error::source(&call_failure).is_some(), + "the call's failure has its code as a source" + ); + assert!(std::error::Error::source(&refusal).is_none()); + assert!(refusal.to_string().contains("second")); + assert!(refusal.to_string().contains(&(1u64 << 40).to_string())); + assert!(call_failure.to_string().contains("CompareStringOrdinal")); +} diff --git a/crates/win-text-sys/src/lib.rs b/crates/win-text-sys/src/lib.rs new file mode 100644 index 000000000..266a8bc46 --- /dev/null +++ b/crates/win-text-sys/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Mike Grier +#![doc = include_str!("../README.md")] +#![cfg(windows)] +#![deny(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] + +mod compare; + +pub use compare::{Case, CompareStringOrdinalError, Operand, compare_string_ordinal}; diff --git a/crates/win-volume-sys/CHECKLIST.md b/crates/win-volume-sys/CHECKLIST.md index 1798f935c..22f186182 100644 --- a/crates/win-volume-sys/CHECKLIST.md +++ b/crates/win-volume-sys/CHECKLIST.md @@ -3,77 +3,5 @@ Memory-safe Rust over the Win32 volume device controls. See [README.md](README.md) for what the crate is and [DESIGN-NOTES.md](DESIGN-NOTES.md) for its decisions. -## WV-M1 -- The controls durable-ioring's raw volume backing needs - -- [x] **WV-1.1** -- The disks a volume sits on, `volume_disk_extents`. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#wv-11) - -- [x] **WV-1.2** -- The sector size a volume is addressed in, `bytes_per_sector`. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#wv-12) - -- [x] **WV-1.3** -- A volume's lock, `lock_volume` and `unlock_volume`. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#wv-13) - -## WV-M2 -- Take in the storage-locality entries - -Opened 2026-10-15 by [WV-D-5](DESIGN-NOTES.md#wv-d-5) and [WV-D-6](DESIGN-NOTES.md#wv-d-6). The -entries built in [windows-namespace-request-sys](../windows-namespace-request-sys/CHECKLIST.md) as -`NR-2.1` to `NR-2.5` and `NR-2.7` are volume and disk operations, so they move here, with their tests, -their sabotage entries, and the decisions that justify them (`D-20` to `D-24` in that crate's -[DESIGN-NOTES.md](../windows-namespace-request-sys/DESIGN-NOTES.md), each given a `WV-D` number here -when its item lands and replaced there by a pointer). **Each item carries its own records**, in the -same commit, so the notes are never a step behind the code. - -An item that changes a signature `win-durable-ioring` calls (it uses `volume_disk_extents`, -`bytes_per_sector` and `lock_volume`) **adapts that crate in the same commit**, so the workspace is -never left unbuilt; the commit is a `feat!` of this crate and a `chore` of the consumer, split per -the workspace's release-scope rule where the two do not have to build together. - -> **-> CROSS-COMPONENT PREREQUISITE:** `WV-2.1` needs [WE-1.1](../win-error-codes/CHECKLIST.md) -> (`Win32Error`), in `crates/win-error-codes`. - -- [x] **WV-2.1** -- The overlapped-safe `DeviceIoControl` helper, with an input buffer and exact error codes, replacing `control()`. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#wv-21) - - **Next:** `WV-2.2`. - -- [x] **WV-2.2** -- `volume_disk_extents` takes the stricter checks of the namespace crate's entry, and `WV-D-3` and `D-21` are one decision. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#wv-22) - - **Next:** `WV-2.3`. - -- [x] **WV-2.3** -- `volume_numa_node`, the NUMA node a volume resides on (`FSCTL_QUERY_VOLUME_NUMA_INFO`), as the device wrote it. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#wv-23) - - **Next:** `WV-2.4`. - -- [x] **WV-2.4** -- The storage property queries (`IOCTL_STORAGE_QUERY_PROPERTY`): the shared mechanics, the generic query, the unique id and the disk NUMA node. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#wv-24) - - **Next:** `WV-2.5`. - -- [x] **WV-2.5** -- `storage_dependency_information`, the virtdisk dependency query, with the version as an input and names copied out of the reply. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#wv-25) - - **Next:** nothing further in this milestone; the order across crates is in - [durable-ioring's checklist](../durable-ioring/CHECKLIST.md#where-the-open-surface-work-goes-next-read-this-first-when-picking-the-work-back-up). - -## WV-M3 -- What telling a volume or a disk from a file needs - -Opened 2026-10-18 by `durable-ioring`'s `DI-14.12`. `GetFileType` answers `DISK` for a file, -a volume and a physical disk alike, so a caller that must tell them apart needs a control that only a -volume or a disk answers. - -- [x] **WV-3.1** -- `storage_device_number`, the device type, number and partition number a volume or disk reports (`IOCTL_STORAGE_GET_DEVICE_NUMBER`), as it wrote them. -> [completed 2026-10-18](COMPLETED-CHECKLIST.md#wv-31) - - **Next:** `durable-ioring`'s `DI-14.12`, which also needs `windows-namespace-request-sys`' `NR-3.9`. - -## M-inf -- Parked - -Device controls a volume answers that this crate does not yet wrap, ungated: each graduates to a -numbered milestone when it is taken up. - -- [ ] **WV-inf.1** -- **The volume's length** (`IOCTL_DISK_GET_LENGTH_INFO`), so a caller can bound a - region it allocates inside a volume without asking the file system. - -- [ ] **WV-inf.2** -- **Enumerating the machine's volumes** (`FindFirstVolumeW`, `FindNextVolumeW`, - `FindVolumeClose`), with each volume's disks, so a caller can find which other volumes share a - disk with its own. - -- [ ] **WV-inf.3** -- **The rest of the storage stack** ([WV-D-5](DESIGN-NOTES.md#wv-d-5)): - partition management, raw partition access, and volume management beyond a lock. Coarse on - purpose: which calls these are (drive layout, partition table changes, and so on) has not been - researched, so naming them now would be inventing precision. It graduates to a numbered milestone - when that research is done, and the research is the first step of it. +Nothing is pending. `WV-M1` to `WV-M3` are complete and archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md); the +device controls and storage-stack calls not yet wrapped are parked in [PARKED.md](PARKED.md). diff --git a/crates/win-volume-sys/COMPLETED-CHECKLIST.md b/crates/win-volume-sys/COMPLETED-CHECKLIST.md index 0f9abddb4..b49032b21 100644 --- a/crates/win-volume-sys/COMPLETED-CHECKLIST.md +++ b/crates/win-volume-sys/COMPLETED-CHECKLIST.md @@ -109,3 +109,50 @@ what this left to do is `NR-2.8`, in the namespace crate. The engineer, 2026-10-18 ("build both of these now"), so that a caller can tell a volume or a disk from a file, which `GetFileType` does not. The decision and what was observed are [WV-D-11](DESIGN-NOTES.md#wv-d-11). Four sabotages, one a control that survives by design. + +## Moved 2026-10-21 21:46:17 -04:00 -- win-volume-sys: milestones WV-M1, WV-M2 and WV-M3 are complete + +### WV-M1 -- The controls durable-ioring's raw volume backing needs + +Items, each archived above under its own heading: [WV-1.1](#wv-11), [WV-1.2](#wv-12), [WV-1.3](#wv-13). + +### WV-M2 -- Take in the storage-locality entries + +Opened 2026-10-15 by [WV-D-5](DESIGN-NOTES.md#wv-d-5) and [WV-D-6](DESIGN-NOTES.md#wv-d-6). The +entries built in [windows-namespace-request-sys](../windows-namespace-request-sys/CHECKLIST.md) as +`NR-2.1` to `NR-2.5` and `NR-2.7` are volume and disk operations, so they move here, with their tests, +their sabotage entries, and the decisions that justify them (`D-20` to `D-24` in that crate's +[DESIGN-NOTES.md](../windows-namespace-request-sys/DESIGN-NOTES.md), each given a `WV-D` number here +when its item lands and replaced there by a pointer). **Each item carries its own records**, in the +same commit, so the notes are never a step behind the code. + +An item that changes a signature `win-durable-ioring` calls (it uses `volume_disk_extents`, +`bytes_per_sector` and `lock_volume`) **adapts that crate in the same commit**, so the workspace is +never left unbuilt; the commit is a `feat!` of this crate and a `chore` of the consumer, split per +the workspace's release-scope rule where the two do not have to build together. + +> **-> CROSS-COMPONENT PREREQUISITE:** `WV-2.1` needs [WE-1.1](../win-error-codes/CHECKLIST.md) +> (`Win32Error`), in `crates/win-error-codes`. + + **Next:** `WV-2.2`. + + **Next:** `WV-2.3`. + + **Next:** `WV-2.4`. + + **Next:** `WV-2.5`. + + **Next:** nothing further in this milestone; the order across crates is in + [durable-ioring's archive](../durable-ioring/COMPLETED-CHECKLIST.md#open-surface-order). + +Items, each archived above under its own heading: [WV-2.1](#wv-21), [WV-2.2](#wv-22), [WV-2.3](#wv-23), [WV-2.4](#wv-24), [WV-2.5](#wv-25). + +### WV-M3 -- What telling a volume or a disk from a file needs + +Opened 2026-10-18 by `durable-ioring`'s `DI-14.12`. `GetFileType` answers `DISK` for a file, +a volume and a physical disk alike, so a caller that must tell them apart needs a control that only a +volume or a disk answers. + + **Next:** none. `durable-ioring`'s `DI-14.12`, which this was built for, was withdrawn ([DI-D-73](../durable-ioring/DESIGN-NOTES.md#di-d-73)); the entry stays, since it reports one documented call's result. + +Items, each archived above under its own heading: [WV-3.1](#wv-31). diff --git a/crates/win-volume-sys/COMPLETED-PLANS.md b/crates/win-volume-sys/COMPLETED-PLANS.md new file mode 100644 index 000000000..546990746 --- /dev/null +++ b/crates/win-volume-sys/COMPLETED-PLANS.md @@ -0,0 +1,9 @@ +# Completed plans: win-volume-sys + +Checklists whose planned work is complete. A checklist reappears in +[PLANS.md](PLANS.md) if new work is planned against it; the row here stays as the record of the work that was +finished. Individual items are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + +| Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | +|---|---|---|---| +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-18 | Memory-safe Rust over the Win32 volume device controls: the disks a volume sits on, its sector size and its lock, which durable-ioring's raw volume backing is built on (`WV-M1`), the storage-locality entries taken in from the namespace crate (`WV-M2`), and what telling a volume or a disk from a file needs (`WV-M3`). The calls not yet wrapped are parked in [PARKED.md](PARKED.md). | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/win-volume-sys/DESIGN-NOTES.md b/crates/win-volume-sys/DESIGN-NOTES.md index ef6749c7a..b8fb519c6 100644 --- a/crates/win-volume-sys/DESIGN-NOTES.md +++ b/crates/win-volume-sys/DESIGN-NOTES.md @@ -19,7 +19,7 @@ control reports, and the engineer chose a new crate to hold the `unsafe` over an | WV-D-2 | **Every control takes any handle that lends itself, needs no access for the two queries, and is the same call whether the handle was opened overlapped or not.** A handle opened with `access_mode(0)` can be asked for a volume's extents and sector size, which an account that is not an administrator can open, so the answer that decides a backing's flush domains is not gated on privilege. **The call is made with an `OVERLAPPED` and an event of its own and waited for**, because a device control on an overlapped handle with no `OVERLAPPED` is undefined, and this crate is lent handles whose open mode it cannot inspect (and may not, by the repository's rule: the call that reports it is undocumented). The wait is to completion, so the buffer and the `OVERLAPPED` are never written to after the function returns. A sabotage that drops the `OVERLAPPED` survives, declared: the volumes the tests open take it without incident, and so does `FSCTL_REQUEST_OPLOCK`, the one control that pends, which blocks inside `DeviceIoControl` and answers the same, so no test can tell the two apart. | | WV-D-3 | **An answer is checked against what it says it holds, the call is never made with room for fewer than one extent, and a retry is bounded.** Reconciled on 2026-10-15 by `WV-2.2` with `D-21` of the namespace crate's [DESIGN-NOTES.md](../windows-namespace-request-sys/DESIGN-NOTES.md), which decided the same thing for its copy of this function and had two checks this one lacked; `D-21` is superseded by this row. **The reply** is a count then that many extents. `volume_disk_extents` reads the count from the header and refuses, as `InvalidData` and without reading, a count whose extents do not fit in the bytes the call said it wrote, **a call that says it wrote more than the buffer it was given** (before this, a slice that would have panicked), a negative offset or length, and an answer too short for its header; it never reads outside its buffer. **The call is never made with room for fewer than one extent**: a buffer smaller than the count plus one extent is `ERROR_INVALID_PARAMETER` on the machine measured, and not `ERROR_INSUFFICIENT_BUFFER` or `ERROR_MORE_DATA`, so the first answer has room for at least one, and **a reply that needs more room but counts none is asked again with room for one**, not for none. **A count that does not fit** is the device's `ERROR_MORE_DATA` with the count written, read through `win-error-codes`, and is asked for again at that size, up to `ATTEMPTS`, since a volume whose extents change between calls could otherwise be asked for forever; any other failure is the device's own error, ends the attempts, and is not retried. **A count whose buffer cannot be allocated** is `ERROR_NOT_ENOUGH_MEMORY` and the call is not made again, because the count is the device's and is not trusted to be allocatable. **Measured on 2026-10-15**, Windows 11, one disk, a handle opened with no access: the system volume reports one run, and every buffer size from 0 up to one byte short of the count plus one extent is `ERROR_INVALID_PARAMETER`. **Not observed:** a volume that spans or stripes disks, so the `ERROR_MORE_DATA` half, the count written with it, and the order of several runs are from the documentation and are tested against a stand-in device built to answer that way, whose one measured behaviour is the refusal above. The namespace crate's entry reported the same refusals as typed `Malformed` and `Unstable` variants; here they are `InvalidData`, as the rest of this crate reports an answer that does not hold what it says. | | WV-D-4 | **Withdrawn by [WV-D-12](#wv-d-12): no page says a success shows the volume is local.** **A volume that answers `volume_disk_extents` is local.** A volume on a remote system, or a handle that is not a volume, does not answer the control, so success is what shows a handle names a volume on this machine's own disks. The crate says so; what to do with it is the using crate's. | -| WV-D-5 | **The crate's scope is physical disks, partition management, raw partition access and volume management, and those operations live here and not in the namespace crate.** The engineer, 2026-10-15: "keep it all together, physical disks, partition management, raw partition access, volume management", and "move volume-oriented operations into the volume crate". That list is wider than volumes, so the crate's name now undersells it; **renaming is not decided** (see below). **The storage-locality entries built in [windows-namespace-request-sys](../windows-namespace-request-sys/CHECKLIST.md) as `NR-2.1` to `NR-2.5` and `NR-2.7` (volume NUMA, disk extents, the storage-property queries, the virtdisk dependency query) and the overlapped-safe `DeviceIoControl` helper they share move here**, because that crate's charter is namespace operations and a volume operation does not belong in it. `NR-2.6` ("decide between the two copies") is closed by this: one helper, here, and the namespace crate's copies are deleted. This crate does **not** depend on the namespace crate. The move is [WV-M2](CHECKLIST.md#wv-m2----take-in-the-storage-locality-entries). | +| WV-D-5 | **The crate's scope is physical disks, partition management, raw partition access and volume management, and those operations live here and not in the namespace crate.** The engineer, 2026-10-15: "keep it all together, physical disks, partition management, raw partition access, volume management", and "move volume-oriented operations into the volume crate". That list is wider than volumes, so the crate's name now undersells it; **renaming is not decided** (see below). **The storage-locality entries built in [windows-namespace-request-sys](../windows-namespace-request-sys/CHECKLIST.md) as `NR-2.1` to `NR-2.5` and `NR-2.7` (volume NUMA, disk extents, the storage-property queries, the virtdisk dependency query) and the overlapped-safe `DeviceIoControl` helper they share move here**, because that crate's charter is namespace operations and a volume operation does not belong in it. `NR-2.6` ("decide between the two copies") is closed by this: one helper, here, and the namespace crate's copies are deleted. This crate does **not** depend on the namespace crate. The move is [WV-M2](COMPLETED-CHECKLIST.md#wv-m2----take-in-the-storage-locality-entries). | | WV-D-6 | **A handle that must be shared is a [`SharedHandle`](../win-shared-os-owned-handle/README.md).** The engineer, 2026-10-15: "using the shared-os-owned-handle crate's type(s) is the repo's paradigm so that would seem like the right solution in general". It is an `Arc`, so a clone is a reference-count increment and not a `DuplicateHandle`, and it makes no claim about how the handle was opened, which is what [WV-D-2](#wv-d-2) needs. It implements `AsHandle`, so the functions here keep taking `&impl AsHandle`; a consumer that needs a handle on more than one thread holds a `SharedHandle` and lends it. The namespace crate's `CapturedHandle` (a duplicate owned per request) and its `Request` trait are not carried over: a closure that captures a `SharedHandle` clone is how work moves to another thread. | | WV-D-7 | **Every device control goes through one helper, made the same way on any handle, and a failure carries its code exactly.** From `D-20` of the namespace crate's [DESIGN-NOTES.md](../windows-namespace-request-sys/DESIGN-NOTES.md), where it was built and proven for `NR-2.1` and `NR-2.2`, and amended by [WV-D-2](#wv-d-2)'s facts about this crate. Implemented by `WV-2.1` (`src/device_control.rs`), which deletes `control()`. **The call:** every control gets its own `OVERLAPPED` and a manual-reset event and is waited for, because the crate cannot see how a handle it is lent was opened; the event is passed with its **low bit set**, which `GetQueuedCompletionStatus` documents as suppressing the completion packet, so a handle on a consumer's completion port gets none for a call the consumer never made (`control()` did not do this); a call still running is waited for with `WaitForSingleObject` and its result read with `GetOverlappedResult`, and **if that wait fails the process aborts**, because the device may still write into memory that unwinding would release. **An input buffer:** an empty one reaches the device as a null pointer and a length of zero. **A failure is an `io::Error` built from the code the call set, read in the statement after the call**, and exact for every code through `win-error-codes` ([WE-D-1](../win-error-codes/DESIGN-NOTES.md#we-d-1)), `ERROR_MORE_DATA` and `ERROR_INSUFFICIENT_BUFFER` included, which a control that reports a size first is sized by. **A buffer over 4 GiB is `InvalidInput`**, where the namespace crate's copy panicked. **The reply helpers** `u32_at`, `zeroed_output`, `invalid_data` and `is_more_data` live with it, so the functions that read a reply share one definition of each: a buffer sized from a device's own count is allocated fallibly, so the failure is `ERROR_NOT_ENOUGH_MEMORY` for the caller and not an abort. **The pending path is pinned through a seam**: the helper is `device_control`, the `DeviceIoControl` argument list, and `overlapped_call`, which owns the event, the `OVERLAPPED`, the wait and the result read and takes the call as a closure, so the tests hand it an overlapped `ReadFile` on a named pipe, which pends until the other end writes; `FSCTL_REQUEST_OPLOCK` reaches `DeviceIoControl` itself through the pending path and shows the input arriving. **Not pinned, and said so:** a control made without its `OVERLAPPED` (see [WV-D-2](#wv-d-2)), the abort when the wait fails, and the failure of `CreateEventW`, which the helper's own event cannot provoke. | | WV-D-8 | **`volume_numa_node` reports the node as the device wrote it, and decides nothing about a value or a failure.** Implemented by `WV-2.3` (`src/volume_numa.rs`), from `NR-2.1` of the namespace crate, which has no decision row of its own. **What the answer is:** the node of the volume that *contains* the file or directory the handle names (or of the volume, if the handle is one); it says nothing of where a file's extents are, and a volume that spans several disks still gets one answer, so it is not evidence about each of them. **What is not decided:** the documentation does not say what a volume that advertises no proximity domain answers, so a failure is the device's own code, unaltered, and a success carries the value written, `u32::MAX` included; whether a value means "no node" is the caller's. **The reply is one `ULONG`**; a call that succeeds and says it wrote any other number of bytes is `InvalidData` and is not read, because a value taken from a buffer the device did not fill would read as a node. **The shape differs from the namespace crate's:** a `u32` and not a `VolumeNumaInfo` with a `Display` and a typed `Unexpected` error, since the function is one number and the rest of this crate returns numbers (`bytes_per_sector`) and reports a malformed answer as `InvalidData` ([WV-D-3](#wv-d-3)). **Measured on 2026-10-15**, Windows 11, one NUMA node, a Hyper-V guest: a file on the system volume answers node `0`; the `NUL` device is refused with `ERROR_INVALID_FUNCTION`; a directory and a file on the same volume answer the same; an overlapped handle answers as a plain one. **Not observed:** a volume that advertises no proximity domain, and a machine with more than one node, so whether a volume there reports the right node is not tested. **Not pinned, declared:** the control code itself, since a wrong code and a volume with no answer are both a refusal, so no test that holds on every machine tells them apart; the sabotage that changes it `survives`. What is pinned on every machine is the room the call is made with: a refusal for want of room is never the answer. | diff --git a/crates/win-volume-sys/PARKED.md b/crates/win-volume-sys/PARKED.md new file mode 100644 index 000000000..b7e52749d --- /dev/null +++ b/crates/win-volume-sys/PARKED.md @@ -0,0 +1,22 @@ +# Parked: win-volume-sys + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M-inf -- Parked + +Device controls a volume answers that this crate does not yet wrap, ungated: each graduates to a +numbered milestone when it is taken up. + +- [ ] **WV-inf.1** -- **The volume's length** (`IOCTL_DISK_GET_LENGTH_INFO`), so a caller can bound a + region it allocates inside a volume without asking the file system. + +- [ ] **WV-inf.2** -- **Enumerating the machine's volumes** (`FindFirstVolumeW`, `FindNextVolumeW`, + `FindVolumeClose`), with each volume's disks, so a caller can find which other volumes share a + disk with its own. + +- [ ] **WV-inf.3** -- **The rest of the storage stack** ([WV-D-5](DESIGN-NOTES.md#wv-d-5)): + partition management, raw partition access, and volume management beyond a lock. Coarse on + purpose: which calls these are (drive layout, partition table changes, and so on) has not been + researched, so naming them now would be inventing precision. It graduates to a numbered milestone + when that research is done, and the research is the first step of it. diff --git a/crates/win-volume-sys/PLANS.md b/crates/win-volume-sys/PLANS.md index 90279f151..da7eda9d4 100644 --- a/crates/win-volume-sys/PLANS.md +++ b/crates/win-volume-sys/PLANS.md @@ -1,5 +1,8 @@ # Plans: win-volume-sys +Completed checklists are recorded in [COMPLETED-PLANS.md](COMPLETED-PLANS.md), and the items they +contained are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). + | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | Memory-safe Rust over the Win32 calls for physical disks, partitions, raw partition access and volumes: the volume controls durable-ioring's raw volume backing is built on (done), and WV-M2, which takes in the storage-locality entries built in the namespace crate. Partition and disk management are parked. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | +| [PARKED.md](PARKED.md) | parked | `WV-inf.1` to `WV-inf.3`: the volume's length, enumerating the machine's volumes, and the rest of the storage stack, none gated and each graduating when taken up. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/windows-file-enumeration-sys/CHECKLIST.md b/crates/windows-file-enumeration-sys/CHECKLIST.md index ef16bce5e..2a632506a 100644 --- a/crates/windows-file-enumeration-sys/CHECKLIST.md +++ b/crates/windows-file-enumeration-sys/CHECKLIST.md @@ -2,77 +2,5 @@ The crate's implementation milestones are M5 through M7 in the workspace [CHECKLIST.md](../../CHECKLIST.md). This file holds work owned by the crate -itself. Status is tracked in [PLANS.md](PLANS.md). - -## PATH -- One path contract across the namespace crates - -- [ ] **PATH-1** -- **Consume the namespace request crate's path contract, and delete this crate's - older copy of the preparation.** This is the merge-or-delete decision of the workspace checklist's - `M26+.3` ([CHECKLIST-thread-ambient.md](../../CHECKLIST-thread-ambient.md)) and of - [D-9](../windows-namespace-request-sys/DESIGN-NOTES.md#d-9) in the request crate, made on - 2026-10-15 at the engineer's request as: this crate depends on `windows-namespace-request-sys` and - its [path.rs](src/path.rs) preparation is deleted. The request crate is released, and the layering - D-9 recorded puts this crate above it, so the dependency runs the right way. What changes for this - crate, to be read before it is done and not assumed: [D-7](DESIGN-NOTES.md#d-7)'s `MAX_PATH` cap - on an ordinary path goes, as it does in the request crate - ([D-19](../windows-namespace-request-sys/DESIGN-NOTES.md#d-19)); an enumeration request captures the - current directory when it is built and grafts a clearly safe child onto it when it is performed, - instead of storing a `GetFullPathNameW`-resolved path; `EnumerationRequest::path()`, which is `pub` - and returns the stored resolved form, has to say what it returns now (the path as given, with a - second accessor for the path that will be handed to Win32, as the request crate's `NR-3.4` does); - an interior NUL is refused. A `feat!` of a published crate. `REVIEW-1` below is re-read against the - new contract when this is done, and may close, since the contract no longer caps ordinary paths or - rewrites any path to a `\\?\` form. - - > **-> CROSS-COMPONENT PREREQUISITE:** waits on `NR-3.3` to `NR-3.6` in - > `crates/windows-namespace-request-sys` ([CHECKLIST.md](../windows-namespace-request-sys/CHECKLIST.md)), - > released. This item is done when the older copy is deleted, and the request crate's `NR-3.8` and - > the workspace's `M26+.3` are done with it. - - **Next:** `REVIEW-1` below is re-read against the new contract. After that nothing further in this - crate; the order across the crates is at the top of - [durable-ioring's CHECKLIST.md](../durable-ioring/CHECKLIST.md#where-the-open-surface-work-goes-next-read-this-first-when-picking-the-work-back-up). - -## ERR -- One error code type across the crates - -- [x] **ERR-1** -- `Win32Error` is `win_error_codes::Win32Error`, re-exported under the same name. -> [completed 2026-10-15](COMPLETED-CHECKLIST.md#err-1) - -## M-inf -- Horizon (ungated) - -- [ ] **REVIEW-1** -- Review the request path contract against a traversal layer, before one is built. - **Re-read after [PATH-1](#path----one-path-contract-across-the-namespace-crates):** that item - replaces the contract below (no `MAX_PATH` cap, no `\\?\` rewriting, a graft onto a captured root), - so most of its questions are about a contract that no longer exists, and this review may close - with a note of which still stand. - **This is a review item: it schedules no change.** Its output is an answer -- possibly "no change - needed" -- and any work it turns out to imply becomes its own item afterwards. Raised from the - `windows-file-watcher` side while recording - [D-85](../windows-file-watcher/DESIGN-NOTES.md#d-85) and the shared principle in - [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction); - flagged here because that principle's *building* half lives in this crate and nothing else would - bring it up. - **Context, not conclusions.** [lib.rs](src/lib.rs) states that recursive traversal belongs in a - separate layer composing this one, so that layer does not exist yet and the contract has never been - exercised by the consumer it was designed for. Facts noted while reading, each of which the review - should confirm rather than take on trust: - - [D-7](DESIGN-NOTES.md#d-7) accepts an *ordinary* path only if both the input and the - `GetFullPathNameW`-resolved form fit `MAX_PATH`, deliberately, so acceptance does not depend on - the host executable's `longPathAware` manifest. - - Win32 has no relative open, so descending means appending to an absolute path -- from a path - already at or near that cap. - - The contract's stated remedy for an over-limit path is "supply a fully qualified `\\?\` path", - which for a traversal layer means converting partway down rather than at the caller's boundary. - - `EnumerationRequest::path()` is `pub` and returns the stored resolved form, so a traversal layer - has access to a Win32-normalised base rather than only the caller's original string. - **Questions the review should answer.** Does D-7's contract still hold once descent is a real - consumer, or does the `MAX_PATH` cap on ordinary paths become reachable in normal use? If a - traversal layer must move into `\\?\` form mid-descent, is that conversion specified anywhere, and - is it the same conversion for every namespace the crate accepts -- a UNC path needs `\\?\UNC\...` - rather than a bare prefix, and a `\\.\` device form does not take one at all? Does - manifest-independence still mean what it should for requests a traversal layer *derives* rather than - receives? And is `path()`'s resolved form the intended base for that, stated as such, rather than - something a traversal layer is left to infer? - **The trap worth naming.** Converting *before* Win32 has normalised under the caller's own parsing - mode silently reinterprets the path; converting *after* preserves it. Both crates reached that rule - independently, which is why it is recorded once at the workspace level -- but reaching for the - prefix at the wrong moment is exactly what a traversal layer under length pressure would do. +itself. Status is tracked in [PLANS.md](PLANS.md). Nothing is pending; `REVIEW-1` is parked in +[PARKED.md](PARKED.md). diff --git a/crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md b/crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md index ae8d882aa..b56e016e7 100644 --- a/crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-file-enumeration-sys/COMPLETED-CHECKLIST.md @@ -24,3 +24,163 @@ caller was this crate's own `last`, which reads the thread's last error and so a the shared type has `last` of its own; nothing in the workspace outside the crate used the function. No sabotage manifest exists for this crate, so the tests carry the change: a code of 2^31 or more is converted and read back, and an error with no code is `None`. + +## Moved 2026-10-18 20:49:59 -04:00 -- PATH: one path contract across the namespace crates + +### PATH-1 -- This crate takes the request crate's path contract, `AnchoredPath`, and its older copy of the preparation is deleted. *(completed 2026-10-18 20:49:59 -04:00)* + +The item, as it was written: consume the namespace request crate's path contract, and delete this crate's older copy +of the preparation. It was the merge-or-delete decision of the workspace checklist's `M26+.3` +([CHECKLIST-thread-ambient.md](../../CHECKLIST-thread-ambient.md)) and of +[D-9](../windows-namespace-request-sys/DESIGN-NOTES.md#d-9) in the request crate, made on 2026-10-15 at the engineer's +request as: this crate depends on `windows-namespace-request-sys` and its `path.rs` preparation is deleted. What +it named as changing: `D-7`'s `MAX_PATH` cap on an ordinary path goes; a request captures the current directory when +it is built and grafts a clearly safe child onto it; `EnumerationRequest::path()` says what it returns, the path as +given, with a second accessor for the path handed to Win32; an interior NUL is refused. A `feat!` of a published +crate. + +What was done. The request crate's `AnchoredPath` was made public to be consumed (its +[D-30](../windows-namespace-request-sys/DESIGN-NOTES.md#d-30)). A request holds one; `path()` is the path as given and +`win32_path()`, new, is what the worker opens. `RequestFailure` loses `EmptyPath`, `PathTooLong`, `NotFullyQualified` +and `PathResolution` and gains `PathCapture`, a failure to read the current directory, with the code when Windows +gave one; so an empty path, a long one and an unqualified `\\?\` path reach the open, and its error is the terminal +`DirectoryOpen`. `src/path.rs` and its tests are deleted. Recorded as [D-22](DESIGN-NOTES.md#d-22), with the rationale +in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). The crate's first [sabotage.json](sabotage.json) covers the binding: +the worker opening the path as given is caught only by a child-process test that moves the current directory after +the request is built. + +## Moved 2026-10-19 20:10:23 -04:00 -- SAFE-1: the metadata test hands the request crate a SharedHandle + +### SAFE-1 -- The metadata test builds its `QueryFileIdInfo` from a `SharedHandle`, not a `CapturedHandle`. *(completed 2026-10-19 20:10:23 -04:00)* + +The item as written: "**The metadata test takes a `SharedHandle`.** +`a_required_identity_is_what_the_request_crate_reads_from_the_file_itself` in +[metadata.rs](tests/integration/metadata.rs) builds its `QueryFileIdInfo` from a `SharedHandle` rather than a +`CapturedHandle`." Prerequisite: the request crate's `NR-6.1`. + +What was done. `QueryFileIdInfo::new` takes `impl Into` since `NR-6.1`, and `SharedHandle` is +`From`, so the test hands it the opened file: the file is the handle's one owner, and nothing is +captured or duplicated. No dependency was added. That was this crate's last use of `CapturedHandle`. Evidence, in +[sabotage.json](sabotage.json): reading the identity from a different file than the one enumerated is caught by the +test, and a control opening the same file another way survives. + +## Moved 2026-10-20 15:42:27 -04:00 -- SAFE-2: the directory's calls are the request crate's entries + +### SAFE-2 -- The directory's open and queries are the request crate's entries, the buffer an `AlignedBuf` and the handle a `SharedHandle`. *(completed 2026-10-20 15:42:27 -04:00)* + +The item as written, with the paragraph that tied it to the workspace's `M21.3` and `M21.4`: + +**The directory's calls through the request crate.** The engine holds the directory as a + `SharedHandle`. [native.rs](src/native.rs)'s four calls become entries: the open is `OpenFile` built from the + request's `AnchoredPath` and performed under the captured token; the directory check is `QueryFileBasicInfo`; + the volume serial is `QueryFileIdInfo`, on a clone of the directory's handle; a refill is `QueryFileInformation` + into the crate's buffer, which becomes an `io_buffers::AlignedBuf` in place of `NativeBuffer`. The typed + outcomes `native.rs` derives (the end of a directory, `UnsupportedExtendedDirectoryInfo`, and the rest) are kept + and read from the entries' codes. Tests: the existing suite unchanged; an enumeration makes no handle beyond its + open. + + This discharges the workspace's [M21.3 and M21.4](../../CHECKLIST.md#m21----reconcile-with-the-shipped-namespace-plane-work), + which are checked off in the same commit, and their constraints are this item's acceptance conditions: the open + keeps its access (`FILE_LIST_DIRECTORY`), share mode and flags (`FILE_FLAG_BACKUP_SEMANTICS`); the directory's + handle stays unassociated; every code reaches the crate unaltered; the open's code is captured before the + impersonation context is restored; and the D-15 Globazog acceptance gate still passes. + + > **CROSS-COMPONENT PREREQUISITE:** the request crate's release carrying `NR-6.1` to `NR-6.5` + > ([COMPLETED-CHECKLIST.md](../windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-65-into)). + +What was done. [native.rs](src/native.rs) makes no call of its own: the open is `OpenFile` built from the request's +`AnchoredPath` and performed under the captured token, with listing access, every share, an existing directory and +backup semantics; the directory check is `QueryFileBasicInfo`; the volume serial is `QueryFileIdInfo`; and a refill is +`QueryFileInformation`, the restart class for the first and the continuing class for the rest, each on a clone of the +directory's `SharedHandle`. The staging buffer is an `io_buffers::AlignedBuf` made at 8, which replaced `NativeBuffer` +and its `buffer.rs`; the engine holds the directory as a `SharedHandle`. The typed outcomes are kept and read from the +entries' codes. `perform_into` now returns the request crate's `PerformIntoError`, whose refusal ahead of the call +cannot apply to the extended-directory classes, so that arm reports a failure with no code. Two dependencies were +added, `io-buffers` and `win-shared-os-owned-handle`; no public item changed. Recorded as +[D-24](DESIGN-NOTES.md#d-24), with the rationale in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Tests: the existing +suites unchanged apart from the buffer's constructor, including the Globazog acceptance gate; new unit tests of the +open's parameters, the refill's class and handle, a held directory not stopping a rename, and the engine and a whole +read leaving the directory with no other holder. Evidence, in [sabotage.json](sabotage.json): nine entries, the open's +delete sharing, access, backup semantics and overlapped flag, the first refill's class, the directory check, and the +volume serial, all caught, one declared survivor for the arm that cannot be reached, and a control. + +## Moved 2026-10-20 16:05:00 -04:00 -- SAFE-3: the doorbell is a shared event + +### SAFE-3 -- The doorbell is a `win-sync-sys` `Event`, and a `ThreadpoolWait` is handed a waitable handle that shares it, not a duplicate. *(completed 2026-10-20 16:05:00 -04:00)* + +The item as written: + +**The doorbell is a shared event.** The completion ring holds the doorbell as a `win-sync-sys` + `Event`, set and reset through it, and hands a `ThreadpoolWait` a waitable handle that shares the event rather + than a duplicate; `completion_ring.rs`'s `DuplicateHandle`, `SetEvent` and `ResetEvent` are gone. The unit tests' + raw waits move to `win-sync-sys`'s `wait_for_single_object`. + + > **CROSS-COMPONENT PREREQUISITE:** [WS-2.2](../win-sync-sys/COMPLETED-CHECKLIST.md#ws-22) and + > [M-T15.1](../windows-threadpool-sys/CHECKLIST.md). + +What was done. [completion_ring.rs](src/completion_ring.rs) holds the doorbell as a `win_sync_sys::Event` and sets and +resets it through that type; it makes no `SetEvent`, `ResetEvent` or `DuplicateHandle` call and its `duplicate` +helper is gone. `Receiver::doorbell_owned` is replaced by `Receiver::doorbell_waitable`, a +`windows_threadpool_sys::wait::WaitableHandle` made from a clone of the ring's event, so the wait shares the ring's +handle; a client cannot set or reset it without `unsafe`. `Receiver::doorbell` is unchanged. The removal is a breaking +change to the public surface. `win-sync-sys` was added as a dependency. The unit tests' raw waits and the model's +check use `wait_for_single_object`, and the model no longer duplicates the handle. Recorded as +[D-25](DESIGN-NOTES.md#d-25), with the rationale in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Tests: the owned-handle +test is replaced by tests that the target's raw handle is the ring's and sees what the ring signals, that the ring and +the target are each other's other holder, and that the event outlives the ring; and a real `ThreadpoolWait` armed on +the target is woken by an entry, drains, and is woken again. Evidence, in [sabotage.json](sabotage.json): four +entries, the target made a second event, set and reset swapped, the initial state ignored, and the refresh removed, all +caught, and a control. The engine tests' raw byte write is not a wait and remains for `SAFE-5`. + +## Moved 2026-10-20 16:55:17 -04:00 -- SAFE-4: the name comparison is win-text-sys's + +### SAFE-4 -- Case-insensitive name matching is `win-text-sys`'s `compare_string_ordinal`. *(completed 2026-10-20 16:55:17 -04:00)* + +The item as written: + +**Case-insensitive name matching through `win-text-sys`.** [pattern.rs](src/pattern.rs)'s + `CompareStringOrdinal` call is that crate's wrapper. + + > **CROSS-COMPONENT PREREQUISITE:** [M43.1](../../COMPLETED-CHECKLIST.md#m431) (the crate is created). + +What was done. [pattern.rs](src/pattern.rs) compares an equal-length pair of runs through `compare_string_ordinal` with +`Case::Insensitive`, and reads the result in `equal_from`: equal is a match, a failure of the call is not, and a run too +long for the call's count is compared exactly. The empty-run shortcut is gone, since the wrapper always passes the +count. `win-text-sys` is a dependency, and the `Win32_Globalization` and `Win32_System_Threading` features of +`windows-sys` are removed, the second having been unused since `SAFE-3`. Recorded as [D-26](DESIGN-NOTES.md#d-26), with +the rationale in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Tests: the existing pattern tests unchanged, and new tests +of the empty run and of each result, including the too-long arm that the earlier code could only argue. Evidence, in +[sabotage.json](sabotage.json): four entries, all caught, and a control. + +## Moved 2026-10-20 17:03:19 -04:00 -- SAFE-5: the crate forbids unsafe code + +### SAFE-5 -- `#![forbid(unsafe_code)]` is on the crate root, and D-23 is current. *(completed 2026-10-20 17:03:19 -04:00)* + +The item as written: + +**`#![forbid(unsafe_code)]`.** On the crate root, so the build refuses any `unsafe` in the + crate or its unit tests. Whatever `unsafe` remains after `SAFE-2` to `SAFE-4` is found by the build, and each + is moved behind a wrapper or brought to the engineer. D-23 is then current rather than a target. + +What was done. [lib.rs](src/lib.rs) carries `#![forbid(unsafe_code)]` in place of `forbid(unsafe_op_in_unsafe_fn)`, +which a crate with no `unsafe` has no use for, and its safety section says the crate holds none. The one `unsafe` the +build found was in a test, a four-byte write that corrupted a record's `NextEntryOffset`; it is now a write through the +engine buffer's `as_mut_slice`, so nothing was moved behind a wrapper or brought to the engineer. [D-23](DESIGN-NOTES.md#d-23) +is stated as current, and the earlier steps' "scheduled work" lines say there is none. Recorded in +[DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Evidence, in [sabotage.json](sabotage.json): an `unsafe` block added to the +library is refused by the build, and a control adding the same item without `unsafe` survives. With this +the `SAFE` group is complete, and its stubs left the checklist with it. + +## Moved 2026-10-21 21:46:17 -04:00 -- windows-file-enumeration-sys: milestones PATH and ERR are complete + +### PATH -- One path contract across the namespace crates + + **Next:** `REVIEW-1`, in [PARKED.md](PARKED.md), is re-read against the new contract. After that nothing further in this + crate; the order across the crates is at the top of + [durable-ioring's CHECKLIST.md](../durable-ioring/COMPLETED-CHECKLIST.md#open-surface-order). + +Items, each archived above under its own heading: [PATH-1](#path-1). + +### ERR -- One error code type across the crates + +Items, each archived above under its own heading: [ERR-1](#err-1). diff --git a/crates/windows-file-enumeration-sys/COMPLETED-PLANS.md b/crates/windows-file-enumeration-sys/COMPLETED-PLANS.md index 0c16a1565..2a48f0622 100644 --- a/crates/windows-file-enumeration-sys/COMPLETED-PLANS.md +++ b/crates/windows-file-enumeration-sys/COMPLETED-PLANS.md @@ -6,3 +6,4 @@ tracked in [PLANS.md](PLANS.md). | Path to CHECKLIST.md | Completion Date | Brief description | Design Notes | |---|---|---|---| | [../../CHECKLIST.md](../../CHECKLIST.md) | 2026-08-27 | FE-1 through FE-16 scaffolded, specified, implemented, verified, documented, and prepared the publishable flat asynchronous directory-enumeration layer: the public contract and two-ring session (M5), the native `GetFileInformationByHandleEx` engine with cancellation and teardown (M6), and verification through publication (M7) -- a real-Windows integration suite, a Globazog adapter demonstration discharging the D-15 acceptance gate, complete crate documentation, and publication validation (blocked only on this branch merging to `main` so release-please can ship `windows-impersonation-token-sys` first). | [DESIGN-NOTES.md](DESIGN-NOTES.md); [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) | +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-18 | The crate's own work: `PATH-1`, which takes the request crate's `AnchoredPath` path contract and deletes the older copy of the preparation, and `ERR-1`, which makes `Win32Error` `win_error_codes::Win32Error`. `REVIEW-1` is parked in [PARKED.md](PARKED.md). | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-22) | diff --git a/crates/windows-file-enumeration-sys/Cargo.toml b/crates/windows-file-enumeration-sys/Cargo.toml index 6576950bd..36a3b2e4a 100644 --- a/crates/windows-file-enumeration-sys/Cargo.toml +++ b/crates/windows-file-enumeration-sys/Cargo.toml @@ -36,6 +36,27 @@ windows-threadpool-sys = { version = "0.3.0", path = "../windows-threadpool-sys" # Paths and returned names remain native-width WTF-16 without lossy conversion. wtf-string = { version = "0.1.0", path = "../wtf-string" } +# The path contract (PATH-1): a path kept as given, and a relative one fixed to the current +# directory when the request is built, so that it means the same directory on whichever worker +# opens it. Taken from the crate that owns it, so this crate keeps no second copy (the request +# crate's D-30), and below it in the layering, which is the direction D-9 recorded. +windows-namespace-request-sys = { version = "0.5.0", path = "../windows-namespace-request-sys" } + +# The one buffer a refill reads into, made at 8 bytes because the directory-information records hold +# `i64` fields. The request crate fills it (SAFE-2), so no `unsafe` here reads or writes it. +io-buffers = { version = "0.1.0", path = "../io-buffers" } + +# The directory is held as one shared handle, and each query the request crate performs on it holds +# a clone of that handle rather than a duplicate (SAFE-2). +win-shared-os-owned-handle = { version = "0.1.0", path = "../win-shared-os-owned-handle" } + +# The completion ring's doorbell is one of its events, set and reset through it and shared with the wait a +# client arms rather than duplicated for it (SAFE-3). +win-sync-sys = { version = "0.0.2", path = "../win-sync-sys" } + +# The case-insensitive name comparison, `CompareStringOrdinal` over counted slices (SAFE-4). +win-text-sys = { version = "0.0.1", path = "../win-text-sys" } + # `Win32Error`, re-exported under the name this crate has always used. Its `io::Error` form keeps a # code of 2^31 or more, where this crate's own earlier type panicked on one (WE-D-1). win-error-codes = { version = "0.0.2", path = "../win-error-codes" } @@ -45,7 +66,5 @@ version = "0.61.2" default-features = false features = [ "Win32_Foundation", - "Win32_Globalization", "Win32_Storage_FileSystem", - "Win32_System_Threading", ] diff --git a/crates/windows-file-enumeration-sys/DESIGN-NOTES.md b/crates/windows-file-enumeration-sys/DESIGN-NOTES.md index b81bf586a..92a710899 100644 --- a/crates/windows-file-enumeration-sys/DESIGN-NOTES.md +++ b/crates/windows-file-enumeration-sys/DESIGN-NOTES.md @@ -21,7 +21,7 @@ making submission, delivery, cancellation, and resource bounds explicit. | D-4 | **Opening a directory uses the submitter's explicitly captured `ImpersonationToken`.** Ordinary begin captures before publishing its SQ message; an explicit-token form lets traversal reuse one captured context. Later refills use the already-open handle and do not impersonate. | | D-5 | **Native values remain native where they express the contract.** Paths and names use `wtf-string` for native-width WTF-16 storage, and Microsoft `windows-sys` value types remain public where no additional crate-owned invariant is required. | | D-6 | **Superseded by D-7 through D-15.** FE-2 closed the v1 public-contract questions that this scaffold deliberately left open. | -| D-7 | **A request owns one NUL-free WTF-16 path snapshot with explicit long-path behavior.** Ordinary path forms are resolved when the request is built and must fit the ordinary Win32 limit; long paths must arrive as fully qualified `\\?\` inputs and remain verbatim. This is the *building* half of a workspace-wide principle -- a layer that constructs paths demands a form it can build on, while one that never constructs any passes the caller's through verbatim (`windows-file-watcher`); see [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction). | +| D-7 | **Superseded by [D-22](#d-22).** **A request owns one NUL-free WTF-16 path snapshot with explicit long-path behavior.** Ordinary path forms are resolved when the request is built and must fit the ordinary Win32 limit; long paths must arrive as fully qualified `\\?\` inputs and remain verbatim. This is the *building* half of a workspace-wide principle -- a layer that constructs paths demands a form it can build on, while one that never constructs any passes the caller's through verbatim (`windows-file-watcher`); see [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction). | | D-8 | **The CQ has only entry and terminal records, and preserves per-request native order without promising a stable sort.** A failed terminal owns its error; dot entries are never delivered. | | D-9 | **Every inline `FILE_ID_EXTD_DIR_INFO` field with defined consumer meaning is always returned in native units.** Undefined `FileIndex` is omitted; only the separately queried volume serial is selected. | | D-10 | **File identity distinguishes an always-present 128-bit file ID from optional volume qualification.** Requests select omitted, best-effort, or required volume qualification without per-entry opens. | @@ -36,6 +36,11 @@ making submission, delivery, cancellation, and resource bounds explicit. | D-19 | **The native buffer is allocated at admission, fallibly, with an 8-byte-aligned base.** A request stays a cheap, clonable, comparable description; the buffer belongs to the enumeration it serves. | | D-20 | **A quantum's progress is bounded by both a record count and an elapsed-time budget; whichever is spent first ends it.** A quantum always examines at least one record regardless of either bound. A quantum parked for completion-ring room remembers that its retained record already needs delivery, so resuming re-checks room with one cheap call before reparsing it. | | D-21 | **Reporting `Parked` re-checks completion-ring room under the same lock `resume_parked` reads.** A quantum decides `Parked` with no lock held, so a receiver can drain the very record it was waiting on, and call `resume_parked`, before the worker ever records that it was waiting -- a missed wakeup that strands the enumeration forever. Re-checking room while reporting closes the window regardless of which side runs last. | +| D-22 | **A request's path is `windows-namespace-request-sys`'s `AnchoredPath`: kept as given, a relative one grafted onto the current directory when the request is built, and only an interior NUL refused.** The engineer, 2026-10-15, as the merge-or-delete decision of the workspace's `M26+.3` and the request crate's D-9: this crate consumes that crate's path contract and deletes its own copy (`PATH-1`). `path()` is the path as given and `win32_path()` is what the worker opens. The `MAX_PATH` cap, the `\\?\` qualification check, the empty-path refusal and the `GetFullPathNameW` resolution are gone, so those paths reach the open and its error is the terminal `DirectoryOpen`; a failure to read the current directory is `RequestFailure::PathCapture`. This crate enumerates one directory and builds no path, so it is no longer the example of a path-building layer in [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction); that contract is for a traversal layer, which `REVIEW-1` reviews. See [Request path contract](#request-path-contract). | +| D-23 | **The crate reaches Windows only through safe wrappers in other crates, and its root carries `#![forbid(unsafe_code)]`.** Current as of `SAFE-5`, 2026-10-20; the three steps before it moved the calls ([D-24](#d-24), [D-25](#d-25), [D-26](#d-26)). The engineer, 2026-10-18, answering "yes" to whether that is the crate's target. The directory is held as a `SharedHandle` and no handle is duplicated, by the same day's ruling in the request crate's [D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32) ("I want us to move forward as much as possible towards better interfaces and this counts"). Each call has its wrapper's home: the directory's open and queries in `windows-namespace-request-sys`, the doorbell event in `win-sync-sys` with a shared thread-pool wait in `windows-threadpool-sys`, and the ordinal comparison in the new `win-text-sys`. The work was `SAFE-1` to `SAFE-5`, archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). | +| D-24 | **The directory's open and queries are the request crate's entries, and a refill is a `QueryFileInformation` performed on a clone of the shared handle into this crate's `AlignedBuf`.** Built as `SAFE-2`, the step of [D-23](#d-23) that moves the directory's four calls. The open is `OpenFile` built from the request's `AnchoredPath` ([D-22](#d-22)), performed under the captured token, with listing access, every share, an existing directory and backup semantics and no other flag; the directory check is `QueryFileBasicInfo`; the volume serial is `QueryFileIdInfo`; and a refill is `QueryFileInformation`, the restart class for the first and the continuing class for every later one. **No handle is duplicated:** the directory is a `SharedHandle`, and each query holds a clone of it, so an entry holds the very handle it was given and a finished enumeration leaves its engine the only holder. **The buffer is an `io_buffers::AlignedBuf` made at 8**, which replaced this crate's own `Vec` buffer: the same fallible allocation at admission ([D-19](#d-19)) and the same 8-byte base, now stated by the type and filled by safe code. **A request per refill, not a held one.** Each refill builds its query over a clone of the handle, one atomic increment next to a system call per batch; holding two prebuilt queries would be engine state that buys nothing a clone does not. **The open's code is taken before the token is restored**, because the request crate reads the thread's last error in the statement after the call and the guard restores afterwards; the crate no longer arranges that itself. **One arm cannot be reached.** `perform_into` also refuses a buffer ahead of the call, as the request crate's D-34 says, but only for the classes whose structure page requires an alignment, and neither extended-directory class is one; this buffer is 8-aligned besides. The match arm for that refusal reports a failure with code zero, which says Windows produced none, as `engine::advance` does for its own unreachable state, and the sabotage that makes it report exhaustion is a declared survivor. The refusal is the request crate's, so a consumer's buffer that did violate a structure page would be refused there. **No public item changed:** the request, the completion record and the error types are as they were, and the crate's own error codes are the request crate's `Win32Error`, the same type. Verified by the unit and integration suites unchanged apart from the buffer's constructor, the Globazog acceptance gate ([D-15](#d-15)), and new tests of the open's parameters, the refill's class and handle, and the engine holding the only share. **Scheduled work:** none; `SAFE-3` to `SAFE-5` followed and are built. | +| D-25 | **The doorbell is a `win-sync-sys` `Event`, and a client's wait shares its handle through `Receiver::doorbell_waitable`, which replaces `doorbell_owned`.** Built as `SAFE-3`, the step of [D-23](#d-23) that moves the doorbell. The completion ring holds an `Event` made manual-reset on first use, under the ring lock and signalled if the ring already has something to take, and `refresh_doorbell` sets or resets it under the ring lock at the end of every mutation, as before; the ring makes no `SetEvent`, `ResetEvent` or `DuplicateHandle` call. **A wait target, not the event.** `doorbell_waitable` returns a `windows_threadpool_sys::wait::WaitableHandle` made from a clone of the ring's `Event`, so the wait the client arms and the ring hold one handle, the handle closes when the last of them drops, and nothing is duplicated. The target gives a client no way to set or reset the event without `unsafe`, so the doorbell's one invariant (signalled exactly while there is something to take) stays the ring's. **`Receiver::doorbell` is unchanged,** a borrowed view of the same handle. **A breaking change to the public surface:** `Receiver::doorbell_owned` returned a duplicate the caller owned, which [D-23](#d-23) rules legacy, and is removed; a caller that armed a `ThreadpoolWait` on it passes the target `doorbell_waitable` returns. **A refresh that cannot fail is not checked in a release build.** The event is made here with full access and is open while the ring is, so neither call fails; the result is `debug_assert`ed, where the old code ignored it. Verified by unit tests that the target's raw handle is the ring's and sees what the ring signals, that the target and the ring are each other's other holder, that the event outlives the ring while a target holds it, and by a real `ThreadpoolWait` armed on the target and woken by an entry; the sabotage is in [sabotage.json](sabotage.json). **Scheduled work:** none; `SAFE-4` and `SAFE-5` followed and are built. | +| D-26 | **The case-insensitive name comparison is `win-text-sys`'s `compare_string_ordinal`, and a failure of the call is not a match.** Built as `SAFE-4`, the step of [D-23](#d-23) that moves the comparison. `CaseSensitivity::Insensitive` compares two equal-length runs through `compare_string_ordinal` with `Case::Insensitive`, which is `CompareStringOrdinal` over counted slices, so the meaning is as it was: Windows' non-linguistic uppercase table, an interior NUL compared as content, and an unpaired surrogate a unit. The crate makes no `windows-sys` call of its own for it, and its `Win32_Globalization` and `Win32_System_Threading` features are gone with the call and the doorbell ([D-25](#d-25)). **What a result says is one function,** `equal_from`: an `Equal` is a match, any other ordering is not, a failure the call reports is not a match, and a run too long for the call's `int` count, which no name is, is compared exactly and not truncated to a prefix, as it was before. That last arm needs a slice over 2^31 units, so it is tested through the result and not through a slice that long, which the earlier code declared a survivor; it is now caught. **The empty-run shortcut is gone:** it existed because a zero length might be confused with the call's NUL-terminated convention, and the wrapper always passes the count. No public item changed. Verified by the pattern unit tests unchanged, new tests of the empty run and of each result, and sabotage in [sabotage.json](sabotage.json). **Scheduled work:** none; `SAFE-5` followed and is built. | ## Control authority and worker lifetime @@ -116,6 +121,12 @@ a wakeup can be lost (D-21). ## Request path contract +**Superseded by [D-22](#d-22).** What follows is the contract before `PATH-1`, kept as the record of +what `D-7` decided. Since `PATH-1` a request holds the request crate's `AnchoredPath`: the path as +given, grafted onto the current directory only when it is relative and clearly safe to graft (that +crate's D-19), and an interior NUL is the one refusal; everything else this section describes as +refused or resolved reaches the open. + `EnumerationRequest` owns its path as `Wtf16String`; the caller's source value need not outlive construction or submission. Construction rejects an empty path and any interior NUL synchronously because passing either to a NUL-terminated @@ -288,16 +299,18 @@ out-of-bounds name, and negative size. A late query or parse failure does not retract entries already queued: those entries are followed by exactly one `Failed` terminal. -On an extended-directory query made with a live handle this crate opened *and -verified to be a directory*, a valid information class, a non-null -8-byte-aligned buffer base, and an +On an extended-directory query made with a handle this crate opened *and +verified to be a directory*, one of the two extended-directory classes, a +non-null 8-byte-aligned buffer base, and an effective capacity of at least 1 KiB that is both an 8-byte multiple and representable as `u32`, `ERROR_INVALID_FUNCTION`, `ERROR_NOT_SUPPORTED`, and `ERROR_INVALID_PARAMETER` are classified as -`UnsupportedExtendedDirectoryInfo`. Every one of those preconditions is -`debug_assert`ed immediately before the call whose failure this classifies, -so a future regression in handle, class, or buffer handling is caught as this -crate's own bug rather than silently reported as a filesystem incapability. +`UnsupportedExtendedDirectoryInfo`. The handle is held as a shared handle and +the class is named by the request crate, so neither can be wrong by +construction ([D-24](#d-24)); the buffer's preconditions are `debug_assert`ed +immediately before the call whose failure this classifies, so a future +regression in buffer handling is caught as this crate's own bug rather than +silently reported as a filesystem incapability. The classification is crate-owned and the raw code remains available. The crate does not silently switch to find-first/ find-next because that would remove change time, allocation size, extended- @@ -363,6 +376,6 @@ because this environment cannot manufacture one. The crate is Windows-only, independently versioned, and published to crates.io. It depends on `windows-impersonation-token-sys`, `windows-threadpool-sys`, and `wtf-string` by both path and version. Its direct `windows-sys` surface enables -Foundation and Storage FileSystem for enumeration, Globalization for -`CompareStringOrdinal`, and System Threading for the completion-ring event -doorbell. Release automation is registered at the workspace level. +Foundation and Storage FileSystem for enumeration. The case-insensitive name +comparison and the completion-ring doorbell are the `win-text-sys` and +`win-sync-sys` crates' ([D-25](#d-25), [D-26](#d-26)). Release automation is registered at the workspace level. diff --git a/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md b/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md index 28d9c9d1f..ca84014d4 100644 --- a/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md +++ b/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md @@ -37,13 +37,11 @@ Windows contracts. ## Why these direct windows-sys features are sufficient `Win32_Foundation` provides native handles, errors, and metadata value types. -`Win32_Globalization` provides `CompareStringOrdinal` for crate-owned -non-linguistic name comparison. `Win32_Storage_FileSystem` provides directory open/enumeration APIs and record -layouts. `Win32_System_Threading` provides signaling and resetting the lazily -created manual-reset CQ event. Event creation itself comes from the safe -`WaitableHandle` constructor in `windows-threadpool-sys`, so this crate does not -need direct Security bindings. The enumeration API is not overlapped, so direct +layouts. The lazily created manual-reset CQ event is a `win-sync-sys` `Event` +(D-25), and the non-linguistic name comparison is `win-text-sys`'s (D-26), so +this crate needs neither Threading nor Globalization bindings and no direct +Security bindings. The enumeration API is not overlapped, so direct System IO bindings would claim a capability this design intentionally excludes. ## Why paths are snapshotted before submission @@ -376,3 +374,123 @@ first was reverted once the actual defect was fixed: the original 30s ceiling was never the problem, and inflating it for every integration test would only have made a future genuine hang in any other test take four times as long to be reported as a failure. + +## Why the path contract is taken from the request crate, and what that gave up (D-22) + +`D-7` had this crate resolve an ordinary path with `GetFullPathNameW` when the request was built, cap it at +`MAX_PATH` so that acceptance would not depend on the host executable's long-path manifest, and validate a +`\\?\` path's qualification. The request crate was given a copy of that preparation when it was made, rather +than a dependency, because this crate was released and that one was not (its D-9). The copy was then replaced +there by a graft onto a captured root (its D-19), and the engineer decided on 2026-10-15 that this crate takes +that contract and deletes its own (`PATH-1`, the workspace's `M26+.3`). + +What was given up is the manifest independence `D-7` argued for: an unprefixed path past `MAX_PATH` now opens +or fails as the host's manifest and the machine's policy decide. `D-7` argued it for a layer that builds paths. +This crate enumerates one directory and builds none; the layer that would build paths, a traversal composed on +this one, does not exist, and whether it needs that contract is `REVIEW-1`'s question. The refusals that went +were checks ahead of `CreateFileW` that its documentation does not make conditions of the call, which [the +workspace rule](../../DESIGN-NOTES.md#refusals-rest-on-the-documented-contract) does not keep. + +The request crate's type had to be public for this (its D-30). Holding that crate's `OpenFile` in the request +instead was not taken: it is not `Clone` or `Eq`, which `EnumerationRequest` is, and the worker's open, under +the captured token, is this crate's. + +## Why the crate's target is `forbid(unsafe_code)` (D-23) + +**Asked on 2026-10-18**, after `NR-3.10` left this crate reading a volume serial with its own `unsafe` call +because the request crate's typed entry would have duplicated the directory handle. The engineer ruled that +duplication legacy and asked what moving this crate onto shared handles and the safe `win-*` wrappers would take. +The census then was ten `unsafe` blocks outside tests: five in the native calls, one in the buffer, three in the +completion ring's doorbell, one in the case-insensitive comparison. Each has a wrapper that exists or is planned +(D-23), so the attribute can be the end state rather than an aspiration, and the build is what checks it. + +**What was not chosen.** Keeping the native calls private and only removing the duplication would have left this +crate a second implementation of calls the request crate already makes faithfully, with its own reading of their +codes; the typed outcomes this crate derives from those codes are kept, and only the calls move. + +## Why a refill is a request on a clone, and how the move went (D-24) + +**Built 2026-10-20** as `SAFE-2`. Of the census D-23 gives, the native calls and the buffer are gone: five `unsafe` +blocks in `native.rs` and the one in the buffer's byte view. The completion ring's three and the comparison's one +remain, for `SAFE-3` and `SAFE-4`. + +**A request per refill was chosen over holding the two queries.** The engine could build the restart and continuing +`QueryFileInformation` once, when the directory opens, and perform them for the enumeration's life. That moves a +clone of the handle from each refill to the open, and adds two fields of engine state to save one atomic increment per +batch beside a system call. A request built where the query is made also keeps the class and the handle visible at the +one site that uses them, which is what the class and handle test reads. + +**The buffer's replacement was not a new type.** This crate's own buffer was a `Vec` with a fallible reservation +and a byte view that needed an `unsafe` slice. `io_buffers::AlignedBuf` is the type the request crate's +`perform_into` takes, it states its alignment as a property of the type and reports an allocation failure instead of +aborting, and its slice is safe. Considered and not taken: **keeping `NativeBuffer` and copying into an `AlignedBuf` +per call**, which would have added a copy of every batch to keep a type whose only remaining job was the one the +other type does. + +**The arm that cannot be reached.** `perform_into` returns a refusal ahead of the call as well as the call's code. +Considered for the refusal: **a new `EnumerationError` variant**, a public addition for a case no caller can cause; +**a panic**, which in a thread-pool callback loses the enumeration's terminal record; and **a failure with code zero**, +which is what `engine::advance` already does for its own unreachable state and which says Windows produced no code. +The third was taken. That it cannot be reached is an argument from the request crate's table of classes and this +crate's buffer, not a measurement, so the sabotage that would make it report exhaustion is declared a survivor with +what would reach it: a refusal added to the extended-directory classes. + +**What the request crate's entries changed about the open's error.** The old open read the thread's last error itself, +before the guard restored the token, and said so in a comment. The request crate's `perform` reads it in the statement +after the call, so the same ordering now holds without this crate arranging it, and the open's code reaches the +enumeration unaltered for the missing directory and the file named as a directory, which the existing tests pin. + +## Why the doorbell's wait target shares the ring's handle, and what it gave up (D-25) + +**Built 2026-10-20** as `SAFE-3`. Of the census D-23 gives, the completion ring's three `unsafe` blocks are gone: the +`SetEvent` and `ResetEvent` calls and the duplicate. The comparison's one remains, for `SAFE-4`. + +**A wait target was chosen over handing out the event.** The shared handle could have been a clone of the ring's +`Event`, which is also a share. That clone can be set and reset in safe code, so a client could break the one +invariant the doorbell has, that it is signalled exactly while there is something to take. A `WaitableHandle` made +from the clone is the same handle, and it exposes only a borrowed view and the handle's return, so what a client can +do with it is what a `ThreadpoolWait` needs. Considered and not taken: **a `WaitOnlyEvent`**, which needs a +duplicate and so is the thing the item removes; and **keeping `doorbell_owned` beside the new method**, which would +leave the duplicate in the public surface after the engineer's ruling that duplication is legacy. + +**The refresh's result.** `Event::set` and `Event::reset` return a result where the raw calls returned a value this +crate ignored. The event is made by the ring with full access, so a failure would be a defect in this crate and not a +condition a caller can cause; `debug_assert` reports it in a build that runs the tests, and a release build does what +it always did. Considered: **returning the error from every mutation**, which would give sends and takes a failure +that no caller could act on. + +**What the tests no longer need.** The model's check made a duplicate of the doorbell only to hand a wait an owned +handle; the wait takes a borrowed one, so the duplicate is gone with the helper. The engine tests' raw byte write is +not a wait and is `SAFE-5`'s to find. + +## Why a comparison's result is read in one function, and what the earlier fallbacks were (D-26) + +**Built 2026-10-20** as `SAFE-4`. Of the census D-23 gives, the comparison's one `unsafe` block is gone, and with it +the last in this crate's non-test code: what remains is a raw byte write in an engine test, for `SAFE-5`. + +**The earlier code declared two fallbacks unreachable.** Both turned a run too long for the call's count into an exact +comparison, and its mutation run showed them surviving, which it explained by the four gigabytes a test would need. +The wrapper reports that case as an error and not as a count, so what the crate does with it is a function of a +result, and a result can be made in a test. The fallback is therefore tested, where it had only been argued. +Considered: **treating the refusal as a non-match**, which would make a name that long unequal to itself under +`Insensitive` and equal under `Sensitive`; the exact comparison keeps the two consistent for a run that is the same +units on both sides. + +**A failure of the call is a non-match, as it was.** Under the earlier code any result other than `CSTR_EQUAL` was +unequal, a failure included. Reporting the failure instead would change `matches`' signature for a call that, with +the arguments this crate passes, has no documented way to fail. + +## What `forbid(unsafe_code)` covers, and what the census ended at (D-23) + +**Built 2026-10-20** as `SAFE-5`. The census D-23 gives was ten `unsafe` blocks outside tests, and the last of them +went with `SAFE-4`. The build then found one more, which the census did not count because it was in a test: a raw +four-byte write that corrupted a record's `NextEntryOffset`. It is a slice write through the buffer's own +`as_mut_slice`, which is why the engine's buffer being an `io_buffers::AlignedBuf` ([D-24](DESIGN-NOTES.md#d-24)) was what +made the attribute reachable. + +**What the attribute covers.** It sits on the library root, so it covers the library and its unit tests, which are +compiled into it. It does not reach the integration tests or the doctests, which are separate crates; neither holds +an `unsafe` token, and what the attribute guarantees is only the library's. + +**The older attribute is replaced and not kept beside it.** `forbid(unsafe_op_in_unsafe_fn)` governs the body of an +`unsafe fn`, and a crate with no `unsafe` has none. diff --git a/crates/windows-file-enumeration-sys/PARKED.md b/crates/windows-file-enumeration-sys/PARKED.md new file mode 100644 index 000000000..a36246cd7 --- /dev/null +++ b/crates/windows-file-enumeration-sys/PARKED.md @@ -0,0 +1,44 @@ +# Parked: windows-file-enumeration-sys + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M-inf -- Horizon (ungated) + +- [ ] **REVIEW-1** -- Review the request path contract against a traversal layer, before one is built. + **Re-read after [PATH-1](COMPLETED-CHECKLIST.md#path----one-path-contract-across-the-namespace-crates):** that item + replaces the contract below (no `MAX_PATH` cap, no `\\?\` rewriting, a graft onto a captured root), + so most of its questions are about a contract that no longer exists, and this review may close + with a note of which still stand. + **This is a review item: it schedules no change.** Its output is an answer -- possibly "no change + needed" -- and any work it turns out to imply becomes its own item afterwards. Raised from the + `windows-file-watcher` side while recording + [D-85](../windows-file-watcher/DESIGN-NOTES.md#d-85) and the shared principle in + [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction); + flagged here because that principle's *building* half lives in this crate and nothing else would + bring it up. + **Context, not conclusions.** [lib.rs](src/lib.rs) states that recursive traversal belongs in a + separate layer composing this one, so that layer does not exist yet and the contract has never been + exercised by the consumer it was designed for. Facts noted while reading, each of which the review + should confirm rather than take on trust: + - [D-7](DESIGN-NOTES.md#d-7) accepts an *ordinary* path only if both the input and the + `GetFullPathNameW`-resolved form fit `MAX_PATH`, deliberately, so acceptance does not depend on + the host executable's `longPathAware` manifest. + - Win32 has no relative open, so descending means appending to an absolute path -- from a path + already at or near that cap. + - The contract's stated remedy for an over-limit path is "supply a fully qualified `\\?\` path", + which for a traversal layer means converting partway down rather than at the caller's boundary. + - `EnumerationRequest::path()` is `pub` and returns the stored resolved form, so a traversal layer + has access to a Win32-normalised base rather than only the caller's original string. + **Questions the review should answer.** Does D-7's contract still hold once descent is a real + consumer, or does the `MAX_PATH` cap on ordinary paths become reachable in normal use? If a + traversal layer must move into `\\?\` form mid-descent, is that conversion specified anywhere, and + is it the same conversion for every namespace the crate accepts -- a UNC path needs `\\?\UNC\...` + rather than a bare prefix, and a `\\.\` device form does not take one at all? Does + manifest-independence still mean what it should for requests a traversal layer *derives* rather than + receives? And is `path()`'s resolved form the intended base for that, stated as such, rather than + something a traversal layer is left to infer? + **The trap worth naming.** Converting *before* Win32 has normalised under the caller's own parsing + mode silently reinterprets the path; converting *after* preserves it. Both crates reached that rule + independently, which is why it is recorded once at the workspace level -- but reaching for the + prefix at the wrong moment is exactly what a traversal layer under length pressure would do. diff --git a/crates/windows-file-enumeration-sys/PLANS.md b/crates/windows-file-enumeration-sys/PLANS.md index e69863b2d..4c5e651fd 100644 --- a/crates/windows-file-enumeration-sys/PLANS.md +++ b/crates/windows-file-enumeration-sys/PLANS.md @@ -8,4 +8,4 @@ The crate's implementation is M5 through M7 in the workspace | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | not started | PATH-1: consume the namespace request crate's path contract and delete this crate's older copy of the preparation (the workspace's `M26+.3`), waiting on that crate's `NR-3.3` to `NR-3.6`; and REVIEW-1: review the request path contract against a traversal layer before one is built -- whether the deliberate `MAX_PATH` cap on ordinary paths survives descent, and whether moving into `\\?\` form mid-descent is specified for every namespace the crate accepts. Raised from the `windows-file-watcher` side; schedules no change of its own. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [workspace DESIGN-NOTES.md](../../DESIGN-NOTES.md#path-contracts-follow-path-construction) | +| [PARKED.md](PARKED.md) | parked | REVIEW-1: re-read against that contract, which no longer caps ordinary paths or rewrites any to `\\?\` form, and review it against a traversal layer before one is built. Raised from the `windows-file-watcher` side; schedules no change of its own. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [workspace DESIGN-NOTES.md](../../DESIGN-NOTES.md#path-contracts-follow-path-construction) | diff --git a/crates/windows-file-enumeration-sys/README.md b/crates/windows-file-enumeration-sys/README.md index 23e05ed43..d597ee45a 100644 --- a/crates/windows-file-enumeration-sys/README.md +++ b/crates/windows-file-enumeration-sys/README.md @@ -22,7 +22,8 @@ This crate owns flat one-directory enumeration: - entries and exactly one terminal outcome per accepted request leave through a bounded single-receiver completion ring; - directory handles are opened under an explicitly captured - `ImpersonationToken`; + `ImpersonationToken`, and the directory is opened and queried through + `windows-namespace-request-sys`' entries on one shared handle; - native paths and names retain WTF-16 fidelity; and - caller-owned `GetFileInformationByHandleEx` buffers provide lossless bounded staging under completion-ring backpressure. diff --git a/crates/windows-file-enumeration-sys/sabotage.json b/crates/windows-file-enumeration-sys/sabotage.json new file mode 100644 index 000000000..0c6eaa8c7 --- /dev/null +++ b/crates/windows-file-enumeration-sys/sabotage.json @@ -0,0 +1,232 @@ +{ + "package": "windows-file-enumeration-sys", + "description": "Sabotages for windows-file-enumeration-sys. Run with tools/run-sabotage.ps1; see tools/README-sabotage.md for the format and for why the results are read the way they are. Begun with PATH-1, so it covers the path contract this crate now takes from windows-namespace-request-sys and nothing older.", + "notCoveredHere": "The graft's own rules are the request crate's and are sabotaged in its manifest (the NR-3.3 entries); this one covers only this crate's use of them. A failure to read the current directory cannot be made by a test, so the code it carries is a declared survivor.", + "sabotages": [ + { + "name": "SAFE-1: the identity is read from a different file than the one enumerated", + "file": "tests/integration/metadata.rs", + "expect": "caught", + "why": "The test now hands the request crate a SharedHandle made from the opened file; this shows the comparison still tells one file from another rather than passing on any identity.", + "find": [" let file = std::fs::File::open(scratch.child(\"identified.txt\")).expect(\"open the file\");"], + "replace": [" let other = scratch.child(\"other.txt\");\n std::fs::write(&other, b\"x\").expect(\"write a second file\");\n let file = std::fs::File::open(other).expect(\"open the file\");"] + }, + { + "name": "SAFE-1 control: the same file is opened another way", + "file": "tests/integration/metadata.rs", + "expect": "survives", + "why": "A control: how the file is opened is incidental; which file it is, is what the test claims.", + "find": [" let file = std::fs::File::open(scratch.child(\"identified.txt\")).expect(\"open the file\");"], + "replace": [" let file = std::fs::OpenOptions::new().read(true).open(scratch.child(\"identified.txt\")).expect(\"open the file\");"] + }, + { + "name": "PATH-1: the worker opens the path as given and not the grafted one", + "file": "src/engine.rs", + "expect": "caught", + "why": "The two name the same directory until the current directory moves, so only the child-process test that moves it after the request is built tells them apart. Since SAFE-2 the open takes the request's AnchoredPath, so this opens the given path anchored again at open time instead.", + "find": ["native::open_directory(engine.request.anchored_path(), &engine.token)"], + "replace": ["native::open_directory(&windows_namespace_request_sys::AnchoredPath::new(wtf_string::Wtf16String::from_units(engine.request.path().as_units())).expect(\"anchored\"), &engine.token)"] + }, + { + "name": "PATH-1: the path as given is reported as the grafted one", + "file": "src/request.rs", + "expect": "caught", + "why": "path() is documented as the path the caller gave, and a relative one differs from what the worker opens.", + "find": [" pub fn path(&self) -> &Wtf16Str {\n self.path.given()\n"], + "replace": [" pub fn path(&self) -> &Wtf16Str {\n self.path.win32()\n"] + }, + { + "name": "PATH-1: an interior NUL is reported as a failure to read the current directory", + "file": "src/request.rs", + "expect": "caught", + "why": "The two failures say different things to the caller, and only the first is about the path itself.", + "find": [" PathCaptureError::InteriorNul => RequestError::new(RequestFailure::InteriorNul),\n"], + "replace": [" PathCaptureError::InteriorNul => RequestError::new(RequestFailure::PathCapture),\n"] + }, + { + "name": "PATH-1: the code of a failure to read the current directory is dropped", + "file": "src/request.rs", + "expect": "survives", + "why": "Declared, and not a gap a test can close: GetCurrentDirectoryW cannot be made to fail from a test without breaking the process it runs in. What would reach it is a fault seam in the request crate's current-directory read, which it does not have.", + "find": [" RequestError::with_code(RequestFailure::PathCapture, code)\n"], + "replace": [" {\n let _ = code;\n RequestError::new(RequestFailure::PathCapture)\n }\n"] + }, + { + "name": "PATH-1 control: the request's path is built through a binding", + "file": "src/request.rs", + "expect": "survives", + "why": "A control: the same value through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" Ok(Self {\n path,\n"], + "replace": [" Ok(Self {\n path: { let anchored = path; anchored },\n"] + }, + { + "name": "SAFE-2: the open drops delete sharing", + "file": "src/native.rs", + "expect": "caught", + "why": "A held enumeration would stop a rename of its directory; the rename test and the request-shape test see it.", + "find": [" .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)\n"], + "replace": [" .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)\n"] + }, + { + "name": "SAFE-2: the open asks for read access instead of listing", + "file": "src/native.rs", + "expect": "caught", + "why": "M21.3's constraint: listing access and not GENERIC_READ, so a directory a caller may list but not read still opens.", + "find": [" .with_desired_access(FILE_LIST_DIRECTORY)\n"], + "replace": [" .with_desired_access(0x8000_0000)\n"] + }, + { + "name": "SAFE-2: the open drops backup semantics", + "file": "src/native.rs", + "expect": "caught", + "why": "Without it CreateFileW fails on any directory.", + "find": [" .with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS)\n"], + "replace": [" .with_flags_and_attributes(0)\n"] + }, + { + "name": "SAFE-2: the open adds the overlapped flag", + "file": "src/native.rs", + "expect": "caught", + "why": "The handle must not be overlapped; the request-shape test pins the flags to backup semantics alone.", + "find": [" .with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS)\n"], + "replace": [" .with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS | 0x4000_0000)\n"] + }, + { + "name": "SAFE-2: the first refill continues instead of restarting", + "file": "src/native.rs", + "expect": "caught", + "why": "The first query restarts the enumeration; the class test sees the swap.", + "find": [" Refill::First => FileInformationClass::ID_EXTD_DIRECTORY_RESTART,\n"], + "replace": [" Refill::First => FileInformationClass::ID_EXTD_DIRECTORY,\n"] + }, + { + "name": "SAFE-2: every directory is taken for one", + "file": "src/native.rs", + "expect": "caught", + "why": "FILE_LIST_DIRECTORY opens an ordinary file too; the check is what keeps naming a file from reading as a capability failure.", + "find": [" Ok(info.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0)\n"], + "replace": [" let _ = info;\n Ok(true)\n"] + }, + { + "name": "SAFE-2: the volume serial is reported as zero", + "file": "src/native.rs", + "expect": "caught", + "why": "A real volume's serial is non-zero.", + "find": [" .map(|info| info.volume_serial_number())\n"], + "replace": [" .map(|_| 0)\n"] + }, + { + "name": "SAFE-2: a refused refill buffer is reported as exhaustion", + "file": "src/native.rs", + "expect": "survives", + "why": "Declared: the arm cannot be reached. The request crate refuses a buffer ahead of the call only for classes whose structure page requires an alignment, neither extended-directory class is one, and this buffer is 8-aligned besides. What would reach it is a refusal added to those classes' table.", + "find": [" RefillOutcome::Failed(EnumerationError::DirectoryQuery(Win32Error::from_code(0)))\n"], + "replace": [" RefillOutcome::Exhausted\n"] + }, + { + "name": "SAFE-2 control: the volume serial is read through a binding", + "file": "src/native.rs", + "expect": "survives", + "why": "A control: the same value through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" .map(|info| info.volume_serial_number())\n"], + "replace": [" .map(|info| {\n let serial = info.volume_serial_number();\n serial\n })\n"] + }, + { + "name": "SAFE-3: the shared wait target is a second event, not the ring's", + "file": "src/completion_ring.rs", + "expect": "caught", + "why": "A wait armed on a separate event would never be woken by the ring; the shared-handle test and the thread pool test see it.", + "find": [" Ok(WaitableHandle::from(self.doorbell_event()?.clone()))\n"], + "replace": [" Ok(WaitableHandle::from(Event::new(ResetMode::Manual, false)?))\n"] + }, + { + "name": "SAFE-3: the doorbell is set when nothing is pending and reset when something is", + "file": "src/completion_ring.rs", + "expect": "caught", + "why": "The doorbell's one invariant, inverted.", + "find": [" event.set()\n } else {\n event.reset()\n"], + "replace": [" event.reset()\n } else {\n event.set()\n"] + }, + { + "name": "SAFE-3: the doorbell starts unsignalled however much has already arrived", + "file": "src/completion_ring.rs", + "expect": "caught", + "why": "A client that asks for the doorbell after records have arrived must find it signalled.", + "find": [" let event = Event::new(ResetMode::Manual, state.pending())?;\n"], + "replace": [" let event = Event::new(ResetMode::Manual, false)?;\n"] + }, + { + "name": "SAFE-3: the doorbell is never refreshed", + "file": "src/completion_ring.rs", + "expect": "caught", + "why": "Without the refresh the event stays as it was created.", + "find": [" let refreshed = if state.pending() {\n event.set()\n } else {\n event.reset()\n };\n"], + "replace": [" let _ = event;\n let refreshed: io::Result<()> = Ok(());\n"] + }, + { + "name": "SAFE-3 control: the shared target is made through a binding", + "file": "src/completion_ring.rs", + "expect": "survives", + "why": "A control: the same clone through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" Ok(WaitableHandle::from(self.doorbell_event()?.clone()))\n"], + "replace": [" let event = self.doorbell_event()?.clone();\n Ok(WaitableHandle::from(event))\n"] + }, + { + "name": "SAFE-4: a case-insensitive run is compared case-sensitively", + "file": "src/pattern.rs", + "expect": "caught", + "why": "The case choice is the only thing that makes the comparison insensitive; matching \"README\" against \"readme\" observes it.", + "find": [" compare_string_ordinal(left, right, Case::Insensitive),\n"], + "replace": [" compare_string_ordinal(left, right, Case::Sensitive),\n"] + }, + { + "name": "SAFE-4: a comparison that says less is read as equal", + "file": "src/pattern.rs", + "expect": "caught", + "why": "Only an equal result is a match.", + "find": [" Ok(ordering) => ordering == Ordering::Equal,\n"], + "replace": [" Ok(ordering) => ordering != Ordering::Greater,\n"] + }, + { + "name": "SAFE-4: a run too long for the call is not compared exactly", + "file": "src/pattern.rs", + "expect": "caught", + "why": "Reaching it takes a slice of more than 2^31 units, so it is observed through the comparison's result: the fallback is the exact comparison, and equal runs must still match.", + "find": [" Err(CompareStringOrdinalError::TooLong { .. }) => left == right,\n"], + "replace": [" Err(CompareStringOrdinalError::TooLong { .. }) => false,\n"] + }, + { + "name": "SAFE-4: a failure of the call is read as equality", + "file": "src/pattern.rs", + "expect": "caught", + "why": "A failed comparison must never produce a match.", + "find": [" Err(CompareStringOrdinalError::Call(_)) => false,\n"], + "replace": [" Err(CompareStringOrdinalError::Call(_)) => true,\n"] + }, + { + "name": "SAFE-4 control: the equal result is read through a binding", + "file": "src/pattern.rs", + "expect": "survives", + "why": "A control: the same comparison through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" Ok(ordering) => ordering == Ordering::Equal,\n"], + "replace": [" Ok(ordering) => {\n let equal = ordering == Ordering::Equal;\n equal\n }\n"] + }, + { + "name": "SAFE-5: an unsafe block is added to the library", + "file": "src/lib.rs", + "expect": "refused-by-build", + "buildError": "usage of an `unsafe` block", + "why": "The attribute is the check D-23 rests on; a block added anywhere in the crate has to stop the build.", + "find": ["mod admission;\n"], + "replace": ["mod admission;\nconst _: u8 = unsafe { 0 };\n"] + }, + { + "name": "SAFE-5 control: a constant is added to the library", + "file": "src/lib.rs", + "expect": "survives", + "why": "A control: the same item with no unsafe, which builds. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": ["mod admission;\n"], + "replace": ["mod admission;\nconst _: u8 = { 0 };\n"] + } + ] +} diff --git a/crates/windows-file-enumeration-sys/src/admission.rs b/crates/windows-file-enumeration-sys/src/admission.rs index fa16e8ee8..9a4532cfb 100644 --- a/crates/windows-file-enumeration-sys/src/admission.rs +++ b/crates/windows-file-enumeration-sys/src/admission.rs @@ -37,13 +37,13 @@ //! outlive its handle says so with [`EnumerationHandle::detach`], which gives //! the reservation back rather than spending it. +use io_buffers::AlignedBuf; use windows_impersonation_token_sys::{CaptureError, ImpersonationToken}; -use crate::buffer::NativeBuffer; use crate::completion::EnumerationId; use crate::engine::EngineState; use crate::error::{BeginError, BeginFailure}; -use crate::request::EnumerationRequest; +use crate::request::{EnumerationRequest, RECORD_ALIGNMENT}; use crate::session::SessionShared; use crate::submission_ring::{ BeginMessage, CancelSlot, ControlMessage, SubmitRejection, release_cancel_slot, @@ -200,7 +200,7 @@ pub(crate) fn try_begin_with_token( // because the buffer belongs to the enumeration it serves: a request is a // cheap, clonable, comparable description that may be submitted more than // once and is handed straight back when a begin is refused. - let Some(buffer) = NativeBuffer::try_new(request.buffer_capacity()) else { + let Ok(buffer) = AlignedBuf::zeroed(request.buffer_capacity(), RECORD_ALIGNMENT) else { release_cancel_slot(&shared.submissions, cancel); release_retire_slot(&shared.submissions, retire); drop(terminal); diff --git a/crates/windows-file-enumeration-sys/src/buffer.rs b/crates/windows-file-enumeration-sys/src/buffer.rs deleted file mode 100644 index e08442f9f..000000000 --- a/crates/windows-file-enumeration-sys/src/buffer.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! The fixed native staging buffer one enumeration reads into. -//! -//! # Why the element type is `u64` -//! -//! A `FILE_ID_EXTD_DIR_INFO` contains `i64` fields, and the API keeps every -//! record in a batch on an 8-byte boundary -- but only if the batch itself -//! starts on one. A `Vec` guarantees byte alignment and nothing more, and a -//! misaligned batch is not a subtle problem: the very first query fails with -//! `ERROR_NOACCESS`, and reading those fields would be undefined behaviour in -//! Rust regardless. Storing `u64` words makes the base address 8-byte aligned by -//! construction rather than by hope. -//! -//! # Why allocation is fallible -//! -//! The ordinary growable-vector path aborts the process when an allocation -//! fails. A caller that asked for a large buffer deserves an error instead, so -//! the reservation is made with `try_reserve_exact` and the buffer is only then -//! given its length -- a resize that cannot reallocate, and so cannot abort. - -use crate::request::RECORD_ALIGNMENT; - -/// One enumeration's reusable staging buffer. -/// -/// Its capacity is fixed for the enumeration's whole life: the buffer never -/// grows in response to what a directory turns out to contain, because a bound -/// that silently moves is not a bound. -pub(crate) struct NativeBuffer { - words: Vec, -} - -impl NativeBuffer { - /// Allocate `capacity` bytes, or report that the allocation failed. - /// - /// `capacity` is the effective capacity a request computed, which is already - /// clamped, aligned, and known to fit a Win32 `u32`. - pub(crate) fn try_new(capacity: usize) -> Option { - debug_assert_eq!( - capacity % RECORD_ALIGNMENT, - 0, - "an effective capacity is always a whole number of words" - ); - let words = capacity / RECORD_ALIGNMENT; - let mut storage: Vec = Vec::new(); - storage.try_reserve_exact(words).ok()?; - // Cannot reallocate, and so cannot abort: the capacity is already held. - storage.resize(words, 0); - Some(Self { words: storage }) - } - - /// The buffer's capacity in bytes, in the form Win32 wants it. - pub(crate) fn capacity(&self) -> u32 { - let bytes = self.words.len() * RECORD_ALIGNMENT; - u32::try_from(bytes).expect("an effective capacity always fits a u32") - } - - /// A writable pointer to the start of the buffer. - /// - /// 8-byte aligned by construction, which is what the directory-information - /// classes require of a batch. - pub(crate) fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void { - self.words.as_mut_ptr().cast() - } - - /// The bytes the last query wrote into, as a shared slice. - /// - /// The whole buffer is exposed rather than the written extent, because - /// `GetFileInformationByHandleEx` reports no written length: a batch is - /// walked by its own next-entry offsets, and the parser bounds every read - /// against this slice. - pub(crate) fn as_bytes(&self) -> &[u8] { - // SAFETY: `u64` has no padding or invalid bit patterns, so its storage - // is always readable as initialised bytes, and the lifetime is the - // borrow's. - unsafe { - core::slice::from_raw_parts( - self.words.as_ptr().cast::(), - self.words.len() * RECORD_ALIGNMENT, - ) - } - } -} - -impl std::fmt::Debug for NativeBuffer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NativeBuffer") - .field("capacity", &self.capacity()) - .finish() - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/windows-file-enumeration-sys/src/buffer/tests.rs b/crates/windows-file-enumeration-sys/src/buffer/tests.rs deleted file mode 100644 index 8e650bdba..000000000 --- a/crates/windows-file-enumeration-sys/src/buffer/tests.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! Tests for the fixed native staging buffer. - -use super::*; -use crate::request::{DEFAULT_BUFFER_CAPACITY, MINIMUM_BUFFER_CAPACITY}; - -#[test] -fn a_buffer_reports_the_capacity_it_was_asked_for() { - let buffer = NativeBuffer::try_new(MINIMUM_BUFFER_CAPACITY).expect("a small allocation"); - assert_eq!(buffer.capacity(), MINIMUM_BUFFER_CAPACITY as u32); - assert_eq!(buffer.as_bytes().len(), MINIMUM_BUFFER_CAPACITY); -} - -#[test] -fn the_default_capacity_allocates() { - let buffer = NativeBuffer::try_new(DEFAULT_BUFFER_CAPACITY).expect("64 KiB"); - assert_eq!(buffer.capacity(), DEFAULT_BUFFER_CAPACITY as u32); -} - -#[test] -fn the_base_address_is_eight_byte_aligned() { - // The directory-information classes reject a misaligned batch outright, and - // reading the record's i64 fields from one would be undefined behaviour. - for capacity in [ - MINIMUM_BUFFER_CAPACITY, - MINIMUM_BUFFER_CAPACITY + RECORD_ALIGNMENT, - DEFAULT_BUFFER_CAPACITY, - ] { - let mut buffer = NativeBuffer::try_new(capacity).expect("allocation"); - let address = buffer.as_mut_ptr() as usize; - assert_eq!(address % RECORD_ALIGNMENT, 0, "for {capacity} bytes"); - } -} - -#[test] -fn a_fresh_buffer_is_zeroed() { - let buffer = NativeBuffer::try_new(MINIMUM_BUFFER_CAPACITY).expect("allocation"); - assert!(buffer.as_bytes().iter().all(|byte| *byte == 0)); -} - -#[test] -fn an_impossible_allocation_is_reported_rather_than_aborting() { - // The ordinary growable-vector path aborts the process here; a caller that - // asked for too much deserves an answer instead. - assert!(NativeBuffer::try_new(usize::MAX & !(RECORD_ALIGNMENT - 1)).is_none()); -} - -#[test] -fn debug_output_names_the_capacity() { - let buffer = NativeBuffer::try_new(MINIMUM_BUFFER_CAPACITY).expect("allocation"); - let rendered = format!("{buffer:?}"); - assert!( - rendered.contains(&MINIMUM_BUFFER_CAPACITY.to_string()), - "{rendered}" - ); -} diff --git a/crates/windows-file-enumeration-sys/src/completion_ring.rs b/crates/windows-file-enumeration-sys/src/completion_ring.rs index 710df6052..78762017d 100644 --- a/crates/windows-file-enumeration-sys/src/completion_ring.rs +++ b/crates/windows-file-enumeration-sys/src/completion_ring.rs @@ -36,6 +36,10 @@ //! //! The receiver hands out a manual-reset event so a client that already owns a //! thread pool can integrate without dedicating a thread to a blocking receive. +//! The ring holds it as a `win-sync-sys` [`Event`] and sets and resets it through +//! that type; what a client arms a wait on is a [`WaitableHandle`] that shares the +//! ring's handle, so no handle is duplicated and a client cannot set or reset it +//! without `unsafe`. //! Its invariant is one line: **signalled exactly when the receiver has //! something to observe** -- a queued record, or the end of the stream. It is //! re-established under the ring lock at the end of every mutation, which is the @@ -47,12 +51,11 @@ use std::collections::VecDeque; use std::io; -use std::os::windows::io::{AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::{AsHandle, BorrowedHandle}; use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock}; use std::time::{Duration, Instant}; -use windows_sys::Win32::Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle, FALSE, HANDLE}; -use windows_sys::Win32::System::Threading::{GetCurrentProcess, ResetEvent, SetEvent}; +use win_sync_sys::{Event, ResetMode}; use windows_threadpool_sys::wait::WaitableHandle; use crate::completion::{Completion, EnumerationId, TerminalOutcome}; @@ -67,7 +70,7 @@ pub(crate) const MINIMUM_COMPLETION_CAPACITY: usize = 2; pub(crate) struct CompletionRing { state: Mutex, arrived: Condvar, - doorbell: OnceLock, + doorbell: OnceLock, } struct RingState { @@ -288,24 +291,30 @@ impl CompletionRing { /// ring it reports on: a client that asks for it after records have already /// arrived must find it signalled. pub(crate) fn doorbell(&self) -> io::Result> { + Ok(self.doorbell_event()?.as_handle()) + } + + /// The doorbell as a wait target that shares the ring's handle, as a + /// `ThreadpoolWait` requires. + /// + /// Made from a clone of the ring's [`Event`], so the wait and the ring hold + /// the one handle and the handle closes when the last of them drops. The + /// wait target gives a client no way to set or reset the event without + /// `unsafe`, so the doorbell's invariant stays the ring's. + pub(crate) fn doorbell_waitable(&self) -> io::Result { + Ok(WaitableHandle::from(self.doorbell_event()?.clone())) + } + + /// The ring's doorbell event, created on first use under the ring lock. + fn doorbell_event(&self) -> io::Result<&Event> { if self.doorbell.get().is_none() { let state = self.lock(); if self.doorbell.get().is_none() { - let event = WaitableHandle::event(true, state.pending())?; + let event = Event::new(ResetMode::Manual, state.pending())?; let _ = self.doorbell.set(event); } } - Ok(self - .doorbell - .get() - .expect("the doorbell was just created") - .handle()) - } - - /// A duplicate of the doorbell that the caller owns, as a `ThreadpoolWait` - /// requires. - pub(crate) fn doorbell_owned(&self) -> io::Result { - duplicate(self.doorbell()?) + Ok(self.doorbell.get().expect("the doorbell was just created")) } /// Re-establish the doorbell's one invariant. @@ -317,15 +326,18 @@ impl CompletionRing { let Some(event) = self.doorbell.get() else { return; }; - let handle = event.handle().as_raw_handle() as HANDLE; - // SAFETY: the handle is a live manual-reset event owned by this ring. - unsafe { - if state.pending() { - SetEvent(handle); - } else { - ResetEvent(handle); - } - } + // The event was made here, so it carries full access and is open while the ring is: neither + // call can fail. Checked in a debug build, so a change that broke that is caught as this + // crate's own bug, and ignored in a release build as the doorbell always has been. + let refreshed = if state.pending() { + event.set() + } else { + event.reset() + }; + debug_assert!( + refreshed.is_ok(), + "the doorbell could not be refreshed: {refreshed:?}" + ); } /// Give back a terminal reservation that was never used. @@ -381,28 +393,5 @@ impl Drop for TerminalSlot { } } -/// Duplicate a handle into one the caller owns. -fn duplicate(handle: BorrowedHandle<'_>) -> io::Result { - let mut copy: HANDLE = std::ptr::null_mut(); - // SAFETY: `handle` is live for the borrow, `copy` is a valid out-pointer, - // and both process handles are the current-process pseudo handle. - let ok = unsafe { - DuplicateHandle( - GetCurrentProcess(), - handle.as_raw_handle() as HANDLE, - GetCurrentProcess(), - &raw mut copy, - 0, - FALSE, - DUPLICATE_SAME_ACCESS, - ) - }; - if ok == 0 { - return Err(io::Error::last_os_error()); - } - // SAFETY: `DuplicateHandle` succeeded, so `copy` owns a live handle. - Ok(unsafe { OwnedHandle::from_raw_handle(copy as _) }) -} - #[cfg(test)] mod tests; diff --git a/crates/windows-file-enumeration-sys/src/completion_ring/tests.rs b/crates/windows-file-enumeration-sys/src/completion_ring/tests.rs index 3e52ef1e3..178d54518 100644 --- a/crates/windows-file-enumeration-sys/src/completion_ring/tests.rs +++ b/crates/windows-file-enumeration-sys/src/completion_ring/tests.rs @@ -4,8 +4,12 @@ use super::*; use crate::error::{EnumerationError, Win32Error}; use crate::testing::named_file; -use std::os::windows::io::AsHandle; +use std::os::windows::io::{AsHandle, AsRawHandle}; +use std::sync::Mutex; +use std::sync::mpsc; +use win_sync_sys::{WaitOutcome, wait_for_single_object}; use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED; +use windows_threadpool_sys::wait::ThreadpoolWait; fn ring(capacity: usize) -> Arc { Arc::new(CompletionRing::new(capacity)) @@ -289,21 +293,83 @@ fn a_terminal_delivery_signals_the_doorbell() { } #[test] -fn an_owned_doorbell_refers_to_the_same_event() { +fn a_waitable_doorbell_is_the_rings_own_handle_and_sees_what_the_ring_signals() { + // The same raw handle: shared, where `doorbell_owned` made a second one. let ring = ring(2); - let owned = ring.doorbell_owned().expect("a duplicate"); + let waitable = ring.doorbell_waitable().expect("a shared event"); + let own = ring.doorbell().expect("an event"); + assert_eq!(waitable.handle().as_raw_handle(), own.as_raw_handle()); + + assert!(!is_signalled(waitable.handle())); ring.try_send_entry(entry(1, "a")).expect("room"); - assert!(is_signalled(owned.as_handle())); + assert!(is_signalled(waitable.handle())); + ring.try_take().expect("a record"); + assert!(!is_signalled(waitable.handle())); } -/// Whether a waitable handle is currently signalled. -fn is_signalled(handle: BorrowedHandle<'_>) -> bool { - use windows_sys::Win32::Foundation::WAIT_OBJECT_0; - use windows_sys::Win32::System::Threading::WaitForSingleObject; +#[test] +fn a_waitable_doorbell_gives_its_handle_back_only_to_its_last_holder() { + // A duplicate would be the wait's alone and come back at once; a share is the ring's as well. + let ring = ring(2); + let waitable = ring.doorbell_waitable().expect("a shared event"); + + let waitable = waitable + .into_handle() + .expect_err("the ring still holds the handle"); + drop(ring); + waitable + .into_handle() + .expect("the wait is the last holder once the ring has gone"); +} + +#[test] +fn a_waitable_doorbell_keeps_the_event_open_after_the_ring_is_gone() { + let ring = ring(2); + let waitable = ring.doorbell_waitable().expect("a shared event"); + ring.try_send_entry(entry(1, "a")).expect("room"); + drop(ring); + assert!(is_signalled(waitable.handle())); +} + +#[test] +fn a_thread_pool_wait_on_the_doorbell_runs_when_the_ring_has_something_to_observe() { + // The whole path a client takes: a wait built from the shared target, armed, woken by an + // entry, and woken again by the next once the receiver has drained. + let ring = ring(4); + let (woken, wakes) = mpsc::channel(); + let woken = Mutex::new(woken); + let wait = ThreadpoolWait::new( + ring.doorbell_waitable().expect("a shared event"), + move |_| { + if let Ok(woken) = woken.lock() { + let _ = woken.send(()); + } + }, + None, + ) + .expect("a wait"); + let bound = Duration::from_secs(5); + + wait.arm(None); + assert!(wakes.recv_timeout(Duration::from_millis(50)).is_err()); + ring.try_send_entry(entry(1, "a")).expect("room"); + wakes.recv_timeout(bound).expect("woken by the first entry"); - // SAFETY: the handle is a live event owned by the ring under test. - let result = unsafe { WaitForSingleObject(handle.as_raw_handle() as HANDLE, 0) }; - result == WAIT_OBJECT_0 + ring.try_take().expect("a record"); + wait.arm(None); + assert!(wakes.recv_timeout(Duration::from_millis(50)).is_err()); + ring.try_send_entry(entry(1, "b")).expect("room"); + wakes + .recv_timeout(bound) + .expect("woken by the second entry"); +} + +/// Whether a waitable handle is currently signalled. +/// +/// A poll with no time to wait, which takes nothing from a manual-reset event. +fn is_signalled(handle: impl AsHandle) -> bool { + wait_for_single_object(handle, Some(Duration::ZERO)).expect("a wait on the doorbell") + == WaitOutcome::Signalled } #[test] diff --git a/crates/windows-file-enumeration-sys/src/engine.rs b/crates/windows-file-enumeration-sys/src/engine.rs index 495c999ab..6c2dcdc53 100644 --- a/crates/windows-file-enumeration-sys/src/engine.rs +++ b/crates/windows-file-enumeration-sys/src/engine.rs @@ -19,7 +19,8 @@ //! Open the directory and obtain any volume identity the request asked for, on //! the quantum that finds it unopened. A refill happens at most once per //! quantum: when the record cursor says the loaded batch is exhausted, one -//! `GetFileInformationByHandleEx` call loads the next one. Whatever batch is +//! directory query (`GetFileInformationByHandleEx`, through the request crate's +//! `QueryFileInformation`) loads the next one. Whatever batch is //! then current -- freshly loaded or left over from a quantum that could not //! finish it -- is parsed record by record: `.` and `..` are dropped, the //! request's predicate is evaluated, and every match is offered to the @@ -48,12 +49,12 @@ //! - A record fails validation, which ends the enumeration outright rather //! than leaving a cursor to resume from. -use std::os::windows::io::OwnedHandle; use std::time::{Duration, Instant}; +use io_buffers::AlignedBuf; +use win_shared_os_owned_handle::SharedHandle; use windows_impersonation_token_sys::ImpersonationToken; -use crate::buffer::NativeBuffer; use crate::completion::{Completion, EnumerationId, TerminalOutcome}; use crate::completion_ring::CompletionRing; use crate::entry::{DirectoryEntry, FileIdentityMode}; @@ -107,8 +108,8 @@ enum Phase { pub(crate) struct EngineState { request: EnumerationRequest, token: ImpersonationToken, - buffer: NativeBuffer, - directory: Option, + buffer: AlignedBuf, + directory: Option, volume_serial: Option, phase: Phase, /// Where the next record starts in the currently loaded batch. @@ -131,7 +132,7 @@ impl EngineState { pub(crate) fn new( request: EnumerationRequest, token: ImpersonationToken, - buffer: NativeBuffer, + buffer: AlignedBuf, ) -> Self { Self { request, @@ -247,7 +248,7 @@ pub(crate) fn advance( return QuantumOutcome::Yielded; } - let (parsed, next) = match record::parse_record(engine.buffer.as_bytes(), offset) { + let (parsed, next) = match record::parse_record(engine.buffer.as_slice(), offset) { Ok(parsed) => parsed, Err(detail) => { return QuantumOutcome::Finished(TerminalOutcome::Failed( @@ -288,7 +289,7 @@ pub(crate) fn advance( /// Returns the failure that ends the enumeration, or `None` when it is ready to /// read. fn start(engine: &mut EngineState) -> Option { - let directory = match native::open_directory(engine.request.path(), &engine.token) { + let directory = match native::open_directory(engine.request.anchored_path(), &engine.token) { Ok(directory) => directory, Err(error) => return Some(error), }; diff --git a/crates/windows-file-enumeration-sys/src/engine/tests.rs b/crates/windows-file-enumeration-sys/src/engine/tests.rs index fda62dce9..89213e7ee 100644 --- a/crates/windows-file-enumeration-sys/src/engine/tests.rs +++ b/crates/windows-file-enumeration-sys/src/engine/tests.rs @@ -2,13 +2,12 @@ //! Tests for one enumeration's quantum, against real directories. use super::*; -use crate::buffer::NativeBuffer; use crate::completion::{Completion, EnumerationId}; use crate::completion_ring::{CompletionRing, MINIMUM_COMPLETION_CAPACITY}; use crate::entry::FileIdentityMode; use crate::pattern::{CaseSensitivity, NamePattern}; use crate::predicate::{PredicateClause, QueryByExample}; -use crate::request::MINIMUM_BUFFER_CAPACITY; +use crate::request::{MINIMUM_BUFFER_CAPACITY, RECORD_ALIGNMENT}; use crate::scratch::Scratch; use wtf_string::Wtf16Str; @@ -16,6 +15,11 @@ use wtf_string::Wtf16Str; /// ring with a second enumeration, so one arbitrary value names it throughout. const ENUMERATION: EnumerationId = EnumerationId::from_raw(0); +/// The staging buffer a request asks for, as admission makes it. +fn buffer_for(request: &EnumerationRequest) -> AlignedBuf { + AlignedBuf::zeroed(request.buffer_capacity(), RECORD_ALIGNMENT).expect("allocation") +} + fn engine_for(path: &std::path::Path, mode: FileIdentityMode) -> EngineState { let request = EnumerationRequest::for_path(path) .expect("a resolvable path") @@ -23,7 +27,7 @@ fn engine_for(path: &std::path::Path, mode: FileIdentityMode) -> EngineState { .with_buffer_capacity(MINIMUM_BUFFER_CAPACITY) .expect("representable"); let token = ImpersonationToken::capture().expect("the calling thread has a context"); - let buffer = NativeBuffer::try_new(request.buffer_capacity()).expect("allocation"); + let buffer = buffer_for(&request); EngineState::new(request, token, buffer) } @@ -34,7 +38,7 @@ fn engine_with_predicate(path: &std::path::Path, predicate: QueryByExample) -> E .with_buffer_capacity(MINIMUM_BUFFER_CAPACITY) .expect("representable"); let token = ImpersonationToken::capture().expect("the calling thread has a context"); - let buffer = NativeBuffer::try_new(request.buffer_capacity()).expect("allocation"); + let buffer = buffer_for(&request); EngineState::new(request, token, buffer) } @@ -44,7 +48,7 @@ fn engine_with_capacity(path: &std::path::Path, capacity: usize) -> EngineState .with_buffer_capacity(capacity) .expect("representable"); let token = ImpersonationToken::capture().expect("the calling thread has a context"); - let buffer = NativeBuffer::try_new(request.buffer_capacity()).expect("allocation"); + let buffer = buffer_for(&request); EngineState::new(request, token, buffer) } @@ -100,6 +104,24 @@ fn an_empty_directory_completes() { ); } +#[test] +fn a_finished_enumeration_leaves_its_directory_with_no_other_holder() { + // `try_into_owned` succeeds only for the last holder: no query, request or clone outlives the + // quanta that made it, and nothing made a handle of its own. + let scratch = Scratch::with_files(&["a.txt", "b.txt"]); + let mut engine = engine_for(scratch.path(), FileIdentityMode::Required); + let completions = CompletionRing::new(8); + run_to_completion(&mut engine, &completions); + assert!(engine.volume_serial().is_some()); + + engine + .directory + .take() + .expect("the directory was opened") + .try_into_owned() + .expect("the engine is the directory's only holder"); +} + #[test] fn a_missing_directory_fails_to_open() { let scratch = Scratch::empty(); @@ -407,13 +429,11 @@ fn a_still_full_ring_parks_again_without_losing_the_pending_entry() { /// record starting there fails validation, without needing to know anything /// about its other fields. fn corrupt_next_entry_offset(engine: &mut EngineState, offset: usize) { - // SAFETY: `offset` is a cursor this same engine reported, which is always - // within its buffer; four bytes are written, matching `NextEntryOffset`'s - // width and position at a record's very start. - unsafe { - let base = engine.buffer.as_mut_ptr().cast::(); - base.add(offset).cast::().write_unaligned(u32::MAX); - } + // `offset` is a cursor this same engine reported, which is always within its + // buffer; four bytes are written, matching `NextEntryOffset`'s width and + // position at a record's very start. + engine.buffer.as_mut_slice()[offset..offset + size_of::()] + .copy_from_slice(&u32::MAX.to_le_bytes()); } #[test] diff --git a/crates/windows-file-enumeration-sys/src/error.rs b/crates/windows-file-enumeration-sys/src/error.rs index 86cf3ff9e..26cace2b4 100644 --- a/crates/windows-file-enumeration-sys/src/error.rs +++ b/crates/windows-file-enumeration-sys/src/error.rs @@ -38,23 +38,14 @@ pub use win_error_codes::Win32Error; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum RequestFailure { - /// The path had no code units. An empty path names no directory. - EmptyPath, /// The path contained an interior NUL. Win32 would stop at it and open a /// different, shorter path than the caller named. InteriorNul, - /// An ordinary path, or the fully qualified form it resolved to, did not fit - /// the ordinary `MAX_PATH` limit including its terminator. - /// - /// This limit is deliberate rather than incidental: it keeps behaviour - /// independent of the host executable's `longPathAware` manifest. Supply a - /// fully qualified `\\?\` path to enumerate a longer one. - PathTooLong, - /// A `\\?\` path was not fully qualified, so Win32 would not interpret it as - /// the verbatim absolute path that prefix promises. - NotFullyQualified, - /// Windows could not resolve an ordinary path to its fully qualified form. - PathResolution, + /// A relative path could not be fixed to the current directory, because + /// the current directory could not be read: [`RequestError::code`] is the + /// code `GetCurrentDirectoryW` reported, or `None` if its size kept + /// changing between attempts. + PathCapture, /// The requested native buffer capacity, after clamping and alignment, /// cannot be passed to Win32 as a `u32`. BufferCapacityUnrepresentable, @@ -64,13 +55,8 @@ impl RequestFailure { /// A short description of the failure, without any raw code. const fn describe(self) -> &'static str { match self { - RequestFailure::EmptyPath => "the path is empty", RequestFailure::InteriorNul => "the path contains an interior NUL", - RequestFailure::PathTooLong => { - "the path exceeds MAX_PATH; supply a fully qualified \\\\?\\ path" - } - RequestFailure::NotFullyQualified => "the \\\\?\\ path is not fully qualified", - RequestFailure::PathResolution => "the path could not be resolved", + RequestFailure::PathCapture => "the path could not be fixed to the current directory", RequestFailure::BufferCapacityUnrepresentable => { "the native buffer capacity does not fit a Win32 u32" } @@ -110,7 +96,8 @@ impl RequestError { /// The raw Win32 code behind the failure, when Windows produced one. /// - /// Only [`RequestFailure::PathResolution`] arises from a Win32 call; the + /// Only [`RequestFailure::PathCapture`] arises from a Win32 call, and it + /// carries no code when the current directory's size kept changing; the /// other failures are decided by this crate before any call is made. #[must_use] pub const fn code(&self) -> Option { diff --git a/crates/windows-file-enumeration-sys/src/error/tests.rs b/crates/windows-file-enumeration-sys/src/error/tests.rs index fae53b1b4..eac726e4a 100644 --- a/crates/windows-file-enumeration-sys/src/error/tests.rs +++ b/crates/windows-file-enumeration-sys/src/error/tests.rs @@ -41,15 +41,15 @@ fn a_win32_code_displays_with_its_numeric_value() { #[test] fn a_request_error_without_a_call_behind_it_carries_no_code() { - let error = RequestError::new(RequestFailure::EmptyPath); - assert_eq!(error.failure(), RequestFailure::EmptyPath); + let error = RequestError::new(RequestFailure::InteriorNul); + assert_eq!(error.failure(), RequestFailure::InteriorNul); assert_eq!(error.code(), None); assert!(!error.to_string().is_empty()); } #[test] -fn a_resolution_failure_keeps_the_code_windows_reported() { - let error = RequestError::with_code(RequestFailure::PathResolution, Win32Error::from_code(123)); +fn a_capture_failure_keeps_the_code_windows_reported() { + let error = RequestError::with_code(RequestFailure::PathCapture, Win32Error::from_code(123)); assert_eq!(error.code(), Some(Win32Error::from_code(123))); assert!(error.to_string().contains("123")); } @@ -161,14 +161,8 @@ fn assert_descriptions_are_distinct(descriptions: &[(&str, &str)]) { #[test] fn every_request_failure_describes_itself_distinctly() { assert_descriptions_are_distinct(&[ - ("EmptyPath", RequestFailure::EmptyPath.describe()), ("InteriorNul", RequestFailure::InteriorNul.describe()), - ("PathTooLong", RequestFailure::PathTooLong.describe()), - ( - "NotFullyQualified", - RequestFailure::NotFullyQualified.describe(), - ), - ("PathResolution", RequestFailure::PathResolution.describe()), + ("PathCapture", RequestFailure::PathCapture.describe()), ( "BufferCapacityUnrepresentable", RequestFailure::BufferCapacityUnrepresentable.describe(), diff --git a/crates/windows-file-enumeration-sys/src/lib.rs b/crates/windows-file-enumeration-sys/src/lib.rs index 7b4aadc04..d18cb5601 100644 --- a/crates/windows-file-enumeration-sys/src/lib.rs +++ b/crates/windows-file-enumeration-sys/src/lib.rs @@ -18,17 +18,19 @@ //! //! # Safety //! -//! The public surface is entirely safe: every FFI call the native engine makes -//! is confined to a single caller-owned, size-checked buffer -//! ([`EnumerationRequest::with_buffer_capacity`]), and no directory entry is -//! ever opened individually -- the engine reads only from the batched +//! The public surface is entirely safe. The directory's own calls are not made +//! here: its open and its queries are `windows-namespace-request-sys` entries on +//! one handle that is shared rather than duplicated, and every query reads into +//! a single caller-owned, size-checked buffer +//! ([`EnumerationRequest::with_buffer_capacity`]). No directory entry is ever +//! opened individually -- the engine reads only from the batched //! `GetFileInformationByHandleEx` listing of the one directory handle the //! request named. A submitted enumeration's security context is captured //! synchronously on the submitter's own thread, before the request becomes //! visible to any worker, so the later directory open always runs as whoever -//! asked for it rather than as the pool. The unsafe internals that make this -//! true -- buffer aliasing, handle ownership, and thread-pool callback -//! lifetime -- are recorded in [DESIGN-NOTES.md][1] and [DESIGN-RATIONALE.md][2]. +//! asked for it rather than as the pool. The crate holds no `unsafe` code: its +//! root forbids it, in the tests as in the library, and the reasoning is +//! recorded in [DESIGN-NOTES.md][1] (D-23) and [DESIGN-RATIONALE.md][2]. //! //! [1]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/windows-file-enumeration-sys/DESIGN-NOTES.md //! [2]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md @@ -119,18 +121,16 @@ //! ``` #![cfg(windows)] -#![forbid(unsafe_op_in_unsafe_fn)] +#![forbid(unsafe_code)] #![warn(missing_docs)] mod admission; -mod buffer; mod completion; mod completion_ring; mod engine; mod entry; mod error; mod native; -mod path; mod pattern; mod predicate; mod record; diff --git a/crates/windows-file-enumeration-sys/src/model.rs b/crates/windows-file-enumeration-sys/src/model.rs index 8021d6bab..6c1f34c04 100644 --- a/crates/windows-file-enumeration-sys/src/model.rs +++ b/crates/windows-file-enumeration-sys/src/model.rs @@ -384,17 +384,13 @@ impl Model { ); // The doorbell's whole contract in one line. - if let Ok(handle) = self.receiver.as_ref().map_or_else( - || Err(std::io::Error::other("no receiver")), - |receiver| { - receiver - .doorbell() - .map(|handle| handle.try_clone_to_owned()) - }, - ) { - let handle = handle.expect("the doorbell can be duplicated"); + if let Some(handle) = self + .receiver + .as_ref() + .and_then(|receiver| receiver.doorbell().ok()) + { assert_eq!( - is_signalled(&handle), + is_signalled(handle), ring.is_pending(), "{context}: the doorbell disagrees with what the receiver can observe" ); @@ -416,15 +412,12 @@ fn request() -> EnumerationRequest { EnumerationRequest::new(&Wtf16String::from(r"C:\Windows")).expect("a resolvable path") } -/// Whether a waitable handle is currently signalled. -fn is_signalled(handle: &std::os::windows::io::OwnedHandle) -> bool { - use std::os::windows::io::AsRawHandle; - use windows_sys::Win32::Foundation::{HANDLE, WAIT_OBJECT_0}; - use windows_sys::Win32::System::Threading::WaitForSingleObject; +/// Whether a waitable handle is currently signalled: a poll with no time to wait. +fn is_signalled(handle: std::os::windows::io::BorrowedHandle<'_>) -> bool { + use win_sync_sys::{WaitOutcome, wait_for_single_object}; - // SAFETY: the handle is a live event duplicated from the ring under test. - let result = unsafe { WaitForSingleObject(handle.as_raw_handle() as HANDLE, 0) }; - result == WAIT_OBJECT_0 + wait_for_single_object(handle, Some(std::time::Duration::ZERO)).expect("a wait on the doorbell") + == WaitOutcome::Signalled } #[cfg(test)] diff --git a/crates/windows-file-enumeration-sys/src/native.rs b/crates/windows-file-enumeration-sys/src/native.rs index 2f4cf788d..1c02821bf 100644 --- a/crates/windows-file-enumeration-sys/src/native.rs +++ b/crates/windows-file-enumeration-sys/src/native.rs @@ -1,10 +1,18 @@ // Copyright (c) 2026 Mike Grier -//! The documented Win32 calls this crate is built on. -//! -//! Three of them: open the directory, ask it which volume it lives on, and read -//! a batch of records out of it. Everything else in the crate is bookkeeping +//! The directory's calls: open it, ask it which volume it lives on, and read a +//! batch of records out of it. Everything else in the crate is bookkeeping //! around these. //! +//! # Every call is an entry of the request crate +//! +//! This crate makes none of these calls itself. The open is +//! `windows-namespace-request-sys`' `OpenFile`, built from the request's +//! `AnchoredPath`; the directory check is its `QueryFileBasicInfo`; the volume +//! serial is its `QueryFileIdInfo`; and a refill is its `QueryFileInformation`, +//! performed into this crate's own buffer. The directory is held as a +//! `SharedHandle` and every query is built from a clone of it, so no handle is +//! duplicated: each entry holds the very handle it was given. +//! //! # Opening under someone else's identity //! //! The directory is opened on a thread-pool worker whose own thread token is @@ -28,23 +36,24 @@ //! guesswork the caller should have to do, so the phase is tracked here and the //! outcome is typed. -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; - +use io_buffers::AlignedBuf; +use win_shared_os_owned_handle::SharedHandle; use windows_impersonation_token_sys::ImpersonationToken; +use windows_namespace_request_sys::query::PerformIntoError; +use windows_namespace_request_sys::{ + AnchoredPath, FileInformationClass, OpenFile, QueryFileBasicInfo, QueryFileIdInfo, + QueryFileInformation, +}; use windows_sys::Win32::Foundation::{ ERROR_BAD_LENGTH, ERROR_DIRECTORY, ERROR_FILE_NOT_FOUND, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER, ERROR_MORE_DATA, ERROR_NO_MORE_FILES, - ERROR_NOT_SUPPORTED, HANDLE, INVALID_HANDLE_VALUE, + ERROR_NOT_SUPPORTED, }; use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_DIRECTORY, FILE_BASIC_INFO, FILE_FLAG_BACKUP_SEMANTICS, - FILE_ID_INFO, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileBasicInfo, - FileIdExtdDirectoryInfo, FileIdExtdDirectoryRestartInfo, FileIdInfo, - GetFileInformationByHandleEx, OPEN_EXISTING, + FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_DELETE, FILE_SHARE_READ, + FILE_SHARE_WRITE, OPEN_EXISTING, }; -use wtf_string::Wtf16Str; -use crate::buffer::NativeBuffer; use crate::error::{EnumerationError, Win32Error}; use crate::request::{MINIMUM_BUFFER_CAPACITY, RECORD_ALIGNMENT}; @@ -60,7 +69,13 @@ const FILE_LIST_DIRECTORY: u32 = 0x0000_0001; /// `FILE_FLAG_BACKUP_SEMANTICS` is what makes `CreateFileW` return a *directory* /// handle at all; without it the call fails on any directory. Sharing is left /// wide open because enumerating a directory must not stop anyone else from -/// using it. +/// using it. Those are the only flags, so the handle is not overlapped and is +/// never associated with a completion port: `GetFileInformationByHandleEx` has +/// no overlapped form, and this handle is queried synchronously on a worker. +/// +/// The open's code is the request crate's: it reads the thread's last error in +/// the statement after the call, so it is taken before the guard restores the +/// thread token and nothing in between can overwrite it. /// /// # Errors /// @@ -68,44 +83,15 @@ const FILE_LIST_DIRECTORY: u32 = 0x0000_0001; /// be applied -- in which case nothing was opened under the wrong identity -- /// and [`EnumerationError::DirectoryOpen`] with the raw code otherwise. pub(crate) fn open_directory( - path: &Wtf16Str, + path: &AnchoredPath, token: &ImpersonationToken, -) -> Result { - // Terminated for `CreateFileW`; the request already rejected interior NULs, - // so this names exactly the path the caller asked for. - let path = wtf_string::Wtf16String::from_units(path.as_units()); +) -> Result { + let open = open_request(path); let opened = token - .with_impersonation(|| { - // SAFETY: `path` is a NUL-terminated wide string that outlives the - // call, and every other argument is a plain value. - let handle = unsafe { - CreateFileW( - path.as_terminated_ptr(), - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - // Read before the guard restores the thread token, so nothing - // in between can overwrite the thread's last error. - Err(Win32Error::last()) - } else { - Ok(handle) - } - }) + .with_impersonation(|| open.perform()) .map_err(EnumerationError::Impersonation)?; - - let handle = match opened { - // SAFETY: `CreateFileW` succeeded, so this is a live handle this call - // exclusively owns. - Ok(handle) => unsafe { OwnedHandle::from_raw_handle(handle as _) }, - Err(code) => return Err(EnumerationError::DirectoryOpen(code)), - }; + let handle = SharedHandle::from(opened.map_err(EnumerationError::DirectoryOpen)?); // `FILE_LIST_DIRECTORY` is the same bit as `FILE_READ_DATA`, so this open // succeeds on an ordinary *file* too. Establishing directory-ness here, @@ -121,29 +107,22 @@ pub(crate) fn open_directory( Ok(handle) } +/// The open, as the request crate's entry: listing access, every share, an existing directory, and +/// backup semantics, with nothing else. +fn open_request(path: &AnchoredPath) -> OpenFile { + OpenFile::from_anchored(path.clone()) + .with_desired_access(FILE_LIST_DIRECTORY) + .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .with_creation_disposition(OPEN_EXISTING) + .with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS) +} + /// Whether an opened handle refers to a directory. -fn is_directory(handle: &OwnedHandle) -> Result { - let mut info = FILE_BASIC_INFO { - CreationTime: 0, - LastAccessTime: 0, - LastWriteTime: 0, - ChangeTime: 0, - FileAttributes: 0, - }; - // SAFETY: `handle` is live, and the buffer matches the requested class in - // both type and size. - let ok = unsafe { - GetFileInformationByHandleEx( - handle.as_raw_handle() as HANDLE, - FileBasicInfo, - (&raw mut info).cast(), - size_of::() as u32, - ) - }; - if ok == 0 { - return Err(EnumerationError::DirectoryOpen(Win32Error::last())); - } - Ok(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0) +fn is_directory(handle: &SharedHandle) -> Result { + let info = QueryFileBasicInfo::new(handle.clone()) + .perform() + .map_err(EnumerationError::DirectoryOpen)?; + Ok(info.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0) } /// Ask which volume a directory lives on. @@ -156,27 +135,10 @@ fn is_directory(handle: &OwnedHandle) -> Result { /// /// Returns the raw code. A redirector may list a directory happily and still /// refuse this class, which is why `BestEffort` exists. -pub(crate) fn volume_serial(directory: &OwnedHandle) -> Result { - let mut info = FILE_ID_INFO { - VolumeSerialNumber: 0, - FileId: windows_sys::Win32::Storage::FileSystem::FILE_ID_128 { - Identifier: [0; 16], - }, - }; - // SAFETY: `directory` is a live handle, and the buffer matches the class - // being requested in both type and size. - let ok = unsafe { - GetFileInformationByHandleEx( - directory.as_raw_handle() as HANDLE, - FileIdInfo, - (&raw mut info).cast(), - size_of::() as u32, - ) - }; - if ok == 0 { - return Err(Win32Error::last()); - } - Ok(info.VolumeSerialNumber) +pub(crate) fn volume_serial(directory: &SharedHandle) -> Result { + QueryFileIdInfo::new(directory.clone()) + .perform() + .map(|info| info.volume_serial_number()) } /// Which directory-information query a refill is. @@ -202,87 +164,99 @@ pub(crate) enum RefillOutcome { Failed(EnumerationError), } +/// The query a refill performs: the restart class for the first, the continuing class for every +/// later one, on a clone of the directory's handle. +fn refill_query(directory: &SharedHandle, which: Refill) -> QueryFileInformation { + let class = match which { + Refill::First => FileInformationClass::ID_EXTD_DIRECTORY_RESTART, + Refill::Next => FileInformationClass::ID_EXTD_DIRECTORY, + }; + QueryFileInformation::new(directory.clone(), class) +} + /// Read one batch of directory records into the buffer. /// +/// The query is the request crate's `QueryFileInformation`, built from a clone +/// of the directory's handle and performed into the buffer this crate owns, so +/// nothing is allocated and no handle is made per refill. +/// /// # Panics /// /// Never in a release build; a failure is reported as [`RefillOutcome::Failed`] /// so a caller cannot forget to classify it. In a debug build, panics if this -/// crate's own preconditions for that classification do not hold: a live -/// crate-opened handle, a valid information class, a non-null 8-byte-aligned -/// buffer base, and an effective capacity that is at least +/// crate's own preconditions for that classification do not hold: a non-null +/// 8-byte-aligned buffer base, and a length that is at least /// [`MINIMUM_BUFFER_CAPACITY`], an 8-byte multiple, and `u32`-representable. /// Every path that reaches this function already establishes all of these /// before calling it; a violation would be a bug in this crate, not a /// filesystem incapability, which is exactly what /// [`classify_refill_failure`] must never confuse the two for. pub(crate) fn refill( - directory: &OwnedHandle, - buffer: &mut NativeBuffer, + directory: &SharedHandle, + buffer: &mut AlignedBuf, which: Refill, ) -> RefillOutcome { - let class = match which { - Refill::First => FileIdExtdDirectoryRestartInfo, - Refill::Next => FileIdExtdDirectoryInfo, - }; - let capacity = buffer.capacity(); + let capacity = buffer.len(); // Asserted here, once, immediately before the call whose // `ERROR_INVALID_FUNCTION` / `ERROR_NOT_SUPPORTED` / `ERROR_INVALID_PARAMETER` // failure `classify_refill_failure` reads as "this filesystem cannot do // extended directory information". Every one of these is already true by // construction on every path that reaches this function; asserting them is - // what stops a future regression in handle, class, or buffer handling from - // being silently misreported as a filesystem incapability instead of - // caught as this crate's own bug. - let handle = directory.as_raw_handle() as HANDLE; + // what stops a future regression in buffer handling from being silently + // misreported as a filesystem incapability instead of caught as this + // crate's own bug. debug_assert!( - !handle.is_null() && handle != INVALID_HANDLE_VALUE, - "the directory handle must be a live handle this crate opened" + !buffer.as_ptr().is_null(), + "the buffer base must not be null" ); - debug_assert!( - class == FileIdExtdDirectoryRestartInfo || class == FileIdExtdDirectoryInfo, - "the information class must be one this crate's refill actually requests" - ); - let base = buffer.as_mut_ptr(); - debug_assert!(!base.is_null(), "the buffer base must not be null"); debug_assert_eq!( - (base as usize) % RECORD_ALIGNMENT, + buffer.as_ptr().addr() % RECORD_ALIGNMENT, 0, "the buffer base must be 8-byte aligned" ); debug_assert!( - capacity as usize >= MINIMUM_BUFFER_CAPACITY, + capacity >= MINIMUM_BUFFER_CAPACITY, "the effective capacity must be at least the minimum buffer capacity" ); debug_assert_eq!( - capacity as usize % RECORD_ALIGNMENT, + capacity % RECORD_ALIGNMENT, 0, "the effective capacity must be an 8-byte multiple" ); + debug_assert!( + u32::try_from(capacity).is_ok(), + "the effective capacity must fit the call's DWORD" + ); - // SAFETY: `directory` is a live directory handle; the buffer is a live, - // 8-byte-aligned allocation of exactly `capacity` bytes, which is what the - // directory-information classes write into. - let ok = unsafe { GetFileInformationByHandleEx(handle, class, base, capacity) }; - if ok != 0 { - return RefillOutcome::Batch; - } - let code = Win32Error::last(); - match classify_refill_failure(code, which, capacity as usize) { - Some(error) => RefillOutcome::Failed(error), - None => RefillOutcome::Exhausted, + match refill_query(directory, which).perform_into(buffer) { + Ok(()) => RefillOutcome::Batch, + Err(PerformIntoError::Win32(code)) => { + match classify_refill_failure(code, which, capacity) { + Some(error) => RefillOutcome::Failed(error), + None => RefillOutcome::Exhausted, + } + } + // The request crate refuses a buffer ahead of the call only for classes whose structure + // page requires an alignment, and neither extended-directory class is one; this buffer is + // 8-aligned besides. So this is unreachable, and reporting a failure beats a panic in a + // thread-pool callback. Code zero says Windows produced none, as `engine::advance` does + // for the other state it cannot reach. + Err(_) => { + debug_assert!(false, "perform_into refused a buffer this crate aligned"); + RefillOutcome::Failed(EnumerationError::DirectoryQuery(Win32Error::from_code(0))) + } } } /// Decide what a failed refill means, or `None` when it means clean exhaustion. /// -/// The preconditions the unsupported-class mapping depends on -- a live handle -/// this crate opened, a valid class, an 8-byte-aligned non-null base, and a -/// clamped, aligned, `u32`-representable capacity -- are asserted in -/// [`refill`], the caller, immediately before the call whose failure this -/// classifies, which is what makes it safe to read those codes as "the -/// filesystem cannot do this" rather than "this crate asked wrongly". +/// The preconditions the unsupported-class mapping depends on -- an +/// 8-byte-aligned non-null base, and a clamped, aligned, `u32`-representable +/// capacity -- are asserted in [`refill`], the caller, immediately before the +/// call whose failure this classifies, which is what makes it safe to read those +/// codes as "the filesystem cannot do this" rather than "this crate asked +/// wrongly". The class is one of the two the request crate names, built there. fn classify_refill_failure( code: Win32Error, which: Refill, diff --git a/crates/windows-file-enumeration-sys/src/native/tests.rs b/crates/windows-file-enumeration-sys/src/native/tests.rs index 9249c3710..43bf4fe5a 100644 --- a/crates/windows-file-enumeration-sys/src/native/tests.rs +++ b/crates/windows-file-enumeration-sys/src/native/tests.rs @@ -15,12 +15,14 @@ fn wide(path: &std::path::Path) -> Wtf16String { Wtf16String::from_os_str(path.as_os_str()) } -fn buffer() -> NativeBuffer { - NativeBuffer::try_new(MINIMUM_BUFFER_CAPACITY).expect("allocation") +fn buffer() -> AlignedBuf { + AlignedBuf::zeroed(MINIMUM_BUFFER_CAPACITY, RECORD_ALIGNMENT).expect("allocation") } -fn open(path: &std::path::Path) -> Result { - open_directory(&wide(path), &token()) +fn open(path: &std::path::Path) -> Result { + let anchored = + AnchoredPath::new(wide(path)).expect("an absolute path has no current directory to read"); + open_directory(&anchored, &token()) } #[test] @@ -212,3 +214,82 @@ fn an_unrecognised_code_is_an_ordinary_query_failure() { "{error:?}" ); } + +// --- The directory's calls are the request crate's entries (SAFE-2) ---------------------------- + +#[test] +fn the_open_asks_for_listing_with_every_share_and_backup_semantics_and_nothing_more() { + // The constraints M21.3 recorded, on the request the open performs: listing access and not + // `GENERIC_READ`; every share, so enumerating stops no one; an existing directory; and backup + // semantics, which a directory handle needs at all. The flags are exactly that one, so the + // handle is not overlapped. + // A relative path, so the Win32 form is the anchored one and differs from the given one: an + // open that anchored it a second time, or opened the given path, would not match. + let anchored = AnchoredPath::new(wide(std::path::Path::new("a-relative-child"))) + .expect("the current directory is readable"); + let request = open_request(&anchored); + + assert_eq!(request.desired_access(), FILE_LIST_DIRECTORY); + assert_eq!( + request.share_mode(), + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE + ); + assert_eq!(request.creation_disposition(), OPEN_EXISTING); + assert_eq!(request.flags_and_attributes(), FILE_FLAG_BACKUP_SEMANTICS); + assert!(request.security().is_none() && request.template().is_none()); + assert_eq!(request.path(), anchored.given()); + assert_eq!(request.win32_path(), anchored.win32()); + assert_ne!(request.win32_path(), request.path()); +} + +#[test] +fn an_open_directory_shares_delete_so_it_does_not_stop_a_rename() { + let scratch = Scratch::with_files(&["inside.txt"]); + let held = scratch.child("held"); + std::fs::create_dir(&held).expect("a subdirectory"); + let directory = open(&held).expect("open"); + + std::fs::rename(&held, scratch.child("renamed")).expect("a held directory can be renamed"); + assert!( + volume_serial(&directory).is_ok(), + "the handle follows the rename" + ); +} + +#[test] +fn a_refill_query_is_the_phases_class_on_the_handle_it_was_given() { + let scratch = Scratch::empty(); + let directory = open(scratch.path()).expect("open"); + + let first = refill_query(&directory, Refill::First); + let next = refill_query(&directory, Refill::Next); + + assert_eq!( + first.class(), + FileInformationClass::ID_EXTD_DIRECTORY_RESTART + ); + assert_eq!(next.class(), FileInformationClass::ID_EXTD_DIRECTORY); + assert!(first.handle().same_handle(&directory)); + assert!(next.handle().same_handle(&directory)); +} + +#[test] +fn a_whole_read_leaves_the_directory_with_no_other_holder() { + // `try_into_owned` succeeds only for the last holder, so a clone kept by the open, the + // directory check, the serial read or any refill would show here. Together with the entries + // holding the very handle they are given, this is what "makes no handle beyond its open" is. + let scratch = Scratch::with_files(&["a.txt", "b.txt", "c.txt"]); + let directory = open(scratch.path()).expect("open"); + let mut buffer = buffer(); + volume_serial(&directory).expect("a serial"); + + assert!(matches!( + refill(&directory, &mut buffer, Refill::First), + RefillOutcome::Batch + )); + while let RefillOutcome::Batch = refill(&directory, &mut buffer, Refill::Next) {} + + directory + .try_into_owned() + .expect("every query's share was released"); +} diff --git a/crates/windows-file-enumeration-sys/src/path.rs b/crates/windows-file-enumeration-sys/src/path.rs deleted file mode 100644 index 0d4d08432..000000000 --- a/crates/windows-file-enumeration-sys/src/path.rs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! The request path contract: what a caller may name, and what gets stored. -//! -//! A request resolves its path **when it is built**, on the submitting thread. -//! Deferring that to a worker would let the meaning of a relative path change -//! between submission and execution, because the process current directory is -//! shared mutable state that nothing in this crate controls. Resolving early -//! also separates the two concerns cleanly: string resolution happens here, and -//! the privileged open happens later under the captured token. -//! -//! # Two path families -//! -//! A `\\?\` path is *verbatim*: Win32 disables path parsing for it, so the crate -//! stores it code unit for code unit. It is checked for full qualification -- -//! the one property the prefix promises and a caller can get wrong -- and -//! otherwise left alone. Trailing separators and `.`/`..` components are -//! preserved, because in verbatim form they are literal name components rather -//! than syntax. -//! -//! Everything else, including `\\.\` device paths, goes through -//! `GetFullPathNameW`. Those forms *are* normalised by Win32, so resolving them -//! here produces exactly the path a later `CreateFileW` would have used. -//! -//! # Why ordinary paths stop at `MAX_PATH` -//! -//! Whether `CreateFileW` accepts a longer ordinary path depends on the host -//! executable's `longPathAware` manifest and on system policy -- neither of -//! which this crate controls, and both of which belong to whoever *embeds* it. -//! Letting them decide would make the same call succeed in one host and fail in -//! another. The crate instead draws the line itself: ordinary paths stop at -//! `MAX_PATH`, and a caller who wants a longer one says so explicitly with a -//! fully qualified `\\?\` path, which has never depended on the manifest. - -use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW; -use wtf_string::{Wtf16Str, Wtf16String}; - -use crate::error::{RequestError, RequestFailure, Win32Error}; - -/// `MAX_PATH`: the ordinary Win32 path limit, counting the terminator. -const MAX_PATH: usize = 260; - -/// The longest ordinary path content, excluding the terminator. -const MAX_PATH_CONTENT: usize = MAX_PATH - 1; - -/// The Win32 verbatim prefix, `\\?\`. -const VERBATIM_PREFIX: [u16; 4] = [b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]; - -/// The `UNC\` component that follows the verbatim prefix for a network path. -const VERBATIM_UNC: [u16; 4] = [b'U' as u16, b'N' as u16, b'C' as u16, b'\\' as u16]; - -const BACKSLASH: u16 = b'\\' as u16; -const COLON: u16 = b':' as u16; - -/// Validate and, where the contract calls for it, resolve a caller's path. -/// -/// The returned value is the exact path a worker will later open. -/// -/// # Errors -/// -/// Returns [`RequestError`] for an empty path, an interior NUL, a `\\?\` path -/// that is not fully qualified, an ordinary path that exceeds `MAX_PATH` before -/// or after resolution, or a resolution failure reported by Windows. -pub(crate) fn prepare(path: &Wtf16Str) -> Result { - if path.is_empty() { - return Err(RequestError::new(RequestFailure::EmptyPath)); - } - if path.has_interior_nul() { - return Err(RequestError::new(RequestFailure::InteriorNul)); - } - - let units = path.as_units(); - if units.starts_with(&VERBATIM_PREFIX) { - validate_verbatim(&units[VERBATIM_PREFIX.len()..])?; - return Ok(Wtf16String::from_units(units)); - } - - if units.len() > MAX_PATH_CONTENT { - return Err(RequestError::new(RequestFailure::PathTooLong)); - } - resolve(path) -} - -/// Check that a `\\?\` path names an absolute root. -/// -/// `rest` is everything after the prefix. Win32 will not parse this path, so it -/// must already be the absolute form: a relative or rootless verbatim path -/// cannot be repaired later and would fail at open with a code that says nothing -/// about why. -fn validate_verbatim(rest: &[u16]) -> Result<(), RequestError> { - let not_qualified = || RequestError::new(RequestFailure::NotFullyQualified); - - if rest.starts_with(&VERBATIM_UNC) { - // `\\?\UNC\server\share`: both components must be present and non-empty, - // because a server with no share names no filesystem to enumerate. - let after_unc = &rest[VERBATIM_UNC.len()..]; - let Some(separator) = after_unc.iter().position(|unit| *unit == BACKSLASH) else { - return Err(not_qualified()); - }; - let server = &after_unc[..separator]; - let share = &after_unc[separator + 1..]; - let share_len = share - .iter() - .position(|unit| *unit == BACKSLASH) - .unwrap_or(share.len()); - if server.is_empty() || share_len == 0 { - return Err(not_qualified()); - } - return Ok(()); - } - - // Any other verbatim form needs a non-empty root component followed by a - // separator: `\\?\C:\`, `\\?\Volume{...}\`, and so on. - let Some(separator) = rest.iter().position(|unit| *unit == BACKSLASH) else { - return Err(not_qualified()); - }; - let root = &rest[..separator]; - if root.is_empty() { - return Err(not_qualified()); - } - // A DOS drive root is spelled exactly `X:`. Rejecting `\\?\C:foo` matters - // because that is drive-*relative*: verbatim parsing would treat the whole - // thing as a literal name rather than the current directory on C:. - if root.contains(&COLON) && !is_drive_designator(root) { - return Err(not_qualified()); - } - Ok(()) -} - -/// Whether `root` is exactly an ASCII drive designator such as `C:`. -fn is_drive_designator(root: &[u16]) -> bool { - let [letter, colon] = root else { - return false; - }; - *colon == COLON && u8::try_from(*letter).is_ok_and(|byte| byte.is_ascii_alphabetic()) -} - -/// Resolve an ordinary path against the current directory, as Win32 would. -fn resolve(path: &Wtf16Str) -> Result { - // `Wtf16Str` is a borrowed slice with no terminator, so the input is copied - // into an owned value first: `GetFullPathNameW` takes a `PCWSTR`. - let input = Wtf16String::from_units(path.as_units()); - let mut resolved = Wtf16String::with_capacity(MAX_PATH); - // SAFETY: `input` has no interior NUL (checked by `prepare`) so its - // terminated pointer is a valid C string, and `resolved` has room for - // `MAX_PATH` content units plus the terminator this call writes. The - // buffer is not observed through any other method until `set_len_from_ffi` - // below restores the always-terminated invariant. - let written = unsafe { - GetFullPathNameW( - input.as_terminated_ptr(), - MAX_PATH as u32, - resolved.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - - if written == 0 { - let code = Win32Error::last(); - // The buffer was handed to Win32 and may hold anything; restore the - // empty-string invariant before the value is dropped or observed. - // SAFETY: zero content units are trivially initialised and within - // capacity. - unsafe { resolved.set_len_from_ffi(0) }; - return Err(RequestError::with_code( - RequestFailure::PathResolution, - code, - )); - } - - let written = written as usize; - // A return at or above the buffer size is the "needed this much including - // the terminator" form, which here can only mean the resolved path does not - // fit the ordinary limit. Nothing usable was written, so the invariant is - // restored the same way as on failure. - if written > MAX_PATH_CONTENT { - // SAFETY: as above. - unsafe { resolved.set_len_from_ffi(0) }; - return Err(RequestError::new(RequestFailure::PathTooLong)); - } - - // SAFETY: `GetFullPathNameW` wrote `written` content units plus its own - // terminator, and `written` is within the requested capacity. - unsafe { resolved.set_len_from_ffi(written) }; - if resolved.is_empty() { - // Defensive: a non-zero return with no content would leave a path that - // names nothing, which must not reach a worker. - return Err(RequestError::new(RequestFailure::PathResolution)); - } - Ok(resolved) -} - -#[cfg(test)] -mod tests; diff --git a/crates/windows-file-enumeration-sys/src/path/tests.rs b/crates/windows-file-enumeration-sys/src/path/tests.rs deleted file mode 100644 index fa68f16c6..000000000 --- a/crates/windows-file-enumeration-sys/src/path/tests.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) 2026 Mike Grier -//! Tests for the request path contract. - -use super::*; -use std::env; - -fn prepare_str(path: &str) -> Result { - prepare(&Wtf16String::from(path)) -} - -fn text(path: &Wtf16Str) -> String { - path.to_string_lossy() -} - -#[test] -fn an_empty_path_is_rejected() { - let error = prepare(&Wtf16String::new()).expect_err("an empty path names nothing"); - assert_eq!(error.failure(), RequestFailure::EmptyPath); - assert_eq!(error.code(), None); -} - -#[test] -fn an_interior_nul_is_rejected() { - // Win32 would stop at the NUL and open a shorter, different path. - let path = Wtf16String::from_units(&[0x0043, 0x003A, 0x005C, 0x0000, 0x0061]); - let error = prepare(&path).expect_err("an interior NUL truncates the path"); - assert_eq!(error.failure(), RequestFailure::InteriorNul); -} - -#[test] -fn a_verbatim_drive_path_is_kept_exactly() { - let prepared = prepare_str(r"\\?\C:\Windows\System32").expect("fully qualified"); - assert_eq!(text(&prepared), r"\\?\C:\Windows\System32"); -} - -#[test] -fn a_verbatim_path_keeps_its_trailing_separator() { - // In verbatim form a trailing separator is a literal component boundary, - // not syntax this crate may tidy away. - let prepared = prepare_str(r"\\?\C:\Windows\").expect("fully qualified"); - assert_eq!(text(&prepared), r"\\?\C:\Windows\"); -} - -#[test] -fn a_verbatim_path_keeps_dot_components_verbatim() { - let prepared = prepare_str(r"\\?\C:\a\..\b").expect("fully qualified"); - assert_eq!(text(&prepared), r"\\?\C:\a\..\b"); -} - -#[test] -fn a_verbatim_path_may_exceed_max_path() { - let long = format!(r"\\?\C:\{}", "a".repeat(400)); - let prepared = prepare_str(&long).expect("verbatim paths carry no MAX_PATH limit"); - assert_eq!(text(&prepared), long); -} - -#[test] -fn a_verbatim_unc_path_is_accepted() { - let prepared = prepare_str(r"\\?\UNC\server\share\dir").expect("fully qualified"); - assert_eq!(text(&prepared), r"\\?\UNC\server\share\dir"); - // The share alone, with no trailing component, is still a directory. - prepare_str(r"\\?\UNC\server\share").expect("a share root is fully qualified"); -} - -#[test] -fn a_verbatim_volume_guid_path_is_accepted() { - prepare_str(r"\\?\Volume{12345678-1234-1234-1234-123456789abc}\dir") - .expect("a volume GUID names an absolute root"); -} - -#[test] -fn a_drive_relative_verbatim_path_is_rejected() { - // `\\?\C:foo` is drive-*relative*, and verbatim parsing would treat the - // whole thing as one literal name rather than resolving it. - let error = prepare_str(r"\\?\C:foo").expect_err("not fully qualified"); - assert_eq!(error.failure(), RequestFailure::NotFullyQualified); -} - -#[test] -fn a_rootless_verbatim_path_is_rejected() { - let error = prepare_str(r"\\?\name").expect_err("no root component"); - assert_eq!(error.failure(), RequestFailure::NotFullyQualified); -} - -#[test] -fn a_verbatim_path_with_an_empty_root_is_rejected() { - let error = prepare_str(r"\\?\\dir").expect_err("the root component is empty"); - assert_eq!(error.failure(), RequestFailure::NotFullyQualified); -} - -#[test] -fn an_incomplete_verbatim_unc_path_is_rejected() { - for path in [ - r"\\?\UNC\server", - r"\\?\UNC\server\", - r"\\?\UNC\\share", - r"\\?\UNC\", - ] { - let error = prepare_str(path).expect_err("a server without a share names no filesystem"); - assert_eq!( - error.failure(), - RequestFailure::NotFullyQualified, - "for {path}" - ); - } -} - -#[test] -fn an_ordinary_absolute_path_is_resolved_and_kept() { - let prepared = prepare_str(r"C:\Windows\System32").expect("resolvable"); - assert_eq!(text(&prepared), r"C:\Windows\System32"); -} - -#[test] -fn an_ordinary_path_is_normalised_by_win32() { - // Unlike a verbatim path, an ordinary one is the form Win32 itself parses, - // so resolving it here yields exactly what a later open would have used. - let prepared = prepare_str(r"C:\Windows\..\Windows\System32").expect("resolvable"); - assert_eq!(text(&prepared), r"C:\Windows\System32"); -} - -#[test] -fn forward_slashes_are_normalised() { - let prepared = prepare_str("C:/Windows/System32").expect("resolvable"); - assert_eq!(text(&prepared), r"C:\Windows\System32"); -} - -#[test] -fn a_relative_path_is_snapshotted_against_the_current_directory() { - // The whole point of resolving at build time: the answer must not depend on - // what the current directory happens to be when a worker later runs. - let current = env::current_dir().expect("a current directory"); - let prepared = prepare_str("subdir").expect("resolvable"); - let expected = current.join("subdir"); - assert_eq!(text(&prepared), expected.to_string_lossy()); -} - -#[test] -fn an_ordinary_path_longer_than_max_path_is_rejected() { - let long = format!(r"C:\{}", "a".repeat(400)); - let error = prepare_str(&long).expect_err("beyond the ordinary limit"); - assert_eq!(error.failure(), RequestFailure::PathTooLong); -} - -#[test] -fn a_relative_path_that_resolves_past_max_path_is_rejected() { - // Short enough on input, too long once the current directory is prepended: - // the limit has to be applied to the resolved form as well. - let current = env::current_dir().expect("a current directory"); - let room = 259usize.saturating_sub(current.to_string_lossy().len()); - let error = prepare_str(&"a".repeat(room + 8)).expect_err("resolves past the ordinary limit"); - assert_eq!(error.failure(), RequestFailure::PathTooLong); -} - -#[test] -fn a_reserved_device_name_resolves_into_the_device_namespace() { - // `NUL` is not a directory, but that is discovered when it is opened; the - // path contract only has to produce a well-formed stored path. - let prepared = prepare_str("NUL").expect("resolvable"); - assert_eq!(text(&prepared), r"\\.\NUL"); -} - -#[test] -fn a_device_namespace_path_is_resolved_rather_than_kept_verbatim() { - // Only `\\?\` disables path parsing; `\\.\` is normalised like any other - // ordinary form. - let prepared = prepare_str(r"\\.\C:\Windows\..\Windows").expect("resolvable"); - assert_eq!(text(&prepared), r"\\.\C:\Windows"); -} - -// --------------------------------------------------------------------------- -// Boundaries. -// -// The tests above use comfortably-wrong values -- a 400-character path, a -// relative path eight units past the limit -- which prove the check exists but -// not that it is in the right place. A mutation sweep moved the limit by one in -// both directions and changed `>` to `>=` and `==`, and every one of those -// survived. These pin the exact unit at which the answer changes. -// --------------------------------------------------------------------------- - -/// An absolute path of exactly `units` UTF-16 units, already in normal form so -/// `GetFullPathNameW` returns it unchanged and the resolved length is the input -/// length. -/// -/// UTF-16 units and not bytes or `char`s, because that is the unit Win32 -/// measures a path in: `MAX_PATH` is a count of `WCHAR`. The three coincide for -/// the ASCII this builds, so counting the prefix any other way would pass today -/// and quietly measure the wrong thing the moment a case uses a character that -/// is not one byte, one scalar, and one unit at once. -fn absolute_path_of_length(units: usize) -> String { - let prefix = r"C:\"; - let prefix_units = prefix.encode_utf16().count(); - assert!( - units >= prefix_units, - "asked for a {units}-unit path, but the {prefix} prefix is already \ - {prefix_units} units; the subtraction below would underflow and panic \ - without saying why" - ); - format!("{prefix}{}", "a".repeat(units - prefix_units)) -} - -#[test] -fn an_ordinary_path_of_exactly_max_path_content_is_accepted() { - // 259 = MAX_PATH - 1, the longest path that leaves room for the terminator. - // Rejecting this is the off-by-one that a "400 characters is too long" test - // cannot see, and it is the expensive direction: it refuses a path Windows - // would have opened. - let path = absolute_path_of_length(259); - assert_eq!(path.encode_utf16().count(), 259); - - let prepared = prepare_str(&path).expect("259 units is within the ordinary limit"); - assert_eq!(text(&prepared), path); -} - -#[test] -fn an_ordinary_path_one_unit_past_max_path_content_is_rejected() { - // 260 counts the terminator, so 260 content units do not fit. - let path = absolute_path_of_length(260); - assert_eq!(path.encode_utf16().count(), 260); - - let error = prepare_str(&path).expect_err("260 units leaves no room for the terminator"); - assert_eq!(error.failure(), RequestFailure::PathTooLong); -} - -#[test] -fn the_ordinary_limit_is_one_less_than_max_path() { - // The relationship the two tests above rest on, stated directly so a change - // to the constant fails here with its reason rather than only as a puzzling - // length assertion elsewhere. - assert_eq!(MAX_PATH_CONTENT, MAX_PATH - 1); - assert_eq!(MAX_PATH_CONTENT, 259); -} - -#[test] -fn a_verbatim_drive_relative_path_with_a_separator_is_rejected() { - // `\\?\C:foo` -- the existing case -- has no separator at all, so it is - // refused before the root is ever inspected and never reaches the - // drive-designator check. This form does reach it: the root is `C:foo`, - // which contains a colon but is not a drive. - // - // Without this, the check could report every root as a drive and nothing - // would notice. - let error = prepare_str(r"\\?\C:foo\bar").expect_err("drive-relative, not fully qualified"); - assert_eq!(error.failure(), RequestFailure::NotFullyQualified); -} - -#[test] -fn a_verbatim_root_needs_a_letter_before_its_colon_not_merely_a_colon() { - // Both halves of the drive-designator test are load-bearing, and only a - // root that satisfies one but not the other shows it. `1:` has the colon in - // the right place and is still not a drive, so a check that accepted - // *either* condition would wave it through. - for path in [r"\\?\1:\", r"\\?\1:\dir"] { - let error = prepare_str(path).expect_err("a digit is not a drive letter"); - assert_eq!( - error.failure(), - RequestFailure::NotFullyQualified, - "for {path}" - ); - } - - // Deliberately no companion case for "second unit is not a colon". The - // check is guarded by `root.contains(&COLON)`, so a colonless root -- a - // volume GUID, say -- never reaches it and is accepted on its own terms. - // `1:` is therefore the only shape that satisfies one half of the rule - // while failing the other, which is what makes it the whole test. - let prepared = prepare_str(r"\\?\Ca\dir").expect("a colonless root is not a drive at all"); - assert_eq!(text(&prepared), r"\\?\Ca\dir"); -} - -#[test] -fn a_verbatim_drive_root_is_still_accepted() { - // The positive case for the two tests above, so a check that rejected - // everything would not pass them by being uniformly strict. - let prepared = prepare_str(r"\\?\C:\dir").expect("a drive root is fully qualified"); - assert_eq!(text(&prepared), r"\\?\C:\dir"); -} diff --git a/crates/windows-file-enumeration-sys/src/pattern.rs b/crates/windows-file-enumeration-sys/src/pattern.rs index 61608e62d..80fe90c71 100644 --- a/crates/windows-file-enumeration-sys/src/pattern.rs +++ b/crates/windows-file-enumeration-sys/src/pattern.rs @@ -18,16 +18,18 @@ //! # Case //! //! [`CaseSensitivity::Sensitive`] is exact code-unit comparison, which needs no -//! Win32 call. [`CaseSensitivity::Insensitive`] uses `CompareStringOrdinal`, -//! Windows' own non-linguistic uppercase table -- the same notion of "equal -//! ignoring case" the filesystem uses, rather than a locale-dependent collation -//! that would make matching depend on the caller's user profile. +//! Win32 call. [`CaseSensitivity::Insensitive`] is `win-text-sys`'s +//! `compare_string_ordinal`, which is `CompareStringOrdinal`: Windows' own +//! non-linguistic uppercase table -- the same notion of "equal ignoring case" +//! the filesystem uses, rather than a locale-dependent collation that would +//! make matching depend on the caller's user profile. //! //! Ordinal case folding is one-to-one per code unit, which is what lets a //! literal run consume exactly its own length even when matched insensitively. -use windows_sys::Win32::Foundation::TRUE; -use windows_sys::Win32::Globalization::{CSTR_EQUAL, CompareStringOrdinal}; +use std::cmp::Ordering; + +use win_text_sys::{Case, CompareStringOrdinalError, compare_string_ordinal}; use wtf_string::{Wtf16Str, Wtf16String}; /// How a name comparison treats case. @@ -187,37 +189,37 @@ fn units_equal(left: &[u16], right: &[u16], case: CaseSensitivity) -> bool { /// Whether two runs are equal through Windows' ordinal uppercase table. /// -/// `CompareStringOrdinal` accepts explicit lengths, so an interior NUL in a name -/// is compared as content rather than terminating the comparison. An empty run -/// is handled here rather than passed on, because a zero length would otherwise -/// have to be distinguished from the API's own "NUL-terminated" convention. +/// The comparison is given each run with its length, so an interior NUL in a +/// name is compared as content rather than terminating the comparison, and an +/// empty run is an ordinary comparison. fn ordinal_equal_ignoring_case(left: &[u16], right: &[u16]) -> bool { if left.len() != right.len() { return false; } - if left.is_empty() { - return true; + equal_from( + compare_string_ordinal(left, right, Case::Insensitive), + left, + right, + ) +} + +/// What a comparison's result says about whether `left` and `right` are equal. +/// +/// A failure of the call is not equality. A run too long for the call's count +/// cannot be a filesystem name, and is compared exactly rather than truncated +/// to a prefix; reaching it takes a slice of more than 2^31 UTF-16 units, over +/// four gigabytes of name, which no unit test should allocate, so it is tested +/// here through the result and not through a slice that long. +fn equal_from( + result: Result, + left: &[u16], + right: &[u16], +) -> bool { + match result { + Ok(ordering) => ordering == Ordering::Equal, + Err(CompareStringOrdinalError::TooLong { .. }) => left == right, + Err(CompareStringOrdinalError::Call(_)) => false, } - // Both fallbacks below survive a mutation run, and neither is a test gap: - // reaching one needs a slice of more than 2^31 UTF-16 units, which is over - // four gigabytes of name. No filesystem produces that and no unit test - // should allocate it, so these are unreachable in practice rather than - // untested. They are kept because "cannot happen" and "is handled" are - // different claims, and the second one costs two lines. - let Ok(left_len) = i32::try_from(left.len()) else { - // A run this long cannot be a filesystem name; fall back to the exact - // comparison rather than truncating the length and comparing a prefix. - return left == right; - }; - let Ok(right_len) = i32::try_from(right.len()) else { - return left == right; - }; - // SAFETY: both pointers address `left_len`/`right_len` initialised code - // units, which is exactly the counted form this API documents; the call - // reads only that many units and writes nothing. - let result = - unsafe { CompareStringOrdinal(left.as_ptr(), left_len, right.as_ptr(), right_len, TRUE) }; - result == CSTR_EQUAL } /// How many code units the first code point of `units` occupies, or `None` when diff --git a/crates/windows-file-enumeration-sys/src/pattern/tests.rs b/crates/windows-file-enumeration-sys/src/pattern/tests.rs index fadfece13..62e40ad69 100644 --- a/crates/windows-file-enumeration-sys/src/pattern/tests.rs +++ b/crates/windows-file-enumeration-sys/src/pattern/tests.rs @@ -202,6 +202,47 @@ fn only_whole_names_match() { assert!(!matches(&subject, "catalog")); } +#[test] +fn an_empty_run_is_equal_to_an_empty_run_under_either_case() { + assert!(ordinal_equal_ignoring_case(&[], &[])); + assert!(units_equal(&[], &[], CaseSensitivity::Insensitive)); + assert!(units_equal(&[], &[], CaseSensitivity::Sensitive)); +} + +#[test] +fn a_comparison_result_is_equal_only_when_it_says_equal() { + let left = [u16::from(b'a')]; + let right = [u16::from(b'b')]; + assert!(equal_from(Ok(Ordering::Equal), &left, &right)); + assert!(!equal_from(Ok(Ordering::Less), &left, &left)); + assert!(!equal_from(Ok(Ordering::Greater), &left, &left)); +} + +#[test] +fn a_run_too_long_for_the_call_is_compared_exactly() { + use win_text_sys::Operand; + + let too_long = |len| { + Err(CompareStringOrdinalError::TooLong { + operand: Operand::First, + len, + }) + }; + let a = [u16::from(b'a')]; + let upper_a = [u16::from(b'A')]; + assert!(equal_from(too_long(1 << 32), &a, &a)); + assert!(!equal_from(too_long(1 << 32), &a, &upper_a)); +} + +#[test] +fn a_failure_of_the_call_is_not_equality() { + use win_error_codes::Win32Error; + + let failed = || Err(CompareStringOrdinalError::Call(Win32Error::from_code(87))); + let a = [u16::from(b'a')]; + assert!(!equal_from(failed(), &a, &a)); +} + #[test] fn sensitive_is_the_default_case_rule() { assert_eq!(CaseSensitivity::default(), CaseSensitivity::Sensitive); diff --git a/crates/windows-file-enumeration-sys/src/record.rs b/crates/windows-file-enumeration-sys/src/record.rs index 8e83dc214..7b9657388 100644 --- a/crates/windows-file-enumeration-sys/src/record.rs +++ b/crates/windows-file-enumeration-sys/src/record.rs @@ -20,8 +20,8 @@ //! //! # Why fields are read from a byte slice rather than a pointer cast //! -//! The batch is already known to be 8-byte aligned as a whole (see -//! [`buffer`](crate::buffer)), but a record's *content* -- its name -- is not +//! The batch is already known to be 8-byte aligned as a whole (it is read +//! into an [`io_buffers::AlignedBuf`] made at 8), but a record's *content* -- its name -- is not //! fixed width, so later records in the same batch are not guaranteed to sit on //! any particular alignment beyond the 8-byte boundary the API itself //! maintains between records. Reading every field with `from_ne_bytes` over a diff --git a/crates/windows-file-enumeration-sys/src/request.rs b/crates/windows-file-enumeration-sys/src/request.rs index 1f83edf2c..f056bf538 100644 --- a/crates/windows-file-enumeration-sys/src/request.rs +++ b/crates/windows-file-enumeration-sys/src/request.rs @@ -9,11 +9,11 @@ use std::path::Path; +use windows_namespace_request_sys::{AnchoredPath, PathCaptureError}; use wtf_string::{Wtf16Str, Wtf16String}; use crate::entry::FileIdentityMode; use crate::error::{RequestError, RequestFailure}; -use crate::path; use crate::predicate::EntryPredicate; /// The default native buffer capacity, in bytes. @@ -42,8 +42,8 @@ pub(crate) const RECORD_ALIGNMENT: usize = 8; /// /// **It says nothing about alignment**, and an earlier version of this comment /// claimed it did -- twice, in two places six lines apart, of which only one -/// was corrected on the first attempt. [`crate::buffer::NativeBuffer`] stores -/// `Vec`, so the allocation is 8-byte aligned; a length that is a whole +/// was corrected on the first attempt. The buffer is an [`io_buffers::AlignedBuf`] +/// made at 8 bytes, so its allocation is 8-byte aligned; a length that is a whole /// number of pages does not decide where the buffer *starts*, and a refill can /// begin partway through a page. What the constraint buys is a round request /// size with no part-page tail. Large-page allocations are a different @@ -68,7 +68,7 @@ const PAGE_SIZE: usize = 4096; // // **And the first repair over-claimed in turn**, which the same review caught: // it said a page-multiple length makes "each refill begin on a page boundary". -// It does not. `NativeBuffer` stores `Vec`, so the allocation is 8-byte +// It does not. The buffer is an `AlignedBuf` made at 8, so the allocation is 8-byte // aligned and nothing more -- a length that is a whole number of pages says // nothing about where the buffer starts. What the constraint actually buys is a // page-sized *request*: a round amount for the kernel to fill per call, and one @@ -108,7 +108,7 @@ const _: () = { /// One directory to enumerate, with the predicate and bounds that apply to it. #[derive(Clone, Debug, PartialEq, Eq)] pub struct EnumerationRequest { - path: Wtf16String, + path: AnchoredPath, predicate: EntryPredicate, file_identity_mode: FileIdentityMode, buffer_capacity: usize, @@ -117,19 +117,25 @@ pub struct EnumerationRequest { impl EnumerationRequest { /// Build a request for a native WTF-16 path. /// - /// The path is validated and, unless it is already a verbatim `\\?\` path, - /// resolved to its fully qualified form now. The stored value is exactly - /// what a worker will later open. + /// The path is kept as it was given, and a relative one is fixed to the + /// process current directory as it is now, so that the request names the + /// same directory wherever and whenever it is performed. That is + /// `windows-namespace-request-sys`'s path contract, its `AnchoredPath`, taken + /// from there and not kept a second time here. Nothing else about the path + /// is checked: an empty path, a path longer than `MAX_PATH` and a `\\?\` path + /// that is not fully qualified reach the open, and its error is the + /// enumeration's terminal [`DirectoryOpen`](crate::EnumerationError::DirectoryOpen). /// /// # Errors /// - /// Returns [`RequestError`] for an empty path, an interior NUL, a `\\?\` - /// path that is not fully qualified, an ordinary path longer than - /// `MAX_PATH` before or after resolution, or a resolution failure reported - /// by Windows. + /// [`RequestFailure::InteriorNul`] for a path with a NUL before its end, and + /// [`RequestFailure::PathCapture`] if the current directory had to be read + /// and could not be. pub fn new(path: &Wtf16Str) -> Result { + let path = + AnchoredPath::new(Wtf16String::from_units(path.as_units())).map_err(request_error)?; Ok(Self { - path: path::prepare(path)?, + path, predicate: EntryPredicate::default(), file_identity_mode: FileIdentityMode::default(), buffer_capacity: DEFAULT_BUFFER_CAPACITY, @@ -183,9 +189,22 @@ impl EnumerationRequest { Ok(self) } - /// The exact path a worker will open. + /// The path exactly as the caller gave it. #[must_use] pub fn path(&self) -> &Wtf16Str { + self.path.given() + } + + /// The path a worker will hand to Win32 to open the directory: the path as + /// given, or, for a relative one, that path grafted onto the current + /// directory of the moment the request was built. + #[must_use] + pub fn win32_path(&self) -> &Wtf16Str { + self.path.win32() + } + + /// The path contract the open is built from, which is the request crate's. + pub(crate) fn anchored_path(&self) -> &AnchoredPath { &self.path } @@ -209,6 +228,19 @@ impl EnumerationRequest { } } +/// A path that could not be captured, as this crate reports it. +fn request_error(error: PathCaptureError) -> RequestError { + match error { + PathCaptureError::InteriorNul => RequestError::new(RequestFailure::InteriorNul), + PathCaptureError::CurrentDirectory(code) => { + RequestError::with_code(RequestFailure::PathCapture, code) + } + // `CurrentDirectoryUnstable`, whose size kept changing and which has no + // code, and any failure of reading the current directory added later. + _ => RequestError::new(RequestFailure::PathCapture), + } +} + /// Clamp and align a requested capacity. fn effective_buffer_capacity(bytes: usize) -> Result { let clamped = bytes.max(MINIMUM_BUFFER_CAPACITY); diff --git a/crates/windows-file-enumeration-sys/src/request/tests.rs b/crates/windows-file-enumeration-sys/src/request/tests.rs index 93a73ad74..da1678b6e 100644 --- a/crates/windows-file-enumeration-sys/src/request/tests.rs +++ b/crates/windows-file-enumeration-sys/src/request/tests.rs @@ -19,9 +19,43 @@ fn a_request_defaults_to_the_documented_bounds() { } #[test] -fn a_request_stores_the_resolved_path() { - let subject = request("C:/Windows/../Windows"); - assert_eq!(subject.path().to_string_lossy(), r"C:\Windows"); +fn an_absolute_path_is_kept_as_given_and_handed_to_win32_as_given() { + // Nothing is resolved or normalised here: `CreateFileW` normalises the + // separators and the `..` itself when the worker opens it. + for given in [ + r"C:\Windows", + "C:/Windows/../Windows", + r"\\?\C:\Windows", + r"\\.\C:\Windows", + r"\\server\share\dir", + r"\Windows", + "C:Windows", + ] { + let subject = request(given); + assert_eq!(subject.path().to_string_lossy(), given); + assert_eq!(subject.win32_path().to_string_lossy(), given, "{given}"); + } +} + +#[test] +fn a_relative_path_is_grafted_onto_the_current_directory_when_built() { + let subject = request(r"a-child\below"); + assert_eq!(subject.path().to_string_lossy(), r"a-child\below"); + let expected = std::env::current_dir() + .expect("the current directory") + .join(r"a-child\below"); + assert_eq!( + subject.win32_path().to_string_lossy(), + expected.to_string_lossy() + ); +} + +#[test] +fn a_relative_name_that_is_a_device_is_not_grafted() { + // The request crate's rule 5: a reserved device name is left for Win32 to + // resolve, as it would have been given. + let subject = request("NUL"); + assert_eq!(subject.win32_path().to_string_lossy(), "NUL"); } #[test] @@ -40,9 +74,39 @@ fn a_request_can_be_built_from_a_std_path() { } #[test] -fn an_invalid_path_is_reported_when_the_request_is_built() { - let error = EnumerationRequest::new(&Wtf16String::new()).expect_err("an empty path"); - assert_eq!(error.failure(), RequestFailure::EmptyPath); +fn an_interior_nul_is_refused_when_the_request_is_built() { + for given in ["\0", "a\0", "\0a", "C:\\Win\0dows"] { + let error = EnumerationRequest::new(&Wtf16String::from_units( + &given.encode_utf16().collect::>(), + )) + .expect_err("an interior NUL"); + assert_eq!(error.failure(), RequestFailure::InteriorNul, "{given:?}"); + assert_eq!(error.code(), None, "no call was made"); + } +} + +#[test] +fn an_empty_path_is_accepted_and_left_to_the_open() { + // The request crate's contract refuses nothing else: what an empty path + // names is `CreateFileW`'s to answer, as a terminal `DirectoryOpen`. + let subject = EnumerationRequest::new(&Wtf16String::new()).expect("accepted"); + assert!(subject.path().is_empty()); + assert!(subject.win32_path().is_empty()); +} + +#[test] +fn a_path_longer_than_max_path_is_accepted_as_given() { + let long = format!(r"C:\{}", "x".repeat(300)); + let subject = request(&long); + assert_eq!(subject.win32_path().to_string_lossy(), long); +} + +#[test] +fn a_verbatim_path_that_is_not_fully_qualified_is_accepted_as_given() { + for given in [r"\\?\", r"\\?\C:foo", r"\\?\name", r"\\?\UNC\server"] { + let subject = request(given); + assert_eq!(subject.win32_path().to_string_lossy(), given); + } } #[test] diff --git a/crates/windows-file-enumeration-sys/src/session.rs b/crates/windows-file-enumeration-sys/src/session.rs index bf5d4a7d5..b8ecdebdd 100644 --- a/crates/windows-file-enumeration-sys/src/session.rs +++ b/crates/windows-file-enumeration-sys/src/session.rs @@ -38,7 +38,7 @@ //! already closed. use std::io; -use std::os::windows::io::{BorrowedHandle, OwnedHandle}; +use std::os::windows::io::BorrowedHandle; #[cfg(test)] use std::sync::atomic::AtomicBool; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; @@ -47,6 +47,7 @@ use std::time::Duration; use windows_impersonation_token_sys::ImpersonationToken; use windows_threadpool_sys::callback_env::CallbackEnviron; +use windows_threadpool_sys::wait::WaitableHandle; use windows_threadpool_sys::work::ThreadpoolWork; use crate::admission::{self, EnumerationHandle}; @@ -797,14 +798,20 @@ impl Receiver { self.shared.completions.doorbell() } - /// A duplicate of [`doorbell`](Self::doorbell) that the caller owns, as - /// arming a `ThreadpoolWait` requires. + /// The doorbell as a wait target for a `ThreadpoolWait`, sharing the ring's + /// handle rather than duplicating it. + /// + /// The target holds the one handle the ring holds, so it signals exactly as + /// [`doorbell`](Self::doorbell) does and the handle closes when the last + /// holder drops. A client cannot set or reset it through the target without + /// `unsafe`: that stays the ring's, which keeps the doorbell signalled + /// exactly while there is something to take. /// /// # Errors /// - /// Returns the error from `CreateEventW` or `DuplicateHandle`. - pub fn doorbell_owned(&self) -> io::Result { - self.shared.completions.doorbell_owned() + /// Returns the error from `CreateEventW` on the first call. + pub fn doorbell_waitable(&self) -> io::Result { + self.shared.completions.doorbell_waitable() } /// Whether the stream has ended. diff --git a/crates/windows-file-enumeration-sys/tests/integration/metadata.rs b/crates/windows-file-enumeration-sys/tests/integration/metadata.rs index ed34bc24c..3e46ea6a9 100644 --- a/crates/windows-file-enumeration-sys/tests/integration/metadata.rs +++ b/crates/windows-file-enumeration-sys/tests/integration/metadata.rs @@ -1,7 +1,7 @@ // Copyright (c) 2026 Mike Grier //! Metadata and file-identity fidelity: what the crate delivers, cross-checked -//! against what the standard library's own metadata query reports for the -//! same real file. +//! against what the standard library's own metadata query, and for identity the +//! request crate's `QueryFileIdInfo`, report for the same real file. use windows_file_enumeration_sys::{Completion, EnumerationRequest, FileIdentityMode, Session}; @@ -129,3 +129,40 @@ fn two_entries_from_the_same_enumeration_share_a_volume_serial() { .collect(); assert_eq!(ids.len(), 2, "distinct files must carry distinct file ids"); } + +#[test] +fn a_required_identity_is_what_the_request_crate_reads_from_the_file_itself() { + // The enumeration reads the identifier from the listing and the serial from the directory; the + // request crate's `QueryFileIdInfo` reads both from a handle on the file. They describe one file. + use windows_namespace_request_sys::QueryFileIdInfo; + + let scratch = Scratch::with_files(&["identified.txt"]); + let request = EnumerationRequest::for_path(scratch.path()) + .expect("resolvable") + .with_file_identity(FileIdentityMode::Required); + + let (session, receiver) = Session::new(8, 8).expect("room"); + let handle = session.try_begin(request).expect("room"); + let enumeration = handle.id(); + handle.detach(); + + let (entries, outcome) = drain_to_terminal(&receiver, enumeration); + assert!(outcome.is_completed(), "{outcome:?}"); + assert_eq!(entries.len(), 1); + let Completion::Entry { entry, .. } = &entries[0] else { + unreachable!(); + }; + + // The entry holds the handle as a `SharedHandle`, made from the opened file: the file is the + // handle's one owner, and nothing duplicates it. + let file = std::fs::File::open(scratch.child("identified.txt")).expect("open the file"); + let read = QueryFileIdInfo::new(file) + .perform() + .expect("the file's identity"); + + assert_eq!(entry.identity().id_bytes(), read.file_id()); + assert_eq!( + entry.identity().volume_serial(), + Some(read.volume_serial_number()) + ); +} diff --git a/crates/windows-file-enumeration-sys/tests/integration/paths.rs b/crates/windows-file-enumeration-sys/tests/integration/paths.rs index f3f613bb8..0324ad000 100644 --- a/crates/windows-file-enumeration-sys/tests/integration/paths.rs +++ b/crates/windows-file-enumeration-sys/tests/integration/paths.rs @@ -1,14 +1,15 @@ // Copyright (c) 2026 Mike Grier //! Invalid and inaccessible paths, a fully qualified `\\?\` path far beyond -//! `MAX_PATH`, and native WTF-16 names -- including one with an unpaired -//! surrogate, which is exactly the kind of filesystem content this crate's -//! native-width name storage exists to survive without loss. +//! `MAX_PATH`, a relative path fixed when the request is built, and native +//! WTF-16 names -- including one with an unpaired surrogate, which is exactly +//! the kind of filesystem content this crate's native-width name storage +//! exists to survive without loss. use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; -use windows_file_enumeration_sys::{EnumerationError, EnumerationRequest, RequestFailure, Session}; +use windows_file_enumeration_sys::{EnumerationError, EnumerationRequest, Session}; use crate::support::{Scratch, drain_to_terminal, entry_names}; @@ -104,11 +105,103 @@ fn a_verbatim_path_far_beyond_max_path_still_enumerates() { } #[test] -fn an_ordinary_path_beyond_max_path_is_rejected_rather_than_silently_truncated() { +fn an_ordinary_path_beyond_max_path_is_accepted_and_its_open_is_the_hosts() { + // The request keeps the path as given. Whether `CreateFileW` takes an + // unprefixed path past `MAX_PATH` depends on the host's long-path manifest + // and the machine's policy, so either outcome is a clean report. let long_name = "x".repeat(300); let path = PathBuf::from(r"C:\").join(long_name); - let error = EnumerationRequest::for_path(&path).expect_err("longer than MAX_PATH"); - assert_eq!(error.failure(), RequestFailure::PathTooLong); + let request = EnumerationRequest::for_path(&path).expect("accepted as given"); + assert_eq!(request.win32_path().len(), path.as_os_str().len()); + + let (session, receiver) = Session::new(8, 8).expect("room"); + let handle = session.try_begin(request).expect("room"); + let enumeration = handle.id(); + handle.detach(); + + let (entries, outcome) = drain_to_terminal(&receiver, enumeration); + assert!(entries.is_empty()); + let failure = outcome.failure().expect("no such directory"); + assert!( + matches!(failure, EnumerationError::DirectoryOpen(_)), + "{failure:?}" + ); +} + +#[test] +fn an_empty_path_is_reported_by_the_open() { + let request = EnumerationRequest::for_path(PathBuf::new().as_path()).expect("accepted"); + let (session, receiver) = Session::new(8, 8).expect("room"); + let handle = session.try_begin(request).expect("room"); + let enumeration = handle.id(); + handle.detach(); + + let (entries, outcome) = drain_to_terminal(&receiver, enumeration); + assert!(entries.is_empty()); + let failure = outcome.failure().expect("an empty path names nothing"); + assert!( + matches!(failure, EnumerationError::DirectoryOpen(_)), + "{failure:?}" + ); +} + +const CHILD_MARKER: &str = "WFE_RELATIVE_CHILD"; +const CHILD_TEST: &str = + "paths::a_relative_path_names_the_directory_it_was_built_in_after_the_directory_changes"; + +// The process current directory is shared by every test in the process, so the +// test that changes it runs in a child process of its own, as the request +// crate's do. It is what tells the path the worker opens from the path as +// given: the two name the same directory until the current directory moves. +#[test] +fn a_relative_path_names_the_directory_it_was_built_in_after_the_directory_changes() { + const NAME: &str = "wfe-relative-subject"; + + if std::env::var_os(CHILD_MARKER).is_some() { + let built_in = std::env::current_dir().expect("the current directory"); + let request = EnumerationRequest::for_path(NAME.as_ref()).expect("accepted"); + assert_eq!(request.path().to_string_lossy(), NAME); + assert_eq!( + request.win32_path().to_string_lossy(), + built_in.join(NAME).to_string_lossy(), + "grafted onto the current directory when the request was built" + ); + + let elsewhere = std::env::var_os("SystemRoot").expect("Windows names its directory"); + std::env::set_current_dir(elsewhere).expect("change the current directory"); + + let (session, receiver) = Session::new(8, 8).expect("room"); + let handle = session.try_begin(request).expect("room"); + let enumeration = handle.id(); + handle.detach(); + + let (entries, outcome) = drain_to_terminal(&receiver, enumeration); + assert!(outcome.is_completed(), "{outcome:?}"); + assert_eq!(entry_names(&entries), ["here.txt"]); + return; + } + + let scratch = Scratch::empty(); + let subject = scratch.child(NAME); + std::fs::create_dir(&subject).expect("the subject directory"); + std::fs::write(subject.join("here.txt"), b"").expect("a file in it"); + + let output = std::process::Command::new(std::env::current_exe().expect("this test binary")) + .arg("--exact") + .arg(CHILD_TEST) + .arg("--nocapture") + .env(CHILD_MARKER, "1") + .current_dir(scratch.path()) + .output() + .expect("run this test in a child process"); + + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + output.status.success() && stdout.contains("1 passed"), + "the child did not pass: {}\n{stdout}\n{}", + output.status, + String::from_utf8_lossy(&output.stderr) + ); } #[test] diff --git a/crates/windows-file-watcher/CHECKLIST.md b/crates/windows-file-watcher/CHECKLIST.md index ba9e91c38..f9abf34ac 100644 --- a/crates/windows-file-watcher/CHECKLIST.md +++ b/crates/windows-file-watcher/CHECKLIST.md @@ -15,8 +15,7 @@ with origin) is standard procedure and is not listed as an item. Completed milestones are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). -> **NEXT ACTIONABLE ITEM: none.** M1 through M16 are done. Only the parked, ungated M-inf horizon items -> remain, and none is a current obligation. +> **NO ACTIONABLE ITEM.** M1 through M17 are done. The M-inf horizon items are parked in [PARKED.md](PARKED.md). ## M4 -- Coalescing by directory and file targets @@ -70,25 +69,10 @@ Archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#moved-2026-08-27---- Archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#m15). -## M-inf -- Horizon (ungated, post-v1) +## M17 -- The ordinal comparison through `win-text-sys` -Parked, not pending. These are the deferred seams recorded in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> D-19, -an explicit design decision that places them outside the v1 scope. That recorded decision -- not the -absence of a current consumer -- is why each is deferred, which is a legitimate deferral rationale (a -resolved, recorded scope decision), not a scope-boundary excuse. Each graduates to a numbered milestone -when a post-v1 line of work takes one up. None is an open obligation of any current milestone. +Archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#m171). -- [ ] **M-inf.1** -- `ReadDirectoryChangesExW` extended records (`FILE_NOTIFY_EXTENDED_INFORMATION`): surface the - richer per-record fields on OS versions that support it, behind capability detection, without disturbing - the basic `FILE_NOTIFY_INFORMATION` surface (D-18/D-19). **Availability is per-filesystem, not merely - per-OS-version:** even on a build that exposes the API, some filesystems reject the extended structure -- - e.g. ReFS still does not support it (for no good reason) -- so detection must probe the actual target - volume and fall back to `FILE_NOTIFY_INFORMATION`, never inferring support from the OS version alone. +## M18 -- The doorbell as a shared handle -- [ ] **M-inf.2** -- Digest-based change *verification*: an optional mode that confirms a reported change by - hashing content, trading cost for fewer spurious notifications (D-19). - -- [ ] **M-inf.3** -- Per-volume capability cache: remember detailed-vs-coarse (and extended-record) support per - volume so establish/re-establish need not re-probe each time (D-17/D-19). - -- [x] **M-inf.4** -- Root-caused M11.2's fast reopen path: not an IOCP defect at all, but Windows refusing a directory-change read on any by-id open, so the path was removed rather than fixed. -> [completed 2026-09-01, under M15.2's entry, which closed both](COMPLETED-CHECKLIST.md#m152) +Archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#m181). diff --git a/crates/windows-file-watcher/COMPLETED-CHECKLIST.md b/crates/windows-file-watcher/COMPLETED-CHECKLIST.md index 69f4e543a..905ac7a29 100644 --- a/crates/windows-file-watcher/COMPLETED-CHECKLIST.md +++ b/crates/windows-file-watcher/COMPLETED-CHECKLIST.md @@ -554,6 +554,7 @@ a red suite, never by reasoning): The remaining 4 missed mutants are all in `StandingHold::drop`, whose body is unreachable; that is M15.1 and is an engineer decision, not a test gap. + ## Moved 2026-09-01 -- M15.1: the unreachable half of `StandingHold::drop` ### M15.1 -- Decide whether `StandingHold::drop`'s release path is dead code, and act on the answer. *(completed 2026-09-01 18:29:36 -04:00)* @@ -607,6 +608,7 @@ body. `queue.rs` now has **4 missed mutants, none in this impl**. `StandingHold`, `StandingState`, and `take` all described this `Drop` as the live release mechanism. Four restatements of one fact, none of which moved when the fact did; all four corrected here, plus the note in `queue/tests.rs`. Recorded in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `Dead code that could not have run`. + ## Moved 2026-09-01 -- M15.2 / M-inf.4: why a by-id reopen cannot be watched ### M15.2 -- Explain, then either fix or document, why a handle from `reopen_by_id` rejects the very read the watcher exists to issue. *(completed 2026-09-01 19:05:00 -04:00)* @@ -672,6 +674,7 @@ checklist entries all described the fast path as live or as pending root-cause; **Spawned M15.8:** `WatcherInner::canonical_path` is now write-only, and no warning will ever surface it because `lock(&self.canonical_path)` counts as a read of the field. + ## Moved 2026-09-01 -- M15.8: the write-only tail of M15.2's removal ### M15.8 -- Decide whether `WatcherInner::canonical_path` and `DirectoryHandle::canonical_path` survive M15.2's removal. *(completed 2026-09-01 19:20:00 -04:00)* @@ -705,6 +708,7 @@ unanswered rather than dissolved, which is what this item had to determine. **The transferable rule:** a diagnostic wants the live handle, not a cached copy. Needing the *value* is not a reason to keep the *field*. + ## Moved 2026-09-01 -- M15.3: paths are the caller's, verbatim (D-85) ### M15.3 -- Decide whether this crate should open paths longer than `MAX_PATH`, and note the consequence for `canonical_path`'s retry either way. *(completed 2026-09-01 19:45:00 -04:00)* @@ -759,6 +763,7 @@ verbatim and only ever reopened, never matched against a canonical path. a future "helpful" prefix fails the suite) and M15.10 (the junction fixture for the 512-unit retry -- the back door M15.3 called hypothetical is confirmed to work, no elevation needed, a 53-character junction resolving to a 578-character target). + ## Moved 2026-09-01 -- M15.9: guarding D-85's pass-through ### M15.9 -- Guard D-85's pass-through with tests, so a future "helpful" `\\?\` prefix fails the suite instead of silently changing what callers' paths mean. *(completed 2026-09-01 20:05:00 -04:00)* @@ -793,6 +798,7 @@ over-escaped -- eight backslashes reached Rust where four were needed -- so ever 23 tests failed instead of 5. The over-broad result is what exposed it; a subtler mis-escape would have read as a real finding. Redone with a direct file edit and a Rust raw string, which removes the escaping layer entirely. This is the same multi-layer escaping trap recorded earlier in this sweep. + ## Moved 2026-09-01 -- M15.10: covering canonical_path's regrow ### M15.10 -- Test `canonical_path`'s 512-unit retry through the junction back door. *(completed 2026-09-01 20:25:00 -04:00)* @@ -820,6 +826,7 @@ excluding the NUL (needing room for it) while a too-small buffer returns the len was confirmed rather than argued -- an `assert_ne!` probe never fired across the whole suite, including the 508-516 walk that straddles the buffer size exactly. The reasoning is now a comment at the comparison, so the next sweep does not re-litigate it. + ## Moved 2026-09-01 -- M15.4: isolating the last notification-filter categories ### M15.4 -- Isolate the last two notification-filter categories, or record that they cannot be isolated from outside. *(completed 2026-09-01 20:35:00 -04:00)* @@ -864,6 +871,7 @@ operations -- the first run reported nothing firing, the second reported everyth harness artifacts. The fix is a fresh handle per probe plus decoding the returned records and matching by file name, and a built-in sanity check (a known operation under a known bit) so a broken harness announces itself instead of producing a plausible table. + ## Moved 2026-09-01 -- M15.5: the arming contract, asserted instead of crashed into ### M15.5 -- Assert the arming contract in `arm_detailed_read`, so a broken one fails a test instead of *sometimes* corrupting the heap. *(completed 2026-09-01 20:45:00 -04:00)* @@ -901,6 +909,7 @@ in every crash report) produced zero failures, and all sixteen crash reports car none equal to the clean build's -- each was its own mutant build. The test binary's filename derives from target and features rather than contents, which is why every report names the same `.exe` and why that name alone proves nothing. + ## Moved 2026-09-01 -- M15.7: the test-side wait budget ### M15.7 -- Decide the test-side wait budget, so a mutation sweep is not dominated by tests that correctly fail slowly. *(completed 2026-09-01 21:05:00 -04:00)* @@ -966,6 +975,7 @@ notification budget: every one is pinned by a single test, it is not**. Each of those mutants breaks that loop's exit condition, so it spins at full speed rather than failing. That is M15.6's shape one level up, and M15.6 missed it because the bound sits on the wait rather than on the loop -- lowering the budget further would not have touched it. + ## Moved 2026-09-01 -- M15.11: a bounded wait inside an unbounded loop ### M15.11 -- Bound the *loop* in `no_wakeup_is_lost_under_a_concurrent_burst`, not just the wait inside it. *(completed 2026-09-01 21:25:00 -04:00)* @@ -1162,3 +1172,43 @@ after a reopen lands on a different directory. Independent of M10 above; M12 bel - [x] **M15.7** -- Decided and implemented: `NOTIFY_TIMEOUT` lowered 30s -> 5s across all three copies, after measuring that 45 of 46 waits finish in <=2.5ms and the whole tail is one structural ~515ms backoff, unchanged under 4x oversubscription. One previously-timing-out mutant: 93.6s -> 31.8s. -> [completed 2026-09-01](COMPLETED-CHECKLIST.md#m157) - [x] **M15.11** -- Bounded the loop in `no_wakeup_is_lost_under_a_concurrent_burst` (a bounded wait inside an unbounded loop is still an unbounded loop) and aligned `await_signal`'s budget with M15.7's. The suite-wide sweep for the same shape found no other instance. -> [completed 2026-09-01](COMPLETED-CHECKLIST.md#m1511) + +## Moved 2026-10-20 17:18:37 -04:00 -- M17: the ordinal comparison through win-text-sys + +### M17.1 -- `route.rs`'s case-insensitive comparison calls `win-text-sys`. *(completed 2026-10-20 17:18:37 -04:00)* + +The item as written: + +**`route.rs`'s case-insensitive comparison calls `win-text-sys`.** The crate's private + `CompareStringOrdinal` call in [route.rs](src/route.rs), the one `unsafe` block there, becomes the wrapper in the + new `win-text-sys` (the workspace's `M43.1`), which `windows-file-enumeration-sys` adopts in the same way. The + comparison's meaning, ordinal with case folded only when asked, is unchanged; the existing route tests are the + check. + + > **CROSS-COMPONENT PREREQUISITE:** [M43.1](../../COMPLETED-CHECKLIST.md#m431) (`win-text-sys` is created). + +What was done. [route.rs](src/route.rs)'s `names_match` is `compare_string_ordinal` with `Case::Sensitive` or +`Case::Insensitive`, and `equal_from` reads the result: equal is a match, a failure of the call is not, and a name too +long for the call's count is compared exactly. The `unsafe` block is gone, and so is the `Win32_Globalization` feature of +`windows-sys`; `win-text-sys` is a dependency, and `win-error-codes` a path-only dev-dependency for the tests. The +existing route tests passed unchanged. Recorded as [D-86](DESIGN-NOTES.md#d-86), which also records the one behaviour +that changed, for a name of more than 2^31 units, and that it cannot be reached. Tests: seven new, of `names_match` in both +modes, an interior NUL, and each result of the comparison. Evidence, in [sabotage.json](sabotage.json): five entries, all +caught, and a control. The workspace's `M43` is complete with this and `SAFE-5` of `windows-file-enumeration-sys`. + +## Moved 2026-10-22 00:12:45 -04:00 -- M18: the doorbell as a shared handle + +### M18.1 -- The receiver's doorbell is held as a `SharedHandle`, and `doorbell_shared` hands out one more owner of it. *(completed 2026-10-22 00:12:45 -04:00)* + +The item as written: + +**Migrate `windows-file-watcher` to the shared owned handle.** The receiver's doorbell is the one place the crate +shares a handle, and it shared it by `DuplicateHandle` per consumer. It is held as a `win-shared-os-owned-handle` +`SharedHandle`, and `Receiver::doorbell_shared` returns a clone, so a `ThreadpoolWait` can take it through +`WaitableHandle::assume_waitable_shared` without a duplicate. `doorbell` and `doorbell_owned` are unchanged. + +What was done. Recorded as [D-87](DESIGN-NOTES.md#d-87), which also records why [D-42](DESIGN-NOTES.md#d-42)'s two +forms stand and what the shared form does not do. The directory handle needed nothing: it goes to +`windows-overlapped-io-sys`, which holds it as a `SharedHandle` itself, and the coarse handle is closed by +`FindCloseChangeNotification`, which `SharedHandle` is not for. Tests: eight new for the shared form and one for a +client's own `ThreadpoolWait` on it. Evidence, in [sabotage.json](sabotage.json): two entries caught and a control. diff --git a/crates/windows-file-watcher/COMPLETED-PLANS.md b/crates/windows-file-watcher/COMPLETED-PLANS.md index 1e0b2ebc6..5e796109b 100644 --- a/crates/windows-file-watcher/COMPLETED-PLANS.md +++ b/crates/windows-file-watcher/COMPLETED-PLANS.md @@ -9,3 +9,4 @@ Append-only archive of completed checklists, moved out of [PLANS.md](PLANS.md). | [CHECKLIST.md](CHECKLIST.md) | 2026-08-25 | M13 consumer test surface: an off-by-default `test-util` feature exposing the feed channel (`channel_with_bound`/`Sender`/`Delivery`/`Reservation`, previously `pub` only inside the private `queue` module) and valid-by-construction `for_test` builders for the two otherwise-unconstructible boundary types (`RelativeName`, `VolumeIdentity`), plus re-documented already-public pieces (`WatchId::from_raw`), a crate-level "Testing your consumer code" section, a runnable example, and a downstream-style integration test -- so a consumer can drive its own notification-handling code with synthetic notifications through the real `Receiver`, with no filesystem and no thread pool. The first instantiation of the "enable consumers to go below" testability pattern. Decisions D-81/D-82/D-83; D-82 reconciled with D-64 by audience. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | | [CHECKLIST.md](CHECKLIST.md) | 2026-08-27 | M14 contract audit: the converse of PR #42's 19 reactive review rounds -- a deliberate pass asking, for each of the ten workspace specification-gap categories, whether this crate states the answer or leaves it to omission. M14.1 (D-12/D-27/D-28/D-30) found three shipped documentation defects: `DesyncCause`'s type-level doc claimed every cause is advisory and answered by a re-scan while its own `Stopped` variant said the opposite, "The Desync primitive" enumerated four causes of five, and "Delivery and saturation" still described the pre-D-29 drop policy plus the exact latch phrasing D-39 corrects. It also stated two load-bearing rules for the first time: the standing slot shared by `RetryQuestion`/`VolumeChanged` rests on a mutual-exclusion invariant, and "every request produces a completion" holds for lifecycle requests only. M14.2 (D-10/D-13/D-17/D-26/D-57) found three legal sequences the contract excluded -- a liveness bracket can open with `Resumed` and can close with `Desync { Stopped }` instead of `Resumed`, `Established` is not necessarily a watch's first notification, and the tier is re-resolved on every reopen -- all folded into the harness's `schedule` docs. M14.3 swept all nine advisory predicates for the `has_room` shape and found one: `Receiver::is_empty` excludes owed latches and end-of-stream, so a client waiting on the doorbell (signalled on all three, D-41) would spin; fixed by publishing D-41's own predicate as `Receiver::has_pending`. The subsequent review round found the paired defect in the original `has_room` fix: the wake edge (`free() == 1`) had not been updated to match the revised predicate (`free() > latched.len()`), so a latch owed meant the prod fired one slot early and never re-fired, wedging the watcher -- both are now derived from a single `best_effort_room()`. Decision D-84 records what a second implementation of the contract exposed. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | | [CHECKLIST.md](CHECKLIST.md) | 2026-09-01 | M16 monitor mutation closure: 15 surviving mutants were covered by focused tests for request formatting, monitor state projection, re-key cleanup, file-parent classification, retry-slot policy, faulted-watcher liveness suppression, and volume-change cleanup. Shared predicates now own the duplicated boolean rules. The confirming all-feature sweep found 47 caught, 18 unviable, 0 missed, and 2 timeout detections with 34/33 tests already failing. | N/A | +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-20 | M17: the route's single-file name comparison is `win-text-sys`'s `compare_string_ordinal`, which removed the crate's `unsafe` block for `CompareStringOrdinal`. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-86) | diff --git a/crates/windows-file-watcher/Cargo.toml b/crates/windows-file-watcher/Cargo.toml index d070c6e1e..5940e8b5a 100644 --- a/crates/windows-file-watcher/Cargo.toml +++ b/crates/windows-file-watcher/Cargo.toml @@ -88,6 +88,11 @@ test-util = [] # `OVERLAPPED` across a re-arm. No features are enabled; only the core completion # machinery is needed. windows-overlapped-io-sys = { version = "0.3.0", path = "../windows-overlapped-io-sys", default-features = false } +# Duplicating a handle: this crate's `DuplicateHandle` call is `win-handles-sys`'s, the repository's one. +win-handles-sys = { version = "0.1.0", path = "../win-handles-sys" } +# The doorbell is held as a shared handle, so a client can wait on it, or arm a thread-pool wait on it, without a +# kernel handle of its own. `windows-threadpool-sys` takes the same type, from the same crate. +win-shared-os-owned-handle = { version = "0.1.0", path = "../win-shared-os-owned-handle" } # The thread pool the monitor runs on: `ThreadpoolIo` services detailed reads, # `ThreadpoolWait` the coarse fallback, `ThreadpoolTimer` the retry backoff, and @@ -95,6 +100,10 @@ windows-overlapped-io-sys = { version = "0.3.0", path = "../windows-overlapped-i # own. windows-threadpool-sys = { version = "0.3.0", path = "../windows-threadpool-sys" } +# A single-file route's case-insensitive leaf-name match (PR #20 review response): `CompareStringOrdinal`, +# the OS's own ordinal case folding and not an ASCII-only approximation, over counted slices (M17.1). +win-text-sys = { version = "0.0.1", path = "../win-text-sys" } + # Relative names decoded from `ReadDirectoryChangesW` are native `u16`, and are # handed back to Windows APIs as `u16`. Storing them as `Wtf16String` keeps them # in that representation end to end, so neither direction pays the WTF-8 @@ -132,10 +141,6 @@ features = [ # wait for notifications on its own thread pool rather than dedicating a # thread to a blocking recv. "Win32_System_Threading", - # CompareStringOrdinal (a single-file route's case-insensitive leaf-name - # match, PR #20 review response) -- the OS's own ordinal case folding, - # not an ASCII-only approximation. - "Win32_Globalization", ] # Integration tests exercise the decoder against a buffer produced by a real @@ -146,6 +151,10 @@ features = [ # `CreateEventW` / `CreateFileW` signatures -- declared here rather than relied on # transitively through `windows-threadpool-sys`. [dev-dependencies] +# The code a failed comparison carries, which `route`'s tests build (M17.1). Path-only, with no `version`, so a +# release of `win-error-codes` cannot break this crate and the published manifest leaves it out. +win-error-codes = { path = "../win-error-codes" } + windows-sys = { version = "0.61.2", default-features = false, features = [ "Win32_Foundation", "Win32_Security", diff --git a/crates/windows-file-watcher/DESIGN-NOTES.md b/crates/windows-file-watcher/DESIGN-NOTES.md index a16a37f8e..f566cc28d 100644 --- a/crates/windows-file-watcher/DESIGN-NOTES.md +++ b/crates/windows-file-watcher/DESIGN-NOTES.md @@ -37,7 +37,7 @@ threads of its own. | D-16 | **Superseded by [D-27](#d-27).** Retry policy was resident data only, with no per-fault exchange. Overturned 2026-08-21: the monitor now *asks* on fault, per subscription. The soonest-recovering reduction survives into D-27; the prohibition on asking does not. | | D-17 | Two-tier watcher: Detailed (`ReadDirectoryChangesW` + `ThreadpoolIo`) preferred, Coarse (`FindFirstChangeNotification` + `ThreadpoolWait`) fallback. Mode is a volume property resolved at establish/re-establish. See [Two-tier watching](#two-tier-watching). | | D-18 | v1 delivers basic `FILE_NOTIFY_INFORMATION`. | -| D-19 | **Deferred to [CHECKLIST.md](CHECKLIST.md) -> M-inf (post-v1 horizon); not part of v1 scope.** This decision scopes these seams out of v1, so they are parked as horizon (M-inf) checklist items -- deferred by recorded scope decision, not for lack of a consumer -- and each is pulled into a numbered milestone post-v1 when a line of work takes it up: `ReadDirectoryChangesExW` extended records; digest-based change *verification*; an optional per-volume capability cache. | +| D-19 | **Deferred to [PARKED.md](PARKED.md) -> M-inf (post-v1 horizon); not part of v1 scope.** This decision scopes these seams out of v1, so they are parked as horizon (M-inf) checklist items -- deferred by recorded scope decision, not for lack of a consumer -- and each is pulled into a numbered milestone post-v1 when a line of work takes it up: `ReadDirectoryChangesExW` extended records; digest-based change *verification*; an optional per-volume capability cache. | | D-20 | `Monitor::Drop` blocks on full rundown (cancel + drain every read/wait, then free), inheriting the `windows-threadpool-sys` teardown discipline. | | D-21 | **The decoder accepts only an exactly-described buffer.** A final record (`NextEntryOffset == 0`) must end the buffer either exactly at its name, or exactly at that name's DWORD-aligned end. A name is a whole number of UTF-16 units, so a record always ends on an even offset and its alignment padding is exactly 0 or 2 bytes -- never 1 or 3. Any other trailing remainder is undescribed data (a truncated or corrupt completion, or records whose link was zeroed), and is reported as a desync rather than silently discarded, since dropping it would understate the batch and lose changes. See [The Desync primitive](#the-desync-primitive) and [the exactly-described-buffer rationale (D-21)](DESIGN-RATIONALE.md#the-decoder-accepts-only-an-exactly-described-buffer-d-21). | | D-22 | **Open failures split into retryable and permanent, and "permanent" means bad caller input, not a fault.** Opening a watched directory classifies into `NotFound`, `Unsupported`, `Retryable` (all retryable) and `NotADirectory`, `InvalidPath` (neither). This does not contradict D-14's "no terminal fault state": the permanent pair is not an environmental fault but the caller naming something that can never be a watched directory, so retrying would spin forever against input that will never become valid. An *unrecognised* error classifies as `Retryable`, so a watcher never silently stops watching because of an error code the crate has not seen. Two checks are ours rather than Win32's: a path with an interior NUL is rejected before the call, because Win32 would stop at the NUL and open a shorter path than the caller named; and a handle is verified to be a directory, because `FILE_LIST_DIRECTORY` and `FILE_READ_DATA` are the same bit, so a plain file opens successfully and would otherwise fail much later as a mis-classified read fault. | @@ -60,7 +60,7 @@ threads of its own. | D-39 | **The latch is drained into the queue at the next enqueue, and a freed slot reports an owed loss before it carries new changes.** [D-11](#d-11) said a latched `QueueFull` is "surfaced ahead of the next successful batch", which read as *emit it immediately*; that is wrong, and would break the ordering [D-12](#d-12) promises. A client seeing a desync at position P concludes everything before P is accounted for, so emitting the report ahead of items already queued would claim the hole is older than it is. The queue was **full** when the loss happened, so everything still queued precedes it -- which fixes the position exactly: flush the latch into the queue at the next successful enqueue, where it lands after the changes that preceded the loss and before the one that followed. A receiver that drains to empty synthesises any remainder directly, so the report does not depend on future traffic ever arriving. When only one slot frees, it goes to the **report**, not to the new notification, which is then re-latched: carrying more changes across a hole the client has not been told about is the silent loss the whole design forbids. A report covers the losses since the previous report, so a subsequent loss latches again rather than being coalesced into a report already delivered. | | D-40 | **The bound is a `NonZeroUsize`, not a checked `usize`.** A zero bound is not a runtime condition to report -- it is a queue that could not carry even the desync announcing its own saturation, making the crate's never-silently-lose guarantee vacuous. [D-11](#d-11) requires rejecting it "at construction"; making it unrepresentable does that in the type system, rejects a literal at compile time, and leaves no error path for a caller to handle or for this crate to test. The bound counts **notifications**, not changes: one decoded completion is one batch ([D-10](#d-10)) and a batch may carry hundreds of records, so the depth is far greater than the number suggests. The default is 1024. | | D-41 | **The doorbell's contract is one predicate, re-established under the queue lock at the end of every mutation.** [D-25](#d-25) specified the mechanism (a lazily created manual-reset event) and the discipline ("the receiver resets under the queue lock on observing empty and the sender sets after enqueue") as two rules on two sides. Implemented that way they can drift, so it is instead a single invariant -- **the event is signalled exactly when the receiver has something to take** -- re-established by one function that every mutation path ends with, under the same lock a receiver holds while deciding there is nothing to take. There is therefore no window between those two decisions for a wakeup to fall into, and the promised "harmless spurious wakeups" do not arise either. "Something to take" is deliberately three things, not one: a queued notification, an **owed loss report** (a latched desync is not in the queue but is still something to collect, per [D-39](#d-39)), and the **end of the stream** -- a disconnected queue stays signalled permanently, because a client waiting on the handle must learn the stream has ended rather than wait for a notification nothing can send. | -| D-42 | **The doorbell is handed out both borrowed and owned, because Win32's waits disagree about ownership.** `Receiver::doorbell` returns a `BorrowedHandle` -- the event belongs to the queue and a caller must not close it -- which is what `WaitForSingleObject` / `WaitForMultipleObjects` / `MsgWaitForMultipleObjects` want. But the integration D-25 exists to enable, arming a `ThreadpoolWait`, *takes ownership* of its target, so a borrow-only API would have forced every such client to write `DuplicateHandle` boilerplate to reach the motivating use case. `Receiver::doorbell_owned` returns a duplicate of the same event, so signalling reaches both and the caller closes its copy whenever it likes. One event, created once and lazily; two handles onto it, because the platform's waits are split on this point. | +| D-42 | **The doorbell is handed out both borrowed and owned, because Win32's waits disagree about ownership.** `Receiver::doorbell` returns a `BorrowedHandle` -- the event belongs to the queue and a caller must not close it -- which is what `WaitForSingleObject` / `WaitForMultipleObjects` / `MsgWaitForMultipleObjects` want. But the integration D-25 exists to enable, arming a `ThreadpoolWait`, *takes ownership* of its target, so a borrow-only API would have forced every such client to write `DuplicateHandle` boilerplate to reach the motivating use case. `Receiver::doorbell_owned` returns a duplicate of the same event, so signalling reaches both and the caller closes its copy whenever it likes. One event, created once and lazily; two handles onto it, because the platform's waits are split on this point. A third form, shared ownership of the one handle, is [D-87](#d-87). | | D-43 | **A `WatchId` is minted per monitor, and registration options are `#[non_exhaustive]` because they are the one call that cannot be added to later.** Identifiers key the monitor's resident state as well as tagging notifications, so two sessions must never mint the same one; the sequence therefore lives in the monitor's shared core rather than in a session. That core deliberately holds only the servicing path and the counter -- **not** the resident state, which the handler captures directly, because a handler reaching back through the object that owns its own servicer is the cycle the watcher's completion callback already had to solve with a `Weak`. Registration options are a `#[non_exhaustive]` struct with setters rather than positional arguments, because [D-27](#d-27)'s retry mode and M4's change-type filter are both properties of a *subscription* that only the registration call can state; adding either to a positional signature would be a breaking change to the one call that has to carry them. The retry mode is stored with the subscription and readable from the monitor, so "registration carries it" is verified now rather than being a claim M5.3 discovers is false. | | D-44 | **Registration is asynchronous, so its failure is not the caller's return value.** `Session::subscribe` fails only when the monitor has shut down -- everything else it could report, it does not yet know: whether a path can be watched is discovered when the servicing path opens it, on a pool thread, after the call has returned. Reporting the open failure synchronously would mean opening the directory on the client's thread, which puts an unbounded filesystem call (a dead network path can take tens of seconds) where the client did not ask for one, and would put watcher construction outside the single servicing authority [D-2](#d-2) establishes. So the call returns a `Watch` optimistically and the outcome arrives as a completion (M3.6/[D-30](#d-30)). Until that lands there is a real gap, stated rather than hidden: a subscription to an unwatchable path starts no watcher and says nothing, which is exactly the "holds a `Watch` that can never fire and never says so" case D-30 exists to close. | | D-45 | **A subscription reserves *two* slots at registration, because `Drop` has nowhere to report a refused reservation.** [D-33](#d-33) says a request reserves its completion slot at submit, which works for subscribe -- a refusal becomes an error on the client's own thread at the call that asked for the work. Cancellation cannot do that: [D-5](#d-5) makes `Drop` a cancellation path, and a destructor has no way to report that the queue was too full to promise its completion. So the cancellation's slot is taken at **registration** and held for the whole life of the `Watch`, which is the same standing-reservation shape [D-28](#d-28) uses for an interactive subscription's fault report. The cost is stated plainly: a subscription occupies two slots of the bound for its lifetime, so a bound of two admits exactly one subscription. That is [D-29](#d-29)'s backpressure landing where it was designed to land -- on `subscribe`, on the client's thread -- rather than a limitation to work around. The taken-or-not state of that reservation also *is* the "already cancelled" flag, so an explicit `cancel` followed by `Drop` cannot enqueue twice. | @@ -104,6 +104,8 @@ threads of its own. | D-83 | **The consumer test surface tests the consumer's reactions, not whether this crate would ever emit a given sequence.** Builders are valid-by-construction in the type-safety sense (memory-safe, lossless), not production-domain-validating: a `RelativeName` can still carry a unit sequence the kernel itself never reports (an interior NUL, say), and an impossible ordering or an impossible relationship between two otherwise valid values (a `VolumeChanged` with equal `previous`/`current` serials, each individually a legal `VolumeIdentity`) both remain the consumer's responsibility, as with any hand-fed test double. This fidelity limit is documented on the surface so a passing handler test is not mistaken for confirmation that the crate produces that traffic. See [Consumer test surface](#consumer-test-surface). | | D-84 | **The delivery contract was under-specified, and a second implementation of it -- not a test suite -- is what proved that.** PR #42's example harness promised contract-legal schedules only (its own D-5), which made its generator a second implementation of *this* crate's contract. Converging it took **19 automated review rounds**: eight fixed generated sequences this crate could never emit, five corrected the contract prose itself, and one found a real shipped reliability defect ([`has_room`](#the-has_room-finding-in-this-crate)) on [D-29](#d-29)'s backpressure path. All 278 of this crate's own tests passed throughout and were never going to fail -- they assert what the watcher *does*, and every gap was in what the contract *permits*. The gap categories are workspace-wide and recorded once, in [the workspace design notes](../../DESIGN-NOTES.md#specifying-a-delivery-contract); the decisions amended in response were [D-9](#d-9) (renames never joined), [D-12](#d-12)/[D-30](#d-30) (branch and terminal paths), [D-17](#d-17) (per-tier emission legality), [D-27](#d-27)/[D-28](#d-28) (a fault question is unconditional, and enters as `Arm`), [D-50](#d-50)/[D-78](#d-78) (volume identity: distinct serials, and continuity across reopens), and [D-83](#d-83) (fidelity is type-safety, not production-domain). See [What the second implementation exposed](#what-the-second-implementation-exposed). | | D-85 | **A caller's path is passed to Win32 verbatim: this crate never adds a `\\?\` prefix, and whether a path longer than `MAX_PATH` opens is the consuming application's decision, not this crate's.** `\\?\` is not a longer-path switch, it is a *different parsing mode*, and adopting it on a caller's behalf silently changes what their path means -- measured, on short paths that open fine today: forward slashes fail with `ERROR_FILE_NOT_FOUND`, and a trailing `.` or an interior `..` fail with `ERROR_INVALID_NAME`. Relative paths would stop resolving entirely, which this crate supports on purpose (`open_file_target` normalises a bare leaf's empty parent to `.`). Long paths *without* the prefix are gated on the machine's `LongPathsEnabled` policy **and** the application's `longPathAware` manifest -- measured: the same source, on the same machine, opens a 300-character path only once that manifest is present. A library cannot set its consumer's manifest, so a caller who wants long-path behaviour either opts in at the application level or passes an explicitly `\\?\`-prefixed path, and both work here because the path is passed through untouched. The complementary case -- a layer that *does* build paths, and so must demand a form it can build on -- is `windows-file-enumeration-sys`; the shared principle is recorded once in [the workspace design notes](../../DESIGN-NOTES.md#path-contracts-follow-path-construction). See [Paths are the caller's, verbatim](#paths-are-the-callers-verbatim). | +| D-86 | **A route's name comparison is `win-text-sys`'s `compare_string_ordinal`, and a failure of the call is not a match.** Built as `M17.1`. `names_match` is `compare_string_ordinal` with `Case::Sensitive` on a case-sensitive directory and `Case::Insensitive` otherwise, which is `CompareStringOrdinal` over counted slices, so the meaning is as it was: ordinal, per UTF-16 unit, the OS's own case folding and never an ASCII-only fold, an interior NUL compared as content. The crate's `unsafe` block for it, and the `Win32_Globalization` feature of `windows-sys`, are gone. **What a result says is one function,** `equal_from`: an `Equal` is a match, any other ordering is not, and a failure the call reports is not. **One behaviour changed, and it cannot be reached:** a name of more than 2^31 UTF-16 units, which no name is, was passed with its length clamped to `i32::MAX`, so two names sharing that prefix compared equal; the comment beside the clamp said the clamp could only make equal names look unequal, which is the other way round. Such a pair is now compared exactly, which is also what `windows-file-enumeration-sys` does ([D-26](../windows-file-enumeration-sys/DESIGN-NOTES.md#d-26)). The two crates each hold the 5-line function rather than sharing one, because `win-text-sys` adds no policy ([WT-D-2](../win-text-sys/DESIGN-NOTES.md#wt-d-2)) and what a caller does with a refused length is the caller's. No public item changed. Verified by the route tests unchanged, new tests of `names_match` in both modes and of each result, and sabotage in [sabotage.json](sabotage.json). | +| D-87 | **The doorbell is also available as a shared handle, `Receiver::doorbell_shared`, which is the queue's own handle held by one more owner.** Built as `M18.1`. The queue holds its doorbell as a `win-shared-os-owned-handle` `SharedHandle`, and `doorbell_shared() -> io::Result` returns a clone of it: the same raw handle `doorbell` borrows, no new kernel handle, closed when the last holder of it drops, the queue's included. It is created on the first ask for any doorbell form, as before. **[D-42](#d-42) stands.** `doorbell` still borrows and `doorbell_owned` still returns a duplicate, because they are different things to hold: a duplicate is a handle of the caller's own that it can close whenever it likes and hand to anything that wants an `OwnedHandle`, and a shared holder is one more owner of the one handle. What the shared form saves is a duplicate per consumer when the consumer is a `ThreadpoolWait`, which takes a shared target through `WaitableHandle::assume_waitable_shared` and which `windows-threadpool-sys` already declares with this same type, so the dependency is the one that crate's signature names. **What it does not do:** holding it does not keep the queue alive, and the queue keeps the event in agreement with its contents only while the queue exists, so a holder that outlives every sender and the receiver is waiting on an event nothing will signal or reset; this is in the method's documentation. Additive: no existing item changed. Verified by tests that the shared handle is the borrow's raw handle and not the duplicate's, that two asks and a clone are one handle, that asking for the shared form first creates the doorbell for every form, that it follows the queue signalled and reset and finds the state of notifications that arrived before the first ask, that dropping holders leaves the queue's working, that it is a live event after the queue is gone, that a thread waits on it and is woken, and that a client's own `ThreadpoolWait` armed on it drains the queue; and by sabotage in [sabotage.json](sabotage.json): a shared form that is an event of its own, and one that skips creating it, both caught, and a control. The first, swept as a whole suite, ends in the thread pool's fail-fast abort because the thread-pool test unwinds with its wait undrained; re-injected on its own, the handle-equality test fails by itself with its own message. | ### Queue mediation diff --git a/crates/windows-file-watcher/PARKED.md b/crates/windows-file-watcher/PARKED.md new file mode 100644 index 000000000..a8338e06b --- /dev/null +++ b/crates/windows-file-watcher/PARKED.md @@ -0,0 +1,25 @@ +# Parked: windows-file-watcher + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M-inf -- Horizon (ungated, post-v1) + +Parked, not pending. These are the deferred seams recorded in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> D-19, +an explicit design decision that places them outside the v1 scope. That recorded decision -- not the +absence of a current consumer -- is why each is deferred, which is a legitimate deferral rationale (a +resolved, recorded scope decision), not a scope-boundary excuse. Each graduates to a numbered milestone +when a post-v1 line of work takes one up. None is an open obligation of any current milestone. + +- [ ] **M-inf.1** -- `ReadDirectoryChangesExW` extended records (`FILE_NOTIFY_EXTENDED_INFORMATION`): surface the + richer per-record fields on OS versions that support it, behind capability detection, without disturbing + the basic `FILE_NOTIFY_INFORMATION` surface (D-18/D-19). **Availability is per-filesystem, not merely + per-OS-version:** even on a build that exposes the API, some filesystems reject the extended structure -- + e.g. ReFS still does not support it (for no good reason) -- so detection must probe the actual target + volume and fall back to `FILE_NOTIFY_INFORMATION`, never inferring support from the OS version alone. + +- [ ] **M-inf.2** -- Digest-based change *verification*: an optional mode that confirms a reported change by + hashing content, trading cost for fewer spurious notifications (D-19). + +- [ ] **M-inf.3** -- Per-volume capability cache: remember detailed-vs-coarse (and extended-record) support per + volume so establish/re-establish need not re-probe each time (D-17/D-19). diff --git a/crates/windows-file-watcher/PLANS.md b/crates/windows-file-watcher/PLANS.md index 6c3da7d3d..d4590fbbb 100644 --- a/crates/windows-file-watcher/PLANS.md +++ b/crates/windows-file-watcher/PLANS.md @@ -4,9 +4,9 @@ Active planned work for the crate. Completed checklists are archived in [COMPLETED-PLANS.md](COMPLETED-PLANS.md), and their milestones in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). -All numbered milestones through M16 are complete, so there is no active plan. [CHECKLIST.md](CHECKLIST.md) -remains for its parked `M-inf` horizon bucket, which is outside v1 by recorded design decision and is not -pending work. +Numbered milestones through M17 are complete. The `M-inf` horizon items, outside v1 by recorded design decision, +are in [PARKED.md](PARKED.md). | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| +| [PARKED.md](PARKED.md) | parked | The `M-inf` horizon: `ReadDirectoryChangesExW` extended records, digest-based change verification, and a per-volume capability cache, outside v1 by D-19. | [DESIGN-NOTES.md](DESIGN-NOTES.md) (D-19) | diff --git a/crates/windows-file-watcher/sabotage.json b/crates/windows-file-watcher/sabotage.json index 2926c8e8f..9835f11ee 100644 --- a/crates/windows-file-watcher/sabotage.json +++ b/crates/windows-file-watcher/sabotage.json @@ -19,6 +19,78 @@ "replace": [ " while false {" ] + }, + { + "name": "M17.1: a case-insensitive directory compares case-sensitively", + "file": "src/route.rs", + "expect": "caught", + "why": "The case choice is the only thing that makes a file route match a differently cased stored name.", + "find": [" Case::Insensitive\n"], + "replace": [" Case::Sensitive\n"] + }, + { + "name": "M17.1: a case-sensitive directory folds case", + "file": "src/route.rs", + "expect": "caught", + "why": "On a case-sensitive directory A.txt and a.txt are different files, and folding routes one's changes to the other's subscription.", + "find": [" Case::Sensitive\n } else {"], + "replace": [" Case::Insensitive\n } else {"] + }, + { + "name": "M17.1: a comparison that says less is a match", + "file": "src/route.rs", + "expect": "caught", + "why": "Only an equal result is the same name.", + "find": [" Ok(ordering) => ordering == Ordering::Equal,\n"], + "replace": [" Ok(ordering) => ordering != Ordering::Greater,\n"] + }, + { + "name": "M17.1: a name too long for the call is not compared exactly", + "file": "src/route.rs", + "expect": "caught", + "why": "Reaching it takes a slice of more than 2^31 units, so it is observed through the comparison's result: the fallback is the exact comparison, and equal names must still match.", + "find": [" Err(CompareStringOrdinalError::TooLong { .. }) => a == b,\n"], + "replace": [" Err(CompareStringOrdinalError::TooLong { .. }) => false,\n"] + }, + { + "name": "M17.1: a failure of the call is a match", + "file": "src/route.rs", + "expect": "caught", + "why": "A failed comparison must never route a change to a subscription.", + "find": [" Err(CompareStringOrdinalError::Call(_)) => false,\n"], + "replace": [" Err(CompareStringOrdinalError::Call(_)) => true,\n"] + }, + { + "name": "M17.1 control: the equal result is read through a binding", + "file": "src/route.rs", + "expect": "survives", + "why": "A control: the same comparison through a local, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" Ok(ordering) => ordering == Ordering::Equal,\n"], + "replace": [" Ok(ordering) => {\n let equal = ordering == Ordering::Equal;\n equal\n }\n"] + }, + { + "name": "D-87: the shared doorbell is an event of its own", + "file": "src/queue.rs", + "expect": "caught", + "why": "A shared holder is meant to be one more owner of the queue's event. A handle to a different event is silent: it never signals, and a thread-pool wait armed on it never fires. The raw handle equal to the queue's and the wake tests see it.", + "find": [" self.ensure_doorbell()?;\n Ok(self\n .shared\n .doorbell\n .get()\n .expect(\"the doorbell was just created\")\n .clone())\n"], + "replace": [" self.ensure_doorbell()?;\n Ok(SharedHandle::new(create_event(false)?))\n"] + }, + { + "name": "D-87: the shared doorbell is taken without creating it", + "file": "src/queue.rs", + "expect": "caught", + "why": "The first ask for any doorbell creates it. A shared form that skips that finds nothing to clone, which the test that asks for the shared form first sees as a panic.", + "find": [" self.ensure_doorbell()?;\n Ok(self\n .shared\n .doorbell\n .get()\n .expect(\"the doorbell was just created\")\n .clone())\n"], + "replace": [" Ok(self\n .shared\n .doorbell\n .get()\n .expect(\"the doorbell was just created\")\n .clone())\n"] + }, + { + "name": "D-87 control: the shared doorbell is converted into its own type", + "file": "src/queue.rs", + "expect": "survives", + "why": "A control: `.into()` from the type to itself, which no test can tell apart. A sweep that reports it caught is reporting everything caught and proves nothing.", + "find": [" self.ensure_doorbell()?;\n Ok(self\n .shared\n .doorbell\n .get()\n .expect(\"the doorbell was just created\")\n .clone())\n"], + "replace": [" self.ensure_doorbell()?;\n Ok(self\n .shared\n .doorbell\n .get()\n .expect(\"the doorbell was just created\")\n .clone().into())\n"] } ] } diff --git a/crates/windows-file-watcher/src/queue.rs b/crates/windows-file-watcher/src/queue.rs index d23a14ae4..45003d345 100644 --- a/crates/windows-file-watcher/src/queue.rs +++ b/crates/windows-file-watcher/src/queue.rs @@ -90,10 +90,10 @@ use std::ptr; use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak}; use std::time::{Duration, Instant}; -use windows_sys::Win32::Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle, FALSE, TRUE}; -use windows_sys::Win32::System::Threading::{ - CreateEventW, GetCurrentProcess, ResetEvent, SetEvent, -}; +use win_handles_sys::{DuplicateAccess, Inheritance, duplicate_handle}; +use win_shared_os_owned_handle::SharedHandle; +use windows_sys::Win32::Foundation::{FALSE, TRUE}; +use windows_sys::Win32::System::Threading::{CreateEventW, ResetEvent, SetEvent}; use crate::directory::{FaultDetail, VolumeIdentity}; use crate::notify::{Change, DesyncCause}; @@ -288,7 +288,7 @@ struct Shared { /// /// Outside the lock so a borrow of it can outlive the guard, but only ever /// signalled or reset *under* the lock -- see [`refresh_doorbell`]. - doorbell: OnceLock, + doorbell: OnceLock, } /// A crate-owned producer that stopped because the queue was full, and must be @@ -989,8 +989,9 @@ impl Receiver { /// arrive. /// /// The borrow is deliberate: the event belongs to this queue and must not be - /// closed by a caller. Use [`Receiver::doorbell_owned`] where ownership is - /// required, such as arming a `ThreadpoolWait`. + /// closed by a caller. Use [`Receiver::doorbell_shared`] or + /// [`Receiver::doorbell_owned`] where ownership is required, such as arming a + /// `ThreadpoolWait`. /// /// # Errors /// @@ -1015,7 +1016,42 @@ impl Receiver { /// /// Returns the error from `CreateEventW` or `DuplicateHandle`. pub fn doorbell_owned(&self) -> io::Result { - duplicate(self.doorbell()?) + duplicate_handle( + self.doorbell()?, + DuplicateAccess::Same, + Inheritance::NotInheritable, + ) + } + + /// The doorbell as a shared handle: the queue's own event, held by one more + /// owner. + /// + /// This is [`Receiver::doorbell`] with the ownership the borrow lacks, and no + /// new kernel handle: the result is a clone of the handle the queue holds, so + /// its raw handle is the borrow's, and the event closes when the last holder + /// of it, the queue's own included, drops. A `ThreadpoolWait` can take it + /// through `WaitableHandle::assume_waitable_shared`, which is what arming one + /// needs without a duplicate per waiter. + /// + /// The queue keeps the event in agreement with its contents only for as long as + /// the queue exists. Holding this does not keep the queue alive, and nothing + /// signals or resets the event once the queue is gone, so a holder that outlives + /// every sender and the receiver is waiting on an event nothing will change. Use + /// [`Receiver::doorbell_owned`] for a handle that is the caller's own and can be + /// closed whenever the caller likes. + /// + /// # Errors + /// + /// Returns the error from `CreateEventW` on the first call to any doorbell + /// method. + pub fn doorbell_shared(&self) -> io::Result { + self.ensure_doorbell()?; + Ok(self + .shared + .doorbell + .get() + .expect("the doorbell was just created") + .clone()) } /// Create the doorbell if it does not exist yet. @@ -1029,7 +1065,7 @@ impl Receiver { let state = lock(&self.shared.items); if self.shared.doorbell.get().is_none() { let event = create_event(state.pending())?; - let _ = self.shared.doorbell.set(event); + let _ = self.shared.doorbell.set(SharedHandle::new(event)); } Ok(()) } @@ -1309,28 +1345,5 @@ fn create_event(signalled: bool) -> io::Result { Ok(unsafe { OwnedHandle::from_raw_handle(raw) }) } -/// Duplicate a handle into this process, so the caller owns its own copy. -fn duplicate(handle: BorrowedHandle<'_>) -> io::Result { - let mut duplicated = ptr::null_mut(); - // SAFETY: duplicates a live handle within this process with the same access; - // `duplicated` is a valid out-pointer for the call's duration. - let ok = unsafe { - DuplicateHandle( - GetCurrentProcess(), - handle.as_raw_handle(), - GetCurrentProcess(), - &raw mut duplicated, - 0, - FALSE, - DUPLICATE_SAME_ACCESS, - ) - }; - if ok == 0 { - return Err(io::Error::last_os_error()); - } - // SAFETY: the call succeeded, so `duplicated` is a fresh owned handle. - Ok(unsafe { OwnedHandle::from_raw_handle(duplicated) }) -} - #[cfg(test)] mod tests; diff --git a/crates/windows-file-watcher/src/queue/tests.rs b/crates/windows-file-watcher/src/queue/tests.rs index 7c4ead55c..f1a56fb50 100644 --- a/crates/windows-file-watcher/src/queue/tests.rs +++ b/crates/windows-file-watcher/src/queue/tests.rs @@ -11,7 +11,7 @@ use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; use std::sync::Arc; use std::time::Duration; -use windows_sys::Win32::Foundation::WAIT_OBJECT_0; +use windows_sys::Win32::Foundation::{WAIT_FAILED, WAIT_OBJECT_0}; use windows_sys::Win32::System::Threading::WaitForSingleObject; use windows_threadpool_sys::wait::{ThreadpoolWait, WaitableHandle}; @@ -1292,6 +1292,119 @@ fn an_owned_doorbell_refers_to_the_same_event() { assert!(is_signalled(receiver.doorbell().expect("still there"))); } +// --- the shared doorbell (D-87) --- + +#[test] +fn a_shared_doorbell_is_the_queues_own_handle_and_not_a_duplicate() { + let (_sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + assert_eq!( + shared.as_raw_handle(), + receiver.doorbell().expect("borrow").as_raw_handle(), + "a shared holder is one more owner of the queue's handle, so no new handle exists" + ); + let owned = receiver.doorbell_owned().expect("a duplicate"); + assert_ne!( + shared.as_raw_handle(), + owned.as_raw_handle(), + "the owned form is a duplicate, which is what the shared one is not" + ); +} + +#[test] +fn every_shared_doorbell_is_a_holder_of_one_handle() { + let (_sender, receiver) = channel(); + let first = receiver.doorbell_shared().expect("first"); + let second = receiver.doorbell_shared().expect("second"); + let cloned = first.clone(); + assert!(first.same_handle(&second), "two asks share one handle"); + assert!(first.same_handle(&cloned), "and so does a clone"); + assert_eq!(first.as_raw_handle(), second.as_raw_handle()); +} + +#[test] +fn asking_for_the_shared_doorbell_first_creates_it_for_every_other_form() { + let (_sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("create"); + assert_eq!( + receiver.doorbell().expect("reuse").as_raw_handle(), + shared.as_raw_handle(), + "the doorbell is created once, whichever method asks first" + ); +} + +#[test] +fn a_shared_doorbell_follows_the_queue_in_both_directions() { + let (sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + assert!(!is_signalled(shared.as_handle()), "an empty queue is quiet"); + + deliver(&sender, batch(WatchId::from_raw(1), &["a.txt"])); + assert!(is_signalled(shared.as_handle()), "a send reaches it"); + + assert!(receiver.try_recv().is_some()); + assert!( + !is_signalled(shared.as_handle()), + "draining the queue resets it" + ); +} + +#[test] +fn a_shared_doorbell_asked_for_late_finds_the_queues_state() { + let (sender, receiver) = channel(); + deliver(&sender, batch(WatchId::from_raw(1), &["early.txt"])); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + assert!( + is_signalled(shared.as_handle()), + "notifications that arrived before the first ask are already signalled" + ); +} + +#[test] +fn dropping_a_shared_holder_leaves_the_queues_doorbell_working() { + let (sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + let other = shared.clone(); + drop(shared); + drop(other); + + deliver(&sender, batch(WatchId::from_raw(1), &["a.txt"])); + assert!( + is_signalled(receiver.doorbell().expect("still there")), + "the queue's own holder keeps the handle open" + ); +} + +#[test] +fn a_shared_doorbell_outlives_the_queue_as_a_valid_handle() { + let (sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + deliver(&sender, batch(WatchId::from_raw(1), &["a.txt"])); + drop(sender); + drop(receiver); + + // SAFETY: the shared holder keeps the handle open for the call. + let waited = unsafe { WaitForSingleObject(shared.as_raw_handle(), 0) }; + assert_ne!(waited, WAIT_FAILED, "the handle is still a live event"); + assert!(shared.try_into_owned().is_ok(), "it is the last holder now"); +} + +#[test] +fn a_waiting_client_on_a_shared_doorbell_is_woken_by_a_send() { + let (sender, receiver) = channel(); + let shared = receiver.doorbell_shared().expect("the shared doorbell"); + + let handle = std::thread::spawn(move || { + std::thread::sleep(Duration::from_millis(50)); + deliver(&sender, batch(WatchId::from_raw(1), &["late.txt"])); + }); + // SAFETY: the shared holder keeps the handle open for the call. + let waited = unsafe { WaitForSingleObject(shared.as_raw_handle(), 10_000) }; + assert_eq!(waited, WAIT_OBJECT_0, "the send woke the waiter"); + handle.join().expect("the sender thread"); + assert!(receiver.try_recv().is_some()); +} + #[test] fn a_waiting_client_is_woken_by_a_send() { let (sender, receiver) = channel(); @@ -1433,6 +1546,61 @@ fn a_client_can_drain_from_its_own_threadpool_wait() { assert_eq!(seen, expected); } +#[test] +fn a_client_can_arm_its_own_threadpool_wait_on_the_shared_doorbell() { + // The same integration as above with no duplicate: the pool object holds the + // queue's own event as one more owner (D-87). + let (sender, receiver) = channel(); + let doorbell = receiver.doorbell_shared().expect("the doorbell"); + + let receiver = Arc::new(receiver); + let drained = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let finished = Arc::new(std::sync::atomic::AtomicBool::new(false)); + + let consumer = Arc::clone(&receiver); + let count = Arc::clone(&drained); + let signal = Arc::clone(&finished); + + // SAFETY: an event is a supported wait target, and the shared handle keeps it + // open for as long as the wait object holds it. + let waitable = unsafe { WaitableHandle::assume_waitable_shared(doorbell) }; + let wait = ThreadpoolWait::new( + waitable, + move |activation| { + while consumer.try_recv().is_some() { + count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + if consumer.is_disconnected() { + signal.store(true, std::sync::atomic::Ordering::SeqCst); + } else { + activation.rearm(None); + } + }, + None, + ) + .expect("create the wait"); + wait.arm(None); + + for index in 0..32 { + deliver( + &sender, + batch(WatchId::from_raw(1), &[&format!("f-{index}")]), + ); + } + drop(sender); + + let deadline = std::time::Instant::now() + Duration::from_secs(30); + while !finished.load(std::sync::atomic::Ordering::SeqCst) { + assert!( + std::time::Instant::now() < deadline, + "the pool callback never drained the queue" + ); + std::thread::sleep(Duration::from_millis(1)); + } + wait.stop_and_drain(); + assert_eq!(drained.load(std::sync::atomic::Ordering::SeqCst), 32); +} + // --- has_pending: the predicate the doorbell is signalled on (D-41, M14.3) --- #[test] diff --git a/crates/windows-file-watcher/src/route.rs b/crates/windows-file-watcher/src/route.rs index 1783f8b45..8cc04cd90 100644 --- a/crates/windows-file-watcher/src/route.rs +++ b/crates/windows-file-watcher/src/route.rs @@ -11,7 +11,8 @@ //! actually opened. //! - **Name**: a directory subscription matches every name within its reach; a //! file subscription (D-7) matches only its own leaf name. The comparison -//! is on raw UTF-16 units via `CompareStringOrdinal`, folding case only when +//! is on raw UTF-16 units via `win-text-sys`'s `compare_string_ordinal`, +//! which is `CompareStringOrdinal`, folding case only when //! the directory itself is case-insensitive: the default Windows filesystem //! is case-insensitive but case-preserving, so `CreateFileW` accepts a //! target whose casing differs from the stored name, while a decoded @@ -27,6 +28,9 @@ //! A desync is never filtered by scope: it means "you may have missed something //! in this directory," which is equally true for every subscription within it. +use std::cmp::Ordering; + +use win_text_sys::{Case, CompareStringOrdinalError, compare_string_ordinal}; use wtf_string::Wtf16String; use crate::notify::Change; @@ -93,7 +97,7 @@ fn is_direct_child(name: &[u16]) -> bool { /// `case_sensitive` selects exact ordinal comparison (a case-sensitive /// directory, a per-directory NTFS opt-in feature, e.g. via `fsutil file /// setCaseSensitiveInfo`, where `A.txt` and `a.txt` genuinely name different -/// files) or `CompareStringOrdinal` with `bIgnoreCase = TRUE` -- the OS's own +/// files) or `win-text-sys`'s `compare_string_ordinal` with `Case::Insensitive` -- the OS's own /// ordinal (per-UTF-16-unit) case folding -- for the overwhelmingly common /// case-insensitive-but-case-preserving directory. Either way this is never /// an ASCII-only fold: that silently drops a match whenever a subscription's @@ -102,20 +106,27 @@ fn is_direct_child(name: &[u16]) -> bool { /// spelling `C9.txt`), which is exactly the kind of event this crate's /// completeness contract (D-77) promises never to drop. fn names_match(a: &[u16], b: &[u16], case_sensitive: bool) -> bool { - use windows_sys::Win32::Foundation::{FALSE, TRUE}; - use windows_sys::Win32::Globalization::{CSTR_EQUAL, CompareStringOrdinal}; + let case = if case_sensitive { + Case::Sensitive + } else { + Case::Insensitive + }; + equal_from(compare_string_ordinal(a, b, case), a, b) +} - let a_len = i32::try_from(a.len()).unwrap_or(i32::MAX); - let b_len = i32::try_from(b.len()).unwrap_or(i32::MAX); - let ignore_case = if case_sensitive { FALSE } else { TRUE }; - // SAFETY: `a`/`b` are valid `u16` slices with lengths that fit the `i32` - // counts just computed; `CompareStringOrdinal` only reads the first - // `a_len`/`b_len` units of each and returns a plain `i32` result code. - let result = unsafe { CompareStringOrdinal(a.as_ptr(), a_len, b.as_ptr(), b_len, ignore_case) }; - // A truncated length (from a name longer than `i32::MAX` units, - // unreachable in practice) would only ever make an equal pair look - // unequal, never the reverse, so the fallback direction is safe. - result == CSTR_EQUAL +/// What a comparison's result says about whether `a` and `b` are the same name. +/// +/// Only an `Equal` is a match, and a failure the call reports is not one. A name +/// too long for the call's count, which no name is (that is more than 2^31 UTF-16 +/// units), is compared exactly: truncating the count would compare a prefix, and +/// two names that share one would match. Reaching that arm takes a slice that +/// long, so it is tested through the result. +fn equal_from(result: Result, a: &[u16], b: &[u16]) -> bool { + match result { + Ok(ordering) => ordering == Ordering::Equal, + Err(CompareStringOrdinalError::TooLong { .. }) => a == b, + Err(CompareStringOrdinalError::Call(_)) => false, + } } /// One subscription's place within a coalesced directory watcher: what it wants diff --git a/crates/windows-file-watcher/src/route/tests.rs b/crates/windows-file-watcher/src/route/tests.rs index 11c089393..f2e12227c 100644 --- a/crates/windows-file-watcher/src/route/tests.rs +++ b/crates/windows-file-watcher/src/route/tests.rs @@ -3,7 +3,12 @@ use wtf_string::Wtf16String; -use super::{Route, RouteScope}; +use std::cmp::Ordering; + +use win_error_codes::Win32Error; +use win_text_sys::{CompareStringOrdinalError, Operand}; + +use super::{Route, RouteScope, equal_from, names_match}; use crate::notify::{Change, ChangeKind, RelativeName}; use crate::queue::{WatchId, channel}; use crate::watch::{RetryMode, VolumeChangePolicy}; @@ -175,3 +180,76 @@ fn order_is_preserved_within_a_selection() { vec!["a.txt", "b.txt", "c.txt"] ); } + +fn units(text: &str) -> Vec { + text.encode_utf16().collect() +} + +#[test] +fn names_match_folds_case_only_when_the_directory_is_case_insensitive() { + for (left, right) in [("a.txt", "A.TXT"), ("\u{e9}", "\u{c9}"), ("Mixed", "mIXED")] { + assert!(names_match(&units(left), &units(right), false), "{left:?}"); + assert!(!names_match(&units(left), &units(right), true), "{left:?}"); + } +} + +#[test] +fn names_match_agrees_with_itself_on_identical_names_in_both_modes() { + for text in ["", "a", "target.txt", "\u{1f600}.txt", "a b"] { + assert!(names_match(&units(text), &units(text), false), "{text:?}"); + assert!(names_match(&units(text), &units(text), true), "{text:?}"); + } +} + +#[test] +fn names_match_rejects_different_names_in_both_modes() { + for (left, right) in [("a", "b"), ("a", ""), ("", "a"), ("ab", "a"), ("a", "ab")] { + assert!(!names_match(&units(left), &units(right), false), "{left:?}"); + assert!(!names_match(&units(left), &units(right), true), "{left:?}"); + } +} + +#[test] +fn names_match_compares_an_interior_nul_as_content() { + let with_b = [u16::from(b'a'), 0, u16::from(b'b')]; + let with_c = [u16::from(b'a'), 0, u16::from(b'c')]; + for case_sensitive in [false, true] { + assert!(names_match(&with_b, &with_b, case_sensitive)); + assert!(!names_match(&with_b, &with_c, case_sensitive)); + assert!(!names_match( + &[u16::from(b'a'), 0], + &[u16::from(b'a')], + case_sensitive + )); + } +} + +#[test] +fn a_comparison_result_is_a_match_only_when_it_says_equal() { + let a = [u16::from(b'a')]; + let b = [u16::from(b'b')]; + assert!(equal_from(Ok(Ordering::Equal), &a, &b)); + assert!(!equal_from(Ok(Ordering::Less), &a, &a)); + assert!(!equal_from(Ok(Ordering::Greater), &a, &a)); +} + +#[test] +fn a_name_too_long_for_the_call_is_compared_exactly() { + let too_long = |len| { + Err(CompareStringOrdinalError::TooLong { + operand: Operand::First, + len, + }) + }; + let a = [u16::from(b'a')]; + let upper_a = [u16::from(b'A')]; + assert!(equal_from(too_long(1 << 32), &a, &a)); + assert!(!equal_from(too_long(1 << 32), &a, &upper_a)); +} + +#[test] +fn a_failure_of_the_call_is_not_a_match() { + let failed = Err(CompareStringOrdinalError::Call(Win32Error::from_code(87))); + let a = [u16::from(b'a')]; + assert!(!equal_from(failed, &a, &a)); +} diff --git a/crates/windows-impersonation-token-sys/CHECKLIST.md b/crates/windows-impersonation-token-sys/CHECKLIST.md index 405bf062b..bb036ca21 100644 --- a/crates/windows-impersonation-token-sys/CHECKLIST.md +++ b/crates/windows-impersonation-token-sys/CHECKLIST.md @@ -4,18 +4,4 @@ Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md), with rationale in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Completed work is in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). -## IT-M-inf -- The horizon (ungated) - -- [ ] **IT-inf.1** -- **`ImpersonationToken` holds its captured token as a - `SharedHandle` instead of an `Arc`.** Raised 2026-10-07 in the - durable-ioring design session, where - [win-shared-os-owned-handle](../win-shared-os-owned-handle/README.md)'s - `SharedHandle` became the workspace's one shared owner of a handle (its - `SH-1+.1`). Deferred by the engineer; nothing gates it. The change is to the private field only: - - **The handle stays unexposed.** [D-6](DESIGN-NOTES.md#d-6) and the public - docs promise that no safe API exposes the captured handle, so no - `AsHandle`, `AsRawHandle`, `From` or accessor reaches the surface. - - **No equality appears.** [D-8](DESIGN-NOTES.md#d-8) refuses equality, so - `SharedHandle::same_handle` stays internal, as `Arc::ptr_eq` is today. - - **What it removes:** the hand-written `Clone`, which a derive replaces. - - Not a breaking change; the public surface is unchanged. +Nothing is pending. diff --git a/crates/windows-impersonation-token-sys/COMPLETED-CHECKLIST.md b/crates/windows-impersonation-token-sys/COMPLETED-CHECKLIST.md index 61164dcf4..972ef7288 100644 --- a/crates/windows-impersonation-token-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-impersonation-token-sys/COMPLETED-CHECKLIST.md @@ -11,3 +11,16 @@ The remaining `THREAD_TOKEN_CAPTURE_ACCESS` mutation from bitwise OR to bitwise XOR is behaviorally equivalent: `TOKEN_DUPLICATE` and `TOKEN_QUERY` are disjoint single-bit flags, so both operators produce the same access mask. No runtime unit test can distinguish identical values. + +## Moved 2026-10-18 22:38:57 -04:00 -- The captured token is a `SharedHandle` + +### IT-inf.1 -- `ImpersonationToken` holds its captured token as a `SharedHandle` instead of an `Arc`. *(completed 2026-10-18 22:38:57 -04:00)* + +Parked 2026-10-07; the engineer asked for it on 2026-10-18 ("yes also fix windows-impersonation-token-sys's +alignment also"), with the request crate's [D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32), which +moved that crate onto `SharedHandle`. The change is to the private field: the hand-written `Clone` is a derive, +the handle stays unexposed ([D-6](DESIGN-NOTES.md#d-6)), and equality is still refused +([D-8](DESIGN-NOTES.md#d-8)), whose text now names `SharedHandle::same_handle` where it named `Arc::ptr_eq`. A unit +test asserts that a clone shares the captured token and that a second capture does not. The capture's +`DuplicateTokenEx` is unchanged: it makes a new token object with narrowed access, which is the reason it exists +and not a duplicate kept out of habit. diff --git a/crates/windows-impersonation-token-sys/COMPLETED-PLANS.md b/crates/windows-impersonation-token-sys/COMPLETED-PLANS.md index e64f8d157..d60892e6a 100644 --- a/crates/windows-impersonation-token-sys/COMPLETED-PLANS.md +++ b/crates/windows-impersonation-token-sys/COMPLETED-PLANS.md @@ -7,3 +7,4 @@ tracked in [PLANS.md](PLANS.md). |---|---|---|---| | [../../CHECKLIST.md](../../CHECKLIST.md) | 2026-08-27 | M4 delivered the publishable captured-impersonation platform layer: synchronous effective-context capture, owned cross-thread transport, scoped application with exact prior-token restoration, deterministic failure and lifecycle coverage, complete public documentation, and publication validation. | [DESIGN-NOTES.md](DESIGN-NOTES.md); [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) | | [CHECKLIST-mutant-tests.md](COMPLETED-CHECKLIST.md#mt-1) (deleted on completion; the link opens its archived entry) | 2026-09-01 | Added unit tests for both actionable mutation-test survivors and recorded why the disjoint access-mask operator mutation is behaviorally equivalent. | N/A | +| [CHECKLIST.md](COMPLETED-CHECKLIST.md#it-inf1) | 2026-10-18 | `IT-inf.1`: the captured token is held as a `SharedHandle` rather than an `Arc`; `Clone` is derived and the public surface is unchanged. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-8) | diff --git a/crates/windows-impersonation-token-sys/Cargo.toml b/crates/windows-impersonation-token-sys/Cargo.toml index 716c0b9d6..312d20942 100644 --- a/crates/windows-impersonation-token-sys/Cargo.toml +++ b/crates/windows-impersonation-token-sys/Cargo.toml @@ -24,6 +24,11 @@ path = "src/lib.rs" default-target = "x86_64-pc-windows-msvc" targets = ["x86_64-pc-windows-msvc"] +[dependencies] +# The captured token is shared by every clone through the workspace's one shared owner of a handle, +# rather than an `Arc` of this crate's own (IT-inf.1). +win-shared-os-owned-handle = { version = "0.1.0", path = "../win-shared-os-owned-handle" } + [dependencies.windows-sys] version = "0.61.2" default-features = false diff --git a/crates/windows-impersonation-token-sys/DESIGN-NOTES.md b/crates/windows-impersonation-token-sys/DESIGN-NOTES.md index 9f4f09c80..0fd3de1aa 100644 --- a/crates/windows-impersonation-token-sys/DESIGN-NOTES.md +++ b/crates/windows-impersonation-token-sys/DESIGN-NOTES.md @@ -22,7 +22,7 @@ for a bounded operation, and restoring the exact prior state. | D-5 | **This is not a general security-helper collection.** New surface belongs only when it is required to preserve the capture, transport, apply, or restore lifecycle of `ImpersonationToken`. | | D-6 | **Capture duplicates the effective token into an immutable `TOKEN_IMPERSONATE`-only handle.** The thread token is opened with `OpenAsSelf` and only query/duplicate rights; its identification, impersonation, or delegation level is preserved. No-thread-token context falls back to a process-token snapshot at `SecurityImpersonation`. Anonymous context is rejected synchronously. Clones share ownership of the captured handle rather than acquiring broader rights. `DuplicateTokenEx` is used rather than `DuplicateHandle`, and that is a correctness requirement rather than a preference -- see [Why `DuplicateTokenEx` and not `DuplicateHandle`](#why-duplicatetokenex). | | D-7 | **Scoped application is exposed only as a closure operation backed by a private thread-bound guard.** The guard saves an exact `TOKEN_IMPERSONATE` handle or explicit no-token state, applies the captured handle with `SetThreadToken`, and restores the saved state before return and during unwind. Its `Rc` marker makes it `!Send` and `!Sync`. Restoration failure panics from `Drop`; callers cannot obtain or forget the guard. | -| D-8 | **`ImpersonationToken` implements no equality, and this is deliberate.** Object-identity equality is buildable (`Arc::ptr_eq`, or `CompareObjectHandles`, neither needing access rights) but is a trap, because `DuplicateTokenEx` mints a *new* token object -- two captures of one context are interchangeable yet would compare unequal. Identity equality is worse: it needs `TOKEN_QUERY`, which [D-6](#d-6) deliberately withholds from the captured handle, and even via capture-time metadata a same-user, same-LUID comparison returns true for a *restricted* token derived from the same logon. `==` on a security principal reads as "same rights" and nothing implementable means that. **No work is scheduled by this decision**; it records why an obvious-looking addition is refused. See [Equality is refused, not deferred](#equality-is-refused). | +| D-8 | **`ImpersonationToken` implements no equality, and this is deliberate.** Object-identity equality is buildable (`SharedHandle::same_handle`, or `CompareObjectHandles`, neither needing access rights) but is a trap, because `DuplicateTokenEx` mints a *new* token object -- two captures of one context are interchangeable yet would compare unequal. Identity equality is worse: it needs `TOKEN_QUERY`, which [D-6](#d-6) deliberately withholds from the captured handle, and even via capture-time metadata a same-user, same-LUID comparison returns true for a *restricted* token derived from the same logon. `==` on a security principal reads as "same rights" and nothing implementable means that. **No work is scheduled by this decision**; it records why an obvious-looking addition is refused. See [Equality is refused, not deferred](#equality-is-refused). | ## Publication boundary The crate is Windows-only, independently versioned, and published to crates.io. @@ -47,7 +47,7 @@ captured handle to permit it would contradict the invariant that no safe API permits rights expansion, in exchange for a convenience. **Object identity is implementable and is still the wrong contract.** Clones -share the handle through an `Arc`, so `Arc::ptr_eq` answers clone-identity for +share the handle through a `SharedHandle`, so `same_handle` answers clone-identity for free, and `CompareObjectHandles` extends that to separately duplicated handles without needing rights on either. But `DuplicateTokenEx` mints a **new token object**, so capturing the same context twice produces two tokens that are diff --git a/crates/windows-impersonation-token-sys/PLANS.md b/crates/windows-impersonation-token-sys/PLANS.md index af37a9de4..9ded472af 100644 --- a/crates/windows-impersonation-token-sys/PLANS.md +++ b/crates/windows-impersonation-token-sys/PLANS.md @@ -7,4 +7,3 @@ Design decisions are in | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | not started | Parked on the horizon: `IT-inf.1`, holding the captured token as a `SharedHandle` instead of an `Arc`. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/windows-impersonation-token-sys/src/lib.rs b/crates/windows-impersonation-token-sys/src/lib.rs index bc5770249..1dbdc9d64 100644 --- a/crates/windows-impersonation-token-sys/src/lib.rs +++ b/crates/windows-impersonation-token-sys/src/lib.rs @@ -73,8 +73,8 @@ use std::marker::PhantomData; use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; use std::ptr; use std::rc::Rc; -use std::sync::Arc; +use win_shared_os_owned_handle::SharedHandle; use windows_sys::Win32::Foundation::{ERROR_CANT_OPEN_ANONYMOUS, ERROR_NO_TOKEN, FALSE, TRUE}; use windows_sys::Win32::Security::{ DuplicateTokenEx, GetTokenInformation, SECURITY_IMPERSONATION_LEVEL, SecurityAnonymous, @@ -241,17 +241,10 @@ impl std::error::Error for ApplyError { /// callers to mutate the captured token. The value is safe to send to and share /// between threads; each call to [`Self::with_impersonation`] affects only the /// calling thread for the duration of its closure. +#[derive(Clone)] #[must_use = "dropping the token discards the captured impersonation context"] pub struct ImpersonationToken { - handle: Arc, -} - -impl Clone for ImpersonationToken { - fn clone(&self) -> Self { - Self { - handle: Arc::clone(&self.handle), - } - } + handle: SharedHandle, } impl ImpersonationToken { @@ -295,7 +288,7 @@ impl ImpersonationToken { // that must be released with CloseHandle, which OwnedHandle does. let handle = unsafe { OwnedHandle::from_raw_handle(captured) }; Ok(Self { - handle: Arc::new(handle), + handle: SharedHandle::new(handle), }) } diff --git a/crates/windows-impersonation-token-sys/src/tests.rs b/crates/windows-impersonation-token-sys/src/tests.rs index 4b1fdd189..6f685525e 100644 --- a/crates/windows-impersonation-token-sys/src/tests.rs +++ b/crates/windows-impersonation-token-sys/src/tests.rs @@ -166,6 +166,16 @@ fn captured_token_type_is_send_sync_and_clone() { assert_traits::(); } +#[test] +fn a_clone_shares_the_captured_token_and_a_second_capture_does_not() { + let token = ImpersonationToken::capture().expect("capture the thread's context"); + let clone = token.clone(); + let recaptured = ImpersonationToken::capture().expect("capture it again"); + + assert!(token.handle.same_handle(&clone.handle)); + assert!(!token.handle.same_handle(&recaptured.handle)); +} + #[test] fn application_guard_is_neither_send_nor_sync() { trait AmbiguousIfSend { diff --git a/crates/windows-ioring-sys/CHECKLIST.md b/crates/windows-ioring-sys/CHECKLIST.md index 25211905e..dc9b4c475 100644 --- a/crates/windows-ioring-sys/CHECKLIST.md +++ b/crates/windows-ioring-sys/CHECKLIST.md @@ -95,33 +95,6 @@ because the sample is wrong whatever becomes of that crate [strategy.rs](examples/epoch_log/strategy.rs)'s `settle`, and check [contract.rs](examples/epoch_log/contract.rs) for a statement that relies on it. -## M31 -- `EventDelivery`'s callback contract is specified, not incidental - -Opened 2026-10-05 from the durable-ioring design session -([DESIGN-SESSION-2026-10-05-epoch-ring.md](../../design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md), -"DI-2.3: completion routing"). durable-ioring's design ([DI-D-18](../durable-ioring/DESIGN-NOTES.md#di-d-18)) -needs two properties of the -`on_completion` callback. When this milestone opened they held only as implementation: the -comments on the private `drain` and on the callback body said so, but `EventDelivery::new`'s -rustdoc did not, so relying on them would have bound a consumer to incidental behaviour. The engineer's framing: using -it "without permission" is not better than getting it properly supported. - -This does **not** make the ring a synchronization provider. That is out of this crate's scope, and -no consumer may share the ring's lock. It states two properties of this crate's own callback that -any callback API owes its users. - -- [x] **M31.1** -- `on_completion`'s re-entrancy and concurrency are stated on `EventDelivery::new` and pinned by a test and a sabotage. -> [completed 2026-10-07](COMPLETED-CHECKLIST.md#m311) - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-M3` -> `DI-3.2.2.1` (dioring's plain I/O and the delivery its front ends share), which relies on this item's contract. See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). - -- [x] **M31.2** -- **Withdrawn 2026-10-06: "every `Completion` is real" is already contract.** - `try_pop`'s rustdoc hands a payload back by that call alone, and [D-79](DESIGN-NOTES.md#d-79) - makes a completion for anything not in flight a panicking defect; the restatement added nothing - a consumer could rely on, and its `compile_fail` doctest would have pinned an implementation - detail. - -- [x] **M31.3** -- `SharedFile` is removed; a safe push's file is `win-shared-os-owned-handle`'s `SharedHandle`. -> [completed 2026-10-07](COMPLETED-CHECKLIST.md#m313) - ## M32 -- Write-through needs an unbuffered handle Opened 2026-10-07 from durable-ioring's `DI-3.2.2.1`, whose test of its caching option found this. @@ -157,38 +130,3 @@ needs the thread pool only to be told when to drain. obligation report differs. > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-3.10.3.5` -> `DI-3.10.3.5.6.1` (pool delivery over the threadless backend, built on this wake). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). - -## M34 -- A registration's span check, asked without pushing - -Opened 2026-10-11 from durable-ioring's `DI-3.10.3.5.6.2.1`, whose core accepts a gated operation -now and pushes it once its gate is durable, and so must refuse at acceptance a span the push would -refuse. - -- [x] **M34.1** -- `RegisteredBuffers::check_span`: the span check every push makes, asked without pushing. -> [completed 2026-10-11](COMPLETED-CHECKLIST.md#m341) - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-3.10.3.5.6.2` -> `DI-3.10.3.5.6.2.1` (the Windows backend's `check` asks the registration). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). - -## M40 -- A batch left queued, for a caller that owns the doorbell - -Opened 2026-10-13 from durable-ioring's `DI-7+.2`. - -- [x] **M40.1** -- `Batch::leave_queued`: close a batch without submitting what it queued. -> [completed 2026-10-13](COMPLETED-CHECKLIST.md#m401) - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-7+.2` (the engine rings the doorbell; the Windows backend's push only queues). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). - -## M41 -- A ring typed by whether it can hold nothing for an operation - -Opened 2026-10-14 from durable-ioring's `DI-7+.3`. - -- [x] **M41.1** -- `IoRing` with `K` `Open` or `Owned`, and `IoRing::into_owned`: a ring that cannot have an operation in flight it holds nothing for. -> [completed 2026-10-14](COMPLETED-CHECKLIST.md#m411) - - > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-7+.3` (the Windows backend's ring is sealed, and its unreachable branch is gone). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). -## M31+ -- ETW for the ring's own lock (withdrawn) - -Opened and withdrawn 2026-10-06 in the durable-ioring design session. - -- [x] **M31+.1** -- **Withdrawn 2026-10-06: the ring crate publishes no ETW provider of its own.** - It would have timed contended acquisitions of `EventDelivery`'s mutex. The engineer: "the etw - events we publish give a surface for diagnosis of issues; clients don't want to search around - across providers to try to correlate issues." durable-ioring publishes the one surface, and sees - this lock's contention as a slow call into the ring, timed at its boundary. diff --git a/crates/windows-ioring-sys/COMPLETED-CHECKLIST.md b/crates/windows-ioring-sys/COMPLETED-CHECKLIST.md index e8c466d89..58f88f0bc 100644 --- a/crates/windows-ioring-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-ioring-sys/COMPLETED-CHECKLIST.md @@ -1310,6 +1310,7 @@ instead prove it reached the state it claims to exercise. **Found while adding tests:** `temp_file` keyed its fixture on the process id alone, so the second test in this binary would have raced the first for one path -- libtest runs them as concurrent threads. Now tagged per test. + ### M18 -- The population no runtime technique reaches Population C, and the one that produced the two most severe findings of the M14 review round. `get_mut` @@ -2640,6 +2641,7 @@ almost every failing test in this crate. > in the field. What the paragraph got right is the finding that prompted it -- the guards were > genuinely uncovered. It is left standing rather than rewritten because the archive is history, > and because the error in it is instructive: it priced a seam it never checked was necessary. + ### M23.5 -- Both asserts in `IoRing::drop` are now reached by tests, and the seam the item priced turned out not to be needed. *(completed 2026-09-23 23:20:27 -04:00)* M23.4 left both asserts in `IoRing::drop` uncovered: suppressing them entirely left every test in @@ -3909,6 +3911,7 @@ The decision, including why naive pruning was rejected as worse than the leak, i so a flush could never say which group of writes it belonged to. It is `Option<(Option, X)>` now: the outer option is whether the ring stowed anything, the inner one whether what it stowed included a buffer. + ## Moved 2026-09-26 00:31:44 -04:00 -- M28.4.1d.2b, the cost of a single payload type ### M28.4.1d.2b -- Record what a single payload type costs a consumer holding heterogeneous buffers, and decide whether anything is owed. *(completed 2026-09-26 00:31:44 -04:00)* @@ -3941,6 +3944,7 @@ that a consumer multiplexing buffer types over one ring "will not generally" fin each free. Of the seventeen ring types the conversion introduced, one carried two buffer types through what had been a single ring, and its writes were already sequential. The prediction was made from a single observation and tested against sixteen more. + ## Moved 2026-09-26 10:26:56 -04:00 -- M28.4.1d.3, the commit that ended the break ### M28.4.1d.3 -- Retire the token API and apply D-74. *(completed 2026-09-26 10:26:56 -04:00)* @@ -4009,6 +4013,7 @@ including the README's headline example, which is the first thing a consumer rea rewritten. The census in [response_space_census.rs](tests/response_space_census.rs) also caught two unbounded `try_pop` spins that the rename made visible to it; both are now bounded pops. + ## Moved 2026-09-26 11:54:28 -04:00 -- M28.4.2, sabotaging the inventory ### M28.4.2 -- Sabotage the inventory: a push that does not record, and a pop that does not retire, must both turn the suite red. *(completed 2026-09-26 11:54:28 -04:00)* @@ -4053,6 +4058,7 @@ one at a time, and `pop_within` went a full milestone without it. guarantee survived its type -- `Appender` keeps a `RingContract` and must observe both ends -- so it is relocated to the appender, alongside a new completion-side case that replaces the retired `M22.2` ordering one. + ## Moved 2026-09-26 13:05:21 -04:00 -- M28.5, the tokenless push ### M28.5 -- Answer the tokenless push. *(completed 2026-09-26 13:05:21 -04:00)* @@ -4088,6 +4094,7 @@ asserts with two cases rather than one. And the first manifest case was crash-ca (STATUS_STACK_BUFFER_OVERRUN): manufacturing a payload needs an `X` from nowhere, and `std::mem::zeroed()` on a ring whose sidecar is `&'static str` is a null reference. The case is arithmetic now, and fails by assertion on every ring. + ## Moved 2026-09-26 13:30:33 -04:00 -- M28.6, sweeping what the break made false ### M28.6 -- Sweep what the break makes false. *(completed 2026-09-26 13:30:33 -04:00)* @@ -4135,6 +4142,7 @@ documentation needs its output read, not just its exit code. `MUTATION-SURVIVORS.md` and the `M18.1` borrow-surface table are dated captures and were **annotated, not edited** -- the record of what was measured then is the thing worth keeping, but a reader should not learn from a compile error that an item is gone. + ## Moved 2026-09-26 16:39:03 -04:00 -- M28.7, decided against ### M28.7 -- Decide whether the ring should check conservation itself. *(completed 2026-09-26 16:39:03 -04:00)* @@ -4184,6 +4192,7 @@ becomes answerable and the question is live again. **Also fixed while here:** three links to `src/pending.rs`, which `M28.4.1d.3` deleted -- this item's own, and one each in `D-71` and `D-74`. All now point at the design session that holds the material. + ## Moved 2026-09-26 17:24:22 -04:00 -- M28.8, the duplication closed properly ### M28.8 -- Give `RingContract` an in-flight count, and delete the counter that duplicates it. *(completed 2026-09-26 17:24:22 -04:00)* @@ -4838,6 +4847,7 @@ a wedged pool and a broken probe are not confusable. never have dispatched in this process, or whether it was put into this state during setup. Nothing in these runs needs a callback before the deliveries are armed at about 2.5ms, so there is no earlier successful dispatch to compare against. A period of one or two milliseconds would give one. + ### M26.13.14 -- At the 15.625ms system tick the heartbeat misses 320 consecutive expiries and the trace window is empty end to end, in 13 of 13; the onset is bracketed to the first 15.7ms, and the sub-millisecond period item 0 asked for is not reachable this way. *(completed 2026-09-27 16:59:53 -04:00)* **Same probe as [M26.13.13](COMPLETED-CHECKLIST.md#m261313), period changed from a hundred @@ -4874,6 +4884,7 @@ without changing the machine's timer behaviour under the measurement. Item 0 is wait on an **already-signalled** event at process start instead: due immediately, no timer resolution needed, and in the kernel-delivered class that fails. [measurements/2026-09-27-at-the-system-tick-it-still-never-starts/](../windows-threadpool-sys/measurements/2026-09-27-at-the-system-tick-it-still-never-starts/README.md). + ### M26.13.15 -- Delaying only the `SubmitThreadpoolWork` call by up to 2000ms moves the delivery with it in 99 of 99, the released worker serves the queued wait ahead of the work that woke it, and an `ntdll` census corrects "user-mode queue push" to "the only path that calls `NtReleaseWorkerFactoryWorker`". *(completed 2026-09-27 19:43:47 -04:00)* **Three results, from one experiment and one census.** @@ -4924,6 +4935,7 @@ is gone -- [M26.13.11](COMPLETED-CHECKLIST.md#m261311) found three parked worker growth is now accounted for. The question becomes: the factory holds parked workers and a queued packet and does not put them together. The next measurement is the factory's own counters via `NtQueryInformationWorkerFactory`, read from a dump taken while stalled. + ### M26.13.16 -- No thread alive at the stall ever runs a callback, in 12 of 12, while a passing run serves the delivery on a pre-existing thread in 30 of 30: the parked workers are present and unused, so `M26.13.11`'s "not starved" survives but the inference drawn from it does not. *(completed 2026-09-27 20:20:01 -04:00)* **Prompted by a question about [M26.13.15](COMPLETED-CHECKLIST.md#m261315):** was the thread that @@ -4982,6 +4994,7 @@ sharpens rather than contradicts. whether the factory believes it has an available worker while three sit parked in `NtWaitForWorkViaWorkerFactory`. If the counters say zero available while the stacks say three parked, that disagreement is the fault. + ### M26.13.17 -- Inline ntdll hooks and a worker-factory scan: the process holds two factories, and the default pool reports zero workers while stalled against one while healthy, so `M26.13.11`'s three parked workers were the *other* factory's all along. *(completed 2026-09-27 21:46:03 -04:00)* **Built the instrument the question needed.** `windows-threadpool-sys` gained inline hooks on @@ -5048,6 +5061,7 @@ carried here. asking each factory, never by reading stacks. A parked `TppWorkerThread` says *a* pool has a worker; in a process with more than one factory it does not say which, and here that distinction was the whole answer. + ### M26.13.18 -- Hooks installed before `main` show the pool's first worker is never created: a healthy run makes one 0.24-0.31ms after the delivery is armed, a stalled run makes none before the release in 8 of 8, and the `AlreadySignaled` race is refuted. *(completed 2026-09-27 22:37:50 -04:00)* **From a suggestion in review**: the hooks were arriving after the tests started, so put something @@ -5095,6 +5109,7 @@ because every observation is of a call *ntdll* makes and neither case involves o **What is left is outside this workspace's reach.** A worker factory with no workers, permitted to create one, not paused and not shut down, owes a callback and does not make the thread to run it until `NtReleaseWorkerFactoryWorker` asks. Why is inside the kernel. + ### M26.13.19 -- An ETW kernel trace confirms the missing worker independently of the ntdll hooks: across 900 traced runs exactly one process has a gap over a second, the failing one, at 5008.7ms against a healthy 0.232-17.928ms. *(completed 2026-09-28 00:08:32 -04:00)* **Why a second instrument.** Everything known about the missing worker came from hooks this @@ -5140,6 +5155,7 @@ event anywhere for a packet arriving at a completion port or for a worker factor **Queued rather than taken:** `DISPATCHER`, which adds `ReadyThread` and would name what causes the worker to appear in a healthy run. Left out here to keep a machine-wide trace small; it is now the whole of M26.13 experiment 1. + ### M26.13.20 -- Re-verified the reproducer's premises from a clean build: the 3-test reduction matches the full binary (16 vs 18 in 4000), the trigger is necessary (0 in 4000 without it), one victim suffices at about half the rate, and "fail together, never singly" was wrong. *(completed 2026-09-28 08:27:01 -04:00)* **Why re-check the setup at all.** Nineteen findings had accumulated on top of a reproducer that was @@ -5200,6 +5216,7 @@ about 2.4 ms later -- so the trigger has finished before the victims arrive. Tha passing ones, with an indistinguishable gap distribution, so it is simply how `libtest` schedules these three tests. Reading the failures alone would have promoted a constant to a discriminator; the passing control is the only reason that did not happen. + ### M26.14.1 -- The trigger's *teardown* is what poisons: built and never dropped it gives 0 in 4000 against a live control's 11, and a ring dropped without a delivery also gives 0. *(completed 2026-09-28 12:57:52 -04:00)* **From a review reading**: that `dropping_with_nothing_outstanding_does_not_hang` poisons the @@ -5238,6 +5255,7 @@ once. and this cannot separate them. Nor why a ring is needed when the wait is on an ordinary event. Both are queued as M26.14.2. [measurements/2026-09-28-the-teardown-is-what-poisons/](../windows-threadpool-sys/measurements/2026-09-28-the-teardown-is-what-poisons/README.md). + ### M26.14.2 -- Closing the wait too soon after disarming it is the poison: a 1ms gap between the disarm and the close, or a true drain in place of a cancel, each give 0 in 20000 against a control's 10. *(completed 2026-09-28 20:39:34 -04:00)* **The decisive run**: three arms interleaved, 20000 each, pipes drained asynchronously, 60s bound @@ -5288,6 +5306,7 @@ why the decisive arms used 20000. **Raised, not taken:** M26.14.3 asks whether `windows-threadpool-sys` should adopt the draining teardown, which is a one-argument change with real semantic weight -- a drop that blocks until a pending callback runs can deadlock a caller. M26.14.4 asks for the threshold sweep if it does not. + ## Moved 2026-10-01 21:19:21 -04:00 -- M20 through M24, and the M21+/M22+/M28+ buckets they filled Eight milestones, every item in them complete. Each item was archived individually as it @@ -6548,4 +6567,67 @@ an `Owned` one has no call that queues such an operation and pops what it held w - **Tested** in [owned_ring.rs](tests/owned_ring.rs) and by `compile_fail` doctests on `AllowsRaw` whose pinned codes were checked under `RUSTC_BOOTSTRAP=1`; the sabotage entries are caught, and the panic an owned ring makes for a completion it held nothing for is a declared blind spot. -- **Borrow-surface audit**: `Batch::new` and `CompletionWake::new` re-asked, unchanged. \ No newline at end of file +- **Borrow-surface audit**: `Batch::new` and `CompletionWake::new` re-asked, unchanged. + +## Moved 2026-10-21 21:46:52 -04:00 -- windows-ioring-sys: milestones M31, M34, M40 and M41 are complete, and M31+ is withdrawn + +### M31 -- `EventDelivery`'s callback contract is specified, not incidental + +Opened 2026-10-05 from the durable-ioring design session +([DESIGN-SESSION-2026-10-05-epoch-ring.md](../../design-sessions/DESIGN-SESSION-2026-10-05-epoch-ring.md), +"DI-2.3: completion routing"). durable-ioring's design ([DI-D-18](../durable-ioring/DESIGN-NOTES.md#di-d-18)) +needs two properties of the +`on_completion` callback. When this milestone opened they held only as implementation: the +comments on the private `drain` and on the callback body said so, but `EventDelivery::new`'s +rustdoc did not, so relying on them would have bound a consumer to incidental behaviour. The engineer's framing: using +it "without permission" is not better than getting it properly supported. + +This does **not** make the ring a synchronization provider. That is out of this crate's scope, and +no consumer may share the ring's lock. It states two properties of this crate's own callback that +any callback API owes its users. + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-M3` -> `DI-3.2.2.1` (dioring's plain I/O and the delivery its front ends share), which relies on this item's contract. See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). + +- [x] **M31.2** -- **Withdrawn 2026-10-06: "every `Completion` is real" is already contract.** + `try_pop`'s rustdoc hands a payload back by that call alone, and [D-79](DESIGN-NOTES.md#d-79) + makes a completion for anything not in flight a panicking defect; the restatement added nothing + a consumer could rely on, and its `compile_fail` doctest would have pinned an implementation + detail. + +Items, each archived above under its own heading: [M31.1](#m311), [M31.3](#m313). + +### M34 -- A registration's span check, asked without pushing + +Opened 2026-10-11 from durable-ioring's `DI-3.10.3.5.6.2.1`, whose core accepts a gated operation +now and pushes it once its gate is durable, and so must refuse at acceptance a span the push would +refuse. + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-3.10.3.5.6.2` -> `DI-3.10.3.5.6.2.1` (the Windows backend's `check` asks the registration). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). + +Items, each archived above under its own heading: [M34.1](#m341). + +### M40 -- A batch left queued, for a caller that owns the doorbell + +Opened 2026-10-13 from durable-ioring's `DI-7+.2`. + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-7+.2` (the engine rings the doorbell; the Windows backend's push only queues). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). + +Items, each archived above under its own heading: [M40.1](#m401). + +### M41 -- A ring typed by whether it can hold nothing for an operation + +Opened 2026-10-14 from durable-ioring's `DI-7+.3`. + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-7+.3` (the Windows backend's ring is sealed, and its unreachable branch is gone). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). + +Items, each archived above under its own heading: [M41.1](#m411). + +### M31+ -- ETW for the ring's own lock (withdrawn) + +Opened and withdrawn 2026-10-06 in the durable-ioring design session. + +- [x] **M31+.1** -- **Withdrawn 2026-10-06: the ring crate publishes no ETW provider of its own.** + It would have timed contended acquisitions of `EventDelivery`'s mutex. The engineer: "the etw + events we publish give a surface for diagnosis of issues; clients don't want to search around + across providers to try to correlate issues." durable-ioring publishes the one surface, and sees + this lock's contention as a slow call into the ring, timed at its boundary. diff --git a/crates/windows-ioring-sys/Cargo.toml b/crates/windows-ioring-sys/Cargo.toml index 369ba5750..8708ff420 100644 --- a/crates/windows-ioring-sys/Cargo.toml +++ b/crates/windows-ioring-sys/Cargo.toml @@ -162,6 +162,9 @@ windows-topology-sys = { path = "../windows-topology-sys", features = [ ] } # M7's ring-copy sample deserializes a fed-in topology description (--topology). serde_json = "1.0" +# `tests/bounded_pop.rs` makes its overlapped named pipe with this crate, in place of an `unsafe` +# `CreateNamedPipeW` call of its own. Path-only, as `windows-topology-sys` above and for its reason. +win-pipes-sys = { path = "../win-pipes-sys" } # M7's ring-copy sample additionally needs `GROUP_AFFINITY` (pinned-thread # affinity); M14.1's epoch-log sample needs `Win32_System_Ioctl` for the # `FSCTL_SET_ZERO_DATA` reclamation it orders against ring epochs, and for the @@ -173,7 +176,6 @@ serde_json = "1.0" # `VirtualAllocExNuma` on its own. windows-sys = { version = "0.61.2", default-features = false, features = [ "Win32_System_Ioctl", - "Win32_System_Pipes", "Win32_System_SystemInformation", ] } # M14.1's epoch-log sample orders a non-ring `FSCTL` against ring epochs, which diff --git a/crates/windows-ioring-sys/tests/bounded_pop.rs b/crates/windows-ioring-sys/tests/bounded_pop.rs index 60fd2fc4c..943037e78 100644 --- a/crates/windows-ioring-sys/tests/bounded_pop.rs +++ b/crates/windows-ioring-sys/tests/bounded_pop.rs @@ -62,21 +62,15 @@ use std::io::Write; use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; use std::time::Duration; +use win_pipes_sys::{NamedPipeOptions, PipeMode, PipeOpenMode, create_named_pipe}; use windows_ioring_sys::{Batch, CompletionWait, IoRing, PushOptions, RingWait, SubmitWait}; /// The ring these tests drive: it holds the read's buffer, so no test has to. type PipeRing = IoRing>; use windows_sys::Win32::Foundation::GENERIC_WRITE; use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, - OPEN_EXISTING, + CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; -use windows_sys::Win32::System::Pipes::CreateNamedPipeW; - -/// `PIPE_ACCESS_INBOUND`. The byte-type, byte-read-mode and wait bits are all -/// zero, so one named constant covers the mode argument. -const PIPE_ACCESS_INBOUND: u32 = 0x0000_0001; -const PIPE_MODE_BYTE_WAIT: u32 = 0; /// The bound these tests give the pop. Any value would do: the read cannot /// complete until the test writes, so this only decides how long the wait @@ -117,23 +111,15 @@ impl Pipe { ); let wide_name = wide(&name); - // SAFETY: a valid NUL-terminated name and standard parameters; the - // returned handle is owned here. - let server = unsafe { - CreateNamedPipeW( - wide_name.as_ptr(), - PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, - PIPE_MODE_BYTE_WAIT, - 1, - 4096, - 4096, - 0, - std::ptr::null(), - ) + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_INBOUND | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE | PipeMode::READMODE_BYTE | PipeMode::WAIT, + max_instances: 1, + out_buffer_size: 4096, + in_buffer_size: 4096, + default_timeout_ms: 0, }; - assert!(!server.is_null(), "CreateNamedPipeW failed"); - // SAFETY: a fresh handle nothing else owns. - let server = unsafe { OwnedHandle::from_raw_handle(server) }; + let server = create_named_pipe(&name, &options, None).expect("create the pipe"); // SAFETY: the pipe exists; this opens its client end. let client = unsafe { diff --git a/crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md b/crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md index b23843c19..02f33d796 100644 --- a/crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md @@ -203,7 +203,7 @@ namespace operations, and `NR-2.1` to `NR-2.5` and `NR-2.7` are not those: they checklist placed them here, and that placement was not questioned when it should have been. The decision is [WV-D-5](../win-volume-sys/DESIGN-NOTES.md#wv-d-5), and a handle that must be shared is a `SharedHandle` ([WV-D-6](../win-volume-sys/DESIGN-NOTES.md#wv-d-6)). What this left to do is the move, -which is [WV-M2](../win-volume-sys/CHECKLIST.md#wv-m2----take-in-the-storage-locality-entries), and +which is [WV-M2](../win-volume-sys/COMPLETED-CHECKLIST.md#wv-m2----take-in-the-storage-locality-entries), and the deletion, which is `NR-2.8`. ## Moved 2026-10-15 23:09:28 -04:00 -- NR-2.8: the storage-locality entries leave this crate diff --git a/crates/windows-namespace-request-sys/DESIGN-NOTES.md b/crates/windows-namespace-request-sys/DESIGN-NOTES.md index c26173611..51143a40c 100644 --- a/crates/windows-namespace-request-sys/DESIGN-NOTES.md +++ b/crates/windows-namespace-request-sys/DESIGN-NOTES.md @@ -1152,8 +1152,9 @@ made the call fail with `ERROR_INVALID_HANDLE`. A handle whose call worked and r found, so the clearing of the last error is not exercised by any handle here; its sabotage is declared as surviving, with that reason, and the clearing rests on the page. -**Scheduled work:** none in this crate. The consumer it was built for is `durable-ioring`'s `DI-14.12`, with -`WV-3.1` in [win-volume-sys](../win-volume-sys/CHECKLIST.md). +**Scheduled work:** none in this crate. The consumer it was built for was `durable-ioring`'s `DI-14.12`, with +`WV-3.1` in [win-volume-sys](../win-volume-sys/CHECKLIST.md), which was withdrawn +([DI-D-73](../durable-ioring/DESIGN-NOTES.md#di-d-73)); the entry stays, since it reports `GetFileType`'s documented values. ## D-30: `AnchoredPath` is public, so a crate that builds a request of its own takes the path contract from here diff --git a/crates/windows-overlapped-io-sys/COMPLETED-CHECKLIST.md b/crates/windows-overlapped-io-sys/COMPLETED-CHECKLIST.md index 7b26b2c9c..d946f935a 100644 --- a/crates/windows-overlapped-io-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-overlapped-io-sys/COMPLETED-CHECKLIST.md @@ -449,6 +449,7 @@ rejected; four sabotages in [sabotage.json](sabotage.json), one of them refused *Milestones M16 and M17 below were numbered M15 and M16 on the branch that built them, and renumbered when that branch merged with `main`, whose M15 above was archived first. Their commit messages keep the branch numbers.* + ## Moved 2026-10-14 19:25:01 -04:00 -- M16.1: owned versus shared endpoint handles ### M16.1 -- Investigated owned versus shared endpoint handles; the engineer chose observers. *(completed 2026-10-14 19:25:01 -04:00)* diff --git a/crates/windows-overlapped-io-sys/Cargo.toml b/crates/windows-overlapped-io-sys/Cargo.toml index 3e290a8c2..47fce7ac8 100644 --- a/crates/windows-overlapped-io-sys/Cargo.toml +++ b/crates/windows-overlapped-io-sys/Cargo.toml @@ -93,6 +93,13 @@ version = "0.1.0" path = "../wtf-string" optional = true +# The tests make their overlapped named pipes with `win-pipes-sys`, in place of an `unsafe` +# `CreateNamedPipeW` call each. Path-only, with no `version`: Cargo checks a pinned version against the +# path at every build, so a release of that crate would break this one, and a versionless +# dev-dependency is left out of the published manifest (see `windows-ioring-sys`'s manifest). +[dev-dependencies] +win-pipes-sys = { path = "../win-pipes-sys" } + # Extra windows-sys features used only by tests that issue real overlapped I/O. # These stay out of the published crate's feature set for downstream consumers. [dev-dependencies.windows-sys] diff --git a/crates/windows-overlapped-io-sys/PLANS.md b/crates/windows-overlapped-io-sys/PLANS.md index ad3dd431d..b865db7f7 100644 --- a/crates/windows-overlapped-io-sys/PLANS.md +++ b/crates/windows-overlapped-io-sys/PLANS.md @@ -5,4 +5,4 @@ contained are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | M14: finish auditing this crate's completion-delivery contract against the ten specification-gap categories. Five of the ten were reached while recording the taxonomy; M14.1 covers the remaining five (1, 2, 6, 8, 9) against the endpoint/association/submission surface, and M14.2 sweeps `outstanding()` for the `has_room` shape -- an advisory predicate a caller's reliability gate depends on. (M16, observers sharing an endpoint's handle, and M17, more observer queries, are complete and archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md).) | [DESIGN-NOTES.md](DESIGN-NOTES.md) | +| [CHECKLIST.md](CHECKLIST.md) | not started | M14: finish auditing this crate's completion-delivery contract against the ten specification-gap categories. Five of the ten were reached while recording the taxonomy; M14.1 covers the remaining five (1, 2, 6, 8, 9) against the endpoint/association/submission surface, and M14.2 sweeps `outstanding()` for the `has_room` shape -- an advisory predicate a caller's reliability gate depends on. (M16, observers sharing an endpoint's handle, and M17, more observer queries, are complete and archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md).) | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/windows-overlapped-io-sys/tests/operation_identity.rs b/crates/windows-overlapped-io-sys/tests/operation_identity.rs index c92fbd996..c8cfcf06b 100644 --- a/crates/windows-overlapped-io-sys/tests/operation_identity.rs +++ b/crates/windows-overlapped-io-sys/tests/operation_identity.rs @@ -58,14 +58,7 @@ fn assert_rejected_without_a_native_call(error: &io::Error) { /// A connected server pipe end carrying no data, so a read on it stays pending /// until it is cancelled. The client end must be kept alive by the caller. fn pending_pipe(tag: &str) -> (UnassociatedEndpoint, std::fs::File) { - use std::os::windows::io::{FromRawHandle, OwnedHandle}; - use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED; - use windows_sys::Win32::System::Pipes::CreateNamedPipeW; - - /// `PIPE_ACCESS_DUPLEX`. Changing this value is a breaking change. - const PIPE_ACCESS_DUPLEX: u32 = 0x0000_0003; - /// `PIPE_TYPE_BYTE`. Changing this value is a breaking change. - const PIPE_TYPE_BYTE: u32 = 0x0000_0000; + use win_pipes_sys::{NamedPipeOptions, PipeMode, PipeOpenMode, create_named_pipe}; // Built from an escaped separator rather than a literal UNC prefix, so the // name survives any tooling that mangles adjacent backslashes. @@ -74,30 +67,18 @@ fn pending_pipe(tag: &str) -> (UnassociatedEndpoint, std::fs::File) { "{sep}{sep}.{sep}pipe{sep}windows-overlapped-io-sys-identity-{tag}-{}", std::process::id() ); - let wide: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - // SAFETY: creates a fresh overlapped named pipe from a valid wide name. - let handle = unsafe { - CreateNamedPipeW( - wide.as_ptr(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE, - 1, - 4096, - 4096, - 0, - ptr::null(), - ) + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_DUPLEX | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE, + max_instances: 1, + out_buffer_size: 4096, + in_buffer_size: 4096, + default_timeout_ms: 0, }; - assert!( - !handle.is_null() && handle as isize != -1, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); + let handle = create_named_pipe(&name, &options, None).expect("create the overlapped pipe"); - // SAFETY: fresh, exclusively owned, and opened with FILE_FLAG_OVERLAPPED. - let endpoint = - unsafe { UnassociatedEndpoint::assume_overlapped(OwnedHandle::from_raw_handle(handle)) }; + // SAFETY: fresh, exclusively owned, and created with `PipeOpenMode::OVERLAPPED`. + let endpoint = unsafe { UnassociatedEndpoint::assume_overlapped(handle) }; let client = std::fs::OpenOptions::new() .read(true) .write(true) diff --git a/crates/windows-overlapped-io-sys/tests/overlapped_cancellation.rs b/crates/windows-overlapped-io-sys/tests/overlapped_cancellation.rs index dcfa6031b..b2837debc 100644 --- a/crates/windows-overlapped-io-sys/tests/overlapped_cancellation.rs +++ b/crates/windows-overlapped-io-sys/tests/overlapped_cancellation.rs @@ -9,49 +9,59 @@ #![cfg(windows)] use std::io; -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::{BorrowedHandle, OwnedHandle}; +use win_pipes_sys::{ + ConnectOutcome, ConnectStart, NamedPipeOptions, PipeMode, PipeOpenMode, + connect_named_pipe_overlapped, create_named_pipe, +}; use windows_overlapped_io_sys::{ CompletionPort, Issued, Operation, OperationState, Submitted, UnassociatedEndpoint, }; -use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_OPERATION_ABORTED, INVALID_HANDLE_VALUE, -}; -use windows_sys::Win32::Storage::FileSystem::{FILE_FLAG_OVERLAPPED, PIPE_ACCESS_DUPLEX}; -use windows_sys::Win32::System::Pipes::{ - ConnectNamedPipe, CreateNamedPipeW, PIPE_TYPE_BYTE, PIPE_WAIT, -}; +use windows_sys::Win32::Foundation::{ERROR_OPERATION_ABORTED, ERROR_PIPE_CONNECTED}; +use windows_sys::Win32::System::IO::OVERLAPPED; + +/// An overlapped, duplex, byte-mode named-pipe server instance with no client, so that a connect +/// on it pends. +fn overlapped_server(name: &str) -> OwnedHandle { + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_DUPLEX | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE | PipeMode::WAIT, + max_instances: 1, + out_buffer_size: 4096, + in_buffer_size: 4096, + default_timeout_ms: 0, + }; + create_named_pipe(name, &options, None).expect("create the overlapped pipe") +} -fn wide(value: &str) -> Vec { - value.encode_utf16().chain(std::iter::once(0)).collect() +/// Issues one overlapped connect and classifies it for the port: an operation that started, or that +/// finished at once, is `Pending`, because a packet is coming either way; a client that was already +/// connected is an error. +/// +/// # Safety +/// +/// `overlapped` must satisfy [`connect_named_pipe_overlapped`]'s contract, which `submit` gives its +/// closure's pointer, and `handle` must be the server end of an overlapped pipe instance. +unsafe fn issue_connect( + handle: BorrowedHandle<'_>, + overlapped: *mut OVERLAPPED, +) -> io::Result { + // SAFETY: forwarded from this function's own contract. + match unsafe { connect_named_pipe_overlapped(handle, overlapped) }? { + ConnectStart::Pending | ConnectStart::Finished(ConnectOutcome::Succeeded) => { + Ok(Issued::Pending) + } + ConnectStart::Finished(ConnectOutcome::AlreadyConnected) => { + Err(io::Error::from_raw_os_error(ERROR_PIPE_CONNECTED as i32)) + } + } } #[test] fn connect_named_pipe_is_cancellable_and_completes_as_aborted() { let name = format!(r"\\.\pipe\windows-overlapped-io-sys-{}", std::process::id()); - let wide_name = wide(&name); - - // SAFETY: standard creation of an overlapped named-pipe server instance. - let raw = unsafe { - CreateNamedPipeW( - wide_name.as_ptr(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_WAIT, - 1, - 4096, - 4096, - 0, - std::ptr::null(), - ) - }; - assert_ne!( - raw, - INVALID_HANDLE_VALUE, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); - // SAFETY: CreateNamedPipeW returned a fresh, exclusively owned handle. - let owned = unsafe { OwnedHandle::from_raw_handle(raw) }; + let owned = overlapped_server(&name); let port = CompletionPort::new(0).expect("create port"); // SAFETY: the pipe was created with FILE_FLAG_OVERLAPPED, is unassociated, @@ -59,20 +69,11 @@ fn connect_named_pipe_is_cancellable_and_completes_as_aborted() { let unassociated = unsafe { UnassociatedEndpoint::assume_overlapped(owned) }; let endpoint = port.associate(unassociated, 1).expect("associate"); - // SAFETY: the closure issues exactly one overlapped ConnectNamedPipe using - // the provided OVERLAPPED pointer and classifies its outcome. + // SAFETY: the closure issues exactly one overlapped connect using the provided OVERLAPPED + // pointer, on an overlapped pipe, and classifies its outcome. let submitted = unsafe { endpoint.submit(Operation::new(()), |handle, overlapped| { - let ok = ConnectNamedPipe(handle.as_raw_handle(), overlapped); - if ok != 0 { - return Ok(Issued::Pending); - } - let error = io::Error::last_os_error(); - if error.raw_os_error() == Some(ERROR_IO_PENDING as i32) { - Ok(Issued::Pending) - } else { - Err(error) - } + issue_connect(handle, overlapped) }) }; @@ -104,49 +105,18 @@ fn run_down_drains_a_cancelled_operation() { r"\\.\pipe\windows-overlapped-io-sys-rundown-{}", std::process::id() ); - let wide_name = wide(&name); - - // SAFETY: standard creation of an overlapped named-pipe server instance. - let raw = unsafe { - CreateNamedPipeW( - wide_name.as_ptr(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_WAIT, - 1, - 4096, - 4096, - 0, - std::ptr::null(), - ) - }; - assert_ne!( - raw, - INVALID_HANDLE_VALUE, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); - // SAFETY: CreateNamedPipeW returned a fresh, exclusively owned handle. - let owned = unsafe { OwnedHandle::from_raw_handle(raw) }; + let owned = overlapped_server(&name); let port = CompletionPort::new(0).expect("create port"); // SAFETY: fresh overlapped pipe, unassociated, unique, moved in exclusively. let unassociated = unsafe { UnassociatedEndpoint::assume_overlapped(owned) }; let endpoint = port.associate(unassociated, 1).expect("associate"); - // SAFETY: the closure issues exactly one overlapped ConnectNamedPipe using - // the provided OVERLAPPED pointer and classifies its outcome. + // SAFETY: the closure issues exactly one overlapped connect using the provided OVERLAPPED + // pointer, on an overlapped pipe, and classifies its outcome. let submitted = unsafe { endpoint.submit(Operation::new(()), |handle, overlapped| { - let ok = ConnectNamedPipe(handle.as_raw_handle(), overlapped); - if ok != 0 { - return Ok(Issued::Pending); - } - let error = io::Error::last_os_error(); - if error.raw_os_error() == Some(ERROR_IO_PENDING as i32) { - Ok(Issued::Pending) - } else { - Err(error) - } + issue_connect(handle, overlapped) }) }; assert!(matches!(submitted, Submitted::Pending(_))); @@ -170,49 +140,18 @@ fn dropping_an_endpoint_drains_its_own_outstanding_operation_before_closing() { r"\\.\pipe\windows-overlapped-io-sys-endpoint-drop-{}", std::process::id() ); - let wide_name = wide(&name); - - // SAFETY: standard creation of an overlapped named-pipe server instance. - let raw = unsafe { - CreateNamedPipeW( - wide_name.as_ptr(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_WAIT, - 1, - 4096, - 4096, - 0, - std::ptr::null(), - ) - }; - assert_ne!( - raw, - INVALID_HANDLE_VALUE, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); - // SAFETY: CreateNamedPipeW returned a fresh, exclusively owned handle. - let owned = unsafe { OwnedHandle::from_raw_handle(raw) }; + let owned = overlapped_server(&name); let port = CompletionPort::new(0).expect("create port"); // SAFETY: fresh overlapped pipe, unassociated, unique, moved in exclusively. let unassociated = unsafe { UnassociatedEndpoint::assume_overlapped(owned) }; let endpoint = port.associate(unassociated, 1).expect("associate"); - // SAFETY: the closure issues exactly one overlapped ConnectNamedPipe using - // the provided OVERLAPPED pointer and classifies its outcome. + // SAFETY: the closure issues exactly one overlapped connect using the provided OVERLAPPED + // pointer, on an overlapped pipe, and classifies its outcome. let submitted = unsafe { endpoint.submit(Operation::new(()), |handle, overlapped| { - let ok = ConnectNamedPipe(handle.as_raw_handle(), overlapped); - if ok != 0 { - return Ok(Issued::Pending); - } - let error = io::Error::last_os_error(); - if error.raw_os_error() == Some(ERROR_IO_PENDING as i32) { - Ok(Issued::Pending) - } else { - Err(error) - } + issue_connect(handle, overlapped) }) }; assert!(matches!(submitted, Submitted::Pending(_))); diff --git a/crates/windows-placement-probe/build.rs b/crates/windows-placement-probe/build.rs index 3bb6b5d5d..b88d3b256 100644 --- a/crates/windows-placement-probe/build.rs +++ b/crates/windows-placement-probe/build.rs @@ -15,23 +15,12 @@ //! `Provenance::Synthetic` being `Default` one layer down: forgetting, or being //! unable to tell, must be the safe direction. -use std::fs; -use std::path::Path; -use std::process::Command; - -/// What CI sets so the build does not have to shell out to `git`. -const COMMIT_ENV: &str = "PLACEMENT_PROBE_COMMIT"; - -/// What CI sets to declare the build official. -const SOURCE_ENV: &str = "PLACEMENT_PROBE_SOURCE"; +include!("build/stamp.rs"); fn main() { - // Without these, a rebuild after a commit would keep the stale stamp -- - // which is the failure this file exists to prevent, arriving by a different - // route. - println!("cargo::rerun-if-env-changed={COMMIT_ENV}"); - println!("cargo::rerun-if-env-changed={SOURCE_ENV}"); - watch_git_head(Path::new("../../.git")); + // The identity, under this crate's names. CI sets `PLACEMENT_PROBE_COMMIT` + // and `PLACEMENT_PROBE_SOURCE`; the binary reads the four `*_OUT` stamps. + stamp_build_identity("PLACEMENT_PROBE"); // **The dirty flag has a second way to go stale, and these close the common // case rather than all of it.** Emitting any `rerun-if-changed` replaces @@ -48,261 +37,5 @@ fn main() { println!("cargo::rerun-if-changed=src"); println!("cargo::rerun-if-changed=Cargo.toml"); println!("cargo::rerun-if-changed=build.rs"); - - // CI knows the commit it checked out, and a CI checkout is clean by - // construction, so no `git` call is needed or wanted there. - let stamped_commit = std::env::var(COMMIT_ENV) - .ok() - .map(|sha| sha.trim().to_owned()) - .filter(|sha| !sha.is_empty()); - - let (commit, dirty) = match &stamped_commit { - Some(sha) => (Some(shorten(sha)), Some(false)), - None => (git_commit(), git_dirty()), - }; - - // **The two stamps are honoured as a unit, never singly.** `ci` is a claim - // about provenance, and the commit stamp is the evidence for it; accepting - // the claim alone lets a local build inherit the rest of its identity from - // the working tree and still pass `BuildIdentity::is_official`, because a - // clean checkout supplies a commit and `dirty = false` on its own. - // - // Measured before fixing: with only `PLACEMENT_PROBE_SOURCE=ci` set, a - // local build reported `v0.1.0 79b9c4666a1b [ci]` -- no `!!UNOFFICIAL!!` - // marker, so a record from it would have pooled with real CI results. - // That inverts this file's stated default, which is that being unable to - // tell must resolve to unofficial. - let source = match std::env::var(SOURCE_ENV) { - Ok(value) if value.trim().eq_ignore_ascii_case("ci") && stamped_commit.is_some() => "ci", - _ if commit.is_some() => "local", - _ => "unknown", - }; - - println!( - "cargo::rustc-env=PLACEMENT_PROBE_COMMIT_OUT={}", - commit.as_deref().unwrap_or("") - ); - println!( - "cargo::rustc-env=PLACEMENT_PROBE_DIRTY_OUT={}", - match dirty { - Some(true) => "1", - Some(false) => "0", - None => "", - } - ); - println!("cargo::rustc-env=PLACEMENT_PROBE_SOURCE_OUT={source}"); - - // **Whether a repository was there to ask, which is not the same as whether - // a commit was found.** The two differ exactly when the build script is - // broken: a repository present but no commit produced is the silent-failure - // case the tests exist to catch, and without this flag a test cannot tell - // that apart from the honest "there was no repository". - // - // It exists because the honest case is reachable in ordinary use. A - // `cargo install` from a crates.io tarball has no repository, a downloaded - // source zip has none, and `cargo mutants` builds from a scratch copy of - // the tree with `.git` left behind -- which is what surfaced this: a suite - // asserting "a commit must have been found" failed in an unmutated tree and - // stopped the run before a single mutant was tested. - // - // Not a `cfg`: this is data about the build, and a test that reads it can - // assert the *right* thing in each case rather than being skipped. - println!( - "cargo::rustc-env=PLACEMENT_PROBE_REPOSITORY_OUT={}", - if repository_present() { "1" } else { "" } - ); -} - -/// Whether this build can see a git repository at all. -/// -/// Deliberately independent of whether a commit was obtained: it answers "was -/// there something to ask", so a test can distinguish a build script that -/// failed to read an available repository from one that correctly reported an -/// unavailable one. -/// -/// `git rev-parse` rather than looking for a `.git` entry, because that entry -/// may be a file redirecting elsewhere, may name a gitdir that no longer -/// exists, and says nothing about whether `git` is on `PATH` -- which is the -/// other way the commit legitimately comes back unknown. -fn repository_present() -> bool { - Command::new("git") - .args(["rev-parse", "--git-dir"]) - .current_dir(env!("CARGO_MANIFEST_DIR")) - .output() - .is_ok_and(|output| output.status.success()) -} - -/// Ask cargo to re-run this script whenever the checked-out commit changes. -/// -/// # Watching `HEAD` alone does not work, and the failure is silent -/// -/// On a branch, `.git/HEAD` holds `ref: refs/heads/` -- a line that -/// does not change when you commit. What git rewrites is the *ref* file the -/// line names. Since a `rerun-if-changed` directive replaces cargo's default of -/// watching the whole package, watching only `HEAD` meant the script never re- -/// ran after a commit and the binary kept whatever commit it was first built -/// with. -/// -/// Measured on this repository rather than reasoned about: `.git/HEAD` had not -/// been touched in 21 hours while fourteen commits landed, and a freshly built -/// binary reported a commit six behind `HEAD`. CI hides this entirely, because -/// there the commit arrives through `PLACEMENT_PROBE_COMMIT` -- so the stamp -/// was wrong only on local builds, which are exactly the ones whose commit is -/// their only traceability. -/// -/// `HEAD` is still watched, because switching branches or detaching does change -/// it. -fn watch_git_head(git_dir: &Path) { - // A `.git` *file* rather than a directory means a worktree or submodule, - // and names the real directory. Not watched further: the redirect is enough - // to find the ref, and a tarball with no `.git` at all is the case this - // whole file is built to survive. - let git_dir = match fs::read_to_string(git_dir.join("HEAD")) { - Ok(_) => git_dir.to_path_buf(), - Err(_) => match fs::read_to_string(git_dir) { - Ok(redirect) => match redirect.trim().strip_prefix("gitdir:") { - // **Resolved against the redirect file's own directory.** A - // `gitdir:` path may be relative, and it is relative to where - // the `.git` file sits -- not to wherever cargo happens to run - // this script. Taking it literally produced watch paths under - // the crate directory, so in a worktree or submodule checkout - // commits would stop refreshing the stamp: the exact failure - // this function was added to fix, surviving in the one layout - // it was added for. `join` still yields an absolute redirect - // unchanged, so both forms work. - Some(path) => git_dir.parent().unwrap_or(Path::new(".")).join(path.trim()), - None => return, - }, - // No repository. The stamp is "unknown", which is the honest answer - // and needs no watching. - Err(_) => return, - }, - }; - - let head = git_dir.join("HEAD"); - let Ok(contents) = fs::read_to_string(&head) else { - return; - }; - watch(&head); - - // A detached HEAD holds the sha itself, so the file already changes with - // the commit and there is nothing further to watch. - let Some(reference) = contents.trim().strip_prefix("ref:") else { - return; - }; - - // **Refs may live somewhere other than the gitdir that holds `HEAD`.** A - // linked worktree keeps a private `HEAD` -- correctly, since each worktree - // is on its own branch -- but shares every ref with the repository it was - // created from, naming that shared directory in a `commondir` file beside - // the private `HEAD`. Looking for the ref in the private gitdir finds - // nothing, so watching would stop at `HEAD`: on a branch, precisely the file - // that does not change when you commit, which is the silent staleness this - // whole function exists to prevent, surviving in a layout it was meant to - // cover. - // - // Measured before fixing rather than reasoned about: in a throwaway - // `git worktree add`, a commit left the worktree's `HEAD` untouched while - // the ref under `commondir` was rewritten eight seconds later. - // - // A submodule has no `commondir` and keeps its refs in the gitdir its - // `gitdir:` redirect names, so it falls through to `git_dir` unchanged. - let refs_dir = match fs::read_to_string(git_dir.join("commondir")) { - // Relative to the gitdir the file sits in (git writes `../..`). - Ok(common) => git_dir.join(common.trim()), - Err(_) => git_dir.clone(), - }; - - // A loose ref is rewritten on every commit, so watching it is exact. - let loose = refs_dir.join(reference.trim()); - if loose.exists() { - watch(&loose); - return; - } - - // Otherwise the ref is packed: it exists only as a line in `packed-refs`, - // and that file is what a `git pack-refs` or `git gc` rewrites. - let packed = refs_dir.join("packed-refs"); - if packed.exists() { - watch(&packed); - } - - // **Packed is a state, not a fate, and this is the half that was missing.** - // The next commit on this branch writes a *loose* ref and leaves - // `packed-refs` completely untouched -- the stale packed line simply loses - // to the new file. So watching `packed-refs` alone sees nothing, cargo does - // not re-run, and the binary keeps the previous commit's stamp. - // - // Measured on a clone of this repository rather than reasoned about: after - // `git pack-refs --all`, a commit moved HEAD from 28f0486205df to - // 64259d9d8320 while the built binary went on reporting 28f0486205df. - // - // Cargo takes a watched directory's newest contained mtime, so naming the - // nearest existing ancestor catches the loose ref being created. It is - // deliberately coarse: another branch's commit re-runs this script too, - // which costs one cheap script run, while missing our own commit costs a - // wrong answer in a shipped record. - if let Some(ancestor) = nearest_existing_dir(&loose) { - watch(&ancestor); - } -} - -/// The closest ancestor of `path` that exists as a directory. -/// -/// A branch name may contain slashes, so the loose ref for `feature/x` sits at -/// `refs/heads/feature/x` and *neither* the file nor its `feature` directory -/// need exist while the ref is packed. Walking up finds the deepest directory -/// that does, which is the one that will observe the ref appearing. -fn nearest_existing_dir(path: &Path) -> Option { - let mut candidate = path.parent()?; - loop { - if candidate.is_dir() { - return Some(candidate.to_path_buf()); - } - candidate = candidate.parent()?; - } -} - -/// Emit one `rerun-if-changed`, if the path can be named. -fn watch(path: &Path) { - if let Some(path) = path.to_str() { - println!("cargo::rerun-if-changed={path}"); - } -} - -/// The first twelve characters, which is unambiguous in practice and short -/// enough to sit in a printed line. -fn shorten(sha: &str) -> String { - sha.chars().take(12).collect() -} - -fn git_commit() -> Option { - let output = Command::new("git") - .args(["rev-parse", "HEAD"]) - .output() - .ok()?; - if !output.status.success() { - return None; - } - let sha = String::from_utf8(output.stdout).ok()?; - let sha = sha.trim(); - if sha.is_empty() { - return None; - } - Some(shorten(sha)) -} - -/// Whether the working tree had uncommitted changes. -/// -/// `None` when the question could not be asked at all, which is a different -/// fact from "clean" and is reported as such rather than assumed either way. -fn git_dirty() -> Option { - let output = Command::new("git") - .args(["status", "--porcelain"]) - .output() - .ok()?; - if !output.status.success() { - return None; - } - Some(!output.stdout.is_empty()) + println!("cargo::rerun-if-changed=build/stamp.rs"); } diff --git a/crates/windows-placement-probe/build/stamp.rs b/crates/windows-placement-probe/build/stamp.rs new file mode 100644 index 000000000..ac5642de1 --- /dev/null +++ b/crates/windows-placement-probe/build/stamp.rs @@ -0,0 +1,289 @@ +// Copyright (c) 2026 Mike Grier +// Split from build.rs at 3cb70b1f. +// Included textually by build.rs, so its items sit in the build script's root module. +// Also included by ../windows-platform-probes/build.rs, by path: a change here changes that +// crate's build script too, and both crates' build-identity tests run against it. +use std::fs; +use std::path::Path; +use std::process::Command; + +/// Stamp the identity of the build into the crate being built, under `prefix`. +/// +/// Reads `{prefix}_COMMIT` -- what CI sets so the build does not have to shell +/// out to `git` -- and `{prefix}_SOURCE`, what CI sets to declare the build +/// official. Emits `{prefix}_COMMIT_OUT`, `{prefix}_DIRTY_OUT`, +/// `{prefix}_SOURCE_OUT` and `{prefix}_REPOSITORY_OUT` for the crate to read +/// with `env!`. Watching the crate's own files is the caller's, because only it +/// knows what they are. +/// +/// One definition shared by every crate that stamps itself, because the +/// staleness rules below were each found by measurement, and a second copy of +/// them is a copy the next fix would miss. +fn stamp_build_identity(prefix: &str) { + let commit_env = format!("{prefix}_COMMIT"); + let source_env = format!("{prefix}_SOURCE"); + + // Without these, a rebuild after a commit would keep the stale stamp -- + // which is the failure this file exists to prevent, arriving by a different + // route. + println!("cargo::rerun-if-env-changed={commit_env}"); + println!("cargo::rerun-if-env-changed={source_env}"); + watch_git_head(Path::new("../../.git")); + + // CI knows the commit it checked out, and a CI checkout is clean by + // construction, so no `git` call is needed or wanted there. + let stamped_commit = std::env::var(&commit_env) + .ok() + .map(|sha| sha.trim().to_owned()) + .filter(|sha| !sha.is_empty()); + + let (commit, dirty) = match &stamped_commit { + Some(sha) => (Some(shorten(sha)), Some(false)), + None => (git_commit(), git_dirty()), + }; + + // **The two stamps are honoured as a unit, never singly.** `ci` is a claim + // about provenance, and the commit stamp is the evidence for it; accepting + // the claim alone lets a local build inherit the rest of its identity from + // the working tree and still pass `BuildIdentity::is_official`, because a + // clean checkout supplies a commit and `dirty = false` on its own. + // + // Measured before fixing: with only `PLACEMENT_PROBE_SOURCE=ci` set, a + // local build reported `v0.1.0 79b9c4666a1b [ci]` -- no `!!UNOFFICIAL!!` + // marker, so a record from it would have pooled with real CI results. + // That inverts this file's stated default, which is that being unable to + // tell must resolve to unofficial. + let source = match std::env::var(&source_env) { + Ok(value) if value.trim().eq_ignore_ascii_case("ci") && stamped_commit.is_some() => "ci", + _ if commit.is_some() => "local", + _ => "unknown", + }; + + println!( + "cargo::rustc-env={prefix}_COMMIT_OUT={}", + commit.as_deref().unwrap_or("") + ); + println!( + "cargo::rustc-env={prefix}_DIRTY_OUT={}", + match dirty { + Some(true) => "1", + Some(false) => "0", + None => "", + } + ); + println!("cargo::rustc-env={prefix}_SOURCE_OUT={source}"); + + // **Whether a repository was there to ask, which is not the same as whether + // a commit was found.** The two differ exactly when the build script is + // broken: a repository present but no commit produced is the silent-failure + // case the tests exist to catch, and without this flag a test cannot tell + // that apart from the honest "there was no repository". + // + // It exists because the honest case is reachable in ordinary use. A + // `cargo install` from a crates.io tarball has no repository, a downloaded + // source zip has none, and `cargo mutants` builds from a scratch copy of + // the tree with `.git` left behind -- which is what surfaced this: a suite + // asserting "a commit must have been found" failed in an unmutated tree and + // stopped the run before a single mutant was tested. + // + // Not a `cfg`: this is data about the build, and a test that reads it can + // assert the *right* thing in each case rather than being skipped. + println!( + "cargo::rustc-env={prefix}_REPOSITORY_OUT={}", + if repository_present() { "1" } else { "" } + ); +} + +/// Whether this build can see a git repository at all. +/// +/// Deliberately independent of whether a commit was obtained: it answers "was +/// there something to ask", so a test can distinguish a build script that +/// failed to read an available repository from one that correctly reported an +/// unavailable one. +/// +/// `git rev-parse` rather than looking for a `.git` entry, because that entry +/// may be a file redirecting elsewhere, may name a gitdir that no longer +/// exists, and says nothing about whether `git` is on `PATH` -- which is the +/// other way the commit legitimately comes back unknown. +fn repository_present() -> bool { + Command::new("git") + .args(["rev-parse", "--git-dir"]) + .current_dir(env!("CARGO_MANIFEST_DIR")) + .output() + .is_ok_and(|output| output.status.success()) +} + +/// Ask cargo to re-run this script whenever the checked-out commit changes. +/// +/// # Watching `HEAD` alone does not work, and the failure is silent +/// +/// On a branch, `.git/HEAD` holds `ref: refs/heads/` -- a line that +/// does not change when you commit. What git rewrites is the *ref* file the +/// line names. Since a `rerun-if-changed` directive replaces cargo's default of +/// watching the whole package, watching only `HEAD` meant the script never re- +/// ran after a commit and the binary kept whatever commit it was first built +/// with. +/// +/// Measured on this repository rather than reasoned about: `.git/HEAD` had not +/// been touched in 21 hours while fourteen commits landed, and a freshly built +/// binary reported a commit six behind `HEAD`. CI hides this entirely, because +/// there the commit arrives through the `{prefix}_COMMIT` variable -- so the stamp +/// was wrong only on local builds, which are exactly the ones whose commit is +/// their only traceability. +/// +/// `HEAD` is still watched, because switching branches or detaching does change +/// it. +fn watch_git_head(git_dir: &Path) { + // A `.git` *file* rather than a directory means a worktree or submodule, + // and names the real directory. Not watched further: the redirect is enough + // to find the ref, and a tarball with no `.git` at all is the case this + // whole file is built to survive. + let git_dir = match fs::read_to_string(git_dir.join("HEAD")) { + Ok(_) => git_dir.to_path_buf(), + Err(_) => match fs::read_to_string(git_dir) { + Ok(redirect) => match redirect.trim().strip_prefix("gitdir:") { + // **Resolved against the redirect file's own directory.** A + // `gitdir:` path may be relative, and it is relative to where + // the `.git` file sits -- not to wherever cargo happens to run + // this script. Taking it literally produced watch paths under + // the crate directory, so in a worktree or submodule checkout + // commits would stop refreshing the stamp: the exact failure + // this function was added to fix, surviving in the one layout + // it was added for. `join` still yields an absolute redirect + // unchanged, so both forms work. + Some(path) => git_dir.parent().unwrap_or(Path::new(".")).join(path.trim()), + None => return, + }, + // No repository. The stamp is "unknown", which is the honest answer + // and needs no watching. + Err(_) => return, + }, + }; + + let head = git_dir.join("HEAD"); + let Ok(contents) = fs::read_to_string(&head) else { + return; + }; + watch(&head); + + // A detached HEAD holds the sha itself, so the file already changes with + // the commit and there is nothing further to watch. + let Some(reference) = contents.trim().strip_prefix("ref:") else { + return; + }; + + // **Refs may live somewhere other than the gitdir that holds `HEAD`.** A + // linked worktree keeps a private `HEAD` -- correctly, since each worktree + // is on its own branch -- but shares every ref with the repository it was + // created from, naming that shared directory in a `commondir` file beside + // the private `HEAD`. Looking for the ref in the private gitdir finds + // nothing, so watching would stop at `HEAD`: on a branch, precisely the file + // that does not change when you commit, which is the silent staleness this + // whole function exists to prevent, surviving in a layout it was meant to + // cover. + // + // Measured before fixing rather than reasoned about: in a throwaway + // `git worktree add`, a commit left the worktree's `HEAD` untouched while + // the ref under `commondir` was rewritten eight seconds later. + // + // A submodule has no `commondir` and keeps its refs in the gitdir its + // `gitdir:` redirect names, so it falls through to `git_dir` unchanged. + let refs_dir = match fs::read_to_string(git_dir.join("commondir")) { + // Relative to the gitdir the file sits in (git writes `../..`). + Ok(common) => git_dir.join(common.trim()), + Err(_) => git_dir.clone(), + }; + + // A loose ref is rewritten on every commit, so watching it is exact. + let loose = refs_dir.join(reference.trim()); + if loose.exists() { + watch(&loose); + return; + } + + // Otherwise the ref is packed: it exists only as a line in `packed-refs`, + // and that file is what a `git pack-refs` or `git gc` rewrites. + let packed = refs_dir.join("packed-refs"); + if packed.exists() { + watch(&packed); + } + + // **Packed is a state, not a fate, and this is the half that was missing.** + // The next commit on this branch writes a *loose* ref and leaves + // `packed-refs` completely untouched -- the stale packed line simply loses + // to the new file. So watching `packed-refs` alone sees nothing, cargo does + // not re-run, and the binary keeps the previous commit's stamp. + // + // Measured on a clone of this repository rather than reasoned about: after + // `git pack-refs --all`, a commit moved HEAD from 28f0486205df to + // 64259d9d8320 while the built binary went on reporting 28f0486205df. + // + // Cargo takes a watched directory's newest contained mtime, so naming the + // nearest existing ancestor catches the loose ref being created. It is + // deliberately coarse: another branch's commit re-runs this script too, + // which costs one cheap script run, while missing our own commit costs a + // wrong answer in a shipped record. + if let Some(ancestor) = nearest_existing_dir(&loose) { + watch(&ancestor); + } +} + +/// The closest ancestor of `path` that exists as a directory. +/// +/// A branch name may contain slashes, so the loose ref for `feature/x` sits at +/// `refs/heads/feature/x` and *neither* the file nor its `feature` directory +/// need exist while the ref is packed. Walking up finds the deepest directory +/// that does, which is the one that will observe the ref appearing. +fn nearest_existing_dir(path: &Path) -> Option { + let mut candidate = path.parent()?; + loop { + if candidate.is_dir() { + return Some(candidate.to_path_buf()); + } + candidate = candidate.parent()?; + } +} + +/// Emit one `rerun-if-changed`, if the path can be named. +fn watch(path: &Path) { + if let Some(path) = path.to_str() { + println!("cargo::rerun-if-changed={path}"); + } +} + +/// The first twelve characters, which is unambiguous in practice and short +/// enough to sit in a printed line. +fn shorten(sha: &str) -> String { + sha.chars().take(12).collect() +} + +fn git_commit() -> Option { + let output = Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let sha = String::from_utf8(output.stdout).ok()?; + let sha = sha.trim(); + if sha.is_empty() { + return None; + } + Some(shorten(sha)) +} + +/// Whether the working tree had uncommitted changes. +/// +/// `None` when the question could not be asked at all, which is a different +/// fact from "clean" and is reported as such rather than assumed either way. +fn git_dirty() -> Option { + let output = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + Some(!output.stdout.is_empty()) +} diff --git a/crates/windows-placement-probe/src/build_identity.rs b/crates/windows-placement-probe/src/build_identity.rs index f16fdd414..0fbf25227 100644 --- a/crates/windows-placement-probe/src/build_identity.rs +++ b/crates/windows-placement-probe/src/build_identity.rs @@ -98,15 +98,39 @@ impl BuildIdentity { /// This binary's identity, as its build script stamped it. #[must_use] pub fn current() -> Self { + Self::from_stamps( + env!("CARGO_PKG_VERSION"), + env!("PLACEMENT_PROBE_COMMIT_OUT"), + env!("PLACEMENT_PROBE_DIRTY_OUT"), + env!("PLACEMENT_PROBE_SOURCE_OUT"), + ) + } + + /// The identity a build script's stamps describe, for a crate that stamps + /// itself with the same build-script code under its own names. + /// + /// The arguments are the `*_COMMIT_OUT`, `*_DIRTY_OUT` and `*_SOURCE_OUT` + /// values as the build script emitted them: an empty commit is unknown, a + /// dirty flag other than `1` or `0` is unknown, and a source other than `ci` + /// or `local` is [`BuildSource::Unknown`]. Read here rather than by each + /// caller, so the stamps mean the same thing in every crate that carries + /// them. + #[must_use] + pub fn from_stamps( + crate_version: &'static str, + commit: &'static str, + dirty: &'static str, + source: &'static str, + ) -> Self { Self { - crate_version: env!("CARGO_PKG_VERSION"), - commit: non_empty(env!("PLACEMENT_PROBE_COMMIT_OUT")), - dirty: match env!("PLACEMENT_PROBE_DIRTY_OUT") { + crate_version, + commit: non_empty(commit), + dirty: match dirty { "1" => Some(true), "0" => Some(false), _ => None, }, - source: match env!("PLACEMENT_PROBE_SOURCE_OUT") { + source: match source { "ci" => BuildSource::Ci, "local" => BuildSource::Local, _ => BuildSource::Unknown, diff --git a/crates/windows-placement-probe/src/build_identity/tests.rs b/crates/windows-placement-probe/src/build_identity/tests.rs index a20f394a8..cc3d27797 100644 --- a/crates/windows-placement-probe/src/build_identity/tests.rs +++ b/crates/windows-placement-probe/src/build_identity/tests.rs @@ -219,3 +219,54 @@ fn a_local_development_build_does_not_claim_to_be_official() { BuildIdentity::current() ); } + +#[test] +fn from_stamps_reads_each_stamp_the_build_script_can_emit() { + let identity = BuildIdentity::from_stamps("1.2.3", "0123456789ab", "0", "ci"); + assert_eq!( + identity, + BuildIdentity { + crate_version: "1.2.3", + commit: Some("0123456789ab"), + dirty: Some(false), + source: BuildSource::Ci, + } + ); + assert!(identity.is_official()); + + assert_eq!( + BuildIdentity::from_stamps("1.2.3", "0123456789ab", "1", "local").dirty, + Some(true) + ); + assert_eq!( + BuildIdentity::from_stamps("1.2.3", "0123456789ab", "1", "local").source, + BuildSource::Local + ); +} + +#[test] +fn from_stamps_reads_anything_it_does_not_recognise_as_unknown() { + // The build script emits an empty value for "could not be determined", and + // nothing else it emits is anything but the values above -- so anything + // else must land on the unofficial side rather than be guessed at. + for (commit, dirty, source) in [("", "", ""), ("", "yes", "CI-ish"), ("", "2", "remote")] { + let identity = BuildIdentity::from_stamps("1.2.3", commit, dirty, source); + assert_eq!(identity.commit, None, "{commit:?}"); + assert_eq!(identity.dirty, None, "{dirty:?}"); + assert_eq!(identity.source, BuildSource::Unknown, "{source:?}"); + assert!(!identity.is_official()); + } +} + +#[test] +fn current_is_from_stamps_over_this_crates_own_stamps() { + assert_eq!( + BuildIdentity::current(), + BuildIdentity::from_stamps( + env!("CARGO_PKG_VERSION"), + env!("PLACEMENT_PROBE_COMMIT_OUT"), + env!("PLACEMENT_PROBE_DIRTY_OUT"), + env!("PLACEMENT_PROBE_SOURCE_OUT"), + ) + ); +} diff --git a/crates/windows-placement-probe/src/core_affinity.rs b/crates/windows-placement-probe/src/core_affinity.rs index 1d280db12..d8f35a7a4 100644 --- a/crates/windows-placement-probe/src/core_affinity.rs +++ b/crates/windows-placement-probe/src/core_affinity.rs @@ -323,6 +323,25 @@ pub enum Placement { } impl Placement { + /// A stable identifier for a record. + /// + /// Separate from [`label`](Self::label) for the reason + /// [`Strategy::name`] is: the label is prose for a table and may be + /// reworded, and this is a token a stored record is keyed on. + #[must_use] + pub fn name(self) -> &'static str { + match self { + Self::SameCoreSiblings => "same_core_siblings", + Self::SameCacheSameClass => "same_cache_same_class", + Self::SameCacheCrossClass => "same_cache_cross_class", + Self::CrossCacheSameClass => "cross_cache_same_class", + Self::CrossCacheCrossClass => "cross_cache_cross_class", + Self::CrossNumaNode => "cross_numa_node", + Self::UnknownCacheSameClass => "unknown_cache_same_class", + Self::UnknownCacheCrossClass => "unknown_cache_cross_class", + } + } + /// A short label for a table. #[must_use] pub fn label(self) -> &'static str { @@ -361,6 +380,20 @@ pub struct Measurement { pub strategy: Strategy, /// Median nanoseconds per item. pub nanos_per_item: f64, + /// Wall-clock nanoseconds of the median pass, which `nanos_per_item` is + /// divided from. + /// + /// **The raw figures are carried beside the derived ones** so a record of + /// what was observed does not have to reverse a division to get them back. + /// The batch depths below divide by `max(1, refreshes)`, which no reversal + /// can undo for a count of zero. + pub elapsed_nanos: f64, + /// How many times the median pass's consumer read the producer's shared + /// position. + pub consumer_refreshes: u64, + /// How many times the median pass's producer read the consumer's shared + /// position. + pub producer_refreshes: u64, /// How many items each consumer-side shared read was amortised over. /// /// **This is the number the hypothesis is about.** Near 1 means the two @@ -717,6 +750,9 @@ pub fn measure() -> std::io::Result { placement, strategy, nanos_per_item: median.nanos / ITEMS as f64, + elapsed_nanos: median.nanos, + consumer_refreshes: median.consumer_refreshes, + producer_refreshes: median.producer_refreshes, consumer_batch: ITEMS as f64 / median.consumer_refreshes.max(1) as f64, producer_batch: ITEMS as f64 / median.producer_refreshes.max(1) as f64, // Placement rows do not choose a node: they vary where the @@ -750,6 +786,9 @@ pub fn measure() -> std::io::Result { placement: classify(producer, consumer), strategy, nanos_per_item: median.nanos / ITEMS as f64, + elapsed_nanos: median.nanos, + consumer_refreshes: median.consumer_refreshes, + producer_refreshes: median.producer_refreshes, consumer_batch: ITEMS as f64 / median.consumer_refreshes.max(1) as f64, producer_batch: ITEMS as f64 / median.producer_refreshes.max(1) as f64, // Placement rows do not choose a node: they vary where the @@ -786,6 +825,9 @@ pub fn measure() -> std::io::Result { placement: classify(producer, consumer), strategy, nanos_per_item: median.nanos / ITEMS as f64, + elapsed_nanos: median.nanos, + consumer_refreshes: median.consumer_refreshes, + producer_refreshes: median.producer_refreshes, consumer_batch: ITEMS as f64 / median.consumer_refreshes.max(1) as f64, producer_batch: ITEMS as f64 / median.producer_refreshes.max(1) as f64, // What the run *achieved*, and separately what it asked diff --git a/crates/windows-placement-probe/src/core_affinity/tests.rs b/crates/windows-placement-probe/src/core_affinity/tests.rs index 2fd6d87c0..0132ad0ee 100644 --- a/crates/windows-placement-probe/src/core_affinity/tests.rs +++ b/crates/windows-placement-probe/src/core_affinity/tests.rs @@ -1235,6 +1235,9 @@ fn hop_row( placement: Placement::CrossNumaNode, strategy, nanos_per_item: nanos, + elapsed_nanos: nanos * ITEMS as f64, + consumer_refreshes: ITEMS as u64, + producer_refreshes: ITEMS as u64, consumer_batch: 1.0, producer_batch: 1.0, memory_node: achieved, @@ -1407,3 +1410,31 @@ fn an_empty_plan_promises_no_time_at_all() { assert_eq!(empty.timed_runs(), 0); assert!((empty.estimated_seconds() - 0.0).abs() < f64::EPSILON); } + +#[test] +fn every_placement_has_a_distinct_record_name_that_is_not_its_label() { + let all = [ + Placement::SameCoreSiblings, + Placement::SameCacheSameClass, + Placement::SameCacheCrossClass, + Placement::CrossCacheSameClass, + Placement::CrossCacheCrossClass, + Placement::CrossNumaNode, + Placement::UnknownCacheSameClass, + Placement::UnknownCacheCrossClass, + ]; + let mut names: Vec<&str> = all.iter().map(|p| p.name()).collect(); + for placement in all { + assert_ne!(placement.name(), placement.label(), "{placement:?}"); + assert!( + placement + .name() + .bytes() + .all(|b| b.is_ascii_lowercase() || b == b'_'), + "{placement:?}: a record name is a lowercase token" + ); + } + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), all.len(), "two placements share a record name"); +} diff --git a/crates/windows-placement-probe/src/peer_index_cache.rs b/crates/windows-placement-probe/src/peer_index_cache.rs index 58e91f7c2..c35c0d34f 100644 --- a/crates/windows-placement-probe/src/peer_index_cache.rs +++ b/crates/windows-placement-probe/src/peer_index_cache.rs @@ -85,7 +85,7 @@ pub const ITEMS: usize = 2_000_000; pub const CAPACITY: usize = 1024; /// Repetitions per configuration; the median is reported. -const REPETITIONS: usize = 5; +pub const REPETITIONS: usize = 5; /// Which peer-index strategy a run uses. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -102,7 +102,15 @@ pub enum Strategy { } impl Strategy { - fn label(self) -> &'static str { + /// The prose a report's table prints for this strategy, and what + /// [`Run::label`] holds for its run. + /// + /// Prose, not a key: it may be reworded whenever the table reads better a + /// different way, so a stored record keys on [`name`](Self::name) instead. + /// Public so code building an [`Observation`] by hand -- a test of a + /// renderer -- can label a run without copying the text. + #[must_use] + pub fn label(self) -> &'static str { match self { Self::Baseline => "model: baseline", Self::Cached => "model: cached index", @@ -112,7 +120,7 @@ impl Strategy { /// A stable identifier for a record. /// - /// Separate from the private `label` on purpose, and not a duplicate of it. + /// Separate from [`label`](Self::label) on purpose, and not a duplicate of it. /// The label is prose for a terminal table and may be reworded whenever the /// table reads better a different way; this is a token a stored record is /// keyed on, so rewording it would silently break every collector that ever diff --git a/crates/windows-platform-probes/CHECKLIST.md b/crates/windows-platform-probes/CHECKLIST.md index 03db8e2b9..dd35f6c79 100644 --- a/crates/windows-platform-probes/CHECKLIST.md +++ b/crates/windows-platform-probes/CHECKLIST.md @@ -4,299 +4,3 @@ Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md). This crate's *creati separately, in the workspace [CHECKLIST-thread-ambient.md](../../CHECKLIST-thread-ambient.md) milestone M27; that file is feature-scoped and is deleted when its feature completes, so durable follow-up work for the crate belongs here instead. - -## M4 -- Carried over from M2: the items M3 gated - -These were written under M2 and were blocked on M3, which is **now complete and archived** in -[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). They are unblocked, and each was RE-SCOPED rather -than merely delayed -- so each item below carries its own re-scoping note, in the item, where -somebody executing the list will actually meet it. - -**The IDs keep their M2 numbers deliberately.** [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md) is -append-only and its entries are immutable, and two archived entries already cite M2.4 and M2.14 -- -so renumbering would leave dangling references in a file that may not be edited to repair them. -Stable IDs cost a mismatch between an item number and its milestone; renumbering would cost -correctness in the archive. - -- [ ] **M4.1** -- Model the observation-readable `ParseIncomplete` conditions that - `blocking_states` currently omits, so a deleted push site in `cross_check` is caught for all of - them rather than for the subset. - - **Gap:** `blocking_states` justified its absentees as "derived counts whose only source IS the - cross-check's own arithmetic". That is false for most of them -- - `CacheLevelsWithoutPartitions`, `NumaDomainsOnlyInCpuSets`, `CoresOnlyInCpuSets`, - `RelationsWithoutProcessors`, `UnreportedRelations`, `DescribedRelations`, - `OverlappingWalkRelations`, `ProcessorAttributeConflicts`, `NumaDomainsWithConflictingLabels`, - `NumaDomainsUnreported` and `MeasuredButCountsAbsent` all read fields sitting on `Observation` - in plain sight. Deleting one of those push sites lets `verdict()` reach `agree` with - `blocking_states` silent. Reported across three review rounds against two wordings of the claim; - the claims in [src/topology/invariant.rs](src/topology/invariant.rs) and in - `every_numa_counter_branch_...` were narrowed in the same review round that reported this, - to stop overstating the coverage, - which is why this item is the fix rather than the discovery. - - **Target:** each gains a `BlockingState` variant, a `blocking_states` branch, a `codes_for` arm, - a perturbation in the invariant tests, and a corpus shape in - [tests/a_real_report_agrees_with_itself.rs](tests/a_real_report_agrees_with_itself.rs) so - `the_corpus_reaches_every_blocking_state` still holds. **Do not add a variant without its corpus - shape** -- that test is what stops the mapping being written and never exercised, which is the - defect this whole area keeps producing. - - Apply the tautology test to each before adding it: a state whose only source is the cross-check's - own arithmetic does NOT belong, and the honest outcome for such a one is a line in the module - header saying so by name rather than a silent absence. - -- [ ] **M4.4** -- Interleave each candidate with a nearby control instead of measuring the control - four runs away from it, and re-measure everything that changes. - - **Gap:** `measure()` runs, per producer count, `baseline_fetch_add`, `slotwise_mpsc`, - `reserving_mpsc`, `permit_mpsc`, then the three drained shapes, then the layout rows starting with - `reserving(32/32)`. The same-code control is the `reserving_mpsc` row against the - `reserving(32/32)` row -- **four measurements apart**, each five repetitions of 50,000 pushes per - producer. Frequency, thermal and scheduler drift across that interval is folded into the control, - and into every candidate the control is used to judge. At sixteen and thirty-two producers, where - the machine is oversubscribed and the layout differences are smallest, that is exactly where it - matters most. - - This is the first *specific* mechanism proposed for the 7-61% same-configuration spread recorded in - [DESIGN-NOTES.md](DESIGN-NOTES.md#d-variance-is-a-finding); the other candidates there are general. - Reported by review. - - **Target:** measure each candidate adjacent to a control run of the same code, or randomise and - balance the order across repetitions so drift cannot align with position in the sequence. Whichever - is chosen, the control must end up measuring the same interval the candidate did. - - **BLOCKER, same as M4.3:** interleaving changes the measurement, so every figure published in - [DESIGN-NOTES.md](DESIGN-NOTES.md) becomes a measurement of a different procedure. The item is - "change it *and* re-run the sweep *and* rewrite the sections", not a reordering. Doing it - mid-branch would invalidate figures that ten review rounds have been read against. Raised rather - than silently deferred, per the PRIME DIRECTIVE. - - **It is placed ahead of M4.2 deliberately**, since a control that is not paired cannot answer - whether lengthening the run narrows the spread -- that answer would be confounded by the same - drift this item removes. Taking M4.2 first would produce a diagnosis nobody could trust. - -- [ ] **M4.2** -- Give the measurement probes the controls needed to act on a dispersion finding, - so "gather more data along this axis" does not require editing a `const` and rebuilding. - - **Ordered after M4.4, which is why it appears second despite the lower number.** The first - diagnostic step this item unblocks is "lengthen the run and see whether the control narrows", and - that cannot be read while the control is measured four runs away from its candidate -- drift would - confound it either way. - - **This item is deliberately small in software and large in guidance.** The diagnostic method - belongs in [DESIGN-NOTES.md](DESIGN-NOTES.md) -- see - [What to try first, and how to tell when you have reached the - floor](DESIGN-NOTES.md#d-variance-is-a-finding) -- and this item exists only to make that method - executable. The judgement stays with the person; the probe stops being the obstacle. - - **Gap:** [src/queue_contention.rs](src/queue_contention.rs) fixes every sampling parameter as a - compile-time constant -- `PUSHES_PER_PRODUCER` (50,000) and `REPETITIONS` (5) -- and `measure()` - takes no arguments. They were made `pub` and are now printed in the report, so a captured run at - least says what produced it; but reading a constant is not setting one. The first move the design - note prescribes on seeing a wide control is to lengthen the span and raise the repetition count on - the unchanged configuration, which is still a source edit and a rebuild. A control that cannot be - turned is not a control, and the cheapest diagnostic step is the one being blocked. - - **Target:** `measure()` takes a settings value carrying at least the pushes-per-producer count, - the repetition count, and the producer counts to sweep (`PRODUCER_COUNTS` is already public and - is the model for the others). Existing defaults stay exactly as they are, so a default run remains - the run the notes describe and every published figure stays reproducible. The binary exposes the - same knobs so a human or an agent can act without a rebuild. - - Apply the same treatment to the sibling cost probes where the sampling parameters are equally - fixed; the axes we anticipate varying are **duration, repetitions, and concurrency**, so those are - the ones that need to be reachable. Do not add knobs beyond what a stated diagnostic step needs -- - an unused parameter is a configuration surface to maintain and a way for two runs to differ - without anyone noticing. - - **Report what was used.** Whatever settings a run was given must appear in its output beside the - host banner, for the reason - [D-observations-not-verdicts](DESIGN-NOTES.md#d-observations-not-verdicts) already gives: a figure - is only interpretable with its capture parameters, and these are now among them. Making the - sampling adjustable without recording it would turn one reproducibility problem into a worse one. - - **Not in scope:** deciding why the control is wide. That is the judgement this tooling supports, - and per the design note a negative result -- "lengthening and repeating do not narrow it, so the - floor is here" -- is a real answer that gets recorded beside the figures. - - **Also not in scope, because it is done:** emitting the dispersion. See M4.5 below. - -- [x] **M4.5** -- Emit the dispersion, not just the median. -> [completed 2026-09-15 UTC-07:00](COMPLETED-CHECKLIST.md#m45) - -- [x] **M4.3** -- Close the undrained window at the start of the drained regime with a readiness handshake, and re-measure everything that changes. -> [completed 2026-09-16 UTC-04:00](COMPLETED-CHECKLIST.md#m43) - -- [x] **M4.6** -- Make the start gate releasable, so a failed thread spawn cannot deadlock the probe. -> [completed 2026-09-16 UTC-04:00](COMPLETED-CHECKLIST.md#m46) - -- [x] **M4.7** -- Make the queue-contention report renderer testable, by taking the observation as an argument instead of measuring inside it. -> [completed 2026-09-16 UTC-04:00](COMPLETED-CHECKLIST.md#m47) - -- [ ] **M2.15** -- Run the probe suite on a second architecture in CI. - - **Keeps its conclusion but loses its evidence.** The five failures cited below were all - `prose: "x86_64"` against `ndjson: "x86"` -- instances of exactly the correspondence M3.4 - retired, so they can no longer occur and a re-run now looks clean. The point stands without them: - CI builds `aarch64` and never tests it, and architecture is the one shape dimension a corpus - cannot vary because it is fixed at compile time. Restate it on that basis when picked up. - - A reviewer asked whether the suite was portable and it was not: three renderer fixtures and the - shape corpus' banner builder each hard-coded `x86_64` while the row they are compared against - publishes `std::env::consts::ARCH`. Measured on `i686-pc-windows-msvc`: five failures, every one - `prose: "x86_64"` against `ndjson: "x86"`. CI BUILDS `aarch64` and never TESTS it, so a - build-and-clippy matrix cannot see this class at all. - - Architecture is the one shape dimension the M2.12 corpus cannot vary, because it is fixed at - compile time rather than chosen per report -- so the corpus that exists precisely to defeat shape - blindness is blind here by construction, and only a second test target can close it. Add one - (`i686-pc-windows-msvc` runs natively on the existing runners; `aarch64` would need its own). - -- [ ] **M2.17** -- Cross the corpus dimensions instead of varying one at a time. - - Re-scoped by M3.5: the dimensions worth crossing are the ROW's. Crossing prose shapes that have - since stopped being checked would have aimed at the retiring half. - - [tests/a_real_report_agrees_with_itself.rs](tests/a_real_report_agrees_with_itself.rs)'s `shapes()` - builds each shape by taking `base()` and changing ONE thing. That makes every shape easy to read and - is why the corpus found what it found -- but it means any renderer branch selected by TWO - dimensions at once is unreachable by construction, and the corpus cannot report the gap because it - does not know the branch exists. - - Measured: `CrossCheck` tags a `parse_incomplete` entry `- {caveat}` under `INCOMPLETE` but - `(parse incomplete) {caveat}` under `DISAGREE`. Anomalies appeared only in an agreeing-counter - shape and disagreements only in a zero-anomaly shape, so the second spelling was never rendered, - and the anomaly-count rule was silently unread on every disagreeing report -- while the comment - above it said it was read for every verdict. One hand-written crossed shape closed it, and the - accounting test went red under sabotage only once that shape existed. - - Enumerate the dimensions the renderer actually branches on (verdict, bracket, coherence, the - partitioning arm, presence of each diagnostic list) and generate the cross product, or a pairwise - covering set if the full product is too slow. The corpus already asserts self-consistency and runs - the fact accounting per shape, so nothing new has to be written to check them -- only to produce - them. Until this lands, a shape that needs two dimensions must be added by hand, which is exactly - the imagination-driven process M2.12 exists to replace. - -- [ ] **M4.8** -- Have the queue-contention report carry the build identity that produced it, and - have the capture scripts require it to agree. - - **Gap:** a run's report states its `host:`, `profile:` and `sampling:`, and the capture scripts - now refuse a set whose runs disagree on any of those. None of it identifies the *instrument*. Two - runs from different probe commits, on one machine, under one profile, pass that check -- and this - is the capture where that matters most, because `M4.3` changed the drained procedure, so a - pre-handshake and a post-handshake run would have their medians combined as though one procedure - produced both. The instrument commit is recorded in the capture README, which is an assertion by - whoever took the capture rather than something anything verifies. - - **Target:** `windows-placement-probe`'s `build_identity` module is the worked example -- a build - script stamps the commit, the dirty flag and the build source into env vars that the binary reads - at run time, and `BuildIdentity::current()` renders them. `windows-platform-probes` has no build - script today, so this adds one. The report prints the identity beside the existing attribution - lines, `requireOneConfiguration` in both capture scripts includes it, and the sabotage is two runs - of different commits being refused. - - **Blocker recorded when queued:** none. The dependency exists next door and is already proven by - that crate's own tests. - -- [x] **M4.9** -- Route a placement probe's refusal-to-measure through the report sink instead of a - panic, so a host that cannot be pinned is a reported observation rather than a crash. - - **Gap:** `windows-placement-probe`'s pinning helper asserted on `SetThreadGroupAffinity`, and - `core_affinity::measure()` reaches it through `time_model_on` / `time_model_placed`. **The - decision to stop was correct and did not change** -- the assert's own message makes the argument, - that an unpinned thread "would produce a plausible number that answers a different question, and - nothing in the output would say so." What was wrong is the *mechanism*: a panic bypasses - `emit_report`, so the refusal landed on stderr while stdout carried a truncated report with no - row saying why it stopped. - - **Measured, before and after.** With every pin forced to fail, the old binary wrote three lines - to stdout -- banner, heading, blank -- and exited 101, with the explanation only on stderr. It - now writes the refusal itself to stdout and exits 0, matching how the same binary already reports - a topology-discovery failure. - - **Done:** `pin_current_thread` returns `io::Result`; `time_model`, `time_model_on`, - `time_model_placed`, `median` and `peer_index_cache::measure` propagate it; both probe binaries - handle it. The assert's message text is kept -- it was the right message, in the wrong channel. - `PinSignal` still publishes `PIN_FAILED`, now on the early-return path as well as on an unwind, - so the consumer never waits on a producer that has left. - - **Two things a later review corrected in the first attempt at this item, both the same mistake -- - a claim made about the code rather than from it.** - - `check_group_support` (then `assert_group_support`) tests the *same* predicate as the - `usize::BITS` branch, over every discovered processor, and runs before any pin. So converting - only the branch left this one deciding the outcome: the probe still died with banner, heading and - nothing else. Fixing one site of a rule and leaving its twin is the blast-radius miss this - repository has a rule against, and the before/after measurement recorded here did not catch it - because it forced the pin argument rather than the discovered set. Both sites now refuse. - - `peer_index_cache::measure` **cannot** refuse -- it starts only unpinned runs -- so its `# Errors` - section documented a failure mode no host can produce, and the entry here said "both probe - binaries render it" as though both could decline. Only `probe-core-affinity` pins. The signature - stays `Result` because its helpers are fallible in general; the docs now say that instead of the - opposite. - - **Also corrected:** the refusal text was shared by both causes while being written for one. For - the mask-width cause it claimed the processor "was reported by this machine's own topology, so - this is unexpected rather than a limit of the tool" and invited a bug report -- the exact reverse - of that cause, which *is* a limit of the tool. The explanation is now a parameter, verified in - both directions. - - **One correction to this item as written.** The blocker recorded when queuing was **wrong** -- - `windows-placement-probe` was absent from release-please configuration at the - time. It is now a managed binary release under the root - [DESIGN-NOTES.md](../../DESIGN-NOTES.md#d-probe-releases) -> `D-PB`; `publish = false` - prohibits crates.io publication, not binary releases. The `x-probe-*` row - it called for is deferred to `M4.10`, which turned out to be a larger question than these two - probes. - -- [ ] **M4.10** -- Decide whether a probe's report owes a machine-readable row, and make the answer - uniform. - - **Gap:** three of this crate's sixteen probe binaries emit an `x-probe-*` NDJSON line beside - their prose -- `doorbell_cost`, `request_cost`, and `topology` through `topology_report`. The - other thirteen emit prose only, `probe-core-affinity` and `probe-peer-index-cache` among them, - and so does `queue_contention` despite being the probe whose captures are analysed by committed - scripts. So a survey can mine three probes and must scrape or skip the rest. - - **This item was first written the other way round**, asserting that the two placement probes were - the exception and that `queue_contention` emitted a row. Both halves were false: prose-only is - the majority, by four to one. The correction changes what is being proposed -- not "bring two - stragglers up to the norm" but "the crate has two conventions and no stated rule for which - applies." - - **Target:** state the rule first, in [DESIGN-NOTES.md](DESIGN-NOTES.md), since it decides twelve - more binaries than it decides these two: which probes owe a row, and what a refusal looks like in - one. `report_unmeasured` already draws the "measured and declined" against "never ran" - distinction for the topology probe, and the cost probes' `EVERY_LABEL` arrangement is the worked - example for keeping a row and its prose from drifting. Then apply it. - - **Blocker recorded when queued:** none, but note this is a design decision before it is a code - change, so it wants the engineer's call on scope rather than a unilateral sweep. - - -## M5 -- Carried over from M2: unblocked hygiene - -**Nothing gates these.** They are grouped last by priority, not by dependency -- none of them touches -the report pipeline, so any of them may be pulled forward ahead of M4 at any time. They were -discovered during M2 and parked there under a heading none of them fit. M3, which gated M4 but never -gated these, is complete and archived. - -IDs keep their M2 numbers, for the reason given under M4. - -- [ ] **M2.13** -- Lint the completed-checklist archive mechanically in CI. - - Three bookkeeping defects reached review on this branch, and all three are decidable by a script: a - `###` heading concatenated onto the previous line so it did not parse as a heading at all, three - more headings with no blank line above them, an archived item body left `- [ ]` after being checked - off, and a pre-existing entry's heading rewritten -- which the file forbids in its own second line. - - Assert, for every `COMPLETED-CHECKLIST.md`: every `###` heading is preceded by a blank line; no - `- [ ]` remains; and the file has ZERO deleted lines against the merge base. The last one is the - append-only invariant, and it is the one a human reviewer is least likely to notice. - - -- [x] **M2.14** -- Make the two authoring rules this branch earned actually bite. -> [completed 2026-09-13](COMPLETED-CHECKLIST.md#m214) - -- [x] **M2.14.1** -- Give this crate a sabotage manifest, so "observed to fail" is a recorded artifact rather than a habit. -> [completed 2026-09-13](COMPLETED-CHECKLIST.md#m2141) - -- [x] **M2.14.2** -- Add to CONTRACT INTEGRITY rule 1 the one thing this branch learned that it does NOT already say. -> [completed 2026-09-13](COMPLETED-CHECKLIST.md#m2142) - -- [x] **M2.16** -- Repair the garbled `Report` doc comment, and drop the two counts that had rotted beside it. -> [completed 2026-09-16 UTC-04:00](COMPLETED-CHECKLIST.md#m216) diff --git a/crates/windows-platform-probes/COMPLETED-CHECKLIST.md b/crates/windows-platform-probes/COMPLETED-CHECKLIST.md index 45ee2c94b..d6cdeed19 100644 --- a/crates/windows-platform-probes/COMPLETED-CHECKLIST.md +++ b/crates/windows-platform-probes/COMPLETED-CHECKLIST.md @@ -509,6 +509,7 @@ speculative list to extend by imagination -- a fourth is added when a fourth con than noise: across two rounds one reader raised this twice while two others cleared it, one of them explicitly after being pointed at the question. Nothing in the suite decides it either way, which is itself the argument for the oracle. + ## Moved 2026-09-09 -- M2.6: what `GetFullPathNameW` does, and whether it stays ### M2.6 -- Say what `GetFullPathNameW` does, in the crate that owns it, and whether it stays. *(completed 2026-09-09 22:54:01 UTC-04:00)* @@ -608,6 +609,7 @@ request as it was written, and quotes the module doc as it read before the corre operation completed successfully" under a message saying something failed. And attach it only to a condition that is genuinely an OS failure: a call that returned a size rather than an error should not carry one, since `GetLastError` says nothing about it. + ## Moved 2026-09-10 -- M2.1: the report oracle ### M2.1 -- Add a report oracle to this crate. *(completed 2026-09-10 19:45:50 UTC-04:00)* @@ -1797,3 +1799,317 @@ entries were added, one a control. > **-> CROSS-COMPONENT HANDOFF:** the request crate's > [NR-6.2](../windows-namespace-request-sys/COMPLETED-CHECKLIST.md#nr-62-capture) no longer waits on this crate. + +## Moved 2026-10-19 13:14:01 -04:00 -- M4.2: the probes' sampling is set on the command line + +### M4.2 -- Give the measurement probes the controls needed to act on a dispersion finding, so "gather more data along this axis" does not require editing a `const` and rebuilding. *(completed 2026-10-19 13:14:01 -04:00)* + +The decision, with what was built and why, is +[The probes' sampling is set on the command line](DESIGN-NOTES.md#d-settable-sampling); the +alternatives are in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). Built ahead of `M4.4`, against the +item's own ordering note, because building the settings moves no figure; the note on `M4.4` in +[CHECKLIST.md](CHECKLIST.md) was re-planned to say that reading a lengthened run still waits on it. + +Preceded by a pure-relocation split of `src/queue_contention.rs` (75 KiB, XL) into the facade plus +`measure.rs` and `render.rs`, in its own commit. The sabotage harness's inert check was found to +ignore case while writing this item's entries, and was fixed in its own commit. + +## Moved 2026-10-19 14:32:11 -04:00 -- M2.15: the probe suite runs on Windows ARM64 in CI + +### M2.15 -- Run the probe suite on a second architecture in CI. *(completed 2026-10-19 14:32:11 -04:00)* + +The `platform-probes` job in [ci.yml](../../.github/workflows/ci.yml) is a two-entry matrix: +`windows-latest` (x64) and `windows-11-arm`, the GitHub-hosted Windows 11 ARM64 runner, which is +free for public repositories. The x64 entry keeps its job name, and `fail-fast` is off so one +architecture's result does not cancel the other's. Commit `11f595c2`. + +Run once before completion, from a throwaway branch carrying a temporary push trigger (deleted +afterwards): the ARM64 entry ran on host `aarch64-pc-windows-msvc`, every probe's NDJSON row +reported `"arch":"aarch64"`, and the whole job passed -- the suite with `--include-ignored`, the +`oracle-in-renderer` target, and every probe binary. + +The engineer, 2026-10-19: "arm64 completes m2.15." The `i686-pc-windows-msvc` target the item +also named, where the original five failures were measured, was therefore not added. + +## Moved 2026-10-19 14:51:46 -04:00 -- M4.1: every parse_incomplete condition is a blocking state + +### M4.1 -- Model the observation-readable `ParseIncomplete` conditions that `blocking_states` currently omits, so a deleted push site in `cross_check` is caught for all of them rather than for the subset. *(completed 2026-10-19 14:51:46 -04:00)* + +All eleven passed the tautology test: each reads fields on `Observation`, none reads +`cross_check`'s lists. Each gained a `BlockingState` variant, a `blocking_states` branch, a +`codes_for` arm and a perturbation. Seven gained a corpus shape; the other four were already reached +by existing shapes, which the shapes' comment names. + +Two additions beyond the item's target: + +- `every_parse_incomplete_code_is_answered_by_a_blocking_state` compares + `ParseIncomplete::ALL_CODES` with the codes the states answer to, both generated lists, so the + census stays closed for conditions added later. It also checks the other direction: every code a + state names is one some diagnostic list can publish. +- `a_state_read_from_two_fields_is_reached_by_each_of_them_alone`, because the perturbation table + reaches `RelationsWithoutProcessors` and `MeasuredButCountsAbsent` through one field each, and + checks the measured guard both ways. + +[sabotage.json](sabotage.json) now runs with `--all-features`, because the corpus target requires +`oracle-in-renderer` and `codes_for` lives there. That changed one existing entry: a state added with +no perturbation is now refused by the build, since `codes_for` matches every state exhaustively, so +the entry is declared `refused-by-build` and a new entry deletes a perturbation row to keep the +unit-test guard sabotaged. The sweep also found the two `share_handle` entries stale since `M4.2` +renamed the loop count; their finds were repaired. A deleted `described_relations` push does not +reach `agree`, because a described relation also files `counts_include_unparsed_relations`; it is +caught by a dedicated unit test and by the corpus rule. See +[DESIGN-NOTES.md](DESIGN-NOTES.md#d-encoded-row-is-the-contract). + +## Moved 2026-10-19 15:43:40 -04:00 -- M4.4: the queue probe interleaves its passes + +### M4.4 -- Interleave each candidate with a nearby control instead of measuring the control four runs away from it, and re-measure everything that changes. *(completed 2026-10-19 15:43:40 -04:00)* + +The decision is +[The queue probe interleaves its passes](DESIGN-NOTES.md#d-interleaved-order), with the alternatives +in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md); the measurement is +[captures/2026-10-19-interleaved-order/](captures/2026-10-19-interleaved-order/README.md), seven runs of +each order alternating on the host the earlier figures came from. + +**The item's blocker did not apply as written.** It said interleaving makes every published figure a +measurement of a different procedure, and that doing it mid-branch would invalidate figures review +rounds had been read against. That branch had long since merged, and keeping the old order as +`--order sequential` means the published figures remain measurements of a procedure the probe still +runs: each place that gives them now names the order that produced them, and none is retaken. +"Re-measure everything that changes" was met by the paired capture rather than by replacing the +published figures. + +## Moved 2026-10-19 16:16:40 -04:00 -- M2.17: the shape corpus is crossed + +### M2.17 -- Cross the corpus dimensions instead of varying one at a time. *(completed 2026-10-19 16:16:40 -04:00)* + +[tests/a_real_report_agrees_with_itself.rs](tests/a_real_report_agrees_with_itself.rs)'s `shapes()` +now ends with the full cross product of `crossed_dimensions()`: the partitioning arm, the bracket, +coherence, the processor counter (which fills `disagreements`), the NUMA counter (`not_compared`), +anomalies (`parse_incomplete`) and the efficiency classes, which the renderer gates on the parse being +in doubt. The verdict is not its own dimension, being a function of the three lists. The full product +is 720 shapes and the target runs in well under a second, so the pairwise fallback the item allowed +was not needed. Every existing per-shape check runs over them unchanged, as the item expected. + +Two guards were added because a product can collapse silently: +`the_crossed_dimensions_are_independent_of_each_other` requires each non-first value to change the +observation and every cross-dimension pair to commute, and +`the_crossed_corpus_reaches_each_verdict_under_each_partitioning_arm` requires the crossing to have +produced crossed states, including a shape filling all three diagnostic lists. + +Evidence, in [sabotage.json](sabotage.json): checking coherence only when the bracket held -- a +two-dimension defect of the kind this code has shipped -- **survived** the corpus before this change +and is caught after it, by `every_state_that_blocks_agreement_reaches_the_row`. A crossed value that +changes nothing is caught by the independence guard, and renaming a value survives as the control. + +## Moved 2026-10-19 16:29:02 -04:00 -- M4.11: the coherence probes run in CI on both architectures + +### M4.11 -- Run the queue-contention and peer-index probes in the CI probe job, so each build carries an ARM64 run of both. *(completed 2026-10-19 16:29:02 -04:00)* + +Added and completed in one step on the engineer's direction, 2026-10-19: "please implement the +missing coverage, I assume in the ci pipeline?" -- the coverage being the ARM64 run of +`probe-queue-contention` that the queue crate's README recorded as lost. + +The `platform-probes` job's two matrix entries now run `probe-queue-contention --producers 1,2,4` and +`probe-peer-index-cache`, both in release. The decision is +[The coherence probes run in CI](DESIGN-NOTES.md#d-coherence-probes-in-ci), and the queue section of +[DESIGN-NOTES.md](DESIGN-NOTES.md) was amended from "must not run in the CI probe job" to say what CI +runs and why the full curve stays manual. + +Verified before recording, from a throwaway branch carrying a temporary push trigger (deleted +afterwards): both steps passed on both entries, on runners whose banners read +`aarch64 4p/4c smt- L2[1,1,1,1] ec[0:4] numa[4]` and `x86_64 4p/2c smt+ L2[2,2] ec[0:4] numa[4]` -- +four processors each, as the producer limit assumes. + +## Moved 2026-10-19 16:43:02 -04:00 -- M4.8: the queue report names its build + +### M4.8 -- Have the queue-contention report carry the build identity that produced it, and have the capture scripts require it to agree. *(completed 2026-10-19 16:43:02 -04:00)* + +The decision is +[A queue report names the build that produced it](DESIGN-NOTES.md#d-report-names-its-build), and the +choice of included code over a copy is in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). + +**The item's premise that this crate had no build script was out of date** -- it has carried one +since the long-path probe, which embeds a manifest -- so the stamping was added to it rather than to a +new one. The stamping itself was moved out of `windows-placement-probe`'s build script into +`build/stamp.rs` in two commits of that crate, a pure-relocation split and then a refactor making it a +function of a prefix, with `BuildIdentity::from_stamps` added so both crates read the stamps alike. + +The capture scripts treat a missing build line as "not stamped", so every committed capture +regenerates byte for byte; both copies of each script were changed identically. Two script comments +claimed `M4.8` would replace their byte-identical-run check, which it does not: a build identity +names the build, which every run in a capture shares. Those comments were corrected. + +The sabotage the item asked for -- runs of two builds being combined -- is in +[sabotage.json](sabotage.json) and caught by +`tests/the_capture_scripts_refuse_runs_of_different_builds.rs`, alongside the report dropping its +build line (caught), the build script stamping nothing (refused by the build) and a control. + +## Moved 2026-10-19 17:18:23 -04:00 -- M4.10: every probe's normal run emits a row of raw data + +### M4.10 -- Decide whether a probe's report owes a machine-readable row, and make the answer uniform. *(completed 2026-10-19 17:18:23 -04:00)* + +Decided by the engineer on 2026-10-19: every probe's normal run emits a row of raw data, with +interpreting readings possible as other modes, and rows are not formal contracts but are renamed when +their meaning changes. The decision is +[Every probe's normal run emits a row of raw data](DESIGN-NOTES.md#d-every-probe-emits-a-row), with +the three options put to the engineer and his words in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). +"Make the answer uniform" is spawned as milestone `M8` in [CHECKLIST.md](CHECKLIST.md), one item per +probe without a row; the census of which probes emit one still read 3 of 16 when this was decided. + +## Moved 2026-10-19 17:37:29 -04:00 -- M4 and M5 complete; M2.13 moved to the root checklist + +Every item of `M4` and `M5` was complete except `M2.13`, which moved to the root +[CHECKLIST.md](../../CHECKLIST.md) as `M34.18` because the check it asks for spans the repository. The +checked stubs that stood in the checklist for items already archived above were deleted rather than +copied here, on the engineer's ruling of 2026-10-19 that such a placeholder "does not have to move, it +may simply be deleted". The milestone's preamble, as it stood: + +> These were written under M2 and were blocked on M3, which is **now complete and archived** in +> [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). They are unblocked, and each was RE-SCOPED rather +> than merely delayed -- so each item below carries its own re-scoping note, in the item, where +> somebody executing the list will actually meet it. +> +> **The IDs keep their M2 numbers deliberately.** [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md) is +> append-only and its entries are immutable, and two archived entries already cite M2.4 and M2.14 -- +> so renumbering would leave dangling references in a file that may not be edited to repair them. +> Stable IDs cost a mismatch between an item number and its milestone; renumbering would cost +> correctness in the archive. + +### M4.9 -- Route a placement probe's refusal-to-measure through the report sink instead of a panic, so a host that cannot be pinned is a reported observation rather than a crash. + +Its completion was recorded in the item itself rather than here; the item as it stood: + +- [x] **M4.9** -- Route a placement probe's refusal-to-measure through the report sink instead of a + panic, so a host that cannot be pinned is a reported observation rather than a crash. + + **Gap:** `windows-placement-probe`'s pinning helper asserted on `SetThreadGroupAffinity`, and + `core_affinity::measure()` reaches it through `time_model_on` / `time_model_placed`. **The + decision to stop was correct and did not change** -- the assert's own message makes the argument, + that an unpinned thread "would produce a plausible number that answers a different question, and + nothing in the output would say so." What was wrong is the *mechanism*: a panic bypasses + `emit_report`, so the refusal landed on stderr while stdout carried a truncated report with no + row saying why it stopped. + + **Measured, before and after.** With every pin forced to fail, the old binary wrote three lines + to stdout -- banner, heading, blank -- and exited 101, with the explanation only on stderr. It + now writes the refusal itself to stdout and exits 0, matching how the same binary already reports + a topology-discovery failure. + + **Done:** `pin_current_thread` returns `io::Result`; `time_model`, `time_model_on`, + `time_model_placed`, `median` and `peer_index_cache::measure` propagate it; both probe binaries + handle it. The assert's message text is kept -- it was the right message, in the wrong channel. + `PinSignal` still publishes `PIN_FAILED`, now on the early-return path as well as on an unwind, + so the consumer never waits on a producer that has left. + + **Two things a later review corrected in the first attempt at this item, both the same mistake -- + a claim made about the code rather than from it.** + + `check_group_support` (then `assert_group_support`) tests the *same* predicate as the + `usize::BITS` branch, over every discovered processor, and runs before any pin. So converting + only the branch left this one deciding the outcome: the probe still died with banner, heading and + nothing else. Fixing one site of a rule and leaving its twin is the blast-radius miss this + repository has a rule against, and the before/after measurement recorded here did not catch it + because it forced the pin argument rather than the discovered set. Both sites now refuse. + + `peer_index_cache::measure` **cannot** refuse -- it starts only unpinned runs -- so its `# Errors` + section documented a failure mode no host can produce, and the entry here said "both probe + binaries render it" as though both could decline. Only `probe-core-affinity` pins. The signature + stays `Result` because its helpers are fallible in general; the docs now say that instead of the + opposite. + + **Also corrected:** the refusal text was shared by both causes while being written for one. For + the mask-width cause it claimed the processor "was reported by this machine's own topology, so + this is unexpected rather than a limit of the tool" and invited a bug report -- the exact reverse + of that cause, which *is* a limit of the tool. The explanation is now a parameter, verified in + both directions. + + **One correction to this item as written.** The blocker recorded when queuing was **wrong** -- + `windows-placement-probe` was absent from release-please configuration at the + time. It is now a managed binary release under the root + [DESIGN-NOTES.md](../../DESIGN-NOTES.md#d-probe-releases) -> `D-PB`; `publish = false` + prohibits crates.io publication, not binary releases. The `x-probe-*` row + it called for is deferred to `M4.10`, which turned out to be a larger question than these two + probes. + +## Moved 2026-10-19 17:49:58 -04:00 -- M8.1: the row census runs the queue probe + +### M8.1 -- Run `probe-queue-contention` in the row census with a short sampling, replacing the exclusion whose reason `M4.2` made false. *(completed 2026-10-19 17:49:58 -04:00)* + +[tests/the_probes_that_emit_a_machine_readable_row.rs](tests/the_probes_that_emit_a_machine_readable_row.rs) +excluded the probe as "about sixty-five seconds, with no short mode"; `M4.2` gave it one. The census +now runs it with `--producers 1 --repetitions 1 --pushes-per-producer 1000`, from a `SHORT_RUNS` +table guarded like the exclusions are against a renamed probe, which costs about half a second. Only +`probe-cancel-io` is still excluded, for its own reason, and the copilot instructions' description of +the census was corrected from two exclusions to one. + +Evidence, in [sabotage.json](sabotage.json): the queue probe emitting a row built with `Row::new` -- +whose source carries no `"reason":"x-probe` literal for the old source check to find -- **survived** +the census as it stood, checked by running the entry against the previous test file, and is caught by +the census that runs the probe. A control changing the short run's push count survives. Once `M8.2` +gave the probe its real row, the entry was re-targeted to delete that row, which the census catches. + +## Moved 2026-10-19 18:58:37 -04:00 -- M8.6: the long-path probes emit a row + +### M8.6 -- Both long-path probes emit one `x-probe-long-path` row, told apart by `manifest_aware`. *(completed 2026-10-19 18:58:37 -04:00)* + +The item as written: "`probe-long-path-aware` and `probe-long-path-unaware` emit rows, one item +because the finding is the difference between them." + +One reason for both binaries, because their finding is the difference between two runs: a reader +pairs two rows of one shape rather than two shapes. Each carries `manifest_aware`, the machine's +`LongPathsEnabled` (`null` when a refused run never read it), the content ceiling, and every attempt +by shape record name with its resolved length, whether it opened, and the Win32 error only when it +did not. An apparatus failure is published as `declined`. The row is emitted after the shared +renderer's body, which returns early on an apparatus failure. `Shape` gained a stable `name` beside +its prose label. + +Evidence, in [sabotage.json](sabotage.json): six `M8.6` entries are each caught by their intended +test -- among them the row claiming the manifest whatever the binary declares, and an unread +registry value published as off, the defect the module's own note records against the prose -- and +a key-order control survives. + +## Moved 2026-10-19 20:00:57 -04:00 -- M8: every probe's normal run emits a row of raw data + +Applied [Every probe's normal run emits a row of raw data](DESIGN-NOTES.md#d-every-probe-emits-a-row), +decided by `M4.10`. `M8.1` and `M8.6` were archived above when they completed; their stubs were +deleted with the milestone. Each item's row, its refusal form and its sabotage evidence are in the +commit that completed it; the conventions the rows share are recorded in the decision. + +- [x] **M8.2** -- `probe-queue-contention` emits a row. +- [x] **M8.3** -- `probe-peer-index-cache` emits a row. +- [x] **M8.4** -- `probe-core-affinity` emits a row, including when it declines to measure. +- [x] **M8.5** -- `probe-pool-growth` emits a row. +- [x] **M8.7** -- `probe-error-mode` emits a row. +- [x] **M8.8** -- `probe-handle-state` emits a row. +- [x] **M8.9** -- `probe-worker-context` emits a row. +- [x] **M8.10** -- `probe-device-map` emits a row. +- [x] **M8.11** -- `probe-ioring` emits a row. +- [x] **M8.12** -- `probe-completion-port` emits a row. +- [x] **M8.13** -- `probe-cancel-io` emits a row, including for a case its watchdog abandons. + +### M8.14 -- The row census asserts that every registered probe emits a row. *(completed 2026-10-19 20:00:57 -04:00)* + +The item as written: "Make the row census assert that every registered probe emits a row, so a probe +added later without one fails rather than joining an exception list." + +[tests/the_probes_that_emit_a_machine_readable_row.rs](tests/the_probes_that_emit_a_machine_readable_row.rs) +no longer keeps a list of the probes that emit a row; it runs every `[[bin]]` the manifest registers +and fails naming any whose report carries none. Every probe runs under a deadline, its output read +as it arrives, so no probe can hang the suite. `probe-cancel-io`, excluded until now because its +subject is a call that can fail to return, is run too: the census stops at its row rather than +waiting on an exit its own finding says may not come, which the report's line-by-line streaming +(`M1`) makes possible. Its old stand-in -- a check that its sources carried no emission literal -- +is gone, since a row built with `Row::new` answers it falsely. + +The deadline paths are each driven by a test against a `cmd.exe` child: a report that never +finishes, a probe excused from exiting that prints its row and then blocks (and the same child, not +excused, running into the deadline), an unsuccessful exit, and a report read as written with and +without a row. The one path no test reaches -- a child that closes its output and keeps running -- +says so at its definition. + +Evidence, in [sabotage.json](sabotage.json): `probe-cancel-io` losing its row **survived** the census +as it stood, checked by running the entry before the rewrite, and is caught by the census that runs +it. Accepting a timed-out report, accepting an unsuccessful exit, and waiting on an excused probe are +each caught by their intended test; a control shortening the deadline survives. Two `M4.2` entries +whose find text the `M4.4` rewrite had left matching zero and two times were repaired in the same +change, and every entry in the manifest now matches exactly once. diff --git a/crates/windows-platform-probes/COMPLETED-PLANS.md b/crates/windows-platform-probes/COMPLETED-PLANS.md index 87d1a027e..fbefb6194 100644 --- a/crates/windows-platform-probes/COMPLETED-PLANS.md +++ b/crates/windows-platform-probes/COMPLETED-PLANS.md @@ -4,3 +4,4 @@ |---|---|---|---| | [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md) | 2026-09-05 | Measured how `reserving_mpsc`'s claim word apportionment and width affect throughput, then shipped the layouts as caller-selectable options: `Balanced`, `Enduring`, `Perpetual`, and `Wide` behind the `dwcas` feature. Superseded D-36, whose premise was that fixing SH-14.1 required the claim-protocol replacement. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | | [CHECKLIST.md](CHECKLIST.md) | 2026-09-09 | M1: every probe streams its report into the sink as it measures, through a `fmt::Write` adapter that left every `writeln!` call site untouched; the `catch_unwind`/`resume_unwind` pair is gone because there is no longer a buffer to rescue. Measured against a control: a probe killed part-way through a run keeps what it had already written, where the previous build kept nothing -- the figures and the census they rest on are in [DESIGN-NOTES.md](DESIGN-NOTES.md#d-streaming-report), which owns them. M2: a report's parts are now checked against each other, which is the defect class no per-part instrument could see. An oracle reads the rendered artifact and relates the claims already in it; the topology banner is built from the read the body describes rather than from an endpoint; both cost probes derive their prose and NDJSON from one source; and every CI probe step is gated on the build so diagnostics survive a failing test. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-streaming-report), [#d-correspondence-failures](DESIGN-NOTES.md#d-correspondence-failures) | +| [CHECKLIST.md](CHECKLIST.md) | 2026-10-19 | M3: the encoded topology row became the contract -- diagnostic lists as objects with stable codes, value shapes declared beside their names, one typed writer with total escaping, and no structured data read back out of prose. M4 and M5: the leftovers re-sequenced from M2 -- every `parse_incomplete` condition a blocking state, the queue probe's passes interleaved by default, its sampling set on the command line, its report naming the build that produced it, the coherence probes run in CI in release, and the probe suite run on Windows ARM64 as well as x64; M5's archive lint moved to the root checklist as M34.18. M6 and M7: the `request_cost` probe rewritten against the namespace request crate's path design, then stopped capturing. M8: every probe's normal run emits an `x-probe-*` row of raw data, as decided by M4.10, and the row census runs every registered probe under a deadline and fails for any without one. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-encoded-row-is-the-contract), [#d-every-probe-emits-a-row](DESIGN-NOTES.md#d-every-probe-emits-a-row) | diff --git a/crates/windows-platform-probes/Cargo.toml b/crates/windows-platform-probes/Cargo.toml index b9e716a4a..4284f2f46 100644 --- a/crates/windows-platform-probes/Cargo.toml +++ b/crates/windows-platform-probes/Cargo.toml @@ -159,6 +159,9 @@ windows-topology-sys = { path = "../windows-topology-sys" } # describe. That banner is `windows-placement-probe`'s to render, not a second # copy here. windows-placement-probe = { path = "../windows-placement-probe" } +# The io-ring probe's pending-read pipe is made with the workspace's safe wrapper of `CreateNamedPipeW`, +# in place of an `unsafe` call of its own. Creating the pipe is not what the probe measures. +win-pipes-sys = { path = "../win-pipes-sys" } # The request-cost probe measures the real request type the design would put on # a queue, not a stand-in, for the same reason the topology probe reads the # shipping parse: a reimplementation would measure the reimplementation. @@ -267,7 +270,6 @@ features = [ "Win32_System_Environment", "Win32_System_Diagnostics_Debug", "Win32_System_IO", - "Win32_System_Pipes", # GetSystemDirectoryW, so the request probe measures the real system # directory instead of assuming Windows is installed on C:. "Win32_System_SystemInformation", diff --git a/crates/windows-platform-probes/DESIGN-NOTES.md b/crates/windows-platform-probes/DESIGN-NOTES.md index 65c7f616d..78d1f6155 100644 --- a/crates/windows-platform-probes/DESIGN-NOTES.md +++ b/crates/windows-platform-probes/DESIGN-NOTES.md @@ -136,14 +136,16 @@ of them are live here: once cannot attribute a difference to the one under test. - **A control that is not a paired control.** This probe's same-code control is the `reserving_mpsc` row against the `reserving(32/32)` row, and `measure()` - runs them **four measurements apart** -- the permit shape and all three drained + ran them **four measurements apart** -- the permit shape and all three drained shapes fall between them, each five repetitions of 50,000 pushes per producer. Any frequency, thermal or scheduler drift across that interval lands inside the control, and by the same token inside every candidate row it is compared against. This is a specific, mechanical candidate for the spread above rather than a general worry, and it was proposed by review rather than found here. - Queued as M4.4 in [CHECKLIST.md](CHECKLIST.md), because interleaving changes the - measurement and so obsoletes the published figures. + Addressed by `M4.4`: the passes are now interleaved by default, and the old + order is kept as `--order sequential` -- see [The queue probe interleaves its + passes](#d-interleaved-order), which also records what interleaving did to the + control on this host. - **A defect in the probe itself.** This crate has already shipped one -- the timing window that read the coordinator's clock rather than the producers'. That defect was invisible in the numbers until it was found by reading, and it @@ -177,6 +179,14 @@ are the interesting ones. Do this before pinning threads, before quiescing the machine, and before suspecting the probe -- each of those changes what is being measured, and a change made before the cheap check cannot be evaluated. +That move is a command line, not a source edit: `probe-queue-contention` takes +`--pushes-per-producer`, `--repetitions` and `--producers`, and the cost probes take +`--iteration-multiplier`. See [The probes' sampling is set on the command +line](#d-settable-sampling). Take the lengthened run in the default, interleaved +order: under `--order sequential` the control's two halves are measured apart, so +lengthening the run also lengthens the interval between them (see [The queue +probe interleaves its passes](#d-interleaved-order)). + **A warmup pass separates transient cost from ongoing noise, and the two are different things.** This probe already discards one untimed pass -- though not for the reason an earlier version of this paragraph gave. Every timed repetition @@ -699,7 +709,15 @@ architecture. It would fail on any host with a longer user name, on either architecture. Recorded here because it is exactly the kind of result this comparison exists to classify correctly: a red build that is **not** a finding. -## The queue-contention probe, and why it must not run in the CI probe job +## The queue-contention probe, and how much of it the CI probe job runs + +**Amended 2026-10-19: it now runs in the CI probe job at 1, 2 and 4 producers, on both architectures; +see [The coherence probes run in CI](#d-coherence-probes-in-ci).** + +**Every figure this note gives from `probe-queue-contention` that predates `M4.4` was measured in +the sequential order**, each configuration's passes back to back, because that was the only order +the probe had. `--order sequential` still takes it; the default is now interleaved. See +[The queue probe interleaves its passes](#d-interleaved-order). `probe-queue-contention` measures two things a design decision is waiting on: whether the bounded array queue's tail claim contends badly enough to justify the linked and sharded MPSC shapes, and how @@ -718,10 +736,12 @@ rest of the queue work -- so this note deliberately names the QUESTIONS rather t that would dangle. The probe is the instrument; it is useful before the plan that consumes it lands, and it is landed first precisely so the decision is made against measurement rather than argument. -**It is deliberately absent from the `platform-probes` CI job, and the reasons are a core count and a +**The full curve is not run in the `platform-probes` CI job, and the reasons are a core count and a clock rather than a preference.** A contention curve needs more cores than a hosted runner has: the sixteen- and thirty-two-producer rows on a four-core runner would measure the scheduler and report it as -contention. And the run costs about 65 seconds, against a job whose other probes are seconds apiece. +contention. And the full run costs about 65 seconds, against a job whose other probes are seconds +apiece. Both reasons are about producer counts above the runner's processors, which is why the job now +runs the counts below them. **It must be run in release, which is a measurement and not a preference.** In a debug build `slotwise_mpsc` and `reserving_mpsc` come out at 249.7 and 254.0 ns/push at sixteen producers -- @@ -750,10 +770,39 @@ paragraph confused the two -- it said the CI job "runs `cargo run` without `--re of the job it describes: `probe-doorbell-cost` and `probe-request-cost` already run there with `--release`, under a comment establishing exactly the rule this probe would fall under. It also said "unlike every other probe", and `probe-cancel-io` is likewise absent. Corrected by a review. The release precedent exists; what -keeps this one out is that it costs an order of magnitude more than the two probes that use it, on hardware -that cannot answer the question anyway. +keeps the full curve out is that it costs an order of magnitude more than the probes that use it, on +hardware that cannot answer the high-producer question anyway. + +So the full curve is run by hand, on a known machine, and its numbers are recorded with the machine +attached. + +### The coherence probes run in CI, below the runner's processor count + + + +**Status: decided and implemented** by `M4.11`, on the engineer's direction of 2026-10-19 to implement +the ARM64 coverage that [the queue crate's README](../windows-waitable-queues/README.md) had recorded as +missing. -So this one is run by hand, on a known machine, and its numbers are recorded with the machine attached. +The `platform-probes` job runs on an x64 and an ARM64 runner since `M2.15`, and it is the only ARM64 +hardware this workspace reaches. So it now runs, in release, on both: + +- **`probe-queue-contention --producers 1,2,4`.** A hosted runner has four processors, so these are + the counts that measure contention rather than the scheduler. The list is fixed rather than read from + the runner, so rows from different builds are comparable; the report prints the processors the run + had, so a runner with fewer is visible. The rest of the sampling is the default, and the report says + the run was not the defaults. +- **`probe-peer-index-cache`**, whose finding inverts between the two architectures. It uses two + threads, so it fits any runner. Its earlier absence rested on a debug build burying the effects it + studies, which a release step answers. + +At these settings each run takes seconds, against the full queue curve's minute. Neither step asserts a +magnitude: a red step means the probe failed to run, and the figures are in the build's log, from one +runner of a heterogeneous fleet, beside the banner that says which. + +The full queue curve stays a manual run on a known machine, for the reasons above. + +**Scheduled work:** none. ### Reading it @@ -856,8 +905,10 @@ straight past. The warming variant is a control for the hypothesis that a discar substitute for the real thing; it removes no read and moves no time, which is exactly what a control that confirms the null should do. -Like `probe-queue-contention`, this probe is **absent from the CI probe job**, and for the same -measured reason: the effects it studies are coherence effects that a debug build's overhead buries. +**It runs in the CI probe job, in release, on both architectures**; see +[The coherence probes run in CI](#d-coherence-probes-in-ci). It was absent until 2026-10-19 because +the effects it studies are coherence effects that a debug build's overhead buries, which is a reason +to run it in release rather than not at all. ## The fingerprint carries provenance inside the string, not beside it @@ -1147,11 +1198,15 @@ allocation a timed pass will use -- every repetition builds and drops its own queue -- so what it warms is process state: the allocator's size class, the OS page cache, the instruction cache and the branch predictors. So a figure below rests on 35 timed passes per -configuration, and "seven runs" alone would not let anyone reproduce it. These -are fixed at +configuration, and "seven runs" alone would not let anyone reproduce it. Each +configuration's five passes were taken back to back, before the next +configuration's -- the order `--order sequential` still takes, and no longer the +default (see [The queue probe interleaves its passes](#d-interleaved-order)). The +counts are fixed at [src/queue_contention.rs](src/queue_contention.rs)`::PUSHES_PER_PRODUCER` and -`REPETITIONS`; M4.2 in [CHECKLIST.md](CHECKLIST.md) makes them adjustable, which -is what the first diagnostic step above needs and cannot currently do. +`REPETITIONS`, which are the defaults of `Sampling`; a run given other values on the +command line says so beside its banner (see [The probes' sampling is set on the +command line](#d-settable-sampling)). **The banner's `numa[16]` is load-bearing here: it means a single NUMA node holding all sixteen processors**, so every figure below was taken @@ -1203,8 +1258,9 @@ measurements taken on a shared desktop that is doing other things. The dispersio alone cannot distinguish them, and this note does not guess. See [High variance in our own control is a finding about the instrument](#d-variance-is-a-finding) for what to try first and how to recognise -the floor, and M4.2 in [CHECKLIST.md](CHECKLIST.md) for the probe controls that -make those steps executable without a source edit. +the floor, and [The probes' sampling is set on the command +line](#d-settable-sampling) for the flags that make those steps executable without a +source edit. What follows is therefore reported as *data with a known-unexplained spread*. @@ -1920,6 +1976,10 @@ which is the decision below. +**Amended 2026-10-19: the engineer does not treat probe rows as formal contracts; see [Every probe's +normal run emits a row of raw data](#d-every-probe-emits-a-row). What follows still governs the +topology row it was written for.** + A probe is a data pipeline that renders, at its tail, to two artifacts: an NDJSON row and prose. **They are not peers.** The row is a machine contract -- mined across a fleet, joined against other runs, and the thing this workspace's designs @@ -2031,6 +2091,29 @@ blocking states against the row's keys, and `publication_holds` is the shared predicate the corpus rule and its sabotage both call. Landed as M3.5, archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). +**Every `parse_incomplete` condition is a blocking state, and a test holds the +census closed.** Until `M4.1` the states covered part of the list, and the rest +were excused as "derived counts whose only source IS the cross-check's own +arithmetic" -- false for every one of them, since each reads fields on +`Observation` directly. `every_parse_incomplete_code_is_answered_by_a_blocking_state` +compares `ParseIncomplete::ALL_CODES` with the codes the states answer to, both +generated lists, so a new condition with no state fails a test rather than +waiting for a review. The tautology test still governs membership: a state whose +only source is `cross_check`'s own output restates `verdict()` and does not +belong. + +A deleted push site is not always caught by the INVARIANT module, and the +difference is worth knowing before reading a sabotage result. Where a condition +also files a `not_compared` entry -- a described or unreported relation does, +through `counts_include_unparsed_relations` -- the verdict stays `incomplete` +without the push, so there is no agreeing verdict to forbid. What is lost is the +code a survey groups by, and the corpus's per-state publication rule is what sees +that in general; a described relation also has a dedicated unit test. + +[sabotage.json](sabotage.json) runs with `--all-features` because the corpus +target requires `oracle-in-renderer`, and `codes_for` -- the schema between states +and row codes -- lives there, so a defect in it is visible to no other target. + (Until M3.5 landed this section said "nothing here enforces that rule yet" and pointed at CHECKLIST.md for the queued item. Both statements outlived the milestone that made them false -- the reason this Tier 1 file is swept against @@ -2335,7 +2418,7 @@ decision, and the request crate's `NR-3.7` is done with it. The request crate's requests hold a `SharedHandle` ([D-32](../windows-namespace-request-sys/DESIGN-NOTES.md#d-32)), and `CapturedHandle`, whose -`DuplicateHandle` the probe timed as "what a request costs to carry a handle", is going. So the +`DuplicateHandle` the probe timed as "what a request costs to carry a handle", is deleted (`NR-6.2`). So the probe times what a request now does with a handle: - **`share_handle`**: a clone of a `SharedHandle` and its drop, with the original held throughout, @@ -2358,3 +2441,195 @@ the table with the NDJSON line derives its keys from the probe and needed no cha **Scheduled work:** none. This was the change the request crate's `NR-6.2`, which deleted `CapturedHandle`, waited on. + +## The probes' sampling is set on the command line, and a run says when it was not the default + + + +**Status: decided and implemented** by `M4.2`. + +[High variance in our own control is a finding about the instrument](#d-variance-is-a-finding) +makes "more of the same" the first move against a wide control: a longer span and more repetitions +on the unchanged configuration. Before `M4.2` that was a source edit and a rebuild. Now: + +- **`probe-queue-contention` takes `--pushes-per-producer N`, `--repetitions N` and + `--producers a,b,...`.** The three axes the item named -- duration, repetitions, concurrency -- + are these three. +- **The cost probes, `probe-request-cost` and `probe-doorbell-cost`, take one setting, + `--iteration-multiplier N`.** Each times fixed loops and divides; neither repeats a measurement + nor varies a thread count, so lengthening the loops is the whole of + "more of the same" for them. One multiplier scales every loop the probe runs, the doorbell + probe's park-and-wake rounds included, so the loops keep the lengths relative to each other that + they were written with. +- **A value is given as `--name value` or `--name=value`**, parsed by + [src/probe_args.rs](src/probe_args.rs), shared so that every probe that takes settings parses + and refuses the same way. +- **The defaults are the constants, unchanged.** When this decision landed, a run with no + arguments measured exactly what the published figures measured. `M4.4` then changed the default + ORDER of the queue probe's passes, not these counts; see + [The queue probe interleaves its passes](#d-interleaved-order). +- **A refusal is exit status 2, written to stderr before anything is measured**, with stdout left + empty -- so a piped report is never a partial one, and a script can tell a mistyped flag (2) + from a probe that ran and failed (1). Refused: an unknown flag, a flag without a value, a + repeated flag, a value that is not a whole number of at least one, a producer list naming a + count twice, and a setting the probe's own arithmetic cannot carry -- the queue probe sizes its + queue to the next power of two above the largest producer count times the pushes, and a cost + probe counts its loop in `u32`. These are this crate's limits, not a platform's. +- **A run reports what it used, beside the banner, and says when that was not the default.** The + queue probe's sampling line ends `(the defaults)` or `(NOT the defaults: ...)`; the cost probes + print `iteration multiplier: N` followed by the same distinction, and add + `"iteration_multiplier":N` to their NDJSON line. This is + [A probe reports observations parameterized by their capture](#d-observations-not-verdicts) + applied to settings that are now part of the capture. +- **The sampling is carried in the observation, not beside it.** `Observation.sampling` is what + the renderer reads, its producer-count columns included, so a report cannot describe a sampling + other than the one its figures came from. +- **The timer and the divisor read one value.** `median_run` hands each pass the push count the + per-operation figures are divided by, so the pushes a pass performed and the pushes its time is + divided by cannot come from two places. + +**A reading taken with these settings was confounded until `M4.4` landed, and still is under +`--order sequential`.** Back to back, the same-code control's two rows are measured several +configurations apart. Raising the pushes or the repetitions lengthens every measurement between +them, and so lengthens the interval across which drift lands inside the control: the setting +meant to narrow the control also widens the window that can widen it. `M4.4` made interleaving +the default, which puts both halves inside the same rounds; see +[The queue probe interleaves its passes](#d-interleaved-order). + +**Built ahead of `M4.4`, against the checklist's stated order.** That order was about reading a +lengthened run, not about building the means to take one: the defaults are unchanged, so building +the settings changed no figure. The reordering was this implementation's call when the engineer +asked, on 2026-10-19, for `M4.2` to be implemented if it could be; `M4.4`'s note was re-planned in +the same commit. + +**Scheduled work:** none new. The pairing it waited on landed as `M4.4`. + +## The queue probe interleaves its passes, and keeps the sequential order as a setting + + + +**Status: decided and implemented** by `M4.4`. + +`probe-queue-contention`'s same-code control is `reserving_mpsc` against `reserving(32/32)`, the +same code measured twice in one run. Before `M4.4` every configuration's passes ran back to back, so +the control's two halves were measured several configurations apart and any drift across that +interval landed inside the control, and inside every comparison judged against it -- the specific +mechanism [High variance in our own control is a finding about the +instrument](#d-variance-is-a-finding) recorded for the control's width. + +- **The default order interleaves.** At each producer count, every configuration -- both regimes + -- takes its one untimed pass, then the run takes `--repetitions` rounds of one timed pass each, + each round starting one configuration later than the last. So every row's passes are spread + across the same rounds, and the median of each is taken over the same interval. The rotation is + the "balance the order" half of the item: no configuration is always the first pass of a round. + It is deterministic rather than random, so a run's procedure is fully stated by its settings. +- **The sequential order is kept, as `--order sequential`.** It is the order every figure + published before `M4.4` was measured in, so it is what retakes them, and running both orders on + one host is the measurement that says whether interleaving narrowed the control there. A test + pins the list of configurations to that order, because nothing else would notice it changing. +- **Neither order changes which rows a run reports or which table each lands in**; only when the + passes are taken. +- **The order is a capture parameter.** The report's sampling line names it, and a run taken + sequentially says it was not the defaults. Each committed capture's attribution states the order. + +**What it did on the one host available:** the +[2026-10-19 capture](captures/2026-10-19-interleaved-order/README.md) took seven runs of each order, +alternating, and reports per producer count which order's control range was narrower. The figures +are in that capture and are not restated here. + +**The figures published before `M4.4` are not retracted or retaken.** They are measurements of the +sequential procedure, which the probe still runs; each place that gives them says which order +produced them. Retaking them interleaved would replace one host's seven-run reading with another, +and is not scheduled. + +**Scheduled work:** none. + +## A queue report names the build that produced it, and a capture refuses two builds + + + +**Status: decided and implemented** by `M4.8`. + +A capture's runs are comparable only when one build produced them, and before `M4.8` nothing but the +capture README -- a claim by whoever took it -- said which build that was. So: + +- **`probe-queue-contention` prints `build: ` beside its banner.** The identity is + `windows-placement-probe`'s `BuildIdentity` -- crate version, commit, dirty flag, and whether the + build came from CI -- rendered the way that crate renders it, unofficial builds marked. +- **It is stamped by the same build-script code, not a copy.** This crate's + [build.rs](build.rs) includes `windows-placement-probe`'s `build/stamp.rs` and calls it under the + `PLATFORM_PROBES` prefix, and `BuildIdentity::from_stamps` reads the stamps for both crates. The + git-watching rules in that file were each found by measuring a stale stamp; a second copy would be + the copy the next fix missed. +- **The CI probe job stamps its builds as CI builds** with the commit it checked out, so a report + in a build log names that commit. +- **The capture scripts include the build line in the attribution every run must share.** A set + mixing two builds, or a stamped run with an unstamped one, is refused. A run without the line is + read as "not stamped", so every capture committed before `M4.8` still regenerates, byte for byte. + `tests/the_capture_scripts_refuse_runs_of_different_builds.rs` runs every committed copy of both + scripts with `node`, in both directions. + +**What it does not establish.** A dirty build names its commit, not its tree, so two dirty builds at +one commit agree while their sources may differ; the capture README is still the record of what was +built for those, and for every capture taken before this. And the build identity does not replace +the scripts' refusal of byte-identical runs: it names the build, which every run in a capture shares. + +**Scheduled work:** none. + +## Every probe's normal run emits a row of raw data + + + +**Status: decided** by `M4.10`; applied by milestone `M8`, archived in +[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md#m814). + +The engineer, 2026-10-19, choosing the first of three rules put to him (every probe; only probes +something other than a person reads; only host-dependent magnitudes): "The answer is A for the +normal simplified runs. We may have modes that give the other two interpretations/readings but in +general they produce raw data." + +- **Every probe binary's normal run emits an `x-probe-*` row**, beside its prose. Before this, three + of the crate's probes did and the rest printed prose only, with no stated rule for which applied. +- **The row carries raw data**: what the probe observed, not a verdict drawn from it. An + interpreting reading -- a summary, a judgement against a threshold, a filter to what some consumer + wants -- may exist as a separate mode, and is not what a normal run emits. +- **A row is not a formal contract.** The engineer, same date: "I don't necessarily see these as + 'contractual'. Probes produce what they produce." And: "these probes are meant as somewhat + informal means to validate platform behaviors. They are not a formal validation scheme." So adding + a row does not bring the obligations [The encoded row is the contract](#d-encoded-row-is-the-contract) + attached to the topology row; whether a given row's values are machine-checked is decided by the + probe, not required by this rule. +- **What a row does owe is honesty about its identity.** The engineer: "If we retire a probe or if + it measures something different over time, we need to be honest and give it a new name/identifier." + So a row whose meaning changes is renamed rather than reused -- the rule `M6.1` already followed + when it dropped the request probe's old keys instead of carrying them under new meanings. + +**Not decided here, and left to each probe's item:** what a refusal looks like in a row. Topology's +`report_unmeasured` is the existing example of a probe saying it declined to measure; each item +follows it or says why not. Every `M8` item that had a way to decline followed it: the same row, +with a `declined` member carrying the reason and `null` on a run that measured. A probe with no way +to decline -- its measurement completes or panics, and a panic prints no report -- carries no such +member. + +**Conventions the `M8` rows share**, each chosen so a row carries what was observed rather than a +reading of it: + +- **A timing is a whole number of elapsed nanoseconds**, never a per-operation float. The + per-operation figures are the prose's division of an elapsed time, and the row writer has no float + for them; where a measurement kept only the derived figure, the raw one was carried at its source + (`core_affinity::Measurement` gained the median pass's elapsed time and read counts) rather than + reversed out of a division. +- **A value read only on failure is `null` when it was not read.** `GetLastError` after a call that + succeeded is a zero nothing asked for; publishing it reads as an observed code. +- **A signed code or identifier is hexadecimal text** -- an HRESULT as `0x80070057`, a LUID as + `high:low` -- as the prose prints it. The row writer carries no signed number, and these are names + to look up rather than quantities. +- **Every enumerated choice is published by a stable record name** (`name()`), separate from the + prose label the table prints, so the table can be reworded without renaming a row's values. + +**Enforced** by the row census, +[tests/the_probes_that_emit_a_machine_readable_row.rs](tests/the_probes_that_emit_a_machine_readable_row.rs), +which runs every registered probe under a deadline and fails for any whose normal run emits no row +(`M8.14`). A probe added later without one fails it rather than joining a list. + +**Scheduled work:** none. diff --git a/crates/windows-platform-probes/DESIGN-RATIONALE.md b/crates/windows-platform-probes/DESIGN-RATIONALE.md index 034557fd9..19f1dca94 100644 --- a/crates/windows-platform-probes/DESIGN-RATIONALE.md +++ b/crates/windows-platform-probes/DESIGN-RATIONALE.md @@ -643,6 +643,15 @@ result -- so interleaving the control with its candidate has to land first. The paragraph above is kept as the reasoning that was current when the diagnosis was first written down; where the two disagree, the checklist is the execution order. +**`M4.2` was then built before `M4.4` anyway, and that does not reverse the +revision.** The revision orders *reading* a lengthened run after pairing the +control. Building the settings that take one moves no figure -- the defaults are +the old constants -- so it has nothing to be confounded by. What it changes is +that the confounded reading is now one command away rather than one source edit +away, which is why the decision that records it, +[The probes' sampling is set on the command line](DESIGN-NOTES.md#d-settable-sampling), +states the confound in its own body rather than leaving it to this paragraph. + The other half is knowing when to stop. Every setup has a floor, and past it more runs buy nothing; the failure mode is a week spent establishing that two numbers are the same. What makes the floor easy to misjudge is the assumption that it @@ -687,3 +696,152 @@ to the quality of the measurement instead of to its portability. The rule exists to forbid the quiet version: reporting a wide control as though a wide control were ordinary. It is not ordinary, and the moment it stops being remarked upon is the moment nobody investigates it. + +## Why the sampling is set by flags, and why the cost probes get only one + +Decided in [The probes' sampling is set on the command line](DESIGN-NOTES.md#d-settable-sampling). + +**Flags rather than environment variables.** A variable set in a shell outlives the +run it was meant for, and the next run inherits it silently -- two runs differing +without anyone noticing, which `M4.2` named as the thing to avoid. A flag belongs to +one invocation and is visible in whatever ran it. + +**Refusal rather than a warning and the default.** A probe that met an unknown flag +and ran anyway would publish default figures from a run its author believed was +configured. The report would say "the defaults", truthfully, and the author would +be reading it as something else. + +**One multiplier for the cost probes, not a flag per loop.** Each loop's length was +chosen when it was written, and scaling them together keeps those lengths in the +proportions they were written with. A flag per loop would let one run lengthen the +row it is about while the row it is compared against stays short, which is a second +variable entering the comparison. The item also said not to add knobs beyond what a +diagnostic step needs, and the step for these probes is "time more iterations". +Repetitions and concurrency, the other two axes `M4.2` anticipated, do not exist in +those probes: each times a fixed loop and divides, and none repeats a measurement or +varies a thread count. + +**The sampling travels inside `Observation`.** The alternative, passing it to the +renderer next to the observation, would let a caller hand over the defaults with a +non-default measurement -- the renderer cannot tell, and the report would mislabel +its own figures. Carried inside, the label and the figures come from the one +`measure` call. The test corpus accordingly requires a `sampling` key on every case +rather than defaulting a missing one, because a defaulted key is exactly how a case +written before this change would keep passing while describing a run nobody took. + +## Why the probe interleaves by rotation, and why the old order stays + +Decided in [The queue probe interleaves its passes](DESIGN-NOTES.md#d-interleaved-order). + +**The item allowed two designs.** One pairs each candidate with an adjacent control run of the same +code; the other balances the order across repetitions so drift cannot align with position. Pairing +adds a control measurement per candidate, lengthening every run, and gives each candidate its own +copy of the control rather than the one row the published ratios divide by. Interleaving every +configuration costs nothing extra -- the passes are the same passes, reordered -- and puts the +control's two halves, and every candidate, inside the same rounds. So the rounds design was taken. + +**Rotation rather than shuffling.** A random order would balance position as well, but a run would +then not be reproducible from its settings, and this repository's unit tests do not sample at +runtime without the engineer's approval. Rotating the first position by one per round is +deterministic, testable, and still keeps any configuration from always being measured first. + +**The old order stays, against the item's framing of it as something to replace.** The item treated +interleaving as obsoleting every published figure. Keeping the sequential order turns that around: +the published figures remain measurements of a procedure the probe still runs, and the two orders on +one host are the comparison that says whether the change mattered. Removing it would have cost that +comparison and the ability to retake any figure published before `M4.4`, to save one flag. + +**Every configuration at a producer count is interleaved together, both regimes**, rather than each +regime on its own. The control exists in both regimes, and a single list of configurations is also +what makes the sequential order exactly the old one: the old measurement already alternated between +the regimes in a fixed pattern, and the list preserves it. + +## Why the coherence probes joined the CI job, and at what settings + +Decided in [The coherence probes run in CI](DESIGN-NOTES.md#d-coherence-probes-in-ci). + +**Prompted by a gap a previous round had only described.** `M2.15` put the probe suite on an ARM64 +runner, and correcting [the queue crate's README](../windows-waitable-queues/README.md) afterwards +showed that the one probe whose ARM64 figures had been lost was not in that job. The engineer, +2026-10-19: "please implement the missing coverage, I assume in the ci pipeline?" + +**The recorded reasons for keeping the queue probe out were a core count and a run time**, and both +concern producer counts above the runner's processors. At 1, 2 and 4 producers neither applies, so +the decision narrowed what CI runs rather than reversing why the full curve is manual. Reading the +count from the runner instead was rejected: rows from different builds would then measure different +configurations, and the fixed list costs nothing on a runner with more processors. + +**The peer-index probe was added in the same change, unasked**, because it is the probe whose finding +inverts between the two architectures, it needs two threads, and the only reason recorded for its +absence was the debug-build problem a release step solves. Leaving it out would have kept the ARM64 +gap open for the probe it matters most to. + +## Why the build identity is stamped by included code rather than a copy or a new crate + +Decided in [A queue report names the build that produced it](DESIGN-NOTES.md#d-report-names-its-build). + +The item pointed at `windows-placement-probe`'s build script as the worked example. Three ways to +reuse it were weighed: + +- **A copy in this crate's build script** was rejected because that script is the record of several + measured staleness defects -- `HEAD` not changing on commit, packed refs, worktree `commondir` -- + each fixed in place. A copy would carry today's fixes and miss tomorrow's. +- **Reading `windows-placement-probe`'s own stamps at run time** was rejected because they describe + that crate's build: its version, and a dirty flag its script refreshes only when its own files + change. +- **A shared build-dependency crate** would work, at the cost of a new crate for one function. + +So the stamping moved, unchanged, into `windows-placement-probe/build/stamp.rs` and became a function +of a prefix; both build scripts include it with `include!`, which keeps it in each script's root +module and changes no visibility. `windows-placement-probe` is `publish = false`, so a path outside +this crate's directory costs no packaging. + +**Missing stays acceptable in the capture scripts** because every committed capture predates the +line; refusing an unstamped run would have made all of them unregeneratable to gain nothing on runs +nobody can retake. + +## Why every probe emits a row, and why a row is not a contract + +Decided in [Every probe's normal run emits a row of raw data](DESIGN-NOTES.md#d-every-probe-emits-a-row). + +`M4.10` found the crate with two conventions and no rule: three probes emitted a row and the rest +printed prose only. Three rules were put to the engineer on 2026-10-19: + +- **A** -- every probe owes a row; +- **B** -- a probe owes a row when something other than a person reads its output (a CI survey, a + committed script); +- **C** -- a probe owes a row when it reports magnitudes that depend on the host, an asserted + behavioural finding being machine-readable already through its test result. + +The recommendation put forward was C. The engineer chose A for the normal run, keeping B and C as +possible readings: "The answer is A for the normal simplified runs. We may have modes that give the +other two interpretations/readings but in general they produce raw data." A sidesteps the question C +and B each had to answer per probe -- which outputs count -- and makes a probe's default output the +same kind of thing everywhere: what was observed, with interpretation as a separate, optional layer. + +The recommendation had assumed each new row would carry the obligations the topology row carries -- +asserted values, invariants and shape -- which is what made A look expensive. The engineer rejected +that premise rather than the cost: "I don't necessarily see these as 'contractual'. Probes produce +what they produce. If we retire a probe or if it measures something different over time, we need to +be honest and give it a new name/identifier but I don't know if we need to think about this as +formally as being a contract." And: "these probes are meant as somewhat informal means to validate +platform behaviors. They are not a formal validation scheme." The "I don't know if" is recorded as +said; what is settled is that a new row does not inherit the topology row's machinery, and that a +change of meaning is a change of name. + +### How `M8` applied it + +**Floats.** The queue probe's first row needed per-operation figures, and the row writer's `Value` +has no float, with a recorded reason. Adding one was the obvious move and was not made: the +per-operation figure is the prose's division of an elapsed time by a push count, so under rule A it +is a reading rather than the observation. The row publishes the elapsed time, recovered exactly from +the stored figure, and every later timing row followed. Where a measurement had kept only derived +figures -- `core_affinity` stored batch depths as `ITEMS / max(1, reads)`, which no division can +undo at zero -- the raw values were added at the source in `windows-placement-probe`. + +**The cancel-io probe.** It had been excluded from the census because `Command::output` waits on a +child without limit and its subject is a call that can fail to return. Excluding it again would have +left the rule unenforced for exactly the probe least likely to be run by hand. The census instead +runs every probe under a deadline, reading output as it arrives, and for this one probe stops at +its row rather than waiting on an exit its own finding says may not come. The report streams line +by line (`M1`), which is what lets the row arrive before `main` returns. diff --git a/crates/windows-platform-probes/PLANS.md b/crates/windows-platform-probes/PLANS.md index 6844e85fe..60c365ba8 100644 --- a/crates/windows-platform-probes/PLANS.md +++ b/crates/windows-platform-probes/PLANS.md @@ -6,5 +6,4 @@ Completed plans are in [COMPLETED-PLANS.md](COMPLETED-PLANS.md). | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| -| [CHECKLIST.md](CHECKLIST.md) | in progress | M7: the `request_cost` probe stops capturing, after the request crate's shared handles (M7.1); **done and archived**. M6: rewrite the `request_cost` probe against the namespace request crate's new path design (M6.1); **done and archived**. M1: stream a probe's report as it is measured; **done and archived**. The sink USED TO buffer each report into a `String`, so a termination that did not unwind -- Ctrl-C, or an abort during unwinding -- discarded it, where the line-by-line printing it replaced kept it; that cost most on `probe-cancel-io`, which runs about twenty seconds precisely when the wedge it hunts for occurs. Every probe now writes into the sink as it measures, through a `fmt::Write` adapter, and completed lines reach `Stdout` via `LineSink`. M2: check correspondence between a report's parts rather than each part alone, after a pull-request review found the renderer calling a state a bug while the verdict certified the same run as `agree`; complete -- the oracle, the renderer binding, the real-host test, the derived fact set, the partitioning discriminator and the shape corpus all landed, and its ten unrelated leftovers were re-sequenced into M4 and M5. M3: make the encoded row the contract and stop checking the prose against it. Re-reading M2's own evidence showed both defects that motivated the oracle were defects in the ROW -- and that the row published its three diagnostic lists as bare counts, so a survey could not tell a probe self-bug from host flakiness. The row gets the facts and the invariants; the prose gets review. **Complete and archived, ten items.** The three fields publish arrays of OBJECTS, each carrying a stable `code` plus the values its variant holds -- `{"code":"partitioning_summary_missing","level":9}` rather than the bare code of the superseded M3.1 form -- and each key's value SHAPE is now declared beside its name in `MEASURED_ROW_SHAPES`; the surviving correspondences became invariants over the observation rather than over two renderings; the row is emitted from a typed value through one writer with total escaping; the prose oracle and its parsers are gone, and no test extracts structured data from prose anywhere in the crate. The last four items came from reviews: three instruments asserted less than their names claimed, `BlockingState::ALL` was a census the compiler did not check while a doc comment said it did, and the hand-written JSON check was measured against a real parser over 1807 generated corruptions -- 159 disagreements, every one a false ACCEPT -- then replaced by `serde_json`, after which the remaining hand-written string scanners were deleted. M4 holds the four carried-over items M3 gated, now unblocked and re-scoped; M5 the six that nothing gates, which may be pulled forward at any time. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-buffered-report), [#d-encoded-row-is-the-contract](DESIGN-NOTES.md#d-encoded-row-is-the-contract), [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md#d-correspondence-failures) | | [../../CHECKLIST-thread-ambient.md](../../CHECKLIST-thread-ambient.md) | in progress | M27: create the crate, migrate this session's probes into it under the three-tier scheme, and queue migration of the nine earlier measurements that still live only in git-ignored scratch. | [DESIGN-NOTES.md](DESIGN-NOTES.md) | diff --git a/crates/windows-platform-probes/build.rs b/crates/windows-platform-probes/build.rs index 059632304..53e370e50 100644 --- a/crates/windows-platform-probes/build.rs +++ b/crates/windows-platform-probes/build.rs @@ -14,8 +14,27 @@ //! opt every probe in this crate into long paths, silently changing what all //! the others measure -- stated without a count on purpose, so it stays true as //! probes are added. +//! +//! It also stamps the build's identity, so a report can name the build that +//! produced it (`M4.8`). The stamping is `windows-placement-probe`'s, included +//! rather than copied: its staleness rules were each found by measurement, and +//! a second copy is one the next fix would miss. + +include!("../windows-placement-probe/build/stamp.rs"); fn main() { + // CI sets `PLATFORM_PROBES_COMMIT` and `PLATFORM_PROBES_SOURCE`; the crate + // reads the stamps through `build_identity::this_build`. + stamp_build_identity("PLATFORM_PROBES"); + // Emitting any `rerun-if-changed` replaces cargo's default of watching the + // whole package, so the package's own files are named, as + // `windows-placement-probe`'s script names its own -- and the included file, + // which lives in that crate. + println!("cargo::rerun-if-changed=src"); + println!("cargo::rerun-if-changed=Cargo.toml"); + println!("cargo::rerun-if-changed=build.rs"); + println!("cargo::rerun-if-changed=../windows-placement-probe/build/stamp.rs"); + // Declared unconditionally, because the cfg's *absence* is as meaningful as // its presence and an undeclared name would warn under `unexpected_cfgs`. println!("cargo::rustc-check-cfg=cfg(long_path_manifest_embedded)"); diff --git a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/README.md b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/README.md index d824500af..612202b25 100644 --- a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/README.md +++ b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/README.md @@ -21,6 +21,7 @@ each is labelled with the instrument that produced it. | Host | `x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16]` | | Profile | release | | Sampling | 50,000 pushes per producer, median of 5 repetitions, one untimed warmup pass | +| Order | each configuration's passes back to back, the only order the probe had then; `--order sequential` retakes it since `M4.4` made interleaving the default | | Runs | 3 whole-probe invocations, in [run1.txt](run1.txt), [run2.txt](run2.txt), [run3.txt](run3.txt) | | Instrument | `probe-queue-contention`, built from `68198359` (the commit that made the consumer drain once before announcing readiness) | | Taken | 2026-09-16 18:12 UTC-04:00 | diff --git a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/isolated.js b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/isolated.js index 1c523a219..ad0db2de1 100644 --- a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/isolated.js +++ b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/isolated.js @@ -53,8 +53,9 @@ if (files.length === 0) { // be refused -- possible in principle, since the figures are rounded, and // vanishingly unlikely across this many of them. The refusal is loud and // diagnosable; accepting a duplicated run would silently fabricate agreement, - // which is the worse of the two. `M4.8` replaces the heuristic with a real - // identity once the report carries one. + // which is the worse of the two. The build identity `M4.8` added does not + // replace this: it names the build, which every run in a capture shares, and + // only a per-invocation identifier would tell two runs apart. const seen = new Map(); for (const file of files) { const digest = crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); @@ -93,21 +94,31 @@ function attribution(text, where) { // Included because it can differ while the host banner does not: an affinity // mask changes how many processors the process may use without changing the // machine it names, and producer counts are read against that number. - return [line("host:"), profile, line("sampling:"), line("processors available to this process:")].join( - " | ", - ); + // The build that produced the run (`M4.8`). Optional, because runs taken + // before the probe printed it have none -- but present or absent it must + // agree across the inputs like the rest, so a set mixing two builds, or a + // stamped run with an unstamped one, is refused. + const build = text.split(/\r?\n/).find((l) => l.startsWith("build:")); + return [ + line("host:"), + build === undefined ? "build: (not stamped)" : build.trim(), + profile, + line("sampling:"), + line("processors available to this process:"), + ].join(" | "); } function requireOneConfiguration(entries) { - // **What this cannot check.** The report carries no build identity, so two - // runs of DIFFERENT probe commits on one host under one profile agree here. - // That matters most for exactly this capture: `M4.3` changed the drained - // procedure, so a pre-handshake and a post-handshake run would pass and have - // their medians combined as though one procedure produced both. The - // instrument commit is asserted by the capture README, which is a claim by - // the person who took the capture rather than something these scripts verify. - // Closing it needs the probe to stamp its own build identity into the report; - // `M4.8` in CHECKLIST.md owns that. + // **The build is part of what must agree, since `M4.8`.** Before it, two runs + // of DIFFERENT probe commits on one host under one profile agreed here, and a + // pre-handshake and a post-handshake run would have had their medians + // combined as though one procedure produced both. + // + // **What this still cannot check.** A DIRTY build names its commit, not its + // tree, so two dirty builds at one commit agree here while their sources may + // differ; and runs from before the probe printed a build agree with each + // other as "not stamped". For both, the capture README is still the only + // record of what was built. const first = entries[0]; for (const entry of entries.slice(1)) { if (entry.attribution !== first.attribution) { diff --git a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/summarise.js b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/summarise.js index ddbc3baca..42d53ea3a 100644 --- a/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/summarise.js +++ b/crates/windows-platform-probes/captures/2026-09-16-drained-handshake/summarise.js @@ -221,8 +221,9 @@ if (paths.length === 0) { // be refused -- possible in principle, since the figures are rounded, and // vanishingly unlikely across this many of them. The refusal is loud and // diagnosable; accepting a duplicated run would silently fabricate agreement, - // which is the worse of the two. `M4.8` replaces the heuristic with a real - // identity once the report carries one. + // which is the worse of the two. The build identity `M4.8` added does not + // replace this: it names the build, which every run in a capture shares, and + // only a per-invocation identifier would tell two runs apart. const seen = new Map(); for (const path of paths) { const digest = crypto.createHash("sha256").update(fs.readFileSync(path)).digest("hex"); @@ -260,21 +261,31 @@ function attribution(text, where) { // Included because it can differ while the host banner does not: an affinity // mask changes how many processors the process may use without changing the // machine it names, and producer counts are read against that number. - return [line("host:"), profile, line("sampling:"), line("processors available to this process:")].join( - " | ", - ); + // The build that produced the run (`M4.8`). Optional, because runs taken + // before the probe printed it have none -- but present or absent it must + // agree across the inputs like the rest, so a set mixing two builds, or a + // stamped run with an unstamped one, is refused. + const build = text.split(/\r?\n/).find((l) => l.startsWith("build:")); + return [ + line("host:"), + build === undefined ? "build: (not stamped)" : build.trim(), + profile, + line("sampling:"), + line("processors available to this process:"), + ].join(" | "); } function requireOneConfiguration(entries) { - // **What this cannot check.** The report carries no build identity, so two - // runs of DIFFERENT probe commits on one host under one profile agree here. - // That matters most for exactly this capture: `M4.3` changed the drained - // procedure, so a pre-handshake and a post-handshake run would pass and have - // their medians combined as though one procedure produced both. The - // instrument commit is asserted by the capture README, which is a claim by - // the person who took the capture rather than something these scripts verify. - // Closing it needs the probe to stamp its own build identity into the report; - // `M4.8` in CHECKLIST.md owns that. + // **The build is part of what must agree, since `M4.8`.** Before it, two runs + // of DIFFERENT probe commits on one host under one profile agreed here, and a + // pre-handshake and a post-handshake run would have had their medians + // combined as though one procedure produced both. + // + // **What this still cannot check.** A DIRTY build names its commit, not its + // tree, so two dirty builds at one commit agree here while their sources may + // differ; and runs from before the probe printed a build agree with each + // other as "not stamped". For both, the capture README is still the only + // record of what was built. const first = entries[0]; for (const entry of entries.slice(1)) { if (entry.attribution !== first.attribution) { diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/README.md b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/README.md new file mode 100644 index 000000000..e2af4278d --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/README.md @@ -0,0 +1,60 @@ +# Queue contention, interleaved against sequential + +Fourteen whole-probe invocations taken to answer one question: when the passes of +every configuration at a producer count are interleaved, does the same-code control +come out narrower than when each configuration's passes run back to back? + +The control is `reserving_mpsc` against `reserving(32/32)`: the same code measured +twice in one run. Back to back, its two halves are measured several configurations +apart. Interleaved, every configuration takes one pass per round, so both halves' +passes fall within the same rounds. The decision is +[The queue probe interleaves its passes](../../DESIGN-NOTES.md#d-interleaved-order). + +## Attribution + +| | | +|---|---| +| Host | `x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16]` | +| Profile | release | +| Sampling | 50,000 pushes per producer, median of 5 repetitions, one untimed warmup pass | +| Order | 7 runs `--order sequential`, 7 runs `--order interleaved`, alternating, sequential first | +| Runs | [sequential-run1.txt](sequential-run1.txt) to `sequential-run7.txt`, [interleaved-run1.txt](interleaved-run1.txt) to `interleaved-run7.txt` | +| Instrument | `probe-queue-contention`, built from the source the commit adding this directory carries; the only source change after the runs is test-only | +| Taken | 2026-10-19, 15:26 to 15:42 -04:00 | + +The host banner is the one the +[2026-09-16 capture](../2026-09-16-drained-handshake/README.md) carries, so the two +are the same machine; nothing here says anything about any other hardware, and +`numa[16]` is a single node holding all sixteen processors. + +**The runs alternate orders deliberately.** Seven sequential runs followed by seven +interleaved ones would put any drift across the quarter hour between the two +batches, and that drift would read as a difference between the orders. + +## Reading it + +[summarise.js](summarise.js) and [isolated.js](isolated.js) are byte-for-byte copies +of the 2026-09-16 capture's scripts, which parse this report format unchanged. Their +outputs, per order: + +| | sequential | interleaved | +|---|---|---| +| isolated | [sequential-isolated.txt](sequential-isolated.txt) | [interleaved-isolated.txt](interleaved-isolated.txt) | +| drained | [sequential-drained.txt](sequential-drained.txt) | [interleaved-drained.txt](interleaved-drained.txt) | + +regenerated from this directory with, for each order: + +``` +node isolated.js -run1.txt ... -run7.txt +node summarise.js -run1.txt ... -run7.txt +``` + +Each script requires every input to carry the same `sampling:` line, and the order +is part of that line, so the two orders cannot be summarised together by accident. + +**What the runs showed, per producer count.** In the isolated regime, the control's +observed range across the seven runs was narrower interleaved than sequential at 1, +2, 16 and 32 producers, and about as wide at 4 and 8. In the drained regime the +comparison went both ways across producer counts. Each range is seven observations, +which is a range and not a band, so these say what these fourteen runs did, on this +host, and not what a fresh pair of sweeps would do. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-drained.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-drained.txt new file mode 100644 index 000000000..3570cbb58 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-drained.txt @@ -0,0 +1,16 @@ +runs: 7 + +drained, per producer count: the same-code control's observed range, then +each layout's median ratio against 32/32, across runs. + +producers control(n) 16/48 8/56 64/64 + 1 0.93-1.07(7) 1.14x 1.04x 1.08x + 2 0.97-1.04(7) 1.04x 1.01x 1.02x + 4 0.94-1.04(7) 0.98x 0.99x 1.06x + 8 0.94-1.05(7) 1.00x 0.99x 1.08x + 16 0.80-1.15(7) 1.03x 0.94x 0.87x + 32 0.78-1.14(7) 0.97x 0.99x 0.83x + +control observations: 42 across 6 producer counts, 0.78x to 1.15x pooled +Pooled only to show the spread; it is not a band to judge a median against, +for the reason recorded in this script beside the table above. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-isolated.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-isolated.txt new file mode 100644 index 000000000..9b6a97e21 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-isolated.txt @@ -0,0 +1,20 @@ +isolated regime, 7 run(s): interleaved-run1.txt, interleaved-run2.txt, interleaved-run3.txt, interleaved-run4.txt, interleaved-run5.txt, interleaved-run6.txt, interleaved-run7.txt +each layout against reserving(32/32); control is reserving_mpsc against reserving(32/32) +median of the per-run ratios, with the observed range beside it + +producers control (16/48) (8/56) (64/64) +1 1.00x [0.98-1.07] 1.00x [0.96-1.06] 1.04x [1.00-1.06] 1.39x [1.36-1.44] +2 1.01x [0.94-1.04] 1.01x [0.88-1.05] 0.99x [0.98-1.07] 1.17x [1.05-1.22] +4 1.03x [0.91-1.10] 0.98x [0.88-1.07] 0.94x [0.87-1.03] 1.25x [1.13-1.39] +8 1.03x [0.94-1.12] 1.02x [0.98-1.26] 1.07x [0.95-1.31] 1.98x [1.57-2.22] +16 1.01x [0.99-1.06] 1.30x [1.22-1.36] 1.39x [1.26-1.42] 4.00x [3.13-4.36] +32 0.98x [0.94-1.00] 1.25x [1.19-1.31] 1.26x [1.21-1.37] 4.16x [3.45-4.49] + +where every run sat above the control's whole observed range: + reserving(16/48) 16, 32 producers + reserving(8/56) 16, 32 producers + reserving(64/64) 1, 2, 4, 8, 16, 32 producers + +The control's range here is 7 observation(s) per count, which is not +a band. This reports what these runs did; it does not establish that a fresh +run would land the same way. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run1.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run1.txt new file mode 100644 index 000000000..b7e63d6fe --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run1.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433275563 0 2.1-4.7 2.25x +slotwise_mpsc 1 6.5 153374233 0 6.2-7.0 1.12x +reserving_mpsc 1 5.4 184638109 0 5.4-5.8 1.07x +permit_mpsc 1 8.3 120714631 0 8.0-8.7 1.09x +reserving(32/32) 1 5.4 183958793 0 5.4-6.7 1.24x +reserving(16/48) 1 5.7 175131349 0 5.3-6.1 1.14x +reserving(8/56) 1 5.4 186567164 0 5.4-5.8 1.08x +reserving(64/64) 1 7.5 134156158 0 7.4-8.1 1.09x +baseline_fetch_add 2 10.9 91802075 0 10.7-12.0 1.13x +slotwise_mpsc 2 53.5 18688794 0 48.2-55.3 1.15x +reserving_mpsc 2 32.2 31053007 0 30.5-37.1 1.22x +permit_mpsc 2 42.8 23362848 0 33.1-43.5 1.31x +reserving(32/32) 2 31.3 31919308 0 27.5-35.9 1.30x +reserving(16/48) 2 32.7 30564215 0 30.1-35.7 1.19x +reserving(8/56) 2 33.4 29953572 0 18.6-34.3 1.84x +reserving(64/64) 2 37.7 26546323 0 35.8-39.7 1.11x +baseline_fetch_add 4 14.2 70400225 0 11.5-15.2 1.32x +slotwise_mpsc 4 88.4 11308380 0 87.3-93.5 1.07x +reserving_mpsc 4 39.0 25642341 0 32.6-41.3 1.27x +permit_mpsc 4 32.3 30969340 0 30.4-34.2 1.12x +reserving(32/32) 4 37.4 26764804 0 32.5-40.0 1.23x +reserving(16/48) 4 35.8 27904121 0 33.0-37.8 1.14x +reserving(8/56) 4 35.0 28531077 0 29.6-35.9 1.21x +reserving(64/64) 4 50.5 19794925 0 47.2-65.7 1.39x +baseline_fetch_add 8 14.7 67945168 0 14.0-15.2 1.08x +slotwise_mpsc 8 142.5 7016436 0 135.1-148.9 1.10x +reserving_mpsc 8 39.0 25644642 0 33.8-40.7 1.20x +permit_mpsc 8 26.2 38192356 0 25.5-27.3 1.07x +reserving(32/32) 8 38.1 26233809 0 35.2-38.8 1.10x +reserving(16/48) 8 37.7 26551433 0 35.3-40.5 1.15x +reserving(8/56) 8 42.0 23794935 0 33.4-42.2 1.26x +reserving(64/64) 8 60.1 16640943 0 59.5-64.9 1.09x +baseline_fetch_add 16 14.6 68678960 0 14.3-14.9 1.04x +slotwise_mpsc 16 223.0 4485034 0 215.8-269.8 1.25x +reserving_mpsc 16 54.1 18478823 0 50.9-61.1 1.20x +permit_mpsc 16 20.6 48526022 0 19.9-28.2 1.42x +reserving(32/32) 16 51.1 19578715 0 47.3-57.0 1.21x +reserving(16/48) 16 69.4 14414259 0 63.6-72.7 1.14x +reserving(8/56) 16 72.7 13746484 0 63.3-75.7 1.20x +reserving(64/64) 16 174.4 5733152 0 153.7-211.2 1.37x +baseline_fetch_add 32 15.0 66748155 0 14.9-20.1 1.35x +slotwise_mpsc 32 235.7 4242249 0 231.6-265.9 1.15x +reserving_mpsc 32 50.8 19686420 0 45.8-54.4 1.19x +permit_mpsc 32 20.9 47958612 0 20.0-21.5 1.07x +reserving(32/32) 32 52.2 19158579 0 50.7-54.6 1.08x +reserving(16/48) 32 65.5 15266637 0 64.2-72.7 1.13x +reserving(8/56) 32 65.7 15212668 0 64.5-73.8 1.14x +reserving(64/64) 32 217.2 4604914 0 196.3-252.4 1.29x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.4 96116878 11 9.7-10.6 1.10x +reserving_mpsc 1 25.0 40076948 1468 23.1-25.4 1.10x +permit_mpsc 1 58.0 17243163 398 45.9-61.2 1.33x +reserving(32/32) 1 23.5 42560436 1070 22.2-25.6 1.15x +reserving(16/48) 1 26.5 37784327 1379 23.7-29.4 1.24x +reserving(8/56) 1 26.6 37571386 2060 25.1-28.5 1.13x +reserving(64/64) 1 26.9 37218997 581 26.1-27.8 1.07x +slotwise_mpsc 2 63.0 15873520 8239 60.4-74.1 1.23x +reserving_mpsc 2 63.6 15721540 1606 58.8-66.2 1.13x +permit_mpsc 2 53.1 18836294 454 51.0-54.2 1.06x +reserving(32/32) 2 63.4 15769140 521 59.9-68.0 1.14x +reserving(16/48) 2 67.6 14801001 1323 55.9-69.8 1.25x +reserving(8/56) 2 67.4 14828215 1050 41.4-69.2 1.67x +reserving(64/64) 2 67.6 14796620 3238 64.7-71.6 1.11x +slotwise_mpsc 4 109.0 9174354 0 106.9-132.0 1.24x +reserving_mpsc 4 94.3 10600857 2398 91.7-100.1 1.09x +permit_mpsc 4 76.5 13066686 71510 63.9-98.0 1.53x +reserving(32/32) 4 100.8 9921176 4792 92.5-104.5 1.13x +reserving(16/48) 4 95.5 10475098 0 84.4-100.7 1.19x +reserving(8/56) 4 98.8 10120382 1312 84.6-104.8 1.24x +reserving(64/64) 4 100.5 9953319 60 96.5-108.1 1.12x +slotwise_mpsc 8 153.4 6519425 0 138.6-159.8 1.15x +reserving_mpsc 8 170.2 5876425 327 161.4-187.7 1.16x +permit_mpsc 8 121.0 8265214 437463 108.4-152.7 1.41x +reserving(32/32) 8 174.2 5741088 4182 157.2-184.2 1.17x +reserving(16/48) 8 175.4 5701279 2551 161.4-182.5 1.13x +reserving(8/56) 8 167.5 5969561 40082 164.4-172.0 1.05x +reserving(64/64) 8 188.0 5319361 1624 161.1-205.3 1.27x +slotwise_mpsc 16 290.4 3443005 1128565 255.6-331.5 1.30x +reserving_mpsc 16 324.5 3081898 2241447 291.3-405.4 1.39x +permit_mpsc 16 280.4 3565938 3140882 171.4-308.2 1.80x +reserving(32/32) 16 324.2 3084657 2354025 228.1-333.0 1.46x +reserving(16/48) 16 332.9 3004007 2442183 243.6-439.5 1.80x +reserving(8/56) 16 279.9 3572580 1141252 273.2-329.3 1.21x +reserving(64/64) 16 281.8 3549227 1032087 250.3-317.5 1.27x +slotwise_mpsc 32 560.6 1783954 16482897 545.2-735.7 1.35x +reserving_mpsc 32 813.9 1228724 28145996 511.8-937.4 1.83x +permit_mpsc 32 412.3 2425560 9851178 330.0-603.0 1.83x +reserving(32/32) 32 713.8 1400889 27477912 489.6-777.9 1.59x +reserving(16/48) 32 690.0 1449363 27208430 552.0-921.3 1.67x +reserving(8/56) 32 821.8 1216904 32041740 533.5-886.7 1.66x +reserving(64/64) 32 592.0 1689308 16186789 447.0-676.8 1.51x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.14] 0.17x [0.15-0.19] 0.19x [0.18-0.26] 0.21x [0.17-0.44] + 4 0.07x [0.07-0.08] 0.14x [0.13-0.18] 0.26x [0.23-0.29] 0.16x [0.14-0.40] + 8 0.05x [0.04-0.05] 0.14x [0.13-0.17] 0.32x [0.29-0.34] 0.16x [0.14-0.33] + 16 0.03x [0.02-0.03] 0.10x [0.09-0.11] 0.40x [0.28-0.44] 0.16x [0.14-0.33] + 32 0.03x [0.02-0.03] 0.11x [0.10-0.13] 0.40x [0.37-0.43] 0.15x [0.10-0.31] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.4 25.0 2.40x [2.18-2.63] 58.0 2.32x [1.80-2.65] + 2 63.0 63.6 1.01x [0.79-1.10] 53.1 0.83x [0.77-0.92] + 4 109.0 94.3 0.87x [0.69-0.94] 76.5 0.81x [0.64-1.07] + 8 153.4 170.2 1.11x [1.01-1.35] 121.0 0.71x [0.58-0.95] + 16 290.4 324.5 1.12x [0.88-1.59] 280.4 0.86x [0.42-1.06] + 32 560.6 813.9 1.45x [0.70-1.72] 412.3 0.51x [0.35-1.18] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.7 5.4 7.5 1.05x [0.79-1.13] 0.99x [0.80-1.06] 1.37x [1.11-1.50] + 2 31.3 32.7 33.4 37.7 1.04x [0.84-1.30] 1.07x [0.52-1.25] 1.20x [1.00-1.44] + 4 37.4 35.8 35.0 50.5 0.96x [0.82-1.16] 0.94x [0.74-1.11] 1.35x [1.18-2.02] + 8 38.1 37.7 42.0 60.1 0.99x [0.91-1.15] 1.10x [0.86-1.20] 1.58x [1.53-1.84] + 16 51.1 69.4 72.7 174.4 1.36x [1.12-1.54] 1.42x [1.11-1.60] 3.42x [2.70-4.47] + 32 52.2 65.5 65.7 217.2 1.25x [1.18-1.44] 1.26x [1.18-1.46] 4.16x [3.59-4.98] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 23.5 26.5 26.6 26.9 1.13x [0.93-1.32] 1.13x [0.98-1.28] 1.14x [1.02-1.25] + 2 63.4 67.6 67.4 67.6 1.07x [0.82-1.17] 1.06x [0.61-1.16] 1.07x [0.95-1.20] + 4 100.8 95.5 98.8 100.5 0.95x [0.81-1.09] 0.98x [0.81-1.13] 1.00x [0.92-1.17] + 8 174.2 175.4 167.5 188.0 1.01x [0.88-1.16] 0.96x [0.89-1.09] 1.08x [0.87-1.31] + 16 324.2 332.9 279.9 281.8 1.03x [0.73-1.93] 0.86x [0.82-1.44] 0.87x [0.75-1.39] + 32 713.8 690.0 821.8 592.0 0.97x [0.71-1.88] 1.15x [0.69-1.81] 0.83x [0.57-1.38] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run2.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run2.txt new file mode 100644 index 000000000..82686381d --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run2.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433275563 0 2.3-2.6 1.14x +slotwise_mpsc 1 6.3 158328056 0 6.3-6.4 1.02x +reserving_mpsc 1 5.4 183891136 0 5.4-5.8 1.07x +permit_mpsc 1 7.9 127064803 0 7.9-8.2 1.05x +reserving(32/32) 1 5.4 184501845 0 5.4-6.6 1.23x +reserving(16/48) 1 5.4 186636805 0 5.3-5.4 1.00x +reserving(8/56) 1 5.6 178507676 0 5.4-5.7 1.07x +reserving(64/64) 1 7.5 134048257 0 7.5-7.5 1.00x +baseline_fetch_add 2 10.7 93327112 0 8.3-12.4 1.49x +slotwise_mpsc 2 52.3 19109863 0 47.7-60.7 1.27x +reserving_mpsc 2 33.9 29512454 0 33.3-36.2 1.09x +permit_mpsc 2 41.1 24323207 0 35.1-43.6 1.24x +reserving(32/32) 2 33.5 29824928 0 31.1-35.0 1.12x +reserving(16/48) 2 33.2 30091478 0 24.3-35.1 1.44x +reserving(8/56) 2 32.7 30561413 0 31.2-32.9 1.06x +reserving(64/64) 2 39.1 25564986 0 35.4-39.4 1.11x +baseline_fetch_add 4 14.4 69521691 0 13.6-15.0 1.11x +slotwise_mpsc 4 87.4 11435825 0 86.9-88.8 1.02x +reserving_mpsc 4 36.7 27279547 0 34.5-41.9 1.21x +permit_mpsc 4 32.2 31079065 0 31.1-32.4 1.04x +reserving(32/32) 4 40.5 24678562 0 37.2-42.0 1.13x +reserving(16/48) 4 37.2 26873413 0 34.8-38.3 1.10x +reserving(8/56) 4 36.8 27209404 0 32.7-40.2 1.23x +reserving(64/64) 4 46.8 21354061 0 40.2-60.8 1.51x +baseline_fetch_add 8 14.7 67858718 0 14.2-15.7 1.11x +slotwise_mpsc 8 140.4 7122444 0 137.1-147.2 1.07x +reserving_mpsc 8 38.1 26261194 0 34.5-39.7 1.15x +permit_mpsc 8 26.2 38228126 0 25.3-27.2 1.08x +reserving(32/32) 8 40.5 24719587 0 35.0-44.4 1.27x +reserving(16/48) 8 41.4 24130692 0 34.7-54.4 1.57x +reserving(8/56) 8 43.2 23166381 0 38.2-52.3 1.37x +reserving(64/64) 8 80.3 12457101 0 56.1-88.1 1.57x +baseline_fetch_add 16 14.8 67765025 0 14.7-15.8 1.08x +slotwise_mpsc 16 207.9 4810980 0 197.9-255.3 1.29x +reserving_mpsc 16 49.4 20260910 0 46.5-53.3 1.15x +permit_mpsc 16 22.3 44847071 0 20.0-36.4 1.82x +reserving(32/32) 16 50.1 19955997 0 46.0-57.4 1.25x +reserving(16/48) 16 61.2 16343441 0 53.4-66.4 1.24x +reserving(8/56) 16 69.8 14324416 0 55.6-74.4 1.34x +reserving(64/64) 16 156.9 6373796 0 93.6-192.3 2.05x +baseline_fetch_add 32 15.4 65029548 0 14.7-16.2 1.11x +slotwise_mpsc 32 222.1 4502647 0 195.8-241.4 1.23x +reserving_mpsc 32 48.0 20815526 0 46.1-51.2 1.11x +permit_mpsc 32 22.2 44961881 0 21.9-22.8 1.04x +reserving(32/32) 32 48.1 20803104 0 45.3-55.2 1.22x +reserving(16/48) 32 60.1 16639991 0 53.6-65.6 1.22x +reserving(8/56) 32 60.8 16452019 0 54.0-71.2 1.32x +reserving(64/64) 32 189.5 5277459 0 112.2-232.4 2.07x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.2 98077678 366 8.6-10.5 1.21x +reserving_mpsc 1 24.1 41507554 0 23.5-26.9 1.14x +permit_mpsc 1 59.8 16734721 1466 57.8-63.5 1.10x +reserving(32/32) 1 24.4 41003772 1716 23.3-26.0 1.12x +reserving(16/48) 1 26.8 37366415 2187 23.8-28.8 1.21x +reserving(8/56) 1 26.0 38390663 1022 25.2-26.8 1.06x +reserving(64/64) 1 26.4 37927634 288 26.2-27.4 1.05x +slotwise_mpsc 2 71.1 14060742 0 59.5-78.3 1.31x +reserving_mpsc 2 65.2 15332485 3655 60.1-67.9 1.13x +permit_mpsc 2 54.8 18260838 3370 48.9-55.3 1.13x +reserving(32/32) 2 65.2 15328724 2707 62.7-68.2 1.09x +reserving(16/48) 2 66.1 15132256 84 62.7-68.7 1.10x +reserving(8/56) 2 63.7 15706724 597 44.3-66.5 1.50x +reserving(64/64) 2 68.7 14566006 26 57.7-72.1 1.25x +slotwise_mpsc 4 110.4 9060023 0 107.5-113.6 1.06x +reserving_mpsc 4 92.2 10843223 294 81.6-100.0 1.23x +permit_mpsc 4 75.8 13193482 73355 63.5-80.0 1.26x +reserving(32/32) 4 94.9 10541184 212 83.4-100.9 1.21x +reserving(16/48) 4 98.6 10138183 364 84.4-102.6 1.22x +reserving(8/56) 4 97.5 10252152 1493 91.8-102.1 1.11x +reserving(64/64) 4 102.5 9753671 186 93.7-113.6 1.21x +slotwise_mpsc 8 148.6 6731264 59 147.7-156.2 1.06x +reserving_mpsc 8 163.1 6130522 1547 157.1-174.0 1.11x +permit_mpsc 8 147.5 6779799 604611 120.9-151.8 1.26x +reserving(32/32) 8 162.1 6168185 634 157.0-180.4 1.15x +reserving(16/48) 8 161.4 6194328 2602 151.2-171.9 1.14x +reserving(8/56) 8 160.6 6224954 1492 157.9-175.4 1.11x +reserving(64/64) 8 181.6 5505107 0 176.6-187.1 1.06x +slotwise_mpsc 16 229.9 4349091 403686 209.8-267.2 1.27x +reserving_mpsc 16 272.9 3664475 1115768 260.9-434.0 1.66x +permit_mpsc 16 283.2 3530779 3215563 176.0-355.6 2.02x +reserving(32/32) 16 342.5 2920012 3364763 261.8-382.5 1.46x +reserving(16/48) 16 306.0 3267795 2500803 239.1-408.8 1.71x +reserving(8/56) 16 256.0 3906185 1210156 238.8-302.0 1.26x +reserving(64/64) 16 273.2 3660804 1006557 207.1-286.8 1.38x +slotwise_mpsc 32 663.8 1506462 21910882 482.5-784.3 1.63x +reserving_mpsc 32 654.8 1527179 21187146 613.9-754.4 1.23x +permit_mpsc 32 503.5 1986176 11917298 332.4-542.9 1.63x +reserving(32/32) 32 712.1 1404360 28618060 544.3-945.9 1.74x +reserving(16/48) 32 770.8 1297323 32984734 710.2-893.6 1.26x +reserving(8/56) 32 703.2 1422026 24524373 626.1-800.7 1.28x +reserving(64/64) 32 555.5 1800097 15182282 432.3-785.3 1.82x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.10-0.13] 0.16x [0.15-0.17] 0.19x [0.18-0.23] 0.22x [0.19-0.32] + 4 0.07x [0.07-0.07] 0.15x [0.13-0.17] 0.24x [0.24-0.26] 0.16x [0.15-0.19] + 8 0.04x [0.04-0.05] 0.14x [0.14-0.17] 0.30x [0.29-0.33] 0.16x [0.15-0.19] + 16 0.03x [0.02-0.03] 0.11x [0.10-0.12] 0.35x [0.22-0.41] 0.16x [0.15-0.18] + 32 0.03x [0.03-0.03] 0.11x [0.11-0.13] 0.35x [0.34-0.38] 0.15x [0.14-0.18] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.2 24.1 2.36x [2.24-3.11] 59.8 2.48x [2.15-2.70] + 2 71.1 65.2 0.92x [0.77-1.14] 54.8 0.84x [0.72-0.92] + 4 110.4 92.2 0.84x [0.72-0.93] 75.8 0.82x [0.63-0.98] + 8 148.6 163.1 1.10x [1.01-1.18] 147.5 0.90x [0.69-0.97] + 16 229.9 272.9 1.19x [0.98-2.07] 283.2 1.04x [0.41-1.36] + 32 663.8 654.8 0.99x [0.78-1.56] 503.5 0.77x [0.44-0.88] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.6 7.5 0.99x [0.81-0.99] 1.03x [0.81-1.05] 1.38x [1.12-1.38] + 2 33.5 33.2 32.7 39.1 0.99x [0.70-1.13] 0.98x [0.89-1.06] 1.17x [1.01-1.26] + 4 40.5 37.2 36.8 46.8 0.92x [0.83-1.03] 0.91x [0.78-1.08] 1.16x [0.96-1.63] + 8 40.5 41.4 43.2 80.3 1.02x [0.78-1.55] 1.07x [0.86-1.49] 1.98x [1.26-2.51] + 16 50.1 61.2 69.8 156.9 1.22x [0.93-1.44] 1.39x [0.97-1.62] 3.13x [1.63-4.18] + 32 48.1 60.1 60.8 189.5 1.25x [0.97-1.45] 1.26x [0.98-1.57] 3.94x [2.03-5.13] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 24.4 26.8 26.0 26.4 1.10x [0.92-1.23] 1.07x [0.97-1.15] 1.08x [1.01-1.18] + 2 65.2 66.1 63.7 68.7 1.01x [0.92-1.10] 0.98x [0.65-1.06] 1.05x [0.84-1.15] + 4 94.9 98.6 97.5 102.5 1.04x [0.84-1.23] 1.03x [0.91-1.22] 1.08x [0.93-1.36] + 8 162.1 161.4 160.6 181.6 1.00x [0.84-1.10] 0.99x [0.88-1.12] 1.12x [0.98-1.19] + 16 342.5 306.0 256.0 273.2 0.89x [0.62-1.56] 0.75x [0.62-1.15] 0.80x [0.54-1.10] + 32 712.1 770.8 703.2 555.5 1.08x [0.75-1.64] 0.99x [0.66-1.47] 0.78x [0.46-1.44] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run3.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run3.txt new file mode 100644 index 000000000..76cbab769 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run3.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432525952 0 2.3-2.5 1.09x +slotwise_mpsc 1 6.4 156641604 0 6.3-7.9 1.24x +reserving_mpsc 1 5.8 172176309 0 5.4-6.0 1.11x +permit_mpsc 1 7.9 126968004 0 7.9-8.0 1.02x +reserving(32/32) 1 5.4 184501845 0 5.4-7.4 1.36x +reserving(16/48) 1 5.4 186011905 0 5.3-6.7 1.25x +reserving(8/56) 1 5.4 186358554 0 5.3-5.6 1.04x +reserving(64/64) 1 7.5 134012329 0 7.5-8.1 1.08x +baseline_fetch_add 2 11.0 91240876 0 10.7-12.7 1.18x +slotwise_mpsc 2 53.3 18749766 0 46.0-58.8 1.28x +reserving_mpsc 2 34.8 28697698 0 32.3-36.6 1.13x +permit_mpsc 2 43.1 23180343 0 39.6-44.1 1.12x +reserving(32/32) 2 36.0 27812543 0 30.0-36.1 1.20x +reserving(16/48) 2 32.6 30708758 0 28.4-36.4 1.28x +reserving(8/56) 2 35.3 28349493 0 33.5-36.1 1.08x +reserving(64/64) 2 37.8 26474637 0 36.4-40.0 1.10x +baseline_fetch_add 4 13.3 75091988 0 13.1-14.4 1.09x +slotwise_mpsc 4 89.2 11204984 0 85.9-95.8 1.12x +reserving_mpsc 4 36.1 27722717 0 31.8-40.8 1.28x +permit_mpsc 4 32.1 31149737 0 31.4-32.6 1.04x +reserving(32/32) 4 39.5 25338908 0 34.4-40.3 1.17x +reserving(16/48) 4 34.9 28653295 0 31.9-37.6 1.18x +reserving(8/56) 4 36.7 27224589 0 33.6-37.0 1.10x +reserving(64/64) 4 49.4 20259935 0 40.5-53.8 1.33x +baseline_fetch_add 8 14.2 70456027 0 14.0-14.8 1.05x +slotwise_mpsc 8 143.0 6994707 0 129.0-151.2 1.17x +reserving_mpsc 8 39.4 25394407 0 36.2-61.2 1.69x +permit_mpsc 8 26.1 38366728 0 25.7-26.4 1.03x +reserving(32/32) 8 37.6 26574542 0 35.5-38.9 1.10x +reserving(16/48) 8 37.7 26500947 0 35.1-40.0 1.14x +reserving(8/56) 8 36.2 27624309 0 33.4-59.6 1.79x +reserving(64/64) 8 71.4 14011686 0 51.0-86.3 1.69x +baseline_fetch_add 16 14.8 67482644 0 14.5-15.1 1.04x +slotwise_mpsc 16 221.9 4505948 0 215.8-272.0 1.26x +reserving_mpsc 16 51.0 19604239 0 50.2-56.1 1.12x +permit_mpsc 16 19.9 50285369 0 19.6-22.6 1.15x +reserving(32/32) 16 48.0 20814037 0 47.1-54.6 1.16x +reserving(16/48) 16 63.7 15690366 0 58.1-67.4 1.16x +reserving(8/56) 16 67.5 14810289 0 59.9-74.6 1.25x +reserving(64/64) 16 209.1 4781515 0 161.2-216.9 1.35x +baseline_fetch_add 32 15.0 66882362 0 14.8-15.2 1.03x +slotwise_mpsc 32 226.8 4409463 0 221.0-270.3 1.22x +reserving_mpsc 32 52.9 18920133 0 50.1-54.8 1.09x +permit_mpsc 32 20.7 48310054 0 19.7-22.4 1.14x +reserving(32/32) 32 54.0 18506438 0 50.1-61.1 1.22x +reserving(16/48) 32 64.0 15617130 0 61.1-70.9 1.16x +reserving(8/56) 32 65.9 15177026 0 63.0-72.3 1.15x +reserving(64/64) 32 242.1 4131294 0 225.0-247.1 1.10x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.9 91793648 14 9.9-11.6 1.17x +reserving_mpsc 1 26.9 37144343 316 23.9-29.4 1.23x +permit_mpsc 1 59.1 16915894 1155 58.6-64.7 1.10x +reserving(32/32) 1 25.6 39032006 815 22.7-26.8 1.18x +reserving(16/48) 1 29.6 33754135 2508 27.5-31.1 1.13x +reserving(8/56) 1 26.4 37858711 1334 25.9-27.3 1.05x +reserving(64/64) 1 28.4 35193918 795 27.4-29.9 1.09x +slotwise_mpsc 2 69.1 14478695 402 65.3-72.1 1.11x +reserving_mpsc 2 69.6 14362038 669 61.9-71.3 1.15x +permit_mpsc 2 52.3 19135460 19 49.4-55.1 1.12x +reserving(32/32) 2 66.9 14940537 11923 65.8-69.3 1.05x +reserving(16/48) 2 69.4 14401129 0 67.3-69.8 1.04x +reserving(8/56) 2 67.5 14818108 1134 60.2-68.9 1.15x +reserving(64/64) 2 67.7 14774104 298 66.4-73.3 1.10x +slotwise_mpsc 4 108.8 9191514 0 105.9-110.1 1.04x +reserving_mpsc 4 98.4 10163635 1411 94.8-99.7 1.05x +permit_mpsc 4 79.1 12646702 75874 78.0-86.7 1.11x +reserving(32/32) 4 95.4 10484268 0 91.5-100.4 1.10x +reserving(16/48) 4 94.7 10564459 817 89.2-100.2 1.12x +reserving(8/56) 4 95.6 10458555 0 93.8-99.2 1.06x +reserving(64/64) 4 100.7 9926396 0 98.2-112.1 1.14x +slotwise_mpsc 8 155.5 6431923 0 151.5-158.3 1.04x +reserving_mpsc 8 166.9 5993111 2299 164.0-179.4 1.09x +permit_mpsc 8 117.6 8501901 449212 103.2-157.2 1.52x +reserving(32/32) 8 166.3 6012018 0 158.1-179.7 1.14x +reserving(16/48) 8 160.2 6240844 0 156.1-176.0 1.13x +reserving(8/56) 8 164.3 6087956 2177 150.3-172.0 1.14x +reserving(64/64) 8 172.6 5794943 1022 158.8-190.0 1.20x +slotwise_mpsc 16 246.9 4050329 976892 199.1-333.3 1.67x +reserving_mpsc 16 288.8 3462524 1144648 266.5-349.0 1.31x +permit_mpsc 16 296.2 3376503 3388611 212.0-302.2 1.43x +reserving(32/32) 16 262.6 3808160 311950 230.4-314.8 1.37x +reserving(16/48) 16 326.4 3064034 3128980 261.9-363.6 1.39x +reserving(8/56) 16 247.7 4037478 625666 234.0-367.5 1.57x +reserving(64/64) 16 249.0 4015913 979231 213.6-371.9 1.74x +slotwise_mpsc 32 629.1 1589573 19164583 500.6-700.6 1.40x +reserving_mpsc 32 668.7 1495519 22990297 638.8-719.4 1.13x +permit_mpsc 32 458.1 2183042 11018407 381.9-541.5 1.42x +reserving(32/32) 32 681.6 1467135 26610146 478.6-761.5 1.59x +reserving(16/48) 32 708.1 1412158 27704493 590.2-810.1 1.37x +reserving(8/56) 32 525.2 1903908 15588219 372.0-801.9 2.16x +reserving(64/64) 32 670.1 1492354 19331076 502.8-815.4 1.62x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.17] 0.17x [0.15-0.19] 0.18x [0.18-0.20] 0.21x [0.18-0.23] + 4 0.07x [0.07-0.09] 0.16x [0.13-0.19] 0.25x [0.24-0.26] 0.17x [0.16-0.19] + 8 0.04x [0.04-0.06] 0.15x [0.09-0.17] 0.30x [0.30-0.31] 0.16x [0.16-0.18] + 16 0.03x [0.02-0.04] 0.11x [0.10-0.12] 0.40x [0.35-0.41] 0.16x [0.15-0.17] + 32 0.03x [0.02-0.04] 0.11x [0.10-0.12] 0.38x [0.35-0.41] 0.15x [0.15-0.17] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.9 26.9 2.47x [2.06-2.97] 59.1 2.20x [1.99-2.71] + 2 69.1 69.6 1.01x [0.86-1.09] 52.3 0.75x [0.69-0.89] + 4 108.8 98.4 0.90x [0.86-0.94] 79.1 0.80x [0.78-0.91] + 8 155.5 166.9 1.07x [1.04-1.18] 117.6 0.70x [0.58-0.96] + 16 246.9 288.8 1.17x [0.80-1.75] 296.2 1.03x [0.61-1.13] + 32 629.1 668.7 1.06x [0.91-1.44] 458.1 0.69x [0.53-0.85] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.4 7.5 0.99x [0.73-1.24] 0.99x [0.73-1.03] 1.38x [1.01-1.49] + 2 36.0 32.6 35.3 37.8 0.91x [0.79-1.21] 0.98x [0.93-1.20] 1.05x [1.01-1.33] + 4 39.5 34.9 36.7 49.4 0.88x [0.79-1.09] 0.93x [0.84-1.08] 1.25x [1.01-1.56] + 8 37.6 37.7 36.2 71.4 1.00x [0.90-1.13] 0.96x [0.86-1.68] 1.90x [1.31-2.43] + 16 48.0 63.7 67.5 209.1 1.33x [1.06-1.43] 1.41x [1.10-1.58] 4.35x [2.95-4.60] + 32 54.0 64.0 65.9 242.1 1.19x [1.00-1.41] 1.22x [1.03-1.44] 4.48x [3.68-4.93] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 25.6 29.6 26.4 28.4 1.16x [1.03-1.37] 1.03x [0.97-1.21] 1.11x [1.03-1.32] + 2 66.9 69.4 67.5 67.7 1.04x [0.97-1.06] 1.01x [0.87-1.05] 1.01x [0.96-1.11] + 4 95.4 94.7 95.6 100.7 0.99x [0.89-1.10] 1.00x [0.93-1.08] 1.06x [0.98-1.23] + 8 166.3 160.2 164.3 172.6 0.96x [0.87-1.11] 0.99x [0.84-1.09] 1.04x [0.88-1.20] + 16 262.6 326.4 247.7 249.0 1.24x [0.83-1.58] 0.94x [0.74-1.60] 0.95x [0.68-1.61] + 32 681.6 708.1 525.2 670.1 1.04x [0.78-1.69] 0.77x [0.49-1.68] 0.98x [0.66-1.70] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run4.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run4.txt new file mode 100644 index 000000000..03441635b --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run4.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432900433 0 2.3-2.9 1.25x +slotwise_mpsc 1 6.3 158127767 0 6.3-6.7 1.06x +reserving_mpsc 1 5.7 175070028 0 5.4-7.5 1.37x +permit_mpsc 1 7.9 127258844 0 7.9-8.4 1.07x +reserving(32/32) 1 5.4 184229919 0 5.4-6.1 1.12x +reserving(16/48) 1 5.4 186567164 0 5.3-5.9 1.09x +reserving(8/56) 1 5.6 179083095 0 5.3-5.7 1.06x +reserving(64/64) 1 7.8 128008193 0 7.5-8.1 1.09x +baseline_fetch_add 2 12.1 82440231 0 9.0-14.2 1.57x +slotwise_mpsc 2 54.7 18281201 0 47.2-58.7 1.24x +reserving_mpsc 2 35.1 28474615 0 32.4-37.5 1.16x +permit_mpsc 2 42.4 23578233 0 18.0-42.9 2.38x +reserving(32/32) 2 33.9 29533373 0 32.2-36.7 1.14x +reserving(16/48) 2 35.7 28035549 0 33.4-37.1 1.11x +reserving(8/56) 2 34.9 28633604 0 33.7-36.0 1.07x +reserving(64/64) 2 39.8 25102922 0 37.6-40.5 1.08x +baseline_fetch_add 4 15.0 66722269 0 13.3-15.3 1.15x +slotwise_mpsc 4 90.6 11042037 0 88.7-103.2 1.16x +reserving_mpsc 4 38.3 26085142 0 36.3-39.7 1.09x +permit_mpsc 4 31.7 31534301 0 30.9-32.9 1.06x +reserving(32/32) 4 34.8 28719539 0 32.3-39.7 1.23x +reserving(16/48) 4 37.3 26783801 0 30.7-41.7 1.36x +reserving(8/56) 4 34.1 29342723 0 32.7-40.4 1.24x +reserving(64/64) 4 46.9 21320143 0 42.4-49.5 1.17x +baseline_fetch_add 8 15.4 65095691 0 14.4-16.0 1.11x +slotwise_mpsc 8 136.6 7320912 0 131.8-145.6 1.11x +reserving_mpsc 8 42.7 23432786 0 39.9-56.5 1.42x +permit_mpsc 8 25.7 38867782 0 24.9-52.7 2.12x +reserving(32/32) 8 38.1 26239488 0 35.9-62.6 1.75x +reserving(16/48) 8 40.0 25019077 0 37.3-54.6 1.47x +reserving(8/56) 8 50.1 19966257 0 36.1-61.3 1.70x +reserving(64/64) 8 77.7 12865170 0 59.3-79.4 1.34x +baseline_fetch_add 16 14.9 67338367 0 14.4-15.0 1.04x +slotwise_mpsc 16 242.2 4129365 0 191.4-268.7 1.40x +reserving_mpsc 16 51.7 19341658 0 49.6-53.3 1.07x +permit_mpsc 16 20.3 49335512 0 20.1-32.0 1.59x +reserving(32/32) 16 51.5 19419691 0 45.4-57.3 1.26x +reserving(16/48) 16 66.7 14995005 0 53.9-72.9 1.35x +reserving(8/56) 16 67.4 14838171 0 59.6-77.8 1.30x +reserving(64/64) 16 209.4 4775521 0 123.0-236.9 1.93x +baseline_fetch_add 32 15.1 66301181 0 14.4-15.2 1.05x +slotwise_mpsc 32 227.3 4400240 0 209.2-268.0 1.28x +reserving_mpsc 32 49.0 20423038 0 46.4-49.6 1.07x +permit_mpsc 32 22.2 45127245 0 21.1-22.7 1.07x +reserving(32/32) 32 49.6 20168686 0 43.3-53.7 1.24x +reserving(16/48) 32 64.0 15624085 0 59.1-69.8 1.18x +reserving(8/56) 32 65.7 15215677 0 63.5-70.5 1.11x +reserving(64/64) 32 210.0 4763026 0 183.5-241.5 1.32x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.2 97732604 817 9.9-10.3 1.04x +reserving_mpsc 1 24.3 41209923 0 23.9-27.5 1.15x +permit_mpsc 1 56.3 17776514 0 36.4-60.5 1.66x +reserving(32/32) 1 25.1 39840637 0 22.5-30.6 1.36x +reserving(16/48) 1 28.6 34940601 2405 27.5-30.4 1.11x +reserving(8/56) 1 26.1 38276047 1886 23.5-28.1 1.20x +reserving(64/64) 1 26.1 38337678 404 25.3-27.5 1.09x +slotwise_mpsc 2 70.8 14128884 319 63.6-77.3 1.22x +reserving_mpsc 2 66.6 15010733 278 62.9-67.5 1.07x +permit_mpsc 2 53.5 18694035 191 52.5-55.0 1.05x +reserving(32/32) 2 65.0 15380120 0 63.7-65.9 1.04x +reserving(16/48) 2 70.0 14286939 702 64.5-71.5 1.11x +reserving(8/56) 2 65.9 15180266 259 64.1-68.7 1.07x +reserving(64/64) 2 66.4 15063417 121 63.5-74.0 1.17x +slotwise_mpsc 4 104.8 9539482 908 101.9-113.0 1.11x +reserving_mpsc 4 100.1 9986618 0 93.7-103.8 1.11x +permit_mpsc 4 78.7 12708903 79942 74.1-83.7 1.13x +reserving(32/32) 4 96.0 10418186 200 94.1-102.7 1.09x +reserving(16/48) 4 99.4 10063855 0 94.3-128.1 1.36x +reserving(8/56) 4 100.0 9997501 4525 92.8-125.6 1.35x +reserving(64/64) 4 104.5 9569927 3600 98.6-109.2 1.11x +slotwise_mpsc 8 157.1 6363752 0 152.7-234.2 1.53x +reserving_mpsc 8 171.1 5846158 79 162.6-176.6 1.09x +permit_mpsc 8 150.6 6642124 614771 105.0-182.5 1.74x +reserving(32/32) 8 162.3 6163167 1666 156.0-184.0 1.18x +reserving(16/48) 8 165.3 6047805 629 157.5-176.1 1.12x +reserving(8/56) 8 163.4 6119717 1430 147.4-167.1 1.13x +reserving(64/64) 8 175.1 5710321 0 172.9-185.5 1.07x +slotwise_mpsc 16 331.6 3015468 1952802 264.2-368.4 1.39x +reserving_mpsc 16 270.2 3701212 840787 219.1-314.5 1.44x +permit_mpsc 16 285.2 3506022 3156558 259.9-301.8 1.16x +reserving(32/32) 16 307.5 3252113 2574968 243.0-325.5 1.34x +reserving(16/48) 16 325.5 3072326 2557995 260.8-419.3 1.61x +reserving(8/56) 16 251.4 3977620 1096013 228.4-364.6 1.60x +reserving(64/64) 16 276.5 3616628 929875 245.8-292.9 1.19x +slotwise_mpsc 32 571.4 1750058 16618865 433.2-605.6 1.40x +reserving_mpsc 32 550.8 1815617 17143009 461.4-826.5 1.79x +permit_mpsc 32 451.7 2214024 10831731 347.6-567.4 1.63x +reserving(32/32) 32 701.8 1424889 28203195 626.2-894.6 1.43x +reserving(16/48) 32 647.7 1543978 26188988 514.1-759.8 1.48x +reserving(8/56) 32 590.1 1694521 19228644 459.8-801.9 1.74x +reserving(64/64) 32 571.5 1749883 16185757 555.7-617.8 1.11x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.14] 0.16x [0.15-0.23] 0.19x [0.18-0.47] 0.19x [0.16-0.32] + 4 0.07x [0.06-0.08] 0.15x [0.14-0.21] 0.25x [0.24-0.27] 0.15x [0.15-0.22] + 8 0.05x [0.04-0.05] 0.13x [0.10-0.19] 0.31x [0.15-0.34] 0.15x [0.14-0.20] + 16 0.03x [0.02-0.03] 0.11x [0.10-0.15] 0.39x [0.25-0.42] 0.16x [0.15-0.20] + 32 0.03x [0.02-0.03] 0.12x [0.11-0.16] 0.35x [0.35-0.40] 0.15x [0.15-0.20] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.2 24.3 2.37x [2.31-2.78] 56.3 2.32x [1.32-2.53] + 2 70.8 66.6 0.94x [0.81-1.06] 53.5 0.80x [0.78-0.87] + 4 104.8 100.1 0.96x [0.83-1.02] 78.7 0.79x [0.71-0.89] + 8 157.1 171.1 1.09x [0.69-1.16] 150.6 0.88x [0.59-1.12] + 16 331.6 270.2 0.81x [0.59-1.19] 285.2 1.06x [0.83-1.38] + 32 571.4 550.8 0.96x [0.76-1.91] 451.7 0.82x [0.42-1.23] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.6 7.8 0.99x [0.88-1.08] 1.03x [0.88-1.05] 1.44x [1.23-1.50] + 2 33.9 35.7 34.9 39.8 1.05x [0.91-1.15] 1.03x [0.92-1.12] 1.18x [1.02-1.26] + 4 34.8 37.3 34.1 46.9 1.07x [0.77-1.29] 0.98x [0.82-1.25] 1.35x [1.07-1.54] + 8 38.1 40.0 50.1 77.7 1.05x [0.60-1.52] 1.31x [0.58-1.71] 2.04x [0.95-2.21] + 16 51.5 66.7 67.4 209.4 1.30x [0.94-1.61] 1.31x [1.04-1.72] 4.07x [2.15-5.22] + 32 49.6 64.0 65.7 210.0 1.29x [1.10-1.61] 1.33x [1.18-1.63] 4.23x [3.42-5.57] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 25.1 28.6 26.1 26.1 1.14x [0.90-1.35] 1.04x [0.77-1.25] 1.04x [0.83-1.22] + 2 65.0 70.0 65.9 66.4 1.08x [0.98-1.12] 1.01x [0.97-1.08] 1.02x [0.96-1.16] + 4 96.0 99.4 100.0 104.5 1.04x [0.92-1.36] 1.04x [0.90-1.33] 1.09x [0.96-1.16] + 8 162.3 165.3 163.4 175.1 1.02x [0.86-1.13] 1.01x [0.80-1.07] 1.08x [0.94-1.19] + 16 307.5 325.5 251.4 276.5 1.06x [0.80-1.73] 0.82x [0.70-1.50] 0.90x [0.76-1.21] + 32 701.8 647.7 590.1 571.5 0.92x [0.57-1.21] 0.84x [0.51-1.28] 0.81x [0.62-0.99] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run5.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run5.txt new file mode 100644 index 000000000..430c9a98f --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run5.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433275563 0 2.3-2.3 1.01x +slotwise_mpsc 1 6.3 158378207 0 6.2-6.8 1.09x +reserving_mpsc 1 5.4 184569952 0 5.4-6.1 1.12x +permit_mpsc 1 8.0 124470998 0 7.9-8.4 1.07x +reserving(32/32) 1 5.4 184162063 0 5.4-6.9 1.28x +reserving(16/48) 1 5.4 186846039 0 5.3-5.4 1.00x +reserving(8/56) 1 5.6 178954903 0 5.4-7.1 1.33x +reserving(64/64) 1 7.8 128402671 0 7.5-8.1 1.09x +baseline_fetch_add 2 12.9 77663871 0 11.6-14.4 1.24x +slotwise_mpsc 2 56.9 17575310 0 53.7-60.2 1.12x +reserving_mpsc 2 32.8 30465513 0 26.0-36.3 1.40x +permit_mpsc 2 43.2 23140649 0 35.2-45.2 1.28x +reserving(32/32) 2 34.6 28935185 0 32.8-36.4 1.11x +reserving(16/48) 2 35.8 27972028 0 33.0-36.5 1.10x +reserving(8/56) 2 34.3 29152819 0 31.9-34.9 1.09x +reserving(64/64) 2 38.7 25858502 0 37.5-39.8 1.06x +baseline_fetch_add 4 13.0 76890546 0 12.6-13.9 1.10x +slotwise_mpsc 4 92.8 10770987 0 86.9-93.4 1.07x +reserving_mpsc 4 39.5 25309087 0 36.9-40.3 1.09x +permit_mpsc 4 32.0 31252442 0 31.4-33.0 1.05x +reserving(32/32) 4 38.2 26190009 0 32.2-40.8 1.27x +reserving(16/48) 4 37.4 26729034 0 37.1-41.7 1.12x +reserving(8/56) 4 33.1 30242088 0 30.1-39.1 1.30x +reserving(64/64) 4 44.0 22719011 0 43.0-64.8 1.51x +baseline_fetch_add 8 14.4 69414317 0 13.7-15.7 1.15x +slotwise_mpsc 8 146.5 6824005 0 140.0-154.2 1.10x +reserving_mpsc 8 40.1 24910168 0 35.6-54.4 1.53x +permit_mpsc 8 25.5 39281541 0 25.1-27.0 1.08x +reserving(32/32) 8 38.8 25796965 0 35.5-52.7 1.49x +reserving(16/48) 8 38.1 26254816 0 35.2-48.1 1.37x +reserving(8/56) 8 39.2 25513947 0 35.4-46.7 1.32x +reserving(64/64) 8 61.0 16399424 0 60.9-74.5 1.22x +baseline_fetch_add 16 14.8 67405885 0 14.7-15.5 1.05x +slotwise_mpsc 16 268.0 3730917 0 198.7-269.3 1.36x +reserving_mpsc 16 53.9 18559074 0 48.2-55.2 1.14x +permit_mpsc 16 20.3 49286880 0 19.8-32.8 1.66x +reserving(32/32) 16 54.3 18431566 0 48.7-54.4 1.12x +reserving(16/48) 16 70.8 14115621 0 56.8-73.6 1.30x +reserving(8/56) 16 68.3 14641851 0 59.8-72.0 1.20x +reserving(64/64) 16 217.4 4599205 0 161.7-237.6 1.47x +baseline_fetch_add 32 14.8 67654993 0 14.6-15.3 1.05x +slotwise_mpsc 32 232.0 4309864 0 204.1-263.4 1.29x +reserving_mpsc 32 50.9 19654088 0 48.7-54.5 1.12x +permit_mpsc 32 20.0 50006563 0 19.8-20.9 1.05x +reserving(32/32) 32 53.9 18548531 0 48.9-54.5 1.11x +reserving(16/48) 32 70.5 14179872 0 65.3-71.9 1.10x +reserving(8/56) 32 73.7 13576546 0 62.3-74.7 1.20x +reserving(64/64) 32 241.9 4133160 0 231.2-248.8 1.08x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.6 94286253 51 10.4-11.0 1.06x +reserving_mpsc 1 25.0 39955250 0 23.1-28.1 1.21x +permit_mpsc 1 59.4 16821990 29 57.4-61.0 1.06x +reserving(32/32) 1 23.3 42992261 674 22.6-30.5 1.35x +reserving(16/48) 1 27.6 36258158 2268 25.7-30.8 1.20x +reserving(8/56) 1 26.5 37721614 2154 25.0-33.7 1.35x +reserving(64/64) 1 26.6 37613782 0 25.8-29.6 1.15x +slotwise_mpsc 2 71.1 14060347 317 65.4-74.3 1.14x +reserving_mpsc 2 67.7 14768213 0 63.3-68.8 1.09x +permit_mpsc 2 52.2 19143519 1187 51.0-53.3 1.04x +reserving(32/32) 2 67.9 14721687 332 61.9-69.7 1.13x +reserving(16/48) 2 68.6 14569401 934 66.1-71.5 1.08x +reserving(8/56) 2 66.8 14964459 452 60.9-70.5 1.16x +reserving(64/64) 2 66.3 15078180 904 61.6-74.2 1.20x +slotwise_mpsc 4 108.4 9224369 103 104.2-112.7 1.08x +reserving_mpsc 4 95.8 10437433 1251 85.9-100.1 1.17x +permit_mpsc 4 77.7 12868191 77791 70.2-94.5 1.35x +reserving(32/32) 4 96.6 10348967 175 92.0-99.5 1.08x +reserving(16/48) 4 94.6 10574961 1750 93.8-96.8 1.03x +reserving(8/56) 4 95.1 10519782 0 91.2-101.5 1.11x +reserving(64/64) 4 106.1 9426714 0 97.4-110.0 1.13x +slotwise_mpsc 8 147.7 6768270 0 146.4-161.9 1.11x +reserving_mpsc 8 163.4 6121731 2788 160.2-166.0 1.04x +permit_mpsc 8 125.3 7983713 454812 109.0-166.0 1.52x +reserving(32/32) 8 174.6 5726237 64 154.9-180.4 1.16x +reserving(16/48) 8 175.9 5683934 1822 165.4-181.7 1.10x +reserving(8/56) 8 164.0 6098016 8273 154.0-174.0 1.13x +reserving(64/64) 8 188.8 5297031 0 166.0-198.8 1.20x +slotwise_mpsc 16 294.7 3392917 2302447 214.1-332.7 1.55x +reserving_mpsc 16 324.6 3081035 2126214 255.0-351.1 1.38x +permit_mpsc 16 293.1 3412340 3293944 251.5-299.9 1.19x +reserving(32/32) 16 338.9 2951011 2645070 232.6-385.3 1.66x +reserving(16/48) 16 271.3 3685659 1569245 239.0-361.3 1.51x +reserving(8/56) 16 319.4 3130472 1984959 238.8-473.9 1.98x +reserving(64/64) 16 272.2 3673828 932631 225.2-357.7 1.59x +slotwise_mpsc 32 562.0 1779317 16515289 507.3-815.4 1.61x +reserving_mpsc 32 545.5 1833227 14929473 482.2-643.1 1.33x +permit_mpsc 32 491.8 2033260 11507535 445.2-646.6 1.45x +reserving(32/32) 32 576.2 1735537 18913396 552.7-783.8 1.42x +reserving(16/48) 32 547.3 1827048 18297121 536.7-750.3 1.40x +reserving(8/56) 32 680.3 1469883 23104596 558.4-765.9 1.37x +reserving(64/64) 32 676.1 1479128 19675639 514.0-750.9 1.46x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.11x [0.10-0.13] 0.17x [0.15-0.23] 0.19x [0.17-0.24] 0.18x [0.16-0.20] + 4 0.07x [0.07-0.08] 0.14x [0.13-0.16] 0.25x [0.24-0.27] 0.18x [0.16-0.18] + 8 0.04x [0.04-0.05] 0.13x [0.10-0.17] 0.32x [0.29-0.34] 0.16x [0.15-0.17] + 16 0.02x [0.02-0.03] 0.10x [0.10-0.13] 0.40x [0.24-0.42] 0.16x [0.15-0.16] + 32 0.03x [0.02-0.03] 0.11x [0.10-0.12] 0.40x [0.38-0.42] 0.16x [0.15-0.16] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.6 25.0 2.36x [2.10-2.69] 59.4 2.38x [2.04-2.63] + 2 71.1 67.7 0.95x [0.85-1.05] 52.2 0.77x [0.74-0.84] + 4 108.4 95.8 0.88x [0.76-0.96] 77.7 0.81x [0.70-1.10] + 8 147.7 163.4 1.11x [0.99-1.13] 125.3 0.77x [0.66-1.04] + 16 294.7 324.6 1.10x [0.77-1.64] 293.1 0.90x [0.72-1.18] + 32 562.0 545.5 0.97x [0.59-1.27] 491.8 0.90x [0.69-1.34] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.6 7.8 0.99x [0.77-0.99] 1.03x [0.78-1.31] 1.43x [1.08-1.50] + 2 34.6 35.8 34.3 38.7 1.03x [0.91-1.11] 0.99x [0.88-1.07] 1.12x [1.03-1.21] + 4 38.2 37.4 33.1 44.0 0.98x [0.91-1.29] 0.87x [0.74-1.21] 1.15x [1.05-2.01] + 8 38.8 38.1 39.2 61.0 0.98x [0.67-1.36] 1.01x [0.67-1.32] 1.57x [1.16-2.10] + 16 54.3 70.8 68.3 217.4 1.31x [1.04-1.51] 1.26x [1.10-1.48] 4.01x [2.97-4.88] + 32 53.9 70.5 73.7 241.9 1.31x [1.20-1.47] 1.37x [1.14-1.53] 4.49x [4.25-5.08] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 23.3 27.6 26.5 26.6 1.19x [0.84-1.36] 1.14x [0.82-1.49] 1.14x [0.84-1.31] + 2 67.9 68.6 66.8 66.3 1.01x [0.95-1.16] 0.98x [0.87-1.14] 0.98x [0.88-1.20] + 4 96.6 94.6 95.1 106.1 0.98x [0.94-1.05] 0.98x [0.92-1.10] 1.10x [0.98-1.20] + 8 174.6 175.9 164.0 188.8 1.01x [0.92-1.17] 0.94x [0.85-1.12] 1.08x [0.92-1.28] + 16 338.9 271.3 319.4 272.2 0.80x [0.62-1.55] 0.94x [0.62-2.04] 0.80x [0.58-1.54] + 32 576.2 547.3 680.3 676.1 0.95x [0.68-1.36] 1.18x [0.71-1.39] 1.17x [0.66-1.36] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run6.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run6.txt new file mode 100644 index 000000000..d60324b0e --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run6.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433651344 0 2.3-2.3 1.02x +slotwise_mpsc 1 6.4 155860349 0 6.3-6.7 1.07x +reserving_mpsc 1 5.4 183958793 0 5.4-5.5 1.01x +permit_mpsc 1 7.9 127097102 0 7.9-8.4 1.07x +reserving(32/32) 1 5.4 184365782 0 5.4-5.8 1.06x +reserving(16/48) 1 5.4 186706497 0 5.3-5.6 1.04x +reserving(8/56) 1 5.7 175685172 0 5.3-6.8 1.28x +reserving(64/64) 1 7.5 133904660 0 7.5-8.6 1.15x +baseline_fetch_add 2 10.9 92089511 0 9.5-12.1 1.28x +slotwise_mpsc 2 58.7 17032873 0 55.7-61.2 1.10x +reserving_mpsc 2 33.6 29766334 0 30.3-35.2 1.16x +permit_mpsc 2 43.4 23027679 0 41.3-45.4 1.10x +reserving(32/32) 2 32.5 30791969 0 21.1-37.4 1.78x +reserving(16/48) 2 32.8 30522235 0 30.4-36.4 1.20x +reserving(8/56) 2 33.1 30210568 0 29.7-34.8 1.17x +reserving(64/64) 2 39.7 25184476 0 38.2-43.0 1.12x +baseline_fetch_add 4 14.0 71438777 0 13.1-17.3 1.32x +slotwise_mpsc 4 88.0 11358022 0 84.2-89.6 1.06x +reserving_mpsc 4 35.4 28225465 0 34.0-38.6 1.14x +permit_mpsc 4 32.0 31281771 0 31.9-32.1 1.01x +reserving(32/32) 4 34.3 29137529 0 33.0-37.6 1.14x +reserving(16/48) 4 35.1 28523346 0 32.5-41.1 1.27x +reserving(8/56) 4 35.5 28175363 0 30.5-42.2 1.38x +reserving(64/64) 4 47.6 21030052 0 43.0-53.2 1.24x +baseline_fetch_add 8 15.2 65691153 0 14.4-15.5 1.07x +slotwise_mpsc 8 137.2 7289002 0 132.1-145.4 1.10x +reserving_mpsc 8 39.0 25672458 0 37.1-60.5 1.63x +permit_mpsc 8 26.1 38343191 0 24.8-40.2 1.63x +reserving(32/32) 8 37.8 26443833 0 36.0-57.2 1.59x +reserving(16/48) 8 47.6 21012156 0 35.6-59.5 1.67x +reserving(8/56) 8 43.6 22940121 0 40.9-53.8 1.32x +reserving(64/64) 8 83.9 11913342 0 63.2-93.8 1.48x +baseline_fetch_add 16 14.8 67734042 0 14.6-15.4 1.06x +slotwise_mpsc 16 224.0 4464104 0 189.8-249.9 1.32x +reserving_mpsc 16 53.4 18717128 0 50.5-62.0 1.23x +permit_mpsc 16 20.5 48783760 0 19.9-21.4 1.07x +reserving(32/32) 16 51.7 19338199 0 47.9-53.8 1.12x +reserving(16/48) 16 64.6 15474097 0 55.5-72.5 1.31x +reserving(8/56) 16 67.4 14839713 0 52.4-73.6 1.41x +reserving(64/64) 16 170.6 5862550 0 168.0-217.2 1.29x +baseline_fetch_add 32 15.2 65779737 0 15.0-15.2 1.01x +slotwise_mpsc 32 230.3 4342727 0 214.8-258.3 1.20x +reserving_mpsc 32 49.3 20280119 0 45.8-53.7 1.17x +permit_mpsc 32 21.9 45734278 0 20.7-23.6 1.14x +reserving(32/32) 32 51.1 19579697 0 49.8-52.6 1.06x +reserving(16/48) 32 63.5 15739635 0 59.1-70.4 1.19x +reserving(8/56) 32 61.9 16160474 0 55.7-72.9 1.31x +reserving(64/64) 32 176.5 5665869 0 135.1-225.8 1.67x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.0 99980004 10 9.6-10.5 1.09x +reserving_mpsc 1 24.3 41169205 210 22.9-24.9 1.09x +permit_mpsc 1 58.3 17138548 395 56.5-60.7 1.07x +reserving(32/32) 1 25.3 39466414 1546 22.3-29.2 1.31x +reserving(16/48) 1 29.1 34378438 2297 26.4-29.7 1.13x +reserving(8/56) 1 25.8 38810836 1621 24.4-29.4 1.20x +reserving(64/64) 1 26.8 37305081 0 25.6-29.9 1.17x +slotwise_mpsc 2 73.0 13705765 445 67.1-77.2 1.15x +reserving_mpsc 2 65.2 15339305 991 61.0-67.9 1.11x +permit_mpsc 2 53.6 18658805 575 50.1-54.6 1.09x +reserving(32/32) 2 67.3 14861491 241 65.8-69.1 1.05x +reserving(16/48) 2 67.4 14840759 542 66.0-69.1 1.05x +reserving(8/56) 2 67.0 14916913 117 66.6-68.8 1.03x +reserving(64/64) 2 68.7 14566218 61 58.0-70.5 1.22x +slotwise_mpsc 4 104.7 9554064 54 98.1-109.2 1.11x +reserving_mpsc 4 96.0 10411461 0 88.7-101.5 1.14x +permit_mpsc 4 79.9 12523325 82782 70.6-82.0 1.16x +reserving(32/32) 4 99.3 10074602 0 80.7-102.9 1.28x +reserving(16/48) 4 91.3 10954462 23 85.8-93.3 1.09x +reserving(8/56) 4 96.5 10360869 29 78.2-98.7 1.26x +reserving(64/64) 4 101.5 9853333 0 88.0-106.0 1.21x +slotwise_mpsc 8 150.4 6650097 0 146.0-155.7 1.07x +reserving_mpsc 8 167.2 5980325 2335 153.0-175.4 1.15x +permit_mpsc 8 136.5 7326168 540552 111.7-145.7 1.30x +reserving(32/32) 8 159.3 6278400 0 146.3-172.5 1.18x +reserving(16/48) 8 159.5 6268364 779 152.0-165.7 1.09x +reserving(8/56) 8 161.0 6210659 4462 155.3-177.7 1.14x +reserving(64/64) 8 165.3 6049598 239 162.5-180.1 1.11x +slotwise_mpsc 16 304.7 3281888 2123706 247.3-345.5 1.40x +reserving_mpsc 16 301.4 3318312 2363919 239.2-366.7 1.53x +permit_mpsc 16 240.5 4157600 2590013 198.1-282.9 1.43x +reserving(32/32) 16 263.2 3798802 1523659 192.7-320.6 1.66x +reserving(16/48) 16 288.8 3463187 2191392 234.8-361.3 1.54x +reserving(8/56) 16 303.3 3296700 2104514 202.4-394.8 1.95x +reserving(64/64) 16 295.8 3380160 1571118 220.8-320.2 1.45x +slotwise_mpsc 32 494.2 2023467 14803997 471.4-587.6 1.25x +reserving_mpsc 32 667.0 1499225 23065206 582.8-750.3 1.29x +permit_mpsc 32 375.9 2660005 8602953 323.7-657.6 2.03x +reserving(32/32) 32 673.6 1484459 26659560 514.9-864.2 1.68x +reserving(16/48) 32 603.1 1658220 21846151 560.0-855.7 1.53x +reserving(8/56) 32 661.7 1511200 24004293 514.0-810.6 1.58x +reserving(64/64) 32 732.1 1365913 24344667 573.3-886.2 1.55x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.11x [0.10-0.12] 0.16x [0.15-0.18] 0.18x [0.17-0.20] 0.21x [0.19-0.24] + 4 0.07x [0.07-0.08] 0.15x [0.14-0.16] 0.25x [0.24-0.26] 0.16x [0.13-0.18] + 8 0.05x [0.04-0.05] 0.14x [0.09-0.15] 0.30x [0.20-0.34] 0.15x [0.15-0.16] + 16 0.03x [0.03-0.04] 0.10x [0.09-0.11] 0.38x [0.37-0.42] 0.16x [0.15-0.16] + 32 0.03x [0.02-0.03] 0.11x [0.10-0.12] 0.36x [0.33-0.41] 0.15x [0.15-0.15] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.0 24.3 2.43x [2.19-2.58] 58.3 2.40x [2.27-2.65] + 2 73.0 65.2 0.89x [0.79-1.01] 53.6 0.82x [0.74-0.90] + 4 104.7 96.0 0.92x [0.81-1.03] 79.9 0.83x [0.70-0.92] + 8 150.4 167.2 1.11x [0.98-1.20] 136.5 0.82x [0.64-0.95] + 16 304.7 301.4 0.99x [0.69-1.48] 240.5 0.80x [0.54-1.18] + 32 494.2 667.0 1.35x [0.99-1.59] 375.9 0.56x [0.43-1.13] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.7 7.5 0.99x [0.93-1.03] 1.05x [0.93-1.26] 1.38x [1.30-1.59] + 2 32.5 32.8 33.1 39.7 1.01x [0.81-1.73] 1.02x [0.79-1.65] 1.22x [1.02-2.04] + 4 34.3 35.1 35.5 47.6 1.02x [0.86-1.25] 1.03x [0.81-1.28] 1.39x [1.15-1.61] + 8 37.8 47.6 43.6 83.9 1.26x [0.62-1.65] 1.15x [0.72-1.50] 2.22x [1.11-2.61] + 16 51.7 64.6 67.4 170.6 1.25x [1.03-1.51] 1.30x [0.97-1.53] 3.30x [3.12-4.53] + 32 51.1 63.5 61.9 176.5 1.24x [1.12-1.41] 1.21x [1.06-1.46] 3.46x [2.57-4.53] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 25.3 29.1 25.8 26.8 1.15x [0.90-1.33] 1.02x [0.83-1.32] 1.06x [0.88-1.34] + 2 67.3 67.4 67.0 68.7 1.00x [0.96-1.05] 1.00x [0.96-1.05] 1.02x [0.84-1.07] + 4 99.3 91.3 96.5 101.5 0.92x [0.83-1.16] 0.97x [0.76-1.22] 1.02x [0.85-1.31] + 8 159.3 159.5 161.0 165.3 1.00x [0.88-1.13] 1.01x [0.90-1.21] 1.04x [0.94-1.23] + 16 263.2 288.8 303.3 295.8 1.10x [0.73-1.87] 1.15x [0.63-2.05] 1.12x [0.69-1.66] + 32 673.6 603.1 661.7 732.1 0.90x [0.65-1.66] 0.98x [0.59-1.57] 1.09x [0.66-1.72] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run7.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run7.txt new file mode 100644 index 000000000..9bea2eda1 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/interleaved-run7.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32 + (the defaults) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432525952 0 2.3-2.4 1.06x +slotwise_mpsc 1 6.3 158077774 0 6.3-8.0 1.27x +reserving_mpsc 1 5.4 184501845 0 5.4-5.8 1.08x +permit_mpsc 1 8.0 124378109 0 7.9-8.3 1.05x +reserving(32/32) 1 5.5 182681768 0 5.4-5.8 1.06x +reserving(16/48) 1 5.3 186985789 0 5.3-5.5 1.04x +reserving(8/56) 1 5.8 171291538 0 5.4-6.6 1.24x +reserving(64/64) 1 7.5 134048257 0 7.5-8.6 1.16x +baseline_fetch_add 2 11.4 88082445 0 8.3-13.7 1.65x +slotwise_mpsc 2 55.3 18089398 0 29.3-59.6 2.03x +reserving_mpsc 2 33.3 30013806 0 32.2-36.0 1.12x +permit_mpsc 2 44.2 22599381 0 38.3-45.0 1.18x +reserving(32/32) 2 35.3 28313372 0 31.0-38.9 1.26x +reserving(16/48) 2 31.1 32106852 0 28.5-35.0 1.23x +reserving(8/56) 2 34.8 28776978 0 33.7-37.3 1.11x +reserving(64/64) 2 40.1 24958195 0 37.3-41.3 1.11x +baseline_fetch_add 4 13.6 73580810 0 12.7-15.1 1.19x +slotwise_mpsc 4 88.3 11321118 0 85.5-120.5 1.41x +reserving_mpsc 4 37.8 26460627 0 36.4-40.3 1.11x +permit_mpsc 4 31.6 31626054 0 31.2-32.4 1.04x +reserving(32/32) 4 39.4 25360757 0 38.3-41.3 1.08x +reserving(16/48) 4 41.0 24389946 0 36.9-41.7 1.13x +reserving(8/56) 4 39.7 25170847 0 32.5-43.0 1.32x +reserving(64/64) 4 44.6 22431332 0 41.6-73.3 1.76x +baseline_fetch_add 8 14.8 67782824 0 14.4-15.6 1.08x +slotwise_mpsc 8 133.5 7490595 0 126.5-141.9 1.12x +reserving_mpsc 8 38.4 26054728 0 37.0-39.9 1.08x +permit_mpsc 8 26.2 38157380 0 25.7-26.5 1.03x +reserving(32/32) 8 39.0 25618691 0 37.8-40.2 1.06x +reserving(16/48) 8 47.2 21168725 0 41.7-52.4 1.26x +reserving(8/56) 8 37.2 26890214 0 36.4-50.1 1.38x +reserving(64/64) 8 83.4 11996521 0 52.7-86.0 1.63x +baseline_fetch_add 16 15.0 66649449 0 14.7-17.0 1.15x +slotwise_mpsc 16 257.8 3878594 0 208.6-272.7 1.31x +reserving_mpsc 16 53.3 18773702 0 52.0-61.1 1.18x +permit_mpsc 16 19.8 50590966 0 19.6-22.7 1.16x +reserving(32/32) 16 52.6 19019316 0 51.8-53.7 1.04x +reserving(16/48) 16 70.8 14122324 0 65.4-71.9 1.10x +reserving(8/56) 16 73.0 13707714 0 64.7-75.0 1.16x +reserving(64/64) 16 220.7 4530653 0 193.4-238.2 1.23x +baseline_fetch_add 32 14.9 67117472 0 14.7-15.9 1.08x +slotwise_mpsc 32 219.4 4557270 0 210.0-231.5 1.10x +reserving_mpsc 32 50.7 19743044 0 46.5-51.5 1.11x +permit_mpsc 32 21.3 46941057 0 20.0-22.7 1.13x +reserving(32/32) 32 50.6 19752891 0 50.5-60.2 1.19x +reserving(16/48) 32 63.7 15702054 0 57.4-65.1 1.13x +reserving(8/56) 32 65.5 15275309 0 60.7-66.9 1.10x +reserving(64/64) 32 188.9 5293864 0 165.7-200.3 1.21x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 9.8 101791531 654 9.6-10.9 1.14x +reserving_mpsc 1 24.7 40417105 600 22.9-27.0 1.18x +permit_mpsc 1 59.4 16824254 154 57.0-62.8 1.10x +reserving(32/32) 1 26.7 37416748 1696 23.4-30.1 1.29x +reserving(16/48) 1 27.4 36451119 2050 24.4-30.1 1.23x +reserving(8/56) 1 26.6 37653438 1520 24.1-32.2 1.34x +reserving(64/64) 1 26.2 38237993 469 23.8-30.3 1.28x +slotwise_mpsc 2 71.6 13966480 1250 65.7-75.6 1.15x +reserving_mpsc 2 68.4 14622662 541 64.6-69.3 1.07x +permit_mpsc 2 52.3 19102562 208 51.7-54.9 1.06x +reserving(32/32) 2 65.6 15237863 1136 61.9-67.9 1.10x +reserving(16/48) 2 68.4 14614541 261 65.5-70.4 1.07x +reserving(8/56) 2 68.0 14695941 0 63.5-72.6 1.14x +reserving(64/64) 2 69.5 14380833 575 67.1-72.1 1.07x +slotwise_mpsc 4 108.4 9222540 437 102.5-110.0 1.07x +reserving_mpsc 4 95.8 10438032 1043 90.9-103.1 1.13x +permit_mpsc 4 83.3 12000984 89645 75.8-90.9 1.20x +reserving(32/32) 4 101.2 9884158 102 99.4-103.7 1.04x +reserving(16/48) 4 95.1 10511379 953 90.3-97.9 1.08x +reserving(8/56) 4 100.0 10001200 0 94.0-102.2 1.09x +reserving(64/64) 4 97.7 10236462 0 88.2-108.7 1.23x +slotwise_mpsc 8 153.1 6532020 0 145.7-157.3 1.08x +reserving_mpsc 8 170.8 5855221 7182 160.6-183.6 1.14x +permit_mpsc 8 150.0 6668467 611839 136.7-174.3 1.28x +reserving(32/32) 8 172.6 5793214 6294 158.8-175.7 1.11x +reserving(16/48) 8 170.0 5882837 525 159.8-182.7 1.14x +reserving(8/56) 8 165.4 6046964 171 145.4-184.6 1.27x +reserving(64/64) 8 187.0 5347372 1931 163.5-198.7 1.22x +slotwise_mpsc 16 334.4 2990423 1974904 287.1-465.0 1.62x +reserving_mpsc 16 328.0 3048691 1979096 251.9-360.1 1.43x +permit_mpsc 16 299.2 3342274 3434760 251.3-332.0 1.32x +reserving(32/32) 16 329.8 3032461 2381567 282.5-332.6 1.18x +reserving(16/48) 16 329.5 3034607 2442203 248.0-360.3 1.45x +reserving(8/56) 16 364.4 2744488 3193622 329.1-384.7 1.17x +reserving(64/64) 16 274.8 3638795 963194 232.0-295.5 1.27x +slotwise_mpsc 32 506.3 1975280 14805542 457.3-616.6 1.35x +reserving_mpsc 32 506.0 1976119 15416781 465.2-620.4 1.33x +permit_mpsc 32 427.9 2336856 9925338 360.8-501.6 1.39x +reserving(32/32) 32 546.3 1830408 19560996 392.0-821.2 2.09x +reserving(16/48) 32 616.6 1621833 24354849 405.7-815.6 2.01x +reserving(8/56) 32 614.3 1627994 20936859 470.6-810.4 1.72x +reserving(64/64) 32 414.0 2415653 10074795 403.6-761.7 1.89x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.11x [0.11-0.27] 0.16x [0.15-0.18] 0.18x [0.17-0.22] 0.20x [0.17-0.29] + 4 0.07x [0.05-0.09] 0.14x [0.13-0.16] 0.25x [0.24-0.27] 0.17x [0.15-0.19] + 8 0.05x [0.04-0.06] 0.14x [0.14-0.16] 0.31x [0.30-0.32] 0.16x [0.15-0.17] + 16 0.02x [0.02-0.04] 0.10x [0.09-0.11] 0.41x [0.35-0.42] 0.15x [0.14-0.17] + 32 0.03x [0.03-0.04] 0.11x [0.10-0.13] 0.38x [0.35-0.41] 0.16x [0.15-0.17] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 9.8 24.7 2.52x [2.11-2.82] 59.4 2.40x [2.11-2.74] + 2 71.6 68.4 0.96x [0.86-1.05] 52.3 0.77x [0.75-0.85] + 4 108.4 95.8 0.88x [0.83-1.01] 83.3 0.87x [0.73-1.00] + 8 153.1 170.8 1.12x [1.02-1.26] 150.0 0.88x [0.74-1.09] + 16 334.4 328.0 0.98x [0.54-1.25] 299.2 0.91x [0.70-1.32] + 32 506.3 506.0 1.00x [0.75-1.36] 427.9 0.85x [0.58-1.08] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.5 5.3 5.8 7.5 0.98x [0.93-1.02] 1.07x [0.93-1.22] 1.36x [1.29-1.59] + 2 35.3 31.1 34.8 40.1 0.88x [0.73-1.13] 0.98x [0.86-1.21] 1.13x [0.96-1.33] + 4 39.4 41.0 39.7 44.6 1.04x [0.89-1.09] 1.01x [0.79-1.12] 1.13x [1.01-1.91] + 8 39.0 47.2 37.2 83.4 1.21x [1.04-1.38] 0.95x [0.91-1.32] 2.14x [1.31-2.27] + 16 52.6 70.8 73.0 220.7 1.35x [1.22-1.39] 1.39x [1.20-1.45] 4.20x [3.60-4.60] + 32 50.6 63.7 65.5 188.9 1.26x [0.95-1.29] 1.29x [1.01-1.32] 3.73x [2.75-3.97] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 26.7 27.4 26.6 26.2 1.03x [0.81-1.29] 0.99x [0.80-1.38] 0.98x [0.79-1.30] + 2 65.6 68.4 68.0 69.5 1.04x [0.96-1.14] 1.04x [0.93-1.17] 1.06x [0.99-1.17] + 4 101.2 95.1 100.0 97.7 0.94x [0.87-0.98] 0.99x [0.91-1.03] 0.97x [0.85-1.09] + 8 172.6 170.0 165.4 187.0 0.98x [0.91-1.15] 0.96x [0.83-1.16] 1.08x [0.93-1.25] + 16 329.8 329.5 364.4 274.8 1.00x [0.75-1.28] 1.10x [0.99-1.36] 0.83x [0.70-1.05] + 32 546.3 616.6 614.3 414.0 1.13x [0.49-2.08] 1.12x [0.57-2.07] 0.76x [0.49-1.94] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/isolated.js b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/isolated.js new file mode 100644 index 000000000..ad0db2de1 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/isolated.js @@ -0,0 +1,287 @@ +// Copyright (c) Mike Grier. + +// Isolated regime: each claim-word layout against the default, read against a +// same-code control. +// +// `summarise.js` beside this derives the DRAINED tables only. The isolated +// figures are cited too -- by the queue crate's docs, which say the whole push +// path was measured as slower under `Wide` at every producer count -- so they +// need a derivation a reader can run, not a number taken on trust. +// +// The control is the point. `reserving_mpsc` and `reserving(32/32)` are the SAME +// CODE under two names, so the gap between them is what "no difference" looks +// like on this host; a layout ratio only means something read against it. +// +// Three runs give three control observations per count. The range of three +// samples is NOT a band -- a fresh draw falls outside the range of three priors +// about half the time by construction -- so the last column reports what these +// runs did, and is not a claim about what the next run would do. +// +// Usage: node isolated.js run1.txt run2.txt run3.txt +"use strict"; + +const fs = require("fs"); +const crypto = require("crypto"); + +// One writer for the generated artifact, per this repository's output rule: no +// formatting site picks a destination, so retargeting the report to a file is a +// change here and nowhere else. `fail` is the diagnostic path and stays separate +// from the artifact, which is why they are two sinks rather than one with a flag. +let sink = (text) => process.stdout.write(text + "\n"); +const out = (text = "") => sink(text); +const fail = (text) => process.stderr.write(text + "\n"); + +const files = process.argv.slice(2); +if (files.length === 0) { + fail("usage: node isolated.js [run.txt ...]"); + process.exit(2); +} +// `files.length` is reported as the number of independent runs, so the same +// path given twice would publish a two-run comparison -- with a zero-width +// control range -- derived from one set of bytes. A typo must not make the +// capture look more reproducible than it is. +{ + // Compared by CONTENT, not by name. A resolved path catches `./run1.txt` and + // a symlink, but not `copy-of-run1.txt`, which has a different real path and + // identical bytes -- and the run count would still present it as a second + // observation, narrowing the reported range without adding data. What is being + // claimed here is independent runs, so identical bytes cannot be two of them. + // + // **This is a heuristic, and its assumption is worth stating.** The report + // carries no per-invocation identifier, so "identical bytes" stands in for + // "same run". Two genuinely independent runs producing identical bytes would + // be refused -- possible in principle, since the figures are rounded, and + // vanishingly unlikely across this many of them. The refusal is loud and + // diagnosable; accepting a duplicated run would silently fabricate agreement, + // which is the worse of the two. The build identity `M4.8` added does not + // replace this: it names the build, which every run in a capture shares, and + // only a per-invocation identifier would tell two runs apart. + const seen = new Map(); + for (const file of files) { + const digest = crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); + if (seen.has(digest)) { + fail(`the same capture was given twice: ${file} is byte-identical to ${seen.get(digest)}`); + process.exit(2); + } + seen.set(digest, file); + } +} + + +// The attribution a run prints about itself. A capture is runs of one build on +// one host under one sampling regime, so these must agree across the inputs -- +// mixing a debug run, or another machine's, yields a median and a control span +// that describe no configuration that was ever measured, and nothing else here +// would notice. +function attribution(text, where) { + const line = (prefix) => { + const found = text.split(/\r?\n/).find((l) => l.startsWith(prefix)); + if (found === undefined) { + fail(`${where}: no "${prefix}" line; this does not look like a probe report`); + process.exit(2); + } + return found.trim(); + }; + const profile = line("profile:"); + // Agreement is not enough on its own: three debug reports agree with each + // other, and the probe stamps a debug run "NOT A MEASUREMENT" precisely + // because its figures are not one. A capture built from them would be + // internally consistent and meaningless. + if (profile !== "profile: release") { + fail(`${where}: ${profile} -- only a release run is a measurement`); + process.exit(2); + } + // Included because it can differ while the host banner does not: an affinity + // mask changes how many processors the process may use without changing the + // machine it names, and producer counts are read against that number. + // The build that produced the run (`M4.8`). Optional, because runs taken + // before the probe printed it have none -- but present or absent it must + // agree across the inputs like the rest, so a set mixing two builds, or a + // stamped run with an unstamped one, is refused. + const build = text.split(/\r?\n/).find((l) => l.startsWith("build:")); + return [ + line("host:"), + build === undefined ? "build: (not stamped)" : build.trim(), + profile, + line("sampling:"), + line("processors available to this process:"), + ].join(" | "); +} + +function requireOneConfiguration(entries) { + // **The build is part of what must agree, since `M4.8`.** Before it, two runs + // of DIFFERENT probe commits on one host under one profile agreed here, and a + // pre-handshake and a post-handshake run would have had their medians + // combined as though one procedure produced both. + // + // **What this still cannot check.** A DIRTY build names its commit, not its + // tree, so two dirty builds at one commit agree here while their sources may + // differ; and runs from before the probe printed a build agree with each + // other as "not stamped". For both, the capture README is still the only + // record of what was built. + const first = entries[0]; + for (const entry of entries.slice(1)) { + if (entry.attribution !== first.attribution) { + fail( + `${entry.name} was taken under different conditions from ${first.name}:\n` + + ` ${first.name}: ${first.attribution}\n` + + ` ${entry.name}: ${entry.attribution}`, + ); + process.exit(2); + } + } +} + +const COUNTS = [1, 2, 4, 8, 16, 32]; +const DEFAULT_LAYOUT = "reserving(32/32)"; +// Same code as DEFAULT_LAYOUT, under the shipping type's own name. +const CONTROL_TWIN = "reserving_mpsc"; +const LAYOUTS = ["reserving(16/48)", "reserving(8/56)", "reserving(64/64)"]; + +// The isolated raw table only. The drained table repeats every shape name, so a +// whole-file scan would silently average the two regimes together. +function isolatedRows(file) { + const lines = fs.readFileSync(file, "utf8").split("\n"); + const start = lines.findIndex((l) => l.startsWith("-- isolated:")); + const end = lines.findIndex((l) => l.startsWith("-- drained:")); + if (start < 0 || end < 0 || end <= start) { + throw new Error(`${file}: expected an isolated marker followed by a drained one`); + } + const rows = new Map(); + for (const line of lines.slice(start, end)) { + const m = line.match(/^(\S+)\s+(\d+)\s+([\d.]+)\s/); + if (m) { + const key = `${m[1]}@${m[2]}`; + // A `Map` keeps the last write. A run carrying the whole expected producer + // set plus one duplicated row would pass every completeness check while + // one measurement was silently discarded in favour of another. + if (rows.has(key)) { + throw new Error(`${file}: a second isolated row for ${key}`); + } + rows.set(key, Number(m[3])); + } + } + // Every shape this script reads must appear at exactly the swept producer + // counts, and no *layout* it does not read may appear at all. `ratios()` looks + // up only the counts in `COUNTS` for only the shapes it knows, so both an + // extra count and an unrecognised layout would sit in the map unread while the + // summary reported success over a capture it had only partly used. + // + // Scoped to `reserving(...)` rows deliberately: the isolated table also + // carries `baseline_fetch_add`, `slotwise_mpsc` and `permit_mpsc`, which this + // script does not derive from and which are not anomalies. An earlier version + // rejected every unknown shape and refused the committed capture on its first + // row. + const known = [DEFAULT_LAYOUT, CONTROL_TWIN, ...LAYOUTS]; + for (const key of rows.keys()) { + const shape = key.slice(0, key.lastIndexOf("@")); + if (shape.startsWith("reserving(") && !known.includes(shape)) { + throw new Error(`${file}: unexpected claim-word layout ${shape}`); + } + } + for (const shape of known) { + const seen = [...rows.keys()] + .filter((k) => k.startsWith(`${shape}@`)) + .map((k) => Number(k.slice(shape.length + 1))) + .sort((a, b) => a - b); + if (seen.length !== 0 && seen.join(",") !== COUNTS.join(",")) { + throw new Error( + `${file}: ${shape} covers producers [${seen}], expected [${COUNTS}]`, + ); + } + } + return rows; +} + +requireOneConfiguration( + files.map((f) => ({ name: f, attribution: attribution(fs.readFileSync(f, "utf8"), f) })), +); +const tables = files.map(isolatedRows); + +function ratios(numerator, denominator) { + const out = new Map(); + for (const n of COUNTS) { + out.set( + n, + tables.map((t, i) => { + const a = t.get(`${numerator}@${n}`); + const b = t.get(`${denominator}@${n}`); + // A missing or unusable row is an error, not a skipped count: silently + // dropping one would quietly narrow every range printed below. + // Both operands must be positive, not merely finite and the denominator + // non-zero: a zero or negative numerator divides cleanly and publishes + // an ordinary-looking `0.00x`. The probe's did-not-run sentinel used to + // be `0.0`, so that is the shape a legacy capture actually takes. + if (!Number.isFinite(a) || !Number.isFinite(b) || a <= 0 || b <= 0) { + throw new Error(`${files[i]}: no usable ${numerator}/${denominator} at ${n} producers`); + } + return a / b; + }), + ); + } + return out; +} + +// The middle of an odd set, and the mean of the two middle values otherwise -- +// matching `summarise.js`. The CLI takes any number of runs, and picking the +// upper-middle for an even set would report the slower of two runs as their +// median, which is a different statistic under the same name. +const median = (xs) => { + const sorted = [...xs].sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +}; +const fmt = (x) => x.toFixed(2); +const cell = (xs) => `${fmt(median(xs))}x [${fmt(Math.min(...xs))}-${fmt(Math.max(...xs))}]`; + +// Same code under two names. The denominator is `DEFAULT_LAYOUT`, exactly as it +// is for every layout ratio above -- a control read against ratios computed the +// other way round is not a control, because these ranges are not symmetric about +// 1.00 and the reciprocal of [0.95-1.26] is [0.79-1.05]. `summarise.js` computes +// its control in this same direction. +const control = ratios(CONTROL_TWIN, DEFAULT_LAYOUT); +const measured = new Map(LAYOUTS.map((l) => [l, ratios(l, DEFAULT_LAYOUT)])); + +// Widths derived from the cells, not fixed. `cell()` renders a median and a +// range of measured ratios, which have no upper bound, so a fixed field shifts +// every column after it the first time a value outgrows it -- the same argument +// `column_width` makes for the Rust report's columns. +const COLUMNS = ["control", ...LAYOUTS.map((l) => l.replace("reserving", ""))]; +const body = COUNTS.map((n) => [cell(control.get(n)), ...LAYOUTS.map((l) => cell(measured.get(l).get(n)))]); +// The widths this table has always used, kept as floors so the committed +// output is unchanged; the derivation only ever widens. +const width = COLUMNS.map((name, column) => + Math.max(18, name.length, ...body.map((row) => row[column].length)), +); +const PRODUCERS_WIDTH = Math.max(9, ...COUNTS.map((n) => String(n).length)); + +out(`isolated regime, ${files.length} run(s): ${files.join(", ")}`); +out(`each layout against ${DEFAULT_LAYOUT}; control is ${CONTROL_TWIN} against ${DEFAULT_LAYOUT}`); +out("median of the per-run ratios, with the observed range beside it\n"); + +out( + ["producers".padEnd(PRODUCERS_WIDTH + 2), ...COLUMNS.map((name, i) => name.padEnd(width[i] + 2))].join(""), +); +COUNTS.forEach((n, row) => { + out( + [ + String(n).padEnd(PRODUCERS_WIDTH + 2), + ...body[row].map((c, i) => c.padEnd(width[i] + 2)), + ].join(""), + ); +}); + +out("\nwhere every run sat above the control's whole observed range:"); +for (const l of LAYOUTS) { + const above = COUNTS.filter((n) => { + const top = Math.max(...control.get(n)); + return measured.get(l).get(n).every((x) => x > top); + }); + out(` ${l.padEnd(18)} ${above.length ? above.join(", ") + " producers" : "no producer count"}`); +} + +out( + `\nThe control's range here is ${files.length} observation(s) per count, which is not\n` + + "a band. This reports what these runs did; it does not establish that a fresh\n" + + "run would land the same way.", +); diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-drained.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-drained.txt new file mode 100644 index 000000000..6561ae774 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-drained.txt @@ -0,0 +1,16 @@ +runs: 7 + +drained, per producer count: the same-code control's observed range, then +each layout's median ratio against 32/32, across runs. + +producers control(n) 16/48 8/56 64/64 + 1 0.93-1.11(7) 1.14x 1.07x 1.06x + 2 0.87-1.07(7) 1.03x 1.04x 1.05x + 4 0.98-1.09(7) 0.97x 0.99x 1.09x + 8 0.97-1.05(7) 1.02x 1.03x 1.09x + 16 0.90-1.17(7) 0.94x 1.06x 0.93x + 32 0.80-1.17(7) 1.01x 1.02x 0.89x + +control observations: 42 across 6 producer counts, 0.80x to 1.17x pooled +Pooled only to show the spread; it is not a band to judge a median against, +for the reason recorded in this script beside the table above. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-isolated.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-isolated.txt new file mode 100644 index 000000000..a399c3d54 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-isolated.txt @@ -0,0 +1,20 @@ +isolated regime, 7 run(s): sequential-run1.txt, sequential-run2.txt, sequential-run3.txt, sequential-run4.txt, sequential-run5.txt, sequential-run6.txt, sequential-run7.txt +each layout against reserving(32/32); control is reserving_mpsc against reserving(32/32) +median of the per-run ratios, with the observed range beside it + +producers control (16/48) (8/56) (64/64) +1 1.00x [0.66-1.09] 0.98x [0.67-1.00] 1.00x [0.79-1.04] 1.39x [0.85-1.39] +2 0.98x [0.83-1.04] 0.96x [0.93-1.07] 0.93x [0.87-1.03] 1.18x [1.08-1.22] +4 1.07x [0.96-1.13] 1.04x [0.96-1.11] 1.03x [0.95-1.13] 1.45x [1.33-1.77] +8 0.96x [0.91-1.07] 1.04x [0.91-1.38] 1.00x [0.87-1.27] 1.57x [1.45-2.30] +16 1.01x [0.89-1.19] 1.30x [1.14-1.37] 1.32x [1.18-1.44] 3.66x [2.85-4.47] +32 0.96x [0.89-1.06] 1.31x [1.17-1.37] 1.27x [1.10-1.35] 3.91x [3.66-4.89] + +where every run sat above the control's whole observed range: + reserving(16/48) 32 producers + reserving(8/56) 32 producers + reserving(64/64) 2, 4, 8, 16, 32 producers + +The control's range here is 7 observation(s) per count, which is not +a band. This reports what these runs did; it does not establish that a fresh +run would land the same way. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run1.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run1.txt new file mode 100644 index 000000000..21ce8fae2 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run1.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432152118 0 2.3-2.4 1.03x +slotwise_mpsc 1 6.3 158579131 0 6.2-6.3 1.02x +reserving_mpsc 1 5.5 180180180 0 5.4-6.7 1.25x +permit_mpsc 1 7.9 126968004 0 7.8-8.8 1.12x +reserving(32/32) 1 5.4 184569952 0 5.4-6.9 1.27x +reserving(16/48) 1 5.4 186497576 0 5.4-5.8 1.09x +reserving(8/56) 1 5.4 186846039 0 5.3-5.7 1.07x +reserving(64/64) 1 7.5 132590825 0 7.5-8.0 1.08x +baseline_fetch_add 2 12.1 82836315 0 11.3-14.1 1.25x +slotwise_mpsc 2 55.0 18176861 0 47.8-58.1 1.22x +reserving_mpsc 2 32.9 30408076 0 31.7-35.1 1.11x +permit_mpsc 2 43.5 22994849 0 40.9-44.4 1.08x +reserving(32/32) 2 33.1 30231574 0 27.0-35.8 1.33x +reserving(16/48) 2 34.7 28787748 0 31.3-36.8 1.17x +reserving(8/56) 2 34.0 29381519 0 32.3-35.0 1.08x +reserving(64/64) 2 39.4 25406504 0 37.7-42.9 1.14x +baseline_fetch_add 4 12.8 77918030 0 12.2-14.7 1.21x +slotwise_mpsc 4 90.7 11025237 0 85.3-128.0 1.50x +reserving_mpsc 4 34.4 29091332 0 33.5-36.0 1.07x +permit_mpsc 4 31.8 31492591 0 31.1-32.2 1.04x +reserving(32/32) 4 35.9 27850886 0 33.4-37.3 1.12x +reserving(16/48) 4 38.0 26340050 0 32.4-40.2 1.24x +reserving(8/56) 4 34.2 29255591 0 29.9-36.8 1.23x +reserving(64/64) 4 63.4 15766032 0 41.5-64.5 1.56x +baseline_fetch_add 8 14.3 69904406 0 13.7-15.4 1.12x +slotwise_mpsc 8 141.5 7064816 0 130.1-146.2 1.12x +reserving_mpsc 8 39.3 25454522 0 38.6-41.6 1.08x +permit_mpsc 8 25.1 39779621 0 23.6-26.0 1.10x +reserving(32/32) 8 41.6 24056678 0 35.1-54.2 1.54x +reserving(16/48) 8 39.8 25116320 0 33.8-43.3 1.28x +reserving(8/56) 8 41.3 24197256 0 32.9-41.9 1.28x +reserving(64/64) 8 63.1 15850310 0 51.8-71.2 1.37x +baseline_fetch_add 16 15.5 64585923 0 15.1-16.2 1.07x +slotwise_mpsc 16 226.2 4421661 0 199.4-234.5 1.18x +reserving_mpsc 16 44.8 22315015 0 41.4-51.5 1.24x +permit_mpsc 16 21.6 46342424 0 20.5-23.9 1.17x +reserving(32/32) 16 50.1 19945002 0 48.0-54.6 1.14x +reserving(16/48) 16 57.3 17443141 0 50.1-59.9 1.19x +reserving(8/56) 16 59.9 16696616 0 52.9-70.0 1.32x +reserving(64/64) 16 177.0 5648385 0 136.8-217.5 1.59x +baseline_fetch_add 32 15.7 63603875 0 15.4-16.4 1.07x +slotwise_mpsc 32 231.5 4319810 0 207.1-234.2 1.13x +reserving_mpsc 32 50.9 19629301 0 45.2-51.8 1.15x +permit_mpsc 32 22.5 44504154 0 21.5-24.2 1.13x +reserving(32/32) 32 47.8 20929423 0 44.7-49.2 1.10x +reserving(16/48) 32 62.5 16004289 0 56.2-64.0 1.14x +reserving(8/56) 32 64.4 15516626 0 58.1-68.4 1.18x +reserving(64/64) 32 190.4 5250817 0 176.2-210.4 1.19x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.1 98561009 32 9.5-10.7 1.12x +reserving_mpsc 1 25.0 40054474 635 22.4-31.9 1.42x +permit_mpsc 1 60.8 16444123 222 59.4-62.0 1.04x +reserving(32/32) 1 26.8 37310648 0 23.6-29.1 1.24x +reserving(16/48) 1 29.0 34497033 2060 20.8-30.2 1.45x +reserving(8/56) 1 25.0 40057683 813 23.3-27.6 1.18x +reserving(64/64) 1 26.8 37330148 223 25.5-29.9 1.17x +slotwise_mpsc 2 74.4 13446101 300 66.0-76.8 1.16x +reserving_mpsc 2 69.1 14481001 841 68.0-71.1 1.05x +permit_mpsc 2 55.1 18152115 183 52.9-56.0 1.06x +reserving(32/32) 2 65.1 15357916 1930 63.4-69.0 1.09x +reserving(16/48) 2 67.1 14893807 296 65.5-69.0 1.05x +reserving(8/56) 2 67.7 14771485 16 65.2-69.9 1.07x +reserving(64/64) 2 68.6 14569614 30 66.4-73.0 1.10x +slotwise_mpsc 4 111.4 8978353 103 105.2-113.1 1.08x +reserving_mpsc 4 99.0 10103613 0 94.9-100.2 1.06x +permit_mpsc 4 84.9 11776136 91178 58.9-93.7 1.59x +reserving(32/32) 4 96.9 10321568 221 95.7-98.7 1.03x +reserving(16/48) 4 96.6 10353146 1510 92.6-102.4 1.11x +reserving(8/56) 4 98.5 10154655 778 84.6-104.8 1.24x +reserving(64/64) 4 108.7 9197263 0 100.0-115.4 1.15x +slotwise_mpsc 8 152.7 6548317 36713 144.7-185.9 1.28x +reserving_mpsc 8 165.4 6044277 0 155.7-177.4 1.14x +permit_mpsc 8 144.1 6937651 568670 62.0-206.6 3.33x +reserving(32/32) 8 158.1 6327102 1243 151.6-160.1 1.06x +reserving(16/48) 8 161.1 6208221 0 151.7-202.7 1.34x +reserving(8/56) 8 154.5 6472481 121816 145.0-157.1 1.08x +reserving(64/64) 8 156.5 6390859 48573 152.3-169.5 1.11x +slotwise_mpsc 16 297.3 3363415 2266036 196.8-435.2 2.21x +reserving_mpsc 16 392.2 2549863 4795382 313.6-396.1 1.26x +permit_mpsc 16 315.2 3173090 3576769 269.0-387.7 1.44x +reserving(32/32) 16 334.3 2991500 3605128 259.2-390.5 1.51x +reserving(16/48) 16 304.9 3279944 2571762 240.4-342.9 1.43x +reserving(8/56) 16 288.6 3465130 2128904 227.1-300.6 1.32x +reserving(64/64) 16 302.3 3308339 1964405 272.6-397.3 1.46x +slotwise_mpsc 32 688.9 1451493 25470031 486.2-893.3 1.84x +reserving_mpsc 32 613.7 1629505 22033980 472.6-1026.9 2.17x +permit_mpsc 32 438.4 2281237 9978158 407.0-558.0 1.37x +reserving(32/32) 32 768.5 1301295 29849121 622.9-929.0 1.49x +reserving(16/48) 32 665.5 1502723 27218406 512.3-984.7 1.92x +reserving(8/56) 32 787.0 1270593 29326468 648.2-975.9 1.51x +reserving(64/64) 32 648.2 1542676 19617874 543.4-682.9 1.26x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.11x [0.11-0.13] 0.17x [0.15-0.21] 0.18x [0.18-0.21] 0.19x [0.16-0.21] + 4 0.07x [0.05-0.07] 0.16x [0.15-0.20] 0.25x [0.24-0.28] 0.18x [0.16-0.20] + 8 0.04x [0.04-0.05] 0.14x [0.13-0.17] 0.31x [0.30-0.37] 0.16x [0.15-0.17] + 16 0.03x [0.03-0.03] 0.12x [0.11-0.16] 0.36x [0.33-0.43] 0.15x [0.14-0.16] + 32 0.03x [0.03-0.03] 0.11x [0.10-0.15] 0.35x [0.32-0.41] 0.15x [0.14-0.15] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.1 25.0 2.46x [2.09-3.34] 60.8 2.44x [1.87-2.77] + 2 74.4 69.1 0.93x [0.89-1.08] 55.1 0.80x [0.74-0.82] + 4 111.4 99.0 0.89x [0.84-0.95] 84.9 0.86x [0.59-0.99] + 8 152.7 165.4 1.08x [0.84-1.23] 144.1 0.87x [0.35-1.33] + 16 297.3 392.2 1.32x [0.72-2.01] 315.2 0.80x [0.68-1.24] + 32 688.9 613.7 0.89x [0.53-2.11] 438.4 0.71x [0.40-1.18] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.4 7.5 0.99x [0.78-1.08] 0.99x [0.78-1.05] 1.39x [1.09-1.49] + 2 33.1 34.7 34.0 39.4 1.05x [0.87-1.36] 1.03x [0.90-1.30] 1.19x [1.05-1.59] + 4 35.9 38.0 34.2 63.4 1.06x [0.87-1.20] 0.95x [0.80-1.10] 1.77x [1.11-1.93] + 8 41.6 39.8 41.3 63.1 0.96x [0.62-1.23] 0.99x [0.61-1.19] 1.52x [0.96-2.03] + 16 50.1 57.3 59.9 177.0 1.14x [0.92-1.25] 1.19x [0.97-1.46] 3.53x [2.50-4.53] + 32 47.8 62.5 64.4 190.4 1.31x [1.14-1.43] 1.35x [1.18-1.53] 3.99x [3.58-4.71] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 26.8 29.0 25.0 26.8 1.08x [0.71-1.28] 0.93x [0.80-1.17] 1.00x [0.88-1.27] + 2 65.1 67.1 67.7 68.6 1.03x [0.95-1.09] 1.04x [0.95-1.10] 1.05x [0.96-1.15] + 4 96.9 96.6 98.5 108.7 1.00x [0.94-1.07] 1.02x [0.86-1.10] 1.12x [1.01-1.21] + 8 158.1 161.1 154.5 156.5 1.02x [0.95-1.34] 0.98x [0.91-1.04] 0.99x [0.95-1.12] + 16 334.3 304.9 288.6 302.3 0.91x [0.62-1.32] 0.86x [0.58-1.16] 0.90x [0.70-1.53] + 32 768.5 665.5 787.0 648.2 0.87x [0.55-1.58] 1.02x [0.70-1.57] 0.84x [0.58-1.10] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run2.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run2.txt new file mode 100644 index 000000000..d6d71be90 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run2.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433275563 0 2.3-2.3 1.01x +slotwise_mpsc 1 6.3 158077774 0 6.3-6.3 1.00x +reserving_mpsc 1 5.5 182149362 0 5.4-5.8 1.07x +permit_mpsc 1 8.0 124501992 0 7.9-8.4 1.07x +reserving(32/32) 1 6.0 167785235 0 5.4-6.1 1.12x +reserving(16/48) 1 5.3 186985789 0 5.3-6.5 1.22x +reserving(8/56) 1 5.5 183486239 0 5.4-5.7 1.06x +reserving(64/64) 1 8.0 125219133 0 7.5-8.2 1.09x +baseline_fetch_add 2 11.5 87252421 0 8.9-12.3 1.38x +slotwise_mpsc 2 54.4 18391819 0 42.1-58.0 1.38x +reserving_mpsc 2 35.5 28153946 0 30.8-36.2 1.17x +permit_mpsc 2 42.8 23341036 0 40.4-45.5 1.13x +reserving(32/32) 2 34.1 29341862 0 31.8-35.5 1.12x +reserving(16/48) 2 32.4 30842303 0 25.0-37.1 1.48x +reserving(8/56) 2 31.7 31524857 0 30.2-34.4 1.14x +reserving(64/64) 2 37.7 26524495 0 35.7-40.2 1.13x +baseline_fetch_add 4 14.1 70896845 0 13.2-16.2 1.22x +slotwise_mpsc 4 87.2 11465983 0 77.9-99.0 1.27x +reserving_mpsc 4 38.4 26049807 0 33.1-39.5 1.19x +permit_mpsc 4 32.0 31284706 0 31.8-32.1 1.01x +reserving(32/32) 4 34.8 28737284 0 34.1-41.5 1.22x +reserving(16/48) 4 38.5 25959866 0 35.6-69.1 1.94x +reserving(8/56) 4 39.2 25482576 0 35.2-40.0 1.14x +reserving(64/64) 4 46.3 21618817 0 42.3-58.1 1.37x +baseline_fetch_add 8 14.7 67986743 0 14.1-15.6 1.10x +slotwise_mpsc 8 138.5 7220360 0 133.8-143.1 1.07x +reserving_mpsc 8 41.0 24376421 0 35.1-44.6 1.27x +permit_mpsc 8 25.8 38767954 0 24.5-26.2 1.07x +reserving(32/32) 8 42.7 23434159 0 37.1-44.9 1.21x +reserving(16/48) 8 48.3 20703184 0 38.5-52.6 1.37x +reserving(8/56) 8 45.9 21796583 0 41.4-51.3 1.24x +reserving(64/64) 8 87.1 11481188 0 67.7-94.0 1.39x +baseline_fetch_add 16 14.7 67899034 0 14.5-14.8 1.02x +slotwise_mpsc 16 223.6 4472802 0 177.8-244.9 1.38x +reserving_mpsc 16 49.6 20165407 0 47.4-56.3 1.19x +permit_mpsc 16 20.1 49734542 0 19.8-20.2 1.02x +reserving(32/32) 16 49.3 20289326 0 46.1-51.4 1.11x +reserving(16/48) 16 65.8 15191162 0 58.6-69.0 1.18x +reserving(8/56) 16 58.2 17194576 0 55.7-62.7 1.13x +reserving(64/64) 16 140.4 7123427 0 129.4-196.4 1.52x +baseline_fetch_add 32 14.9 67049407 0 14.5-15.4 1.07x +slotwise_mpsc 32 236.2 4233662 0 201.2-246.7 1.23x +reserving_mpsc 32 49.2 20314004 0 48.6-50.8 1.05x +permit_mpsc 32 21.5 46551955 0 21.4-21.5 1.01x +reserving(32/32) 32 52.7 18988844 0 48.3-53.6 1.11x +reserving(16/48) 32 61.5 16252301 0 60.5-66.5 1.10x +reserving(8/56) 32 58.0 17252032 0 56.2-69.4 1.23x +reserving(64/64) 32 193.9 5157038 0 154.8-213.9 1.38x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.3 97106234 6 10.1-10.8 1.07x +reserving_mpsc 1 25.7 38910506 0 21.7-28.2 1.30x +permit_mpsc 1 60.2 16617368 223 58.2-62.5 1.07x +reserving(32/32) 1 24.5 40846336 1227 22.8-25.5 1.12x +reserving(16/48) 1 26.7 37500938 2049 24.5-31.2 1.27x +reserving(8/56) 1 28.0 35670971 1818 24.4-32.3 1.33x +reserving(64/64) 1 25.9 38556447 7 25.7-26.9 1.05x +slotwise_mpsc 2 70.7 14146673 249 64.8-76.8 1.18x +reserving_mpsc 2 66.4 15067729 353 62.5-69.6 1.11x +permit_mpsc 2 53.7 18636550 245 51.2-54.3 1.06x +reserving(32/32) 2 62.2 16075361 455 61.3-67.0 1.09x +reserving(16/48) 2 64.9 15398118 89 60.9-68.5 1.12x +reserving(8/56) 2 66.3 15071590 1398 51.0-71.8 1.41x +reserving(64/64) 2 66.1 15134088 2057 62.3-67.8 1.09x +slotwise_mpsc 4 111.8 8941305 0 103.5-112.6 1.09x +reserving_mpsc 4 95.3 10489657 4146 90.7-96.8 1.07x +permit_mpsc 4 78.4 12762265 84270 74.4-86.9 1.17x +reserving(32/32) 4 96.7 10344150 14 84.9-104.7 1.23x +reserving(16/48) 4 97.1 10297919 1324 89.6-101.1 1.13x +reserving(8/56) 4 97.4 10266572 0 90.9-98.3 1.08x +reserving(64/64) 4 104.7 9549457 0 102.4-109.2 1.07x +slotwise_mpsc 8 149.9 6672516 0 139.0-154.1 1.11x +reserving_mpsc 8 161.3 6200349 11054 147.9-169.4 1.14x +permit_mpsc 8 130.8 7647657 538321 125.0-143.3 1.15x +reserving(32/32) 8 159.6 6264026 441 154.7-166.6 1.08x +reserving(16/48) 8 154.8 6460042 67771 140.1-185.1 1.32x +reserving(8/56) 8 157.6 6345339 785 151.1-165.4 1.09x +reserving(64/64) 8 172.1 5811175 4845 167.6-192.4 1.15x +slotwise_mpsc 16 303.5 3294902 1947492 288.4-341.4 1.18x +reserving_mpsc 16 306.2 3265418 3075729 242.5-348.2 1.44x +permit_mpsc 16 231.4 4320863 2498146 221.2-285.4 1.29x +reserving(32/32) 16 296.0 3377861 3609207 204.4-377.2 1.85x +reserving(16/48) 16 275.1 3634992 2100939 203.3-346.9 1.71x +reserving(8/56) 16 328.2 3047383 2331583 321.6-405.9 1.26x +reserving(64/64) 16 278.0 3597598 973699 241.0-326.9 1.36x +slotwise_mpsc 32 659.3 1516802 19927052 520.7-953.5 1.83x +reserving_mpsc 32 674.3 1483124 21627298 584.2-772.9 1.32x +permit_mpsc 32 433.4 2307178 10302936 403.4-526.9 1.31x +reserving(32/32) 32 757.3 1320534 32068450 538.4-901.3 1.67x +reserving(16/48) 32 767.4 1303141 32421500 452.6-949.9 2.10x +reserving(8/56) 32 568.5 1759082 17735465 493.0-634.1 1.29x +reserving(64/64) 32 584.9 1709769 17342780 538.5-696.5 1.29x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.15] 0.15x [0.15-0.19] 0.19x [0.17-0.21] 0.20x [0.19-0.26] + 4 0.07x [0.06-0.08] 0.14x [0.14-0.17] 0.25x [0.24-0.27] 0.16x [0.14-0.18] + 8 0.05x [0.04-0.05] 0.13x [0.12-0.17] 0.31x [0.30-0.34] 0.16x [0.15-0.16] + 16 0.03x [0.03-0.04] 0.11x [0.10-0.12] 0.40x [0.39-0.43] 0.16x [0.15-0.16] + 32 0.03x [0.03-0.03] 0.11x [0.11-0.12] 0.37x [0.36-0.39] 0.15x [0.15-0.16] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.3 25.7 2.50x [2.01-2.79] 60.2 2.34x [2.06-2.87] + 2 70.7 66.4 0.94x [0.81-1.07] 53.7 0.81x [0.74-0.87] + 4 111.8 95.3 0.85x [0.81-0.94] 78.4 0.82x [0.77-0.96] + 8 149.9 161.3 1.08x [0.96-1.22] 130.8 0.81x [0.74-0.97] + 16 303.5 306.2 1.01x [0.71-1.21] 231.4 0.76x [0.64-1.18] + 32 659.3 674.3 1.02x [0.61-1.48] 433.4 0.64x [0.52-0.90] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 6.0 5.3 5.5 8.0 0.90x [0.88-1.21] 0.91x [0.88-1.04] 1.34x [1.23-1.51] + 2 34.1 32.4 31.7 37.7 0.95x [0.70-1.17] 0.93x [0.85-1.08] 1.11x [1.00-1.26] + 4 34.8 38.5 39.2 46.3 1.11x [0.86-2.03] 1.13x [0.85-1.17] 1.33x [1.02-1.70] + 8 42.7 48.3 45.9 87.1 1.13x [0.86-1.42] 1.08x [0.92-1.38] 2.04x [1.51-2.53] + 16 49.3 65.8 58.2 140.4 1.34x [1.14-1.50] 1.18x [1.08-1.36] 2.85x [2.52-4.26] + 32 52.7 61.5 58.0 193.9 1.17x [1.13-1.38] 1.10x [1.05-1.44] 3.68x [2.89-4.43] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 24.5 26.7 28.0 25.9 1.09x [0.96-1.37] 1.15x [0.95-1.42] 1.06x [1.01-1.18] + 2 62.2 64.9 66.3 66.1 1.04x [0.91-1.12] 1.07x [0.76-1.17] 1.06x [0.93-1.11] + 4 96.7 97.1 97.4 104.7 1.00x [0.86-1.19] 1.01x [0.87-1.16] 1.08x [0.98-1.29] + 8 159.6 154.8 157.6 172.1 0.97x [0.84-1.20] 0.99x [0.91-1.07] 1.08x [1.01-1.24] + 16 296.0 275.1 328.2 278.0 0.93x [0.54-1.70] 1.11x [0.85-1.99] 0.94x [0.64-1.60] + 32 757.3 767.4 568.5 584.9 1.01x [0.50-1.76] 0.75x [0.55-1.18] 0.77x [0.60-1.29] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run3.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run3.txt new file mode 100644 index 000000000..65258840e --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run3.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.6 381679389 0 2.5-2.8 1.13x +slotwise_mpsc 1 6.7 148898154 0 6.4-9.0 1.42x +reserving_mpsc 1 5.9 168123739 0 5.4-8.2 1.52x +permit_mpsc 1 9.2 109098844 0 8.6-9.4 1.10x +reserving(32/32) 1 8.9 112082493 0 5.9-9.7 1.65x +reserving(16/48) 1 6.0 166444740 0 5.4-6.2 1.16x +reserving(8/56) 1 7.0 143843498 0 5.6-9.8 1.74x +reserving(64/64) 1 7.6 131302521 0 7.5-9.4 1.26x +baseline_fetch_add 2 10.5 94912680 0 9.7-12.1 1.24x +slotwise_mpsc 2 50.0 20016814 0 48.5-57.4 1.18x +reserving_mpsc 2 32.1 31176929 0 21.6-35.9 1.67x +permit_mpsc 2 43.5 22994849 0 36.7-44.6 1.22x +reserving(32/32) 2 33.1 30167733 0 29.0-35.9 1.24x +reserving(16/48) 2 31.7 31566653 0 21.8-38.3 1.75x +reserving(8/56) 2 33.2 30144089 0 31.1-37.1 1.19x +reserving(64/64) 2 39.1 25568908 0 35.9-40.2 1.12x +baseline_fetch_add 4 14.1 71174377 0 13.5-14.9 1.10x +slotwise_mpsc 4 88.2 11342755 0 85.5-101.6 1.19x +reserving_mpsc 4 37.5 26678049 0 33.0-41.2 1.25x +permit_mpsc 4 33.4 29953572 0 31.8-34.1 1.07x +reserving(32/32) 4 37.5 26645705 0 35.6-38.5 1.08x +reserving(16/48) 4 37.5 26631868 0 33.9-58.3 1.72x +reserving(8/56) 4 37.6 26603528 0 35.4-38.6 1.09x +reserving(64/64) 4 51.3 19499449 0 42.5-51.3 1.21x +baseline_fetch_add 8 15.6 64233295 0 14.6-16.2 1.11x +slotwise_mpsc 8 143.2 6982375 0 127.4-148.3 1.16x +reserving_mpsc 8 41.3 24214687 0 39.2-44.3 1.13x +permit_mpsc 8 25.9 38592158 0 25.7-25.9 1.01x +reserving(32/32) 8 41.5 24084778 0 33.6-43.5 1.29x +reserving(16/48) 8 45.3 22063123 0 38.3-58.3 1.52x +reserving(8/56) 8 39.7 25188917 0 34.7-49.7 1.43x +reserving(64/64) 8 64.1 15599772 0 61.4-76.1 1.24x +baseline_fetch_add 16 14.7 67924400 0 14.3-14.9 1.04x +slotwise_mpsc 16 234.2 4269285 0 195.5-244.4 1.25x +reserving_mpsc 16 50.6 19766166 0 48.4-53.1 1.10x +permit_mpsc 16 24.0 41714900 0 19.9-24.5 1.23x +reserving(32/32) 16 46.9 21311397 0 45.3-51.0 1.12x +reserving(16/48) 16 57.6 17349552 0 51.3-62.0 1.21x +reserving(8/56) 16 64.8 15426177 0 57.0-69.0 1.21x +reserving(64/64) 16 171.6 5826339 0 138.7-193.8 1.40x +baseline_fetch_add 32 14.9 67197527 0 14.8-15.2 1.03x +slotwise_mpsc 32 203.7 4909061 0 187.6-244.0 1.30x +reserving_mpsc 32 48.5 20617627 0 47.4-52.3 1.10x +permit_mpsc 32 21.9 45587682 0 20.0-34.0 1.70x +reserving(32/32) 32 48.8 20473422 0 48.1-51.2 1.07x +reserving(16/48) 32 58.2 17190216 0 49.4-63.8 1.29x +reserving(8/56) 32 64.7 15460282 0 62.8-71.1 1.13x +reserving(64/64) 32 178.5 5602867 0 153.0-199.5 1.30x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 12.8 77821012 176 11.8-17.1 1.45x +reserving_mpsc 1 27.4 36552380 1021 20.1-43.5 2.17x +permit_mpsc 1 63.1 15854896 2709 59.2-65.5 1.11x +reserving(32/32) 1 26.9 37144343 987 20.2-27.7 1.37x +reserving(16/48) 1 30.6 32692559 2877 26.8-31.0 1.16x +reserving(8/56) 1 26.2 38197097 540 20.3-28.4 1.40x +reserving(64/64) 1 28.0 35762821 0 26.0-34.0 1.31x +slotwise_mpsc 2 61.4 16283463 2109 39.5-68.8 1.74x +reserving_mpsc 2 59.3 16850619 936 59.0-64.1 1.09x +permit_mpsc 2 54.2 18440658 756 51.5-56.9 1.10x +reserving(32/32) 2 68.5 14596196 0 66.7-70.0 1.05x +reserving(16/48) 2 68.5 14593640 31 62.5-70.1 1.12x +reserving(8/56) 2 65.9 15177731 245 64.1-67.8 1.06x +reserving(64/64) 2 70.0 14289593 1150 67.8-72.9 1.08x +slotwise_mpsc 4 106.6 9379103 967 91.6-110.1 1.20x +reserving_mpsc 4 97.3 10275011 1022 93.5-107.5 1.15x +permit_mpsc 4 75.7 13206114 71406 69.5-78.6 1.13x +reserving(32/32) 4 99.7 10031700 0 96.5-105.2 1.09x +reserving(16/48) 4 96.8 10332340 11302 92.3-100.0 1.08x +reserving(8/56) 4 98.3 10168440 310 94.0-102.5 1.09x +reserving(64/64) 4 110.6 9040733 17 89.7-124.9 1.39x +slotwise_mpsc 8 149.9 6673106 4304 145.7-157.9 1.08x +reserving_mpsc 8 163.0 6136005 332 159.2-168.4 1.06x +permit_mpsc 8 131.0 7633515 538171 111.5-155.0 1.39x +reserving(32/32) 8 161.2 6203907 3488 158.8-177.3 1.12x +reserving(16/48) 8 163.9 6102640 1497 156.7-177.6 1.13x +reserving(8/56) 8 166.6 6003095 2524 147.7-171.8 1.16x +reserving(64/64) 8 184.2 5427534 0 162.0-188.9 1.17x +slotwise_mpsc 16 263.2 3799291 873014 214.4-363.1 1.69x +reserving_mpsc 16 298.8 3346894 2292301 265.3-335.2 1.26x +permit_mpsc 16 255.8 3909703 2737667 182.0-309.8 1.70x +reserving(32/32) 16 298.8 3346328 2499162 241.3-352.5 1.46x +reserving(16/48) 16 305.7 3271130 2932394 232.7-381.7 1.64x +reserving(8/56) 16 279.0 3583743 1089581 230.9-403.4 1.75x +reserving(64/64) 16 277.3 3605943 1230372 236.0-390.6 1.66x +slotwise_mpsc 32 631.6 1583314 18161132 572.1-737.7 1.29x +reserving_mpsc 32 645.1 1550197 22470693 518.1-952.6 1.84x +permit_mpsc 32 480.1 2082829 11301109 327.4-519.8 1.59x +reserving(32/32) 32 659.7 1515904 23937372 546.0-787.5 1.44x +reserving(16/48) 32 752.8 1328441 31946866 513.4-1013.8 1.97x +reserving(8/56) 32 714.0 1400578 26448643 515.4-789.3 1.53x +reserving(64/64) 32 493.4 2026882 13159849 452.1-764.3 1.69x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.13x [0.11-0.19] 0.19x [0.15-0.38] 0.21x [0.19-0.26] 0.25x [0.21-0.29] + 4 0.08x [0.06-0.11] 0.16x [0.13-0.25] 0.27x [0.25-0.30] 0.19x [0.17-0.21] + 8 0.05x [0.04-0.07] 0.14x [0.12-0.21] 0.35x [0.33-0.37] 0.17x [0.16-0.19] + 16 0.03x [0.03-0.05] 0.12x [0.10-0.17] 0.38x [0.35-0.47] 0.18x [0.17-0.20] + 32 0.03x [0.03-0.05] 0.12x [0.10-0.17] 0.42x [0.25-0.47] 0.18x [0.17-0.19] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 12.8 27.4 2.13x [1.17-3.69] 63.1 2.31x [1.36-3.27] + 2 61.4 59.3 0.97x [0.86-1.62] 54.2 0.91x [0.80-0.96] + 4 106.6 97.3 0.91x [0.85-1.17] 75.7 0.78x [0.65-0.84] + 8 149.9 163.0 1.09x [1.01-1.16] 131.0 0.80x [0.66-0.97] + 16 263.2 298.8 1.14x [0.73-1.56] 255.8 0.86x [0.54-1.17] + 32 631.6 645.1 1.02x [0.70-1.67] 480.1 0.74x [0.34-1.00] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 8.9 6.0 7.0 7.6 0.67x [0.55-1.06] 0.78x [0.58-1.66] 0.85x [0.77-1.61] + 2 33.1 31.7 33.2 39.1 0.96x [0.61-1.32] 1.00x [0.87-1.28] 1.18x [1.00-1.39] + 4 37.5 37.5 37.6 51.3 1.00x [0.88-1.64] 1.00x [0.92-1.08] 1.37x [1.10-1.44] + 8 41.5 45.3 39.7 64.1 1.09x [0.88-1.73] 0.96x [0.80-1.48] 1.54x [1.41-2.26] + 16 46.9 57.6 64.8 171.6 1.23x [1.01-1.37] 1.38x [1.12-1.52] 3.66x [2.72-4.27] + 32 48.8 58.2 64.7 178.5 1.19x [0.97-1.33] 1.32x [1.23-1.48] 3.65x [2.99-4.15] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 26.9 30.6 26.2 28.0 1.14x [0.97-1.54] 0.97x [0.73-1.41] 1.04x [0.94-1.69] + 2 68.5 68.5 65.9 70.0 1.00x [0.89-1.05] 0.96x [0.92-1.02] 1.02x [0.97-1.09] + 4 99.7 96.8 98.3 110.6 0.97x [0.88-1.04] 0.99x [0.89-1.06] 1.11x [0.85-1.29] + 8 161.2 163.9 166.6 184.2 1.02x [0.88-1.12] 1.03x [0.83-1.08] 1.14x [0.91-1.19] + 16 298.8 305.7 279.0 277.3 1.02x [0.66-1.58] 0.93x [0.66-1.67] 0.93x [0.67-1.62] + 32 659.7 752.8 714.0 493.4 1.14x [0.65-1.86] 1.08x [0.65-1.45] 0.75x [0.57-1.40] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run4.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run4.txt new file mode 100644 index 000000000..960fbc36e --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run4.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432525952 0 2.3-2.9 1.27x +slotwise_mpsc 1 6.4 156788962 0 6.3-6.6 1.05x +reserving_mpsc 1 5.4 184433788 0 5.4-5.5 1.02x +permit_mpsc 1 7.9 127194098 0 7.8-7.9 1.01x +reserving(32/32) 1 5.4 184162063 0 5.4-6.1 1.12x +reserving(16/48) 1 5.4 186567164 0 5.4-6.0 1.12x +reserving(8/56) 1 5.4 186636805 0 5.4-6.2 1.15x +reserving(64/64) 1 7.5 133904660 0 7.5-9.8 1.31x +baseline_fetch_add 2 10.6 94073377 0 10.2-12.3 1.21x +slotwise_mpsc 2 51.5 19436346 0 49.8-57.0 1.15x +reserving_mpsc 2 29.5 33939723 0 28.3-38.3 1.35x +permit_mpsc 2 42.9 23290479 0 35.4-45.7 1.29x +reserving(32/32) 2 35.5 28167427 0 30.7-37.1 1.21x +reserving(16/48) 2 33.9 29465496 0 33.2-37.1 1.12x +reserving(8/56) 2 32.9 30371135 0 32.0-35.7 1.11x +reserving(64/64) 2 38.3 26115797 0 34.6-40.9 1.18x +baseline_fetch_add 4 13.4 74482348 0 12.8-14.2 1.10x +slotwise_mpsc 4 91.1 10973877 0 84.1-94.9 1.13x +reserving_mpsc 4 37.7 26555840 0 34.0-37.8 1.11x +permit_mpsc 4 31.7 31558185 0 31.0-31.8 1.03x +reserving(32/32) 4 37.9 26365398 0 36.5-41.8 1.15x +reserving(16/48) 4 36.5 27364648 0 34.5-39.9 1.16x +reserving(8/56) 4 36.7 27215698 0 31.8-37.1 1.17x +reserving(64/64) 4 53.8 18600153 0 51.4-57.4 1.12x +baseline_fetch_add 8 14.9 66953451 0 13.2-16.7 1.26x +slotwise_mpsc 8 142.5 7017002 0 135.7-173.3 1.28x +reserving_mpsc 8 39.8 25131311 0 38.4-44.8 1.17x +permit_mpsc 8 25.8 38757061 0 24.6-26.9 1.10x +reserving(32/32) 8 43.5 22972399 0 39.5-45.6 1.15x +reserving(16/48) 8 43.4 23017476 0 32.8-68.6 2.09x +reserving(8/56) 8 53.0 18852451 0 48.8-55.1 1.13x +reserving(64/64) 8 68.4 14628117 0 57.3-82.4 1.44x +baseline_fetch_add 16 14.7 67867929 0 14.3-15.4 1.07x +slotwise_mpsc 16 233.6 4280591 0 213.0-250.1 1.17x +reserving_mpsc 16 47.8 20902136 0 47.5-48.4 1.02x +permit_mpsc 16 20.1 49808859 0 19.8-23.2 1.17x +reserving(32/32) 16 47.1 21222974 0 43.4-53.6 1.24x +reserving(16/48) 16 61.8 16185125 0 57.0-68.9 1.21x +reserving(8/56) 16 64.2 15576172 0 59.9-66.5 1.11x +reserving(64/64) 16 210.5 4750509 0 197.1-236.4 1.20x +baseline_fetch_add 32 15.0 66650837 0 14.8-15.1 1.02x +slotwise_mpsc 32 234.6 4262234 0 222.6-248.8 1.12x +reserving_mpsc 32 48.8 20487999 0 46.2-50.3 1.09x +permit_mpsc 32 22.0 45466816 0 20.6-22.3 1.08x +reserving(32/32) 32 51.7 19355604 0 50.6-55.0 1.09x +reserving(16/48) 32 70.4 14210248 0 64.3-74.2 1.15x +reserving(8/56) 32 65.9 15182527 0 62.5-73.9 1.18x +reserving(64/64) 32 202.3 4942886 0 172.8-251.1 1.45x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 9.9 100867460 43 9.4-10.3 1.09x +reserving_mpsc 1 24.5 40776382 0 23.8-28.4 1.20x +permit_mpsc 1 61.3 16308425 554 57.2-62.4 1.09x +reserving(32/32) 1 22.1 45252964 802 21.3-25.4 1.20x +reserving(16/48) 1 29.8 33575074 4318 26.9-30.5 1.13x +reserving(8/56) 1 27.0 37102998 551 23.1-30.1 1.30x +reserving(64/64) 1 26.1 38281908 1381 25.7-27.6 1.07x +slotwise_mpsc 2 73.1 13682324 336 66.3-83.9 1.27x +reserving_mpsc 2 67.5 14812182 690 65.1-70.5 1.08x +permit_mpsc 2 53.6 18660546 28 52.0-54.9 1.05x +reserving(32/32) 2 67.4 14828655 187 64.4-71.0 1.10x +reserving(16/48) 2 68.9 14507892 125 66.3-71.3 1.08x +reserving(8/56) 2 68.2 14658243 720 66.3-69.7 1.05x +reserving(64/64) 2 65.8 15207506 80 63.2-70.0 1.11x +slotwise_mpsc 4 110.1 9083230 0 106.8-115.8 1.08x +reserving_mpsc 4 97.9 10216801 1 97.0-104.3 1.08x +permit_mpsc 4 83.1 12037678 88962 75.6-84.1 1.11x +reserving(32/32) 4 99.6 10037540 0 92.7-104.0 1.12x +reserving(16/48) 4 92.6 10800302 189 90.1-102.8 1.14x +reserving(8/56) 4 96.3 10383623 0 89.2-100.0 1.12x +reserving(64/64) 4 96.6 10349128 3212 88.4-111.4 1.26x +slotwise_mpsc 8 146.8 6811896 0 141.1-151.8 1.08x +reserving_mpsc 8 160.8 6219341 3658 155.5-167.9 1.08x +permit_mpsc 8 140.5 7119490 563585 117.2-145.1 1.24x +reserving(32/32) 8 159.6 6266135 0 158.2-169.6 1.07x +reserving(16/48) 8 168.0 5951380 0 159.1-181.2 1.14x +reserving(8/56) 8 165.9 6026502 182 149.4-168.8 1.13x +reserving(64/64) 8 169.0 5915620 0 162.2-184.1 1.14x +slotwise_mpsc 16 306.3 3264299 1954269 257.3-394.0 1.53x +reserving_mpsc 16 283.8 3523933 1068286 230.5-304.0 1.32x +permit_mpsc 16 289.0 3460227 3210414 203.7-385.3 1.89x +reserving(32/32) 16 313.5 3189555 2407790 273.3-382.2 1.40x +reserving(16/48) 16 263.0 3802435 1417807 236.7-335.8 1.42x +reserving(8/56) 16 292.2 3422485 1086248 267.7-363.1 1.36x +reserving(64/64) 16 272.7 3666670 951738 236.7-314.4 1.33x +slotwise_mpsc 32 675.5 1480432 21536603 454.1-777.6 1.71x +reserving_mpsc 32 661.8 1511125 21278808 593.9-741.1 1.25x +permit_mpsc 32 496.4 2014699 12000962 384.4-507.6 1.32x +reserving(32/32) 32 658.4 1518829 23291193 417.2-728.8 1.75x +reserving(16/48) 32 738.8 1353572 29284310 508.8-801.4 1.58x +reserving(8/56) 32 689.2 1450974 24384539 470.6-792.2 1.68x +reserving(64/64) 32 584.7 1710309 16576597 534.2-689.1 1.29x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.13] 0.18x [0.14-0.19] 0.18x [0.17-0.22] 0.22x [0.18-0.28] + 4 0.07x [0.07-0.08] 0.14x [0.14-0.16] 0.25x [0.25-0.25] 0.17x [0.16-0.22] + 8 0.04x [0.04-0.05] 0.14x [0.12-0.14] 0.30x [0.29-0.32] 0.15x [0.14-0.22] + 16 0.03x [0.03-0.03] 0.11x [0.11-0.12] 0.39x [0.34-0.40] 0.16x [0.15-0.20] + 32 0.03x [0.03-0.03] 0.11x [0.11-0.12] 0.36x [0.35-0.38] 0.15x [0.15-0.19] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 9.9 24.5 2.47x [2.32-3.02] 61.3 2.50x [2.01-2.63] + 2 73.1 67.5 0.92x [0.78-1.06] 53.6 0.79x [0.74-0.84] + 4 110.1 97.9 0.89x [0.84-0.98] 83.1 0.85x [0.72-0.87] + 8 146.8 160.8 1.10x [1.02-1.19] 140.5 0.87x [0.70-0.93] + 16 306.3 283.8 0.93x [0.58-1.18] 289.0 1.02x [0.67-1.67] + 32 675.5 661.8 0.98x [0.76-1.63] 496.4 0.75x [0.52-0.85] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.4 7.5 0.99x [0.88-1.11] 0.99x [0.88-1.14] 1.38x [1.23-1.81] + 2 35.5 33.9 32.9 38.3 0.96x [0.89-1.21] 0.93x [0.86-1.16] 1.08x [0.93-1.33] + 4 37.9 36.5 36.7 53.8 0.96x [0.82-1.09] 0.97x [0.76-1.02] 1.42x [1.23-1.57] + 8 43.5 43.4 53.0 68.4 1.00x [0.72-1.74] 1.22x [1.07-1.40] 1.57x [1.26-2.09] + 16 47.1 61.8 64.2 210.5 1.31x [1.06-1.59] 1.36x [1.12-1.53] 4.47x [3.68-5.45] + 32 51.7 70.4 65.9 202.3 1.36x [1.17-1.47] 1.27x [1.14-1.46] 3.92x [3.14-4.96] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 22.1 29.8 27.0 26.1 1.35x [1.06-1.43] 1.22x [0.91-1.41] 1.18x [1.01-1.30] + 2 67.4 68.9 68.2 65.8 1.02x [0.93-1.11] 1.01x [0.93-1.08] 0.98x [0.89-1.09] + 4 99.6 92.6 96.3 96.6 0.93x [0.87-1.11] 0.97x [0.86-1.08] 0.97x [0.85-1.20] + 8 159.6 168.0 165.9 169.0 1.05x [0.94-1.15] 1.04x [0.88-1.07] 1.06x [0.96-1.16] + 16 313.5 263.0 292.2 272.7 0.84x [0.62-1.23] 0.93x [0.70-1.33] 0.87x [0.62-1.15] + 32 658.4 738.8 689.2 584.7 1.12x [0.70-1.92] 1.05x [0.65-1.90] 0.89x [0.73-1.65] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run5.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run5.txt new file mode 100644 index 000000000..44af031f0 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run5.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 432152118 0 2.3-2.7 1.16x +slotwise_mpsc 1 6.3 157878118 0 6.2-6.4 1.03x +reserving_mpsc 1 5.4 184706317 0 5.4-5.5 1.02x +permit_mpsc 1 7.9 127194098 0 7.9-8.2 1.05x +reserving(32/32) 1 5.8 173370319 0 5.4-8.5 1.57x +reserving(16/48) 1 5.4 186428039 0 5.4-5.6 1.05x +reserving(8/56) 1 5.4 184433788 0 5.4-5.7 1.07x +reserving(64/64) 1 7.5 133797164 0 7.5-8.3 1.12x +baseline_fetch_add 2 12.0 83180835 0 8.7-13.7 1.58x +slotwise_mpsc 2 50.3 19886646 0 27.3-51.3 1.88x +reserving_mpsc 2 31.5 31787406 0 23.0-37.0 1.61x +permit_mpsc 2 43.6 22953679 0 39.8-44.5 1.12x +reserving(32/32) 2 36.5 27397260 0 34.7-37.9 1.09x +reserving(16/48) 2 33.8 29612082 0 31.3-38.7 1.24x +reserving(8/56) 2 31.9 31358776 0 28.6-34.1 1.19x +reserving(64/64) 2 39.6 25282532 0 38.9-39.6 1.02x +baseline_fetch_add 4 13.9 71945034 0 13.1-14.3 1.09x +slotwise_mpsc 4 89.7 11148210 0 85.1-123.1 1.45x +reserving_mpsc 4 38.5 25976725 0 32.6-39.5 1.21x +permit_mpsc 4 32.0 31201248 0 31.7-32.5 1.03x +reserving(32/32) 4 34.4 29071035 0 32.2-37.5 1.17x +reserving(16/48) 4 34.7 28827167 0 32.9-41.7 1.27x +reserving(8/56) 4 37.0 27054447 0 35.0-38.2 1.09x +reserving(64/64) 4 49.9 20060181 0 45.2-59.0 1.31x +baseline_fetch_add 8 14.7 68252397 0 13.8-14.7 1.07x +slotwise_mpsc 8 138.1 7242952 0 135.7-145.2 1.07x +reserving_mpsc 8 41.4 24178534 0 34.3-42.9 1.25x +permit_mpsc 8 25.0 39924543 0 24.7-25.7 1.04x +reserving(32/32) 8 43.3 23074704 0 39.5-46.0 1.16x +reserving(16/48) 8 39.4 25381194 0 37.0-47.7 1.29x +reserving(8/56) 8 37.7 26530829 0 34.7-48.5 1.40x +reserving(64/64) 8 62.8 15916597 0 53.0-69.2 1.31x +baseline_fetch_add 16 14.8 67491753 0 14.6-14.8 1.02x +slotwise_mpsc 16 204.4 4892305 0 201.9-265.1 1.31x +reserving_mpsc 16 52.3 19121099 0 50.7-54.6 1.08x +permit_mpsc 16 20.2 49610249 0 20.0-29.4 1.47x +reserving(32/32) 16 44.1 22662312 0 39.7-52.7 1.33x +reserving(16/48) 16 60.2 16617955 0 58.0-66.9 1.15x +reserving(8/56) 16 63.4 15784261 0 61.5-70.3 1.14x +reserving(64/64) 16 188.9 5293148 0 168.0-225.5 1.34x +baseline_fetch_add 32 14.9 67273530 0 14.3-15.2 1.06x +slotwise_mpsc 32 241.3 4143952 0 218.6-255.3 1.17x +reserving_mpsc 32 48.1 20783701 0 46.7-50.5 1.08x +permit_mpsc 32 21.7 46016549 0 20.7-21.8 1.05x +reserving(32/32) 32 54.0 18519805 0 53.8-63.3 1.18x +reserving(16/48) 32 71.8 13935656 0 66.6-72.9 1.10x +reserving(8/56) 32 65.5 15260259 0 62.0-66.4 1.07x +reserving(64/64) 32 210.5 4750985 0 139.3-244.1 1.75x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.3 97276265 51 10.1-10.9 1.08x +reserving_mpsc 1 23.2 43155533 277 21.7-26.9 1.24x +permit_mpsc 1 59.8 16719053 334 58.7-64.5 1.10x +reserving(32/32) 1 24.8 40394248 1346 19.7-28.6 1.45x +reserving(16/48) 1 28.9 34554250 0 27.9-31.4 1.13x +reserving(8/56) 1 24.9 40189695 1597 24.7-27.3 1.11x +reserving(64/64) 1 28.5 35055739 1433 27.4-29.6 1.08x +slotwise_mpsc 2 70.8 14129083 1554 66.6-81.8 1.23x +reserving_mpsc 2 69.0 14485826 16467 66.0-71.1 1.08x +permit_mpsc 2 53.5 18681811 855 51.6-56.1 1.09x +reserving(32/32) 2 65.5 15263680 564 61.1-67.9 1.11x +reserving(16/48) 2 69.2 14448362 334 66.3-71.2 1.07x +reserving(8/56) 2 67.4 14827336 5537 64.2-70.0 1.09x +reserving(64/64) 2 71.9 13902598 454 64.7-74.2 1.15x +slotwise_mpsc 4 110.0 9087728 582 102.4-111.0 1.08x +reserving_mpsc 4 100.0 10003601 225 91.2-101.8 1.12x +permit_mpsc 4 76.6 13062078 76955 65.3-83.8 1.28x +reserving(32/32) 4 102.2 9786555 0 97.9-107.9 1.10x +reserving(16/48) 4 101.4 9860960 0 97.1-102.6 1.06x +reserving(8/56) 4 95.2 10498963 161 89.1-100.8 1.13x +reserving(64/64) 4 111.3 8985897 788 104.1-113.2 1.09x +slotwise_mpsc 8 157.2 6361809 299 154.0-157.7 1.02x +reserving_mpsc 8 166.5 6005222 22 159.1-177.3 1.11x +permit_mpsc 8 142.1 7039180 539476 134.1-158.8 1.18x +reserving(32/32) 8 169.8 5887964 1542 154.0-174.3 1.13x +reserving(16/48) 8 161.8 6178961 1999 158.4-172.1 1.09x +reserving(8/56) 8 160.1 6244361 0 154.7-176.4 1.14x +reserving(64/64) 8 185.9 5379612 0 171.7-190.3 1.11x +slotwise_mpsc 16 286.6 3489176 1013667 208.1-336.6 1.62x +reserving_mpsc 16 261.5 3824063 523977 232.6-507.5 2.18x +permit_mpsc 16 226.3 4418259 2406246 216.4-273.9 1.27x +reserving(32/32) 16 283.5 3526990 1979128 242.8-387.9 1.60x +reserving(16/48) 16 266.3 3755239 2257468 198.7-354.3 1.78x +reserving(8/56) 16 326.8 3060094 3663708 251.5-344.7 1.37x +reserving(64/64) 16 327.9 3049464 2844205 235.6-382.2 1.62x +slotwise_mpsc 32 587.7 1701557 17109461 473.5-791.3 1.67x +reserving_mpsc 32 745.1 1342019 25943942 562.1-762.8 1.36x +permit_mpsc 32 465.3 2149073 11020413 438.1-583.2 1.33x +reserving(32/32) 32 706.9 1414646 28368996 552.6-830.9 1.50x +reserving(16/48) 32 674.1 1483430 27218934 441.0-711.9 1.61x +reserving(8/56) 32 619.1 1615151 22008727 516.1-891.1 1.73x +reserving(64/64) 32 638.0 1567321 18840576 531.7-734.4 1.38x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.13x [0.12-0.23] 0.17x [0.15-0.24] 0.18x [0.18-0.21] 0.19x [0.17-0.31] + 4 0.07x [0.05-0.08] 0.14x [0.14-0.17] 0.25x [0.24-0.26] 0.17x [0.16-0.20] + 8 0.05x [0.04-0.05] 0.13x [0.13-0.16] 0.31x [0.31-0.33] 0.16x [0.16-0.19] + 16 0.03x [0.02-0.03] 0.10x [0.10-0.11] 0.39x [0.27-0.41] 0.16x [0.15-0.18] + 32 0.03x [0.02-0.03] 0.11x [0.11-0.12] 0.36x [0.36-0.40] 0.16x [0.15-0.19] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.3 23.2 2.25x [1.98-2.67] 59.8 2.58x [2.18-2.98] + 2 70.8 69.0 0.98x [0.81-1.07] 53.5 0.78x [0.73-0.85] + 4 110.0 100.0 0.91x [0.82-0.99] 76.6 0.77x [0.64-0.92] + 8 157.2 166.5 1.06x [1.01-1.15] 142.1 0.85x [0.76-1.00] + 16 286.6 261.5 0.91x [0.69-2.44] 226.3 0.87x [0.43-1.18] + 32 587.7 745.1 1.27x [0.71-1.61] 465.3 0.62x [0.57-1.04] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.8 5.4 5.4 7.5 0.93x [0.63-1.03] 0.94x [0.63-1.05] 1.30x [0.88-1.54] + 2 36.5 33.8 31.9 39.6 0.93x [0.83-1.12] 0.87x [0.75-0.98] 1.08x [1.03-1.14] + 4 34.4 34.7 37.0 49.9 1.01x [0.88-1.30] 1.07x [0.93-1.19] 1.45x [1.20-1.83] + 8 43.3 39.4 37.7 62.8 0.91x [0.80-1.21] 0.87x [0.75-1.23] 1.45x [1.15-1.75] + 16 44.1 60.2 63.4 188.9 1.36x [1.10-1.69] 1.44x [1.17-1.77] 4.28x [3.19-5.68] + 32 54.0 71.8 65.5 210.5 1.33x [1.05-1.35] 1.21x [0.98-1.23] 3.90x [2.20-4.54] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 24.8 28.9 24.9 28.5 1.17x [0.97-1.59] 1.01x [0.86-1.38] 1.15x [0.96-1.50] + 2 65.5 69.2 67.4 71.9 1.06x [0.98-1.17] 1.03x [0.95-1.14] 1.10x [0.95-1.21] + 4 102.2 101.4 95.2 111.3 0.99x [0.90-1.05] 0.93x [0.83-1.03] 1.09x [0.96-1.16] + 8 169.8 161.8 160.1 185.9 0.95x [0.91-1.12] 0.94x [0.89-1.15] 1.09x [0.98-1.24] + 16 283.5 266.3 326.8 327.9 0.94x [0.51-1.46] 1.15x [0.65-1.42] 1.16x [0.61-1.57] + 32 706.9 674.1 619.1 638.0 0.95x [0.53-1.29] 0.88x [0.62-1.61] 0.90x [0.64-1.33] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run6.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run6.txt new file mode 100644 index 000000000..ca941eb16 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run6.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433651344 0 2.3-2.4 1.03x +slotwise_mpsc 1 6.3 158027813 0 6.3-6.4 1.01x +reserving_mpsc 1 5.9 169721656 0 5.4-5.9 1.10x +permit_mpsc 1 7.9 127129418 0 7.9-8.2 1.05x +reserving(32/32) 1 5.4 184297825 0 5.4-5.8 1.06x +reserving(16/48) 1 5.3 186915888 0 5.3-5.9 1.10x +reserving(8/56) 1 5.4 184433788 0 5.3-6.0 1.13x +reserving(64/64) 1 7.5 134084205 0 7.5-8.3 1.11x +baseline_fetch_add 2 10.7 93083869 0 9.2-13.9 1.51x +slotwise_mpsc 2 51.9 19266709 0 40.5-53.7 1.33x +reserving_mpsc 2 33.7 29691211 0 30.1-37.3 1.24x +permit_mpsc 2 44.0 22704568 0 38.1-44.9 1.18x +reserving(32/32) 2 34.3 29196228 0 27.1-36.4 1.34x +reserving(16/48) 2 34.1 29360815 0 32.0-38.0 1.19x +reserving(8/56) 2 31.2 32005121 0 31.0-35.8 1.15x +reserving(64/64) 2 40.4 24763508 0 37.9-42.6 1.12x +baseline_fetch_add 4 14.6 68662455 0 13.9-16.5 1.19x +slotwise_mpsc 4 89.7 11144358 0 85.8-101.5 1.18x +reserving_mpsc 4 37.4 26718322 0 36.0-39.7 1.10x +permit_mpsc 4 33.2 30092836 0 32.4-33.5 1.03x +reserving(32/32) 4 34.9 28677124 0 30.0-37.9 1.26x +reserving(16/48) 4 36.3 27565675 0 28.4-42.6 1.50x +reserving(8/56) 4 36.1 27675915 0 32.1-36.7 1.15x +reserving(64/64) 4 51.6 19366521 0 48.5-57.6 1.19x +baseline_fetch_add 8 14.5 68913238 0 13.4-15.5 1.16x +slotwise_mpsc 8 137.0 7297179 0 128.5-143.8 1.12x +reserving_mpsc 8 41.8 23904859 0 37.8-43.1 1.14x +permit_mpsc 8 25.8 38755935 0 25.0-56.2 2.25x +reserving(32/32) 8 39.7 25166888 0 37.9-41.7 1.10x +reserving(16/48) 8 41.2 24274791 0 37.1-44.0 1.19x +reserving(8/56) 8 39.8 25152329 0 34.0-42.4 1.25x +reserving(64/64) 8 67.1 14894029 0 56.4-74.4 1.32x +baseline_fetch_add 16 14.7 68082789 0 14.5-14.9 1.02x +slotwise_mpsc 16 254.8 3924000 0 198.1-273.8 1.38x +reserving_mpsc 16 51.9 19271211 0 51.2-55.7 1.09x +permit_mpsc 16 19.6 50928814 0 19.6-32.2 1.64x +reserving(32/32) 16 49.9 20051884 0 48.0-62.3 1.30x +reserving(16/48) 16 64.9 15404434 0 61.7-76.8 1.24x +reserving(8/56) 16 66.0 15148331 0 61.2-70.5 1.15x +reserving(64/64) 16 214.2 4669409 0 203.8-237.8 1.17x +baseline_fetch_add 32 14.9 67121977 0 14.7-14.9 1.02x +slotwise_mpsc 32 248.0 4032938 0 233.4-265.8 1.14x +reserving_mpsc 32 49.2 20337604 0 47.0-52.9 1.13x +permit_mpsc 32 21.6 46255074 0 19.9-33.9 1.70x +reserving(32/32) 32 51.3 19494531 0 50.7-55.9 1.10x +reserving(16/48) 32 70.1 14271978 0 64.0-71.6 1.12x +reserving(8/56) 32 68.1 14683075 0 65.9-74.2 1.13x +reserving(64/64) 32 200.7 4982578 0 176.1-227.2 1.29x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 11.0 91008373 36 10.2-11.4 1.12x +reserving_mpsc 1 24.2 41277966 208 23.6-25.5 1.08x +permit_mpsc 1 59.8 16729122 197 57.3-61.1 1.07x +reserving(32/32) 1 24.8 40303079 1442 21.9-32.1 1.47x +reserving(16/48) 1 27.6 36218761 0 23.4-30.7 1.31x +reserving(8/56) 1 27.4 36520342 816 24.8-30.0 1.21x +reserving(64/64) 1 26.4 37950664 0 25.6-26.7 1.04x +slotwise_mpsc 2 66.7 14996551 1867 54.2-73.2 1.35x +reserving_mpsc 2 64.8 15428051 179 62.4-65.9 1.06x +permit_mpsc 2 52.3 19116073 367 50.7-54.1 1.07x +reserving(32/32) 2 62.7 15952016 199 61.0-67.3 1.10x +reserving(16/48) 2 68.5 14607070 1281 66.8-69.7 1.04x +reserving(8/56) 2 66.3 15075907 346 63.7-69.3 1.09x +reserving(64/64) 2 69.3 14434388 0 66.0-70.8 1.07x +slotwise_mpsc 4 107.8 9276008 117 103.4-110.5 1.07x +reserving_mpsc 4 97.8 10220664 0 94.7-120.4 1.27x +permit_mpsc 4 81.2 12315347 84632 75.0-91.1 1.21x +reserving(32/32) 4 89.6 11161773 0 83.8-102.6 1.22x +reserving(16/48) 4 85.4 11703983 11878 81.4-94.2 1.16x +reserving(8/56) 4 96.3 10386427 60 94.2-98.0 1.04x +reserving(64/64) 4 105.3 9493971 0 103.7-108.5 1.05x +slotwise_mpsc 8 153.7 6506297 118 150.8-175.0 1.16x +reserving_mpsc 8 165.0 6059018 5560 162.1-170.4 1.05x +permit_mpsc 8 145.9 6853728 589688 113.9-163.4 1.43x +reserving(32/32) 8 169.4 5903406 169 162.6-185.2 1.14x +reserving(16/48) 8 178.3 5609422 0 165.6-187.8 1.13x +reserving(8/56) 8 175.1 5709783 33 155.6-180.4 1.16x +reserving(64/64) 8 187.0 5348316 790 173.9-191.1 1.10x +slotwise_mpsc 16 289.2 3457574 1834152 251.4-304.8 1.21x +reserving_mpsc 16 327.7 3051902 2538353 260.7-364.8 1.40x +permit_mpsc 16 290.3 3445052 3300710 210.1-301.5 1.44x +reserving(32/32) 16 280.3 3568168 1811425 259.1-364.4 1.41x +reserving(16/48) 16 332.3 3009633 2922304 318.3-368.4 1.16x +reserving(8/56) 16 296.3 3375198 1141035 278.3-333.6 1.20x +reserving(64/64) 16 277.2 3607138 1061517 258.9-370.0 1.43x +slotwise_mpsc 32 521.8 1916566 13856950 503.7-617.4 1.23x +reserving_mpsc 32 716.6 1395441 26076677 484.0-838.8 1.73x +permit_mpsc 32 496.1 2015844 11845182 298.6-524.8 1.76x +reserving(32/32) 32 610.7 1637370 22115341 484.0-704.0 1.45x +reserving(16/48) 32 663.4 1507318 26247018 623.8-733.6 1.18x +reserving(8/56) 32 696.6 1435587 23703267 553.6-893.0 1.61x +reserving(64/64) 32 751.0 1331640 23556772 529.0-1016.2 1.92x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.12-0.16] 0.17x [0.14-0.20] 0.18x [0.17-0.22] 0.21x [0.16-0.26] + 4 0.07x [0.06-0.07] 0.16x [0.14-0.16] 0.24x [0.23-0.25] 0.16x [0.14-0.17] + 8 0.05x [0.04-0.05] 0.14x [0.13-0.16] 0.30x [0.14-0.33] 0.16x [0.15-0.18] + 16 0.02x [0.02-0.03] 0.11x [0.10-0.12] 0.40x [0.24-0.42] 0.16x [0.15-0.16] + 32 0.03x [0.02-0.03] 0.12x [0.10-0.13] 0.36x [0.23-0.41] 0.15x [0.15-0.16] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 11.0 24.2 2.20x [2.07-2.51] 59.8 2.47x [2.24-2.59] + 2 66.7 64.8 0.97x [0.85-1.22] 52.3 0.81x [0.77-0.87] + 4 107.8 97.8 0.91x [0.86-1.16] 81.2 0.83x [0.62-0.96] + 8 153.7 165.0 1.07x [0.93-1.13] 145.9 0.88x [0.67-1.01] + 16 289.2 327.7 1.13x [0.86-1.45] 290.3 0.89x [0.58-1.16] + 32 521.8 716.6 1.37x [0.78-1.67] 496.1 0.69x [0.36-1.08] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.3 5.4 7.5 0.99x [0.93-1.09] 1.00x [0.93-1.12] 1.37x [1.30-1.53] + 2 34.3 34.1 31.2 40.4 0.99x [0.88-1.40] 0.91x [0.85-1.32] 1.18x [1.04-1.57] + 4 34.9 36.3 36.1 51.6 1.04x [0.75-1.42] 1.04x [0.85-1.23] 1.48x [1.28-1.92] + 8 39.7 41.2 39.8 67.1 1.04x [0.89-1.16] 1.00x [0.81-1.12] 1.69x [1.35-1.96] + 16 49.9 64.9 66.0 214.2 1.30x [0.99-1.60] 1.32x [0.98-1.47] 4.29x [3.27-4.96] + 32 51.3 70.1 68.1 200.7 1.37x [1.14-1.41] 1.33x [1.18-1.46] 3.91x [3.15-4.48] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 24.8 27.6 27.4 26.4 1.11x [0.73-1.40] 1.10x [0.77-1.37] 1.06x [0.80-1.22] + 2 62.7 68.5 66.3 69.3 1.09x [0.99-1.14] 1.06x [0.95-1.13] 1.11x [0.98-1.16] + 4 89.6 85.4 96.3 105.3 0.95x [0.79-1.12] 1.07x [0.92-1.17] 1.18x [1.01-1.29] + 8 169.4 178.3 175.1 187.0 1.05x [0.89-1.16] 1.03x [0.84-1.11] 1.10x [0.94-1.18] + 16 280.3 332.3 296.3 277.2 1.19x [0.87-1.42] 1.06x [0.76-1.29] 0.99x [0.71-1.43] + 32 610.7 663.4 696.6 751.0 1.09x [0.89-1.52] 1.14x [0.79-1.85] 1.23x [0.75-2.10] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run7.txt b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run7.txt new file mode 100644 index 000000000..d21a08342 --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/sequential-run7.txt @@ -0,0 +1,215 @@ +host: x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16] +== how does the array queue's push path scale with producer count? == + +processors available to this process: 16 + +profile: release +sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 2, 4, 8, 16, 32 + (NOT the defaults: set on the command line, so these figures are not the default run's) + +-- isolated: producers only, capacity large enough that nothing is refused -- +shape producers ns/op ops/sec refusals ns/op range spread +baseline_fetch_add 1 2.3 433275563 0 2.1-2.5 1.18x +slotwise_mpsc 1 6.4 157133878 0 6.3-6.4 1.01x +reserving_mpsc 1 5.5 183351669 0 5.4-5.8 1.07x +permit_mpsc 1 7.9 126646403 0 7.9-8.4 1.07x +reserving(32/32) 1 5.4 184842884 0 5.4-6.8 1.26x +reserving(16/48) 1 5.4 186776242 0 5.3-5.7 1.07x +reserving(8/56) 1 5.6 177683014 0 5.4-6.8 1.26x +reserving(64/64) 1 7.5 133832976 0 7.5-7.8 1.04x +baseline_fetch_add 2 11.3 88331419 0 10.3-11.7 1.13x +slotwise_mpsc 2 54.8 18240187 0 54.3-58.7 1.08x +reserving_mpsc 2 32.5 30722910 0 29.3-35.9 1.23x +permit_mpsc 2 43.0 23252569 0 37.4-44.4 1.19x +reserving(32/32) 2 32.1 31125498 0 24.3-35.5 1.46x +reserving(16/48) 2 34.3 29183447 0 33.0-34.8 1.06x +reserving(8/56) 2 29.9 33411293 0 27.9-34.1 1.22x +reserving(64/64) 2 39.2 25503048 0 38.0-39.6 1.04x +baseline_fetch_add 4 13.8 72653298 0 13.1-14.0 1.07x +slotwise_mpsc 4 88.0 11357312 0 86.4-90.3 1.04x +reserving_mpsc 4 38.2 26182123 0 35.5-39.4 1.11x +permit_mpsc 4 30.9 32333161 0 30.6-32.9 1.07x +reserving(32/32) 4 33.7 29716357 0 31.3-39.0 1.25x +reserving(16/48) 4 36.8 27146250 0 35.3-42.9 1.21x +reserving(8/56) 4 37.6 26629740 0 32.6-39.6 1.21x +reserving(64/64) 4 53.1 18823352 0 47.5-65.4 1.38x +baseline_fetch_add 8 16.4 60905976 0 11.2-17.5 1.57x +slotwise_mpsc 8 133.3 7503803 0 116.1-143.5 1.24x +reserving_mpsc 8 41.5 24067389 0 39.2-42.8 1.09x +permit_mpsc 8 24.7 40468217 0 24.1-26.3 1.09x +reserving(32/32) 8 38.8 25774857 0 33.4-44.8 1.34x +reserving(16/48) 8 53.4 18739488 0 44.0-66.1 1.50x +reserving(8/56) 8 49.3 20268969 0 35.8-53.2 1.49x +reserving(64/64) 8 89.3 11193069 0 58.2-96.7 1.66x +baseline_fetch_add 16 14.6 68348612 0 14.4-15.0 1.04x +slotwise_mpsc 16 231.8 4313282 0 198.4-256.4 1.29x +reserving_mpsc 16 52.5 19048163 0 48.4-54.6 1.13x +permit_mpsc 16 21.1 47460282 0 20.3-32.6 1.61x +reserving(32/32) 16 53.9 18569973 0 50.7-57.1 1.13x +reserving(16/48) 16 68.6 14572798 0 64.3-72.8 1.13x +reserving(8/56) 16 64.8 15434421 0 61.8-68.9 1.12x +reserving(64/64) 16 173.0 5781880 0 125.8-178.4 1.42x +baseline_fetch_add 32 15.1 66246827 0 14.6-15.2 1.04x +slotwise_mpsc 32 238.8 4187699 0 234.1-244.8 1.05x +reserving_mpsc 32 52.5 19064914 0 50.5-53.5 1.06x +permit_mpsc 32 20.9 47812860 0 19.9-21.4 1.08x +reserving(32/32) 32 51.4 19439534 0 50.9-59.6 1.17x +reserving(16/48) 32 65.6 15244411 0 59.7-69.5 1.16x +reserving(8/56) 32 58.3 17146299 0 58.0-65.6 1.13x +reserving(64/64) 32 251.3 3978990 0 202.5-252.2 1.25x + +-- drained: a consumer looping on pop, capacity 1024 -- +shape producers ns/op ops/sec refusals ns/op range spread +slotwise_mpsc 1 10.2 98077678 0 9.8-12.8 1.30x +reserving_mpsc 1 24.1 41545492 143 23.2-25.6 1.10x +permit_mpsc 1 62.6 15975462 752 32.7-62.9 1.93x +reserving(32/32) 1 24.6 40607488 566 22.2-26.1 1.18x +reserving(16/48) 1 28.8 34756013 2123 25.9-30.8 1.19x +reserving(8/56) 1 26.2 38124285 1096 24.2-28.3 1.17x +reserving(64/64) 1 26.5 37727307 430 25.6-26.7 1.04x +slotwise_mpsc 2 70.1 14264114 0 65.3-72.8 1.12x +reserving_mpsc 2 64.4 15534463 452 60.5-66.9 1.11x +permit_mpsc 2 51.8 19288635 429 50.6-55.4 1.10x +reserving(32/32) 2 65.7 15211670 789 60.2-69.3 1.15x +reserving(16/48) 2 67.1 14908018 146 66.5-72.4 1.09x +reserving(8/56) 2 69.2 14458389 643 68.1-73.9 1.09x +reserving(64/64) 2 68.7 14551169 3094 65.9-70.3 1.07x +slotwise_mpsc 4 104.1 9604072 0 95.9-106.7 1.11x +reserving_mpsc 4 99.5 10049847 16785 93.1-102.0 1.10x +permit_mpsc 4 77.1 12969075 77355 69.7-81.5 1.17x +reserving(32/32) 4 99.7 10029839 0 86.5-105.0 1.21x +reserving(16/48) 4 95.0 10527812 2547 94.1-98.6 1.05x +reserving(8/56) 4 96.0 10417589 0 94.3-97.3 1.03x +reserving(64/64) 4 95.5 10466985 1153 88.4-102.3 1.16x +slotwise_mpsc 8 144.8 6908463 1477 143.6-147.1 1.02x +reserving_mpsc 8 154.2 6483508 0 145.5-155.5 1.07x +permit_mpsc 8 121.7 8215121 445685 112.6-127.4 1.13x +reserving(32/32) 8 149.9 6670346 2393 147.8-168.6 1.14x +reserving(16/48) 8 164.2 6088688 7977 159.8-171.3 1.07x +reserving(8/56) 8 166.0 6023752 0 153.2-172.0 1.12x +reserving(64/64) 8 179.6 5566549 1174 118.2-196.9 1.67x +slotwise_mpsc 16 242.8 4117912 1153649 199.5-296.0 1.48x +reserving_mpsc 16 276.6 3615259 2214136 191.1-324.4 1.70x +permit_mpsc 16 204.4 4892503 2070682 184.9-261.9 1.42x +reserving(32/32) 16 306.3 3264323 2355611 268.0-419.2 1.56x +reserving(16/48) 16 298.5 3349558 2137747 217.2-332.6 1.53x +reserving(8/56) 16 330.4 3026845 2541319 233.3-386.0 1.65x +reserving(64/64) 16 263.5 3795189 1083806 231.9-322.7 1.39x +slotwise_mpsc 32 597.3 1674176 17022702 569.3-971.0 1.71x +reserving_mpsc 32 598.9 1669693 20904866 354.2-750.4 2.12x +permit_mpsc 32 527.1 1897182 12525873 433.6-547.3 1.26x +reserving(32/32) 32 724.0 1381194 28319904 581.5-756.7 1.30x +reserving(16/48) 32 647.8 1543659 24242287 475.7-907.8 1.91x +reserving(8/56) 32 672.4 1487171 23201051 453.8-812.5 1.79x +reserving(64/64) 32 756.0 1322744 23254003 371.9-790.6 2.13x + +interpretation: + + 1. push-path scaling with producer count (isolated regime) + + producers slotwise reserving permit atomic floor + 1 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] 1.00x [1.00-1.00] + 2 0.12x [0.11-0.12] 0.17x [0.15-0.20] 0.18x [0.18-0.23] 0.20x [0.18-0.24] + 4 0.07x [0.07-0.07] 0.14x [0.14-0.16] 0.26x [0.24-0.28] 0.17x [0.15-0.19] + 8 0.05x [0.04-0.05] 0.13x [0.13-0.15] 0.32x [0.30-0.35] 0.14x [0.12-0.22] + 16 0.03x [0.02-0.03] 0.10x [0.10-0.12] 0.37x [0.24-0.42] 0.16x [0.14-0.17] + 32 0.03x [0.03-0.03] 0.10x [0.10-0.11] 0.38x [0.37-0.42] 0.15x [0.14-0.17] + + Read as: throughput at N producers divided by throughput at one. + 1.00 means N threads together push no faster than one did. + The atomic floor is the cheapest possible contended operation, + so it says how much of any curve is the queue and how much is + simply what this processor does to a fought-over cache line. + + 2. reserving vs slotwise, drained (where `head` is written) + + The ratio is the WHOLE push path of two different shapes, not the + price of reserving's extra `head` load on its own: they use + different claim protocols, slot metadata and retry behaviour. This + regime is where that load is at its most expensive -- but the + ratio still does not isolate it, or bound it either way. + + producers slotwise reserving reserving/slotwise permit permit/reserving + ns/op ns/op ratio [bound] ns/op ratio [bound] + 1 10.2 24.1 2.36x [1.82-2.60] 62.6 2.60x [1.28-2.71] + 2 70.1 64.4 0.92x [0.83-1.03] 51.8 0.81x [0.76-0.92] + 4 104.1 99.5 0.96x [0.87-1.06] 77.1 0.77x [0.68-0.88] + 8 144.8 154.2 1.07x [0.99-1.08] 121.7 0.79x [0.72-0.88] + 16 242.8 276.6 1.14x [0.65-1.63] 204.4 0.74x [0.57-1.37] + 32 597.3 598.9 1.00x [0.36-1.32] 527.1 0.88x [0.58-1.55] + + `reserving_mpsc` reads the consumer's position on every push and + `slotwise_mpsc` does not. This regime is where that read is at its + most expensive, because a consumer is writing the line being read + -- but the ratio does not decompose. It is an END-TO-END + comparison of two shapes: they also differ in claim protocol, + slot metadata and retry behaviour, and those differences are not + ordered. So this ratio neither isolates the read nor bounds it. + + `permit_mpsc` is experimental and is the candidate replacement + for `reserving_mpsc`: it removes that read entirely, and with it + the stale room decision behind SH-14.1, by making admission a + read-modify-write on a permit count instead. The last column is + the trade. Read the INTERVAL, not the point: one that crosses + 1.00 orders nothing, however far the point estimate sits from + it. An interval clear of + 1.00 orders the two WHOLE PUSH PATHS and not the room-decision + race on its own: the shapes differ in refusal and retry + behaviour too, and here their refusal counts differ by orders + of magnitude this harness cannot attribute. + + 3. claim-word layout + + 4 apportionments of reserving_mpsc's claim word, measured on + the shipping type itself rather than on a stand-in. 32/32 is the + default; 16/48 and 8/56 are the same u64 exchange with the bits + apportioned differently; 64/64 is a u128 exchange (cmpxchg16b on + x86-64, ldxp/stxp on aarch64), measured only where that is native. + The three u64 rows issue the same instruction and differ only in + shift and mask constants, so there is no structural reason for one + to be slower -- but these rows time the WHOLE push path, so a + difference between them is not thereby noise. Read it against a + control before calling it either way: the reserving_mpsc row and + the 32/32 row above are the same code, so the gap between them is + what 'no difference' looks like on this host -- read it against + the layout rows before calling any of them apart. + 64/64 vs 32/32 is the double-width layout's effect on the whole + push path -- what moving the recurrence to 2^64 costs, against + 8/56 moving it to 2^56. Both defer the recurrence rather than + removing it. Not the exchange in isolation. + + -- isolated -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 5.4 5.4 5.6 7.5 0.99x [0.79-1.06] 1.04x [0.80-1.26] 1.38x [1.10-1.44] + 2 32.1 34.3 29.9 39.2 1.07x [0.93-1.43] 0.93x [0.78-1.40] 1.22x [1.07-1.63] + 4 33.7 36.8 37.6 53.1 1.09x [0.91-1.37] 1.12x [0.84-1.27] 1.58x [1.22-2.09] + 8 38.8 53.4 49.3 89.3 1.38x [0.98-1.98] 1.27x [0.80-1.59] 2.30x [1.30-2.90] + 16 53.9 68.6 64.8 173.0 1.27x [1.13-1.44] 1.20x [1.08-1.36] 3.21x [2.20-3.52] + 32 51.4 65.6 58.3 251.3 1.28x [1.00-1.37] 1.13x [0.97-1.29] 4.89x [3.40-4.96] + + -- drained -- + producers 32/32 ns/op 16/48 ns/op 8/56 ns/op 64/64 ns/op 16/48 vs 8/56 vs 64/64 vs + 1 24.6 28.8 26.2 26.5 1.17x [0.99-1.39] 1.07x [0.93-1.28] 1.08x [0.98-1.20] + 2 65.7 67.1 69.2 68.7 1.02x [0.96-1.20] 1.05x [0.98-1.23] 1.05x [0.95-1.17] + 4 99.7 95.0 96.0 95.5 0.95x [0.90-1.14] 0.96x [0.90-1.12] 0.96x [0.84-1.18] + 8 149.9 164.2 166.0 179.6 1.10x [0.95-1.16] 1.11x [0.91-1.16] 1.20x [0.70-1.33] + 16 306.3 298.5 330.4 263.5 0.97x [0.52-1.24] 1.08x [0.56-1.44] 0.86x [0.55-1.20] + 32 724.0 647.8 672.4 756.0 0.89x [0.63-1.56] 0.93x [0.60-1.40] 1.04x [0.49-1.36] + + the 32/32 row and the reserving_mpsc row above are the same + configuration run twice, so the gap between them is this host's + same-code control: whatever it shows is dispersion, not a + difference between shapes. Do not read it as noise that can be + discounted -- its width is an open question about this + instrument. They + are no longer a control against a duplicated implementation: the + shipping type takes the layout as a parameter, so there is nothing + left that could drift away from what callers actually run. + + CAUTION: the drained regime has ONE consumer, because that is what + MPSC means. At high producer counts it is expected to become + consumer-bound, and a plateau there says nothing about the claim. + The refusal counts above are what make that visible: a run with + many refusals met a full queue often, so the consumer is one term + in what it measured. That does not rule the tail out -- both can + bind at once, and these counts do not separate them. diff --git a/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/summarise.js b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/summarise.js new file mode 100644 index 000000000..42d53ea3a --- /dev/null +++ b/crates/windows-platform-probes/captures/2026-10-19-interleaved-order/summarise.js @@ -0,0 +1,447 @@ +// Copyright (c) Mike Grier. +// Summarise the drained tables of a queue-contention capture. +// +// Reads the probe's own report text rather than re-deriving anything: the +// medians and bounds are whatever the instrument printed. What this adds is the +// across-run median per producer count, and the same-code control span, which +// no single run states because it is a relation between two tables. + +const fs = require("fs"); +const crypto = require("crypto"); + +// One writer for the generated artifact, per this repository's output rule: no +// formatting site picks a destination, so retargeting the report to a file is a +// change here and nowhere else. `fail` is the diagnostic path and stays separate +// from the artifact, which is why they are two sinks rather than one with a flag. +let sink = (text) => process.stdout.write(text + "\n"); +const out = (text = "") => sink(text); +const fail = (text) => process.stderr.write(text + "\n"); + +// A ratio cell, with all three numbers required to be plain decimals, and the +// whole cell required to stand alone between whitespace. Unanchored, the pattern +// matches a SUFFIX of malformed text -- `1.2.3x [1.0-2.0]` yields `2.3x +// [1.0-2.0]`, which then passes every downstream check as an ordinary cell. +const RATIO = /(?<=^|\s)(\d+\.\d+)x \[(\d+\.\d+)-(\d+\.\d+)\](?=\s|$)/g; + +function median(values) { + const sorted = [...values].sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 === 0 + ? (sorted[middle - 1] + sorted[middle]) / 2 + : sorted[middle]; +} + +// Problems are collected rather than thrown, so one malformed capture reports +// everything wrong with it instead of only the first thing. +// +// **Nothing here may quietly degrade to `NaN`.** Every comparison against `NaN` +// is false, so a `NaN` bound makes the containment filter below find nothing +// outside it and print `true` -- certifying a capture it could not read. A +// report renders `--` for a shape that did not run, and `Number("--")` is +// exactly that `NaN`. +const problems = []; + +function finite(text, what) { + const value = Number(text); + if (!Number.isFinite(value)) { + problems.push(`${what}: ${JSON.stringify(text)} is not a number`); + return null; + } + return value; +} + +// Every quantity this script reads is a cost or a ratio of costs, so zero and +// negative are as invalid as NaN -- and more dangerous, because `0.00x` is +// finite and renders as an ordinary-looking measurement. The probe's own +// did-not-run sentinel used to be `0.0`, so this is the shape a legacy or +// malformed capture actually takes. +function positive(text, what) { + const value = finite(text, what); + if (value === null) return null; + if (value <= 0) { + problems.push(`${what}: ${JSON.stringify(text)} is not positive`); + return null; + } + return value; +} + +// A producer count is a positive whole number, and is used as a `Map` key. Left +// to `finite`, a label of `1.0` becomes the number 1 and matches the expected +// count 1, so a malformed capture would be normalised into a well-formed one on +// the way past the completeness check. +function wholeCount(text, what) { + // The check is on the TEXT, not the parsed value: `Number("2.0")` is 2 and + // `Number.isInteger(2)` is true, so a value-level test normalises the very + // label it is meant to reject. A producer count is written as digits. + if (!/^\d+$/.test(text)) { + problems.push(`${what}: ${JSON.stringify(text)} is not a whole number`); + return null; + } + return positive(text, what); +} + +// A ratio triple must be ordered and must contain its own point estimate. +// `positive` accepts each number on its own, so `2.00x [3.00-1.00]` passes +// three separate checks and is still not an interval any instrument produced. +function orderedTriple(point, low, high, where) { + if (point === null || low === null || high === null) return false; + if (low > high) { + problems.push(`${where}: bound ${low} to ${high} is inverted`); + return false; + } + if (point < low || point > high) { + problems.push(`${where}: median ${point} is outside its own bound ${low}-${high}`); + return false; + } + return true; +} + +// A table's rows, from `from` to the blank line that ends it. +// +// A fixed six-row slice discards a seventh row without reporting it, so a +// capture carrying an extra or duplicated producer row past the sixth would +// satisfy `EXPECTED_PRODUCERS` on the truncated map and be certified while its +// data was silently dropped. Reading to the terminator puts every row in front +// of the duplicate and expected-set checks instead. +function tableBody(lines, from) { + const body = []; + for (let i = from; i < lines.length; i += 1) { + if (lines[i].trim() === "") break; + body.push(lines[i]); + } + return body; +} + +// producers -> { narrowNanos, ratios: [16/48, 8/56, 64/64] } +function drainedLayout(lines, path) { + let start = -1; + lines.forEach((line, i) => { + if (line.includes("-- drained --")) start = i; + }); + // The raw table's marker is `-- drained: ... --`, which this does not match; + // the one it finds is the claim-layout interpretation table, which is the one + // carrying ratios. A missing marker must stop the run rather than silently + // slice from line 1, which would summarise whatever happened to be there. + if (start < 0) { + problems.push(`${path}: no drained layout table (expected a "-- drained --" marker)`); + return new Map(); + } + const rows = new Map(); + for (const line of tableBody(lines, start + 2)) { + const fields = line.trim().split(/\s+/); + const producers = wholeCount(fields[0], `${path}: a drained layout producer count`); + if (producers === null) continue; + const where = `${path}, drained layout, ${producers} producers`; + const narrowNanos = positive(fields[1], `${where}: the 32/32 cost`); + // Pass every value through `positive`, like every other captured quantity, + // and check all three numbers of each cell -- the two bounds are not used by + // this script, but a capture carrying an unreadable bound is not a capture + // this script should certify as summarised. + const ratios = [...line.matchAll(RATIO)].flatMap((m, i) => [ + positive(m[1], `${where}: layout ratio ${i + 1}`), + positive(m[2], `${where}: layout ratio ${i + 1} lower bound`), + positive(m[3], `${where}: layout ratio ${i + 1} upper bound`), + ]); + // A row that did not run renders `--`, which the ratio pattern does not + // match, so a short list is the signal that this row cannot be summarised. + if (ratios.length !== 9) { + problems.push( + `${where}: found ${ratios.length / 3} layout ratios, expected 3`, + ); + continue; + } + if (ratios.some((r) => r === null)) continue; + if (narrowNanos === null) continue; + // Each triple must be an interval containing its own median. + const ordered = [0, 3, 6].every((i) => + orderedTriple(ratios[i], ratios[i + 1], ratios[i + 2], `${where}: layout ratio ${i / 3 + 1}`), + ); + if (!ordered) continue; + // A `Map` keeps the last write, so a duplicated producer row would silently + // discard the earlier measurement while the completeness check -- which sees + // only distinct keys -- still reported a whole capture. + if (rows.has(producers)) { + problems.push(`${where}: a second layout row for this producer count`); + continue; + } + rows.set(producers, { narrowNanos, ratios: [ratios[0], ratios[3], ratios[6]] }); + } + return rows; +} + +// producers -> reserving ns/op, from the comparison table +function drainedComparison(lines, path) { + const start = lines.findIndex((line) => line.includes("reserving/slotwise")); + const rows = new Map(); + for (const line of tableBody(lines, start + 2)) { + const fields = line.trim().split(/\s+/); + const producers = wholeCount(fields[0], `${path}: a comparison producer count`); + if (producers === null) continue; + const reserving = positive( + fields[2], + `${path}, drained comparison, ${producers} producers: the reserving_mpsc cost`, + ); + if (reserving === null) continue; + // Same overwrite hazard as the layout table, and it matters more here: this + // value is the same-code control, so silently keeping the last of two + // duplicates would move the published control range. + if (rows.has(producers)) { + problems.push( + `${path}, drained comparison, ${producers} producers: a second row for this producer count`, + ); + continue; + } + rows.set(producers, reserving); + } + return rows; +} + +const paths = process.argv.slice(2); +// An empty argument list summarises nothing: every completeness check below +// iterates the captures, so with none of them there is nothing to complain +// about, `median([])` is NaN, and the script prints an empty summary and exits +// successfully. Rejected here, as `isolated.js` does. +if (paths.length === 0) { + fail("usage: node summarise.js [run.txt ...]"); + process.exit(2); +} +// The run count and the control's `(n)` are both `paths.length`, so the same +// capture given twice would be certified as two independent runs -- repeated +// bytes reported as agreement. +{ + // Compared by CONTENT, not by name. A resolved path catches `./run1.txt` and + // a symlink, but not `copy-of-run1.txt`, which has a different real path and + // identical bytes -- and `paths.length` is both the run count and the control + // band's `(n)`, so a copy would narrow the reported range and inflate the + // sample size without adding an observation. + // + // **This is a heuristic, and its assumption is worth stating.** The report + // carries no per-invocation identifier, so "identical bytes" stands in for + // "same run". Two genuinely independent runs producing identical bytes would + // be refused -- possible in principle, since the figures are rounded, and + // vanishingly unlikely across this many of them. The refusal is loud and + // diagnosable; accepting a duplicated run would silently fabricate agreement, + // which is the worse of the two. The build identity `M4.8` added does not + // replace this: it names the build, which every run in a capture shares, and + // only a per-invocation identifier would tell two runs apart. + const seen = new Map(); + for (const path of paths) { + const digest = crypto.createHash("sha256").update(fs.readFileSync(path)).digest("hex"); + if (seen.has(digest)) { + fail(`the same capture was given twice: ${path} is byte-identical to ${seen.get(digest)}`); + process.exit(2); + } + seen.set(digest, path); + } +} + +// The attribution a run prints about itself. A capture is runs of one build on +// one host under one sampling regime, so these must agree across the inputs -- +// mixing a debug run, or another machine's, yields a median and a control span +// that describe no configuration that was ever measured, and nothing else here +// would notice. +function attribution(text, where) { + const line = (prefix) => { + const found = text.split(/\r?\n/).find((l) => l.startsWith(prefix)); + if (found === undefined) { + fail(`${where}: no "${prefix}" line; this does not look like a probe report`); + process.exit(2); + } + return found.trim(); + }; + const profile = line("profile:"); + // Agreement is not enough on its own: three debug reports agree with each + // other, and the probe stamps a debug run "NOT A MEASUREMENT" precisely + // because its figures are not one. A capture built from them would be + // internally consistent and meaningless. + if (profile !== "profile: release") { + fail(`${where}: ${profile} -- only a release run is a measurement`); + process.exit(2); + } + // Included because it can differ while the host banner does not: an affinity + // mask changes how many processors the process may use without changing the + // machine it names, and producer counts are read against that number. + // The build that produced the run (`M4.8`). Optional, because runs taken + // before the probe printed it have none -- but present or absent it must + // agree across the inputs like the rest, so a set mixing two builds, or a + // stamped run with an unstamped one, is refused. + const build = text.split(/\r?\n/).find((l) => l.startsWith("build:")); + return [ + line("host:"), + build === undefined ? "build: (not stamped)" : build.trim(), + profile, + line("sampling:"), + line("processors available to this process:"), + ].join(" | "); +} + +function requireOneConfiguration(entries) { + // **The build is part of what must agree, since `M4.8`.** Before it, two runs + // of DIFFERENT probe commits on one host under one profile agreed here, and a + // pre-handshake and a post-handshake run would have had their medians + // combined as though one procedure produced both. + // + // **What this still cannot check.** A DIRTY build names its commit, not its + // tree, so two dirty builds at one commit agree here while their sources may + // differ; and runs from before the probe printed a build agree with each + // other as "not stamped". For both, the capture README is still the only + // record of what was built. + const first = entries[0]; + for (const entry of entries.slice(1)) { + if (entry.attribution !== first.attribution) { + fail( + `${entry.name} was taken under different conditions from ${first.name}:\n` + + ` ${first.name}: ${first.attribution}\n` + + ` ${entry.name}: ${entry.attribution}`, + ); + process.exit(2); + } + } +} + +const layouts = []; +const comparisons = []; +const controls = []; +requireOneConfiguration( + paths.map((p) => ({ name: p, attribution: attribution(fs.readFileSync(p, "utf8"), p) })), +); +for (const path of paths) { + const lines = fs.readFileSync(path, "utf8").split(/\r?\n/); + const layout = drainedLayout(lines, path); + const comparison = drainedComparison(lines, path); + layouts.push(layout); + comparisons.push(comparison); + // The same code measured twice in one run: `reserving_mpsc` in the comparison + // table against `32/32` in the layout table. + const control = new Map(); + for (const [producers, row] of layout) { + const reserving = comparison.get(producers); + if (reserving === undefined) { + problems.push( + `${path}: ${producers} producers has a layout row but no comparison row`, + ); + continue; + } + if (row.narrowNanos > 0) { + control.set(producers, reserving / row.narrowNanos); + } + } + controls.push(control); +} + +// The sweep the probe runs, stated rather than inferred. Deriving the expected +// set from the first capture makes the completeness check circular: three runs +// all truncated at the same producer count agree with each other, `rows` stays +// non-empty, and the script reports a partial capture as a whole one. +const EXPECTED_PRODUCERS = [1, 2, 4, 8, 16, 32]; + +layouts.forEach((layout, i) => { + const seen = [...layout.keys()].sort((a, b) => a - b); + if (seen.join(",") !== EXPECTED_PRODUCERS.join(",")) { + problems.push( + `${paths[i]}: drained layout covers producers [${seen}], expected [${EXPECTED_PRODUCERS}]`, + ); + } +}); + +// The comparison table needs the same check, and for the same reason: the loop +// that builds the control iterates the LAYOUT's keys, so an extra comparison row +// is accepted into its map and then never looked at. Only the layout side was +// checked, which left half the capture able to carry rows nothing reported. +comparisons.forEach((comparison, i) => { + const seen = [...comparison.keys()].sort((a, b) => a - b); + if (seen.join(",") !== EXPECTED_PRODUCERS.join(",")) { + problems.push( + `${paths[i]}: drained comparison covers producers [${seen}], expected [${EXPECTED_PRODUCERS}]`, + ); + } +}); + +const producers = EXPECTED_PRODUCERS; +out(`runs: ${paths.length}`); + +// **Reported per producer count, and deliberately without a verdict.** +// +// An earlier version pooled every control observation into one band and asked +// whether each layout median fell inside it. That answered `true`, and the +// answer was an artifact of the pooling: the control is not independent of +// producer count -- it spans about 0.82-0.98x at one producer against +// 0.95-1.23x at thirty-two on this capture -- so pooling builds a band wider +// than any count's own and containment follows from the method rather than +// from the data. +// +// Comparing per count instead does not rescue a verdict either. Three runs +// give three control observations per count, and the range of three samples is +// not a band: a fresh independent draw falls outside the range of three priors +// about half the time. So neither comparison is strong enough to say a median +// is inside or outside, and this script says so rather than picking whichever +// framing yields an answer. +const rows = []; +for (const p of producers) { + const present = layouts.filter((layout) => layout.has(p)); + if (present.length !== layouts.length) { + problems.push( + `${p} producers is missing from ${layouts.length - present.length} run(s)`, + ); + continue; + } + const control = controls.map((c) => c.get(p)).filter((v) => Number.isFinite(v)); + if (control.length !== controls.length) { + problems.push(`${p} producers has no control ratio in every run`); + continue; + } + rows.push({ + producers: p, + control, + medians: [0, 1, 2].map((column) => + median(present.map((layout) => layout.get(p).ratios[column])), + ), + }); +} + +// **Refuse to summarise a capture that could not be read.** A report renders +// `--` for a shape that did not run, `Number("--")` is `NaN`, and every +// comparison against `NaN` is false -- so an unreadable cell used to empty the +// "outside the band" list and print `true`. +if (problems.length > 0 || rows.length === 0) { + out(""); + out("CAPTURE INCOMPLETE -- not summarised:"); + if (rows.length === 0) out(" - no complete producer counts were read"); + for (const problem of problems) out(` - ${problem}`); + process.exitCode = 1; + return; +} + +out(""); +out( + "drained, per producer count: the same-code control's observed range, then", +); +out("each layout's median ratio against 32/32, across runs."); +out(""); +// Derived, with the table's original width as a floor: a control band is built +// from measured ratios and has no upper bound, so a fixed field would shift the +// layout columns the first time one outgrew it. +const bands = rows.map((row) => { + const low = Math.min(...row.control); + const high = Math.max(...row.control); + return `${low.toFixed(2)}-${high.toFixed(2)}(${row.control.length})`; +}); +const bandWidth = Math.max(16, "control(n)".length, ...bands.map((b) => b.length)); +out(`producers ${"control(n)".padEnd(bandWidth)} 16/48 8/56 64/64`); +rows.forEach((row, i) => { + out( + `${String(row.producers).padStart(9)} ${bands[i].padEnd(bandWidth)} ` + + row.medians.map((m) => `${m.toFixed(2)}x`).join(" "), + ); +}); + +const everyControl = rows.flatMap((row) => row.control); +out(""); +out( + `control observations: ${everyControl.length} across ${rows.length} producer counts, ` + + `${Math.min(...everyControl).toFixed(2)}x to ${Math.max(...everyControl).toFixed(2)}x pooled`, +); +out( + "Pooled only to show the spread; it is not a band to judge a median against,", +); +out("for the reason recorded in this script beside the table above."); diff --git a/crates/windows-platform-probes/sabotage.json b/crates/windows-platform-probes/sabotage.json index 264b828d2..316c3dd2b 100644 --- a/crates/windows-platform-probes/sabotage.json +++ b/crates/windows-platform-probes/sabotage.json @@ -1,6 +1,7 @@ { - "description": "Sabotage manifest for windows-platform-probes. Each entry is a defect that was actually run by hand while building M3; the manifest exists so those one-off verifications stop being discarded. See CHECKLIST.md M2.14.1.", + "description": "Sabotage manifest for windows-platform-probes. Each entry is a defect that was actually run by hand while building M3; the manifest exists so those one-off verifications stop being discarded. See CHECKLIST.md M2.14.1. Run with --all-features because the corpus target, tests/a_real_report_agrees_with_itself.rs, requires oracle-in-renderer: with default features it is not built, so an entry only that target catches -- a wrong arm in its codes_for, for one -- would be scored as surviving.", "package": "windows-platform-probes", + "testArgs": ["-p", "windows-platform-probes", "--locked", "--all-features"], "sabotages": [ { "name": "row drops the packages field", @@ -78,8 +79,9 @@ { "name": "a blocking state with no perturbation", "file": "src/topology/invariant.rs", - "expect": "caught", - "why": "An untested blocking state. Adds rather than deletes, deliberately: the completeness guard exists to catch an ADDITION, and a hand-written `ALL` was measured to let exactly this through while all ten invariant tests stayed green.", + "expect": "refused-by-build", + "buildError": "SabotageSentinel` not covered", + "why": "An untested blocking state. Adds rather than deletes, deliberately: a hand-written `ALL` was measured to let exactly this through while all ten invariant tests stayed green. Declared refused-by-build since M4.1 put this manifest on --all-features: the corpus target's `codes_for` matches every state exhaustively, so a state with no row code no longer compiles, which is the stronger rung. The perturbation guard this entry used to reach is sabotaged by the entry below instead.", "find": [ " BracketNotHeld => \"the bracket did not establish that the machine held still\"," ], @@ -89,6 +91,19 @@ " SabotageSentinel => \"a state no perturbation produces\"," ] }, + { + "name": "a blocking state loses its perturbation", + "file": "src/topology/invariant/tests.rs", + "expect": "caught", + "why": "The deletion twin of the entry above, and since --all-features the one that reaches every_blocking_state_has_a_perturbation: the state still exists, so everything compiles, and only the comparison against BlockingState::ALL notices that nothing shows it fires.", + "find": [ + " (", + " BlockingState::NotMeasured,", + " Box::new(|o: &mut Observation| o.topology_was_measured = false),", + " )," + ], + "replace": [] + }, { "name": "prose reworded to carry the same fact", "file": "src/topology_report.rs", @@ -143,7 +158,7 @@ "expect": "caught", "why": "M7.1: the row that times what a request now does with a handle would silently leave the report and its NDJSON line, which the census of every label exists to refuse.", "find": [ - " timings.push(time_loop(\"share_handle\", ITERATIONS, || shared.clone()));" + " timings.push(time_loop(\"share_handle\", iterations, || shared.clone()));" ], "replace": [ " let _ = time_loop(\"share_handle\", ITERATIONS, || shared.clone());" @@ -155,10 +170,1420 @@ "expect": "survives", "why": "A control: the same clone spelled another way, which no test can tell apart.", "find": [ - " timings.push(time_loop(\"share_handle\", ITERATIONS, || shared.clone()));" + " timings.push(time_loop(\"share_handle\", iterations, || shared.clone()));" + ], + "replace": [ + " timings.push(time_loop(\"share_handle\", iterations, || Clone::clone(&shared)));" + ] + }, + { + "name": "M4.2 sampling: the median ignores the repetitions the run was given", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "A run asked for more repetitions would report the default's median while its settings line said otherwise -- the settings would be decoration.", + "find": [ + " for round in 0..sampling.repetitions {" + ], + "replace": [ + " for round in 0..REPETITIONS {" + ] + }, + { + "name": "M4.2 sampling: each pass performs the default pushes whatever the run was given", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "The timed passes would push the default count while the divisor used the given one, so every per-operation figure would be off by their ratio. The timer and the divisor read one value so that this cannot happen.", + "find": [ + " .map(|_| timer(producers, pushes_per_producer))" + ], + "replace": [ + " .map(|_| timer(producers, PUSHES_PER_PRODUCER))" + ] + }, + { + "name": "M4.2 sampling: --producers is accepted and dropped", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "The flag would parse and validate and then change nothing, so a run asked for other producer counts would silently measure the defaults.", + "find": [ + " sampling.producer_counts = probe_args::positive_list(\"producers\", value)?;" + ], + "replace": [ + " let _ = probe_args::positive_list(\"producers\", value)?;" + ] + }, + { + "name": "M4.2 sampling: every sampling claims to be the default", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "A report sampled other than the defaults would say it was the published configuration -- the one confusion the settings line exists to prevent.", + "find": [ + " *self == Self::default()" + ], + "replace": [ + " true" + ] + }, + { + "name": "M4.2 sampling: every iteration multiplier claims to be the default", + "file": "src/probe_args.rs", + "expect": "caught", + "why": "The cost probes' settings line would call a scaled run the default, so its figures would pass for the published ones.", + "find": [ + " self == Self::default()" + ], + "replace": [ + " true" + ] + }, + { + "name": "M4.2 sampling: a refused command line exits as a success", + "file": "src/report.rs", + "expect": "caught", + "why": "A script that mistyped a flag would read status 0 and an empty stdout as a run that measured nothing.", + "find": [ + " std::process::exit(2)" + ], + "replace": [ + " std::process::exit(0)" + ] + }, + { + "name": "M4.2 sampling: the cost probe's NDJSON line drops the multiplier", + "file": "src/bin/request_cost.rs", + "expect": "caught", + "why": "A machine reader would see a scaled run's figures with nothing saying they were not the default's.", + "find": [ + " r#\"{{\"reason\":\"x-probe-request-cost\",\"arch\":\"{}\",\"iteration_multiplier\":{},{fields}}}\"#," + ], + "replace": [ + " r#\"{{\"reason\":\"x-probe-request-cost\",\"arch\":\"{}\",\"ignored\":{},{fields}}}\"#," + ] + }, + { + "name": "M4.2 sampling control: the warmup pass is discarded another way", + "file": "src/queue_contention.rs", + "expect": "survives", + "why": "A control: the untimed pass still runs with the same arguments and its result is still thrown away, which no test can tell apart.", + "find": [ + " let _ = timer(producers, pushes_per_producer);", + "", + " let results: Vec = (0..sampling.repetitions)" + ], + "replace": [ + " let _warmup = timer(producers, pushes_per_producer);", + "", + " let results: Vec = (0..sampling.repetitions)" + ] + }, + { + "name": "M4.1: cross_check forgets overlapping walk relations", + "file": "src/topology.rs", + "expect": "caught", + "why": "One of the eleven push sites blocking_states did not model before M4.1. Deleting it empties the list, so the verdict reaches agree beside an observation that still holds the overlap; only a rule reading the observation sees it.", + "find": [ + " if self.overlapping_walk_relations > 0 {" + ], + "replace": [ + " if false {" + ] + }, + { + "name": "M4.1: cross_check forgets a cache level with no partitions", + "file": "src/topology.rs", + "expect": "caught", + "why": "The same class through a condition computed from the caches rather than read from a count, which is the case the old exclusion argument mistook for the cross-check's own arithmetic.", + "find": [ + " if !empty_levels.is_empty() {" + ], + "replace": [ + " if false {" + ] + }, + { + "name": "M4.1: cross_check forgets a described relation", + "file": "src/topology.rs", + "expect": "caught", + "why": "Deleting this push does NOT reach agree: a described relation also files counts_include_unparsed_relations under not_compared, so the verdict stays incomplete and the invariant module has nothing to say. What is lost is the code a survey groups by. Two tests see that: a_relation_a_caller_described_is_not_a_measurement, which fails first and so is the one a sweep's transcript names, and the corpus's every_state_that_blocks_agreement_reaches_the_row, confirmed by running the corpus target alone against this patch.", + "find": [ + " if self.described_relations > 0 {" + ], + "replace": [ + " if false {" + ] + }, + { + "name": "M4.1: blocking_states reads only the core half of an empty relation", + "file": "src/topology/invariant.rs", + "expect": "caught", + "why": "The perturbation table reaches this state through the core field alone, so dropping the package half passes every test that iterates the table. A package covering no processors would then leave cross_check free to lose its push unnoticed.", + "find": [ + " if observation.cores_without_processors > 0 || observation.packages_without_processors > 0 {" + ], + "replace": [ + " if observation.cores_without_processors > 0 {" + ] + }, + { + "name": "M4.1: blocking_states drops the measured guard on absent counts", + "file": "src/topology/invariant.rs", + "expect": "caught", + "why": "cross_check files measured_but_counts_absent only for a measured topology. Without the guard the state fires for an unmeasured one too, and the per-state publication rule would demand a code the row never emits for it.", + "find": [ + " if observation.topology_was_measured", + " && (observation.online_processors == 0 || observation.groups == 0)" + ], + "replace": [ + " if (observation.online_processors == 0 || observation.groups == 0)" + ] + }, + { + "name": "M4.1: a state answers the wrong code", + "file": "tests/a_real_report_agrees_with_itself.rs", + "expect": "caught", + "why": "The schema between states and row codes is written by hand because neither side can derive it. A wrong arm leaves described_relations answered by no state, which the census test exists to refuse.", + "find": [ + " State::DescribedRelations => &[\"described_relations\"]," + ], + "replace": [ + " State::DescribedRelations => &[\"unreported_relations\"]," + ] + }, + { + "name": "M4.1 control: a count compared with != rather than >", + "file": "src/topology/invariant.rs", + "expect": "survives", + "why": "A control: for a usize the two comparisons agree on every value, so no test can tell them apart.", + "find": [ + " if observation.numa_domains_unreported > 0 {" + ], + "replace": [ + " if observation.numa_domains_unreported != 0 {" + ] + }, + { + "name": "M2.17: coherence is checked only when the bracket held", + "file": "src/topology.rs", + "expect": "caught", + "why": "A check selected by two dimensions at once: an unheld bracket hides a coherence disagreement, so the row publishes no enumerations_disagreed for an observation in that blocking state. The bracket once suppressed every parse check above it by an early return, so this is a defect this code has shipped. No hand-written shape varies the bracket and the coherence together; the generated cross product does.", + "find": [ + " match &self.coherence {" + ], + "replace": [ + " match if self.bracket == BracketOutcome::HeldStill { &self.coherence } else { &Coherence::Agreed } {" + ] + }, + { + "name": "M2.17: a crossed value changes nothing", + "file": "tests/a_real_report_agrees_with_itself.rs", + "expect": "caught", + "why": "A dimension value that changes nothing collapses every combination it is in onto the one beside it: the product keeps its length and its names while reaching a third fewer states, and nothing that renders the shapes can tell. The independence test exists to refuse it.", + "find": [ + " o.bracket = BracketOutcome::NotEstablished;" + ], + "replace": [ + " let _ = o;" + ] + }, + { + "name": "M2.17 control: a crossed value renamed", + "file": "tests/a_real_report_agrees_with_itself.rs", + "expect": "survives", + "why": "A control: the name is only how a failing shape is reported, so renaming one changes no state the corpus reaches.", + "find": [ + " (\"the bracket left open\", |o| {" + ], + "replace": [ + " (\"the bracket never closed\", |o| {" + ] + }, + { + "name": "M8.1: the queue probe stops emitting its row", + "file": "src/bin/queue_contention/main.rs", + "expect": "caught", + "why": "The census runs this probe rather than reading its sources, so whether its row exists is decided by its output. Before M8.1 the census grepped for the literal \"reason\":\"x-probe, which a row built with Row::new never contains; since M8.2 the probe emits exactly such a row, and since M8.14 the census fails for any probe without one.", + "find": [ + " raw_row(", + " &observation,", + " &windows_platform_probes::build_identity::this_build().to_string()", + " )", + " .render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.2: the row publishes per-operation figures as elapsed times", + "file": "src/queue_contention/raw_row.rs", + "expect": "caught", + "why": "The elapsed fields are recovered by multiplying back by the run's total pushes; publishing the per-operation figure under an elapsed name is the plausible slip.", + "find": [ + " Value::from((per_op * total as f64).round() as usize)" + ], + "replace": [ + " Value::from(per_op.round() as usize)" + ] + }, + { + "name": "M8.2: the recovered elapsed time is truncated instead of rounded", + "file": "src/queue_contention/raw_row.rs", + "expect": "caught", + "why": "Dividing by the pushes and multiplying back can land a hair below the whole nanosecond it started from; truncation then publishes one nanosecond less than the pass took.", + "find": [ + " Value::from((per_op * total as f64).round() as usize)" + ], + "replace": [ + " Value::from((per_op * total as f64) as usize)" + ] + }, + { + "name": "M8.2: the fastest and slowest passes trade places", + "file": "src/queue_contention/raw_row.rs", + "expect": "caught", + "why": "Every test with one repetition has the three passes equal, so only distinct passes can tell the fields apart.", + "find": [ + " (\"fastest_elapsed_ns\", elapsed(run.fastest_nanos_per_op)),", + " (\"slowest_elapsed_ns\", elapsed(run.slowest_nanos_per_op))," + ], + "replace": [ + " (\"fastest_elapsed_ns\", elapsed(run.slowest_nanos_per_op)),", + " (\"slowest_elapsed_ns\", elapsed(run.fastest_nanos_per_op))," + ] + }, + { + "name": "M8.2: a configuration that did not measure publishes zero", + "file": "src/queue_contention/raw_row.rs", + "expect": "caught", + "why": "Zero reads as an immeasurably fast pass; null says there was none.", + "find": [ + " Value::Null" + ], + "replace": [ + " Value::from(0usize)" + ] + }, + { + "name": "M8.2 control: the row names its build before its architecture", + "file": "src/queue_contention/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.3: the peer-index probe stops emitting its row", + "file": "src/bin/peer_index_cache.rs", + "expect": "caught", + "why": "The census runs the probe, so a measured run that prints no row changes the set it reports.", + "find": [ + " raw_row(&observation, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.3: the row publishes the per-item figure as the elapsed time", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "The elapsed field is recovered by multiplying back by the items; publishing the per-item cost under an elapsed name is the plausible slip.", + "find": [ + " Value::from((run.nanos_per_item * ITEMS as f64).round() as usize)" + ], + "replace": [ + " Value::from(run.nanos_per_item.round() as usize)" + ] + }, + { + "name": "M8.3: the recovered elapsed time is truncated instead of rounded", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "Dividing by the items and multiplying back can land a hair below the whole nanosecond it started from; truncation then publishes one nanosecond less than the pass took.", + "find": [ + " Value::from((run.nanos_per_item * ITEMS as f64).round() as usize)" + ], + "replace": [ + " Value::from((run.nanos_per_item * ITEMS as f64) as usize)" + ] + }, + { + "name": "M8.3: the consumer and producer read counts trade places", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "The two counts carry the finding -- which side's reads the technique removed -- and nothing about their types tells them apart.", + "find": [ + " (\"consumer_refreshes\", Value::from(run.consumer_refreshes)),", + " (\"producer_refreshes\", Value::from(run.producer_refreshes))," + ], + "replace": [ + " (\"consumer_refreshes\", Value::from(run.producer_refreshes)),", + " (\"producer_refreshes\", Value::from(run.consumer_refreshes))," + ] + }, + { + "name": "M8.3: the shipping queue publishes its floor as counted reads", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "The shipping queue has no counter; its read fields hold ITEMS as a floor, and publishing that puts a constant under a name that says it was counted.", + "find": [ + " (\"consumer_refreshes\", Value::Null),", + " (\"producer_refreshes\", Value::Null)," + ], + "replace": [ + " (\"consumer_refreshes\", Value::from(observation.calibration.consumer_refreshes)),", + " (\"producer_refreshes\", Value::from(observation.calibration.producer_refreshes))," + ] + }, + { + "name": "M8.3: the strategies follow a different fixed order", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "The order is part of what the row states; a reader comparing two runs by position must find the same strategy in the same place.", + "find": [ + "const STRATEGIES: [Strategy; 3] = [Strategy::Baseline, Strategy::Cached, Strategy::Warmed];" + ], + "replace": [ + "const STRATEGIES: [Strategy; 3] = [Strategy::Cached, Strategy::Baseline, Strategy::Warmed];" + ] + }, + { + "name": "M8.3: a measured row omits the declined field", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "Without it a reader can tell a measured row from a declined one only by which other fields happen to exist.", + "find": [ + " .with(\"declined\", Value::Null)" + ], + "replace": [ + " .with(\"declined_ignored\", Value::Null)" + ] + }, + { + "name": "M8.3: a declined row loses the reason", + "file": "src/peer_index_cache.rs", + "expect": "caught", + "why": "A declined row that says null for why is indistinguishable from a measured one in that field.", + "find": [ + " preamble(build).with(\"declined\", error.to_string())" + ], + "replace": [ + " preamble(build).with(\"declined\", Value::Null)" + ] + }, + { + "name": "M8.3 control: the row names its build before its architecture", + "file": "src/peer_index_cache.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.4: the core-affinity probe stops emitting its row", + "file": "src/bin/core_affinity.rs", + "expect": "caught", + "why": "The census runs the probe, so a measured run that prints no row changes the set it reports.", + "find": [ + " raw_row(", + " observation,", + " announced.as_ref().ok(),", + " settled.as_ref().ok(),", + " &this_build().to_string()", + " )", + " .render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.4: the row publishes the per-item figure as the elapsed time", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "The raw elapsed time now travels on the measurement; publishing the derived per-item cost under an elapsed name is the plausible slip.", + "find": [ + " Value::from(row.elapsed_nanos.round() as usize)," + ], + "replace": [ + " Value::from(row.nanos_per_item.round() as usize)," + ] + }, + { + "name": "M8.4: the consumer and producer read counts trade places", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "Nothing about their types tells the two counts apart.", + "find": [ + " (\"consumer_refreshes\", Value::from(row.consumer_refreshes)),", + " (\"producer_refreshes\", Value::from(row.producer_refreshes))," + ], + "replace": [ + " (\"consumer_refreshes\", Value::from(row.producer_refreshes)),", + " (\"producer_refreshes\", Value::from(row.consumer_refreshes))," + ] + }, + { + "name": "M8.4: the requested and achieved memory nodes trade places", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "A row whose request and result disagree did not measure the placement it names; swapping them reports the redirect backwards.", + "find": [ + " Value::from(row.requested_memory_node),", + " ),", + " (\"memory_node\", Value::from(row.memory_node))," + ], + "replace": [ + " Value::from(row.memory_node),", + " ),", + " (\"memory_node\", Value::from(row.requested_memory_node))," + ] + }, + { + "name": "M8.4: an absent cache domain is published as not observed", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "The prose keeps 'no level partitions the machine' and 'this processor was left out of the level' apart; conflating them states a negative where nothing was observed, or the reverse.", + "find": [ + " Observed::Absent => Value::from(\"absent\")," + ], + "replace": [ + " Observed::Absent => Value::from(\"not_observed\")," + ] + }, + { + "name": "M8.4: the reading after the run is published as the measured one", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "The three readings are what a reader compares to know which machine the rows belong to; publishing one twice hides a change.", + "find": [ + " .with(\"host_after\", after.map(ToString::to_string))" + ], + "replace": [ + " .with(\"host_after\", Some(observation.host.to_string()))" + ] + }, + { + "name": "M8.4: the by-class list publishes the placement rows", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "Each list is a different selection of pairs; filling one from another is a row that names the wrong question.", + "find": [ + " .with(\"by_class\", pairs(&observation.by_class))" + ], + "replace": [ + " .with(\"by_class\", pairs(&observation.measurements))" + ] + }, + { + "name": "M8.4: a declined row loses the reason", + "file": "src/core_affinity.rs", + "expect": "caught", + "why": "A declined row that says null for why is indistinguishable from a measured one in that field.", + "find": [ + " preamble(before, build).with(\"declined\", error.to_string())" + ], + "replace": [ + " preamble(before, build).with(\"declined\", Value::Null)" + ] + }, + { + "name": "M8.4 control: the row names its build before its architecture", + "file": "src/core_affinity.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.5: the pool-growth probe stops emitting its row", + "file": "src/bin/pool_growth.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(&growth, (RAISE_RUN, raise), &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.5: the runs-long flag is not taken from the run", + "file": "src/pool_growth/raw_row.rs", + "expect": "caught", + "why": "Two runs share a maximum and a submission count and differ only in this flag; without it their rows are indistinguishable but for the name.", + "find": [ + " (\"runs_long\", Value::from(run.runs_long))," + ], + "replace": [ + " (\"runs_long\", Value::from(false))," + ] + }, + { + "name": "M8.5: the two observed counts trade places", + "file": "src/pool_growth/raw_row.rs", + "expect": "caught", + "why": "Callbacks started and threads used are equal on a healthy run, which is exactly why a swap would go unseen on one.", + "find": [ + " Value::from(observed.started_while_blocked),", + " ),", + " (\"distinct_threads\", Value::from(observed.distinct_threads))," + ], + "replace": [ + " Value::from(observed.distinct_threads),", + " ),", + " (\"distinct_threads\", Value::from(observed.started_while_blocked))," + ] + }, + { + "name": "M8.5: whether the raise took effect is inverted", + "file": "src/pool_growth/raw_row.rs", + "expect": "caught", + "why": "The elapsed time means the raise's delay or the settle window according to this flag alone.", + "find": [ + " (\"took_effect\", Value::from(observed.took_effect))," + ], + "replace": [ + " (\"took_effect\", Value::from(!observed.took_effect))," + ] + }, + { + "name": "M8.5: the raise's elapsed time is published in microseconds", + "file": "src/pool_growth/raw_row.rs", + "expect": "caught", + "why": "The arrivals are microseconds and the raise is nanoseconds; carrying the wrong unit under the `_ns` name is the plausible slip.", + "find": [ + " (\"elapsed_ns\", wide(observed.delay.as_nanos()))," + ], + "replace": [ + " (\"elapsed_ns\", wide(observed.delay.as_micros()))," + ] + }, + { + "name": "M8.5: a yes-or-no renders backwards", + "file": "src/row.rs", + "expect": "caught", + "why": "Every boolean any row publishes goes through this one arm.", + "find": [ + " Self::Bool(held) => out.push_str(if *held { \"true\" } else { \"false\" })," + ], + "replace": [ + " Self::Bool(held) => out.push_str(if *held { \"false\" } else { \"true\" })," + ] + }, + { + "name": "M8.5 control: the row names its build before its architecture", + "file": "src/pool_growth/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.6: the long-path probes stop emitting their row", + "file": "src/long_path_report.rs", + "expect": "caught", + "why": "Both binaries share this renderer, so the census sees two probes lose their row at once.", + "find": [ + " crate::long_path::raw_row(", + " &observation,", + " &crate::build_identity::this_build().to_string()", + " )", + " .render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.6: the row claims the manifest whatever the binary declares", + "file": "src/long_path/raw_row.rs", + "expect": "caught", + "why": "The finding is the difference between the two binaries; a row that does not say which one it came from cannot be paired.", + "find": [ + " .with(\"manifest_aware\", observation.manifest_aware)" + ], + "replace": [ + " .with(\"manifest_aware\", true)" + ] + }, + { + "name": "M8.6: an attempt that opened publishes the error it never read", + "file": "src/long_path/raw_row.rs", + "expect": "caught", + "why": "The apparatus leaves the error at zero on success without calling GetLastError; publishing it reads as a code that was observed.", + "find": [ + " Value::from((!attempt.opened).then_some(attempt.error))," + ], + "replace": [ + " Value::from(Some(attempt.error))," + ] + }, + { + "name": "M8.6: a registry value never read is published as off", + "file": "src/long_path/raw_row.rs", + "expect": "caught", + "why": "The module's own note records this exact defect in the prose: a refused run reporting the machine half absent on a host where it is set.", + "find": [ + " .with(\"registry_enabled\", observation.registry_enabled)" + ], + "replace": [ + " .with(\"registry_enabled\", observation.registry_enabled.unwrap_or(false))" + ] + }, + { + "name": "M8.6: a refused run loses the reason", + "file": "src/long_path/raw_row.rs", + "expect": "caught", + "why": "When the apparatus fails the attempts say nothing about the machine, and this field is the only thing that says so.", + "find": [ + " .with(\"declined\", observation.apparatus_error.clone())" + ], + "replace": [ + " .with(\"declined\", None::)" + ] + }, + { + "name": "M8.6: two shapes trade record names", + "file": "src/long_path.rs", + "expect": "caught", + "why": "The two shapes that do not survive verbatim parsing are the hypothesis; swapping their names attributes each one's result to the other.", + "find": [ + " Self::DotDot => \"dot_dot\",", + " Self::ForwardSlash => \"forward_slash\"," + ], + "replace": [ + " Self::DotDot => \"forward_slash\",", + " Self::ForwardSlash => \"dot_dot\"," + ] + }, + { + "name": "M8.6 control: the row names its build before its architecture", + "file": "src/long_path/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.7: the error-mode probe stops emitting its row", + "file": "src/bin/error_mode.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(&observed, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.7: a bit that was set publishes the error it never read", + "file": "src/error_mode/raw_row.rs", + "expect": "caught", + "why": "The probe leaves the error at zero on success without calling GetLastError; publishing it reads as a code that was observed.", + "find": [ + " Value::from((!outcome.set_ok).then_some(outcome.last_error))," + ], + "replace": [ + " Value::from(Some(outcome.last_error))," + ] + }, + { + "name": "M8.7: the combined call publishes only its valid bits as the attempt", + "file": "src/error_mode/raw_row.rs", + "expect": "caught", + "why": "The finding is what one invalid bit does to the valid ones beside it; an attempt without the invalid bit is a different experiment.", + "find": [ + " (\"attempted\", Value::from(COMBINED_ATTEMPT))," + ], + "replace": [ + " (\"attempted\", Value::from(COMBINED_VALID))," + ] + }, + { + "name": "M8.7: the process and thread modes trade places", + "file": "src/error_mode/raw_row.rs", + "expect": "caught", + "why": "Whether the thread mode is a view of the process mode is read from exactly these two numbers.", + "find": [ + " (\"process_mode\", Value::from(versus.process_mode)),", + " (\"thread_mode\", Value::from(versus.thread_mode))," + ], + "replace": [ + " (\"process_mode\", Value::from(versus.thread_mode)),", + " (\"thread_mode\", Value::from(versus.process_mode))," + ] + }, + { + "name": "M8.7: the alignment readings trade places", + "file": "src/error_mode/raw_row.rs", + "expect": "caught", + "why": "Stickiness is the bit present after the restore; swapping the two readings reports a sticky bit as cleared.", + "find": [ + " (\"before\", Value::from(before)),", + " (\"after_restore\", Value::from(after))," + ], + "replace": [ + " (\"before\", Value::from(after)),", + " (\"after_restore\", Value::from(before))," + ] + }, + { + "name": "M8.7 control: the row names its build before its architecture", + "file": "src/error_mode/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.8: the handle-state probe stops emitting its row", + "file": "src/bin/handle_state.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(&observed, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.8: the control publishes the duplicate's observation", + "file": "src/handle_state/raw_row.rs", + "expect": "caught", + "why": "The control is what makes the duplicate's continuation attributable to duplication; publishing one in place of the other erases the comparison.", + "find": [ + " Value::Object(cursor(&observation.separate_opens))," + ], + "replace": [ + " Value::Object(cursor(&observation.duplicate))," + ] + }, + { + "name": "M8.8: a failed second call is published as an empty listing", + "file": "src/handle_state/raw_row.rs", + "expect": "caught", + "why": "Exhaustion is reported as an error code; an empty list would read as a call that succeeded and returned nothing.", + "find": [ + " Err(code) => (Value::Null, Value::from(*code))," + ], + "replace": [ + " Err(code) => (Value::List(Vec::new()), Value::from(*code))," + ] + }, + { + "name": "M8.8: whether the source survived the close is not published", + "file": "src/handle_state/raw_row.rs", + "expect": "caught", + "why": "A design in which a request owns a duplicate and drops it depends on this one answer.", + "find": [ + " .with(\"source_survived_close\", observation.source_survived_close)" + ], + "replace": [ + " .with(\"source_survived_close\", true)" + ] + }, + { + "name": "M8.8 control: the row names its build before its architecture", + "file": "src/handle_state/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.9: the worker-context probe stops emitting its row", + "file": "src/bin/worker_context.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(plain, impersonating, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.9: the submitter and its worker trade places", + "file": "src/worker_context/raw_row.rs", + "expect": "caught", + "why": "The finding is the asymmetry; swapping the two sides reports the worker as the one holding the token.", + "find": [ + " (\"submitter\", context(impersonating.submitter)),", + " (\"worker\", context(impersonating.worker))," + ], + "replace": [ + " (\"submitter\", context(impersonating.worker)),", + " (\"worker\", context(impersonating.submitter))," + ] + }, + { + "name": "M8.9: a thread with a token publishes the error it never read", + "file": "src/worker_context/raw_row.rs", + "expect": "caught", + "why": "The probe leaves the error at zero when the token opens, without calling GetLastError; publishing it reads as a code that was observed.", + "find": [ + " Value::from((!seen.has_thread_token).then_some(seen.open_token_error))," + ], + "replace": [ + " Value::from(Some(seen.open_token_error))," + ] + }, + { + "name": "M8.9 control: the row names its build before its architecture", + "file": "src/worker_context/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.10: the device-map probe stops emitting its row", + "file": "src/bin/device_map.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(&finding, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.10: a letter that resolved to nothing is published as an empty list", + "file": "src/device_map/raw_row.rs", + "expect": "caught", + "why": "The finding is the letter vanishing under impersonation; an empty list reads as a definition with no targets rather than none at all.", + "find": [ + " } else {", + " Value::Null", + " }," + ], + "replace": [ + " } else {", + " Value::List(Vec::new())", + " }," + ] + }, + { + "name": "M8.10: the logon session's halves trade places", + "file": "src/device_map/raw_row.rs", + "expect": "caught", + "why": "The session is an identifier a reader compares across rows; the prose prints it high:low, and the row must too.", + "find": [ + " .map(|(low, high)| format!(\"{high:08x}:{low:08x}\"))," + ], + "replace": [ + " .map(|(low, high)| format!(\"{low:08x}:{high:08x}\"))," + ] + }, + { + "name": "M8.10: the two sessions trade places", + "file": "src/device_map/raw_row.rs", + "expect": "caught", + "why": "Which context the letter vanished in is the finding.", + "find": [ + " .with(\"own_session\", session(&finding.own_session))", + " .with(\"anonymous_session\", session(&finding.anonymous_session))" + ], + "replace": [ + " .with(\"own_session\", session(&finding.anonymous_session))", + " .with(\"anonymous_session\", session(&finding.own_session))" + ] + }, + { + "name": "M8.10: a declined run loses the reason", + "file": "src/device_map/raw_row.rs", + "expect": "caught", + "why": "A declined row that says null for why is indistinguishable from a measured one in that field.", + "find": [ + " preamble(build).with(\"declined\", reason)" + ], + "replace": [ + " preamble(build).with(\"declined\", Value::Null)" + ] + }, + { + "name": "M8.10 control: the row names its build before its architecture", + "file": "src/device_map/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.11: the IoRing probe stops emitting its row", + "file": "src/bin/ioring.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(®istration, &agnosticism, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.11: the two registration readings trade places", + "file": "src/ioring/raw_row.rs", + "expect": "caught", + "why": "Index 0 is the control and index 1 the finding; swapped, a table that was replaced reads as a broken probe.", + "find": [ + " Value::from(observed.index_zero_usable_after_second),", + " ),", + " (", + " \"index_one_usable_after_second\",", + " Value::from(observed.index_one_usable_after_second)," + ], + "replace": [ + " Value::from(observed.index_one_usable_after_second),", + " ),", + " (", + " \"index_one_usable_after_second\",", + " Value::from(observed.index_zero_usable_after_second)," + ] + }, + { + "name": "M8.11: the result code is published as a signed decimal", + "file": "src/ioring/raw_row.rs", + "expect": "caught", + "why": "A failing HRESULT is negative as an i32; published as a decimal it is a number nobody can look up.", + "find": [ + " Value::from(format!(\"{:#010x}\", observed.result_code))," + ], + "replace": [ + " Value::from(format!(\"{}\", observed.result_code))," + ] + }, + { + "name": "M8.11: a declined run loses the reason", + "file": "src/ioring/raw_row.rs", + "expect": "caught", + "why": "A declined row that says null for why is indistinguishable from a measured one in that field.", + "find": [ + " preamble(build).with(\"declined\", reason)" + ], + "replace": [ + " preamble(build).with(\"declined\", Value::Null)" + ] + }, + { + "name": "M8.11 control: the row names its build before its architecture", + "file": "src/ioring/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.12: the completion-port probe stops emitting its row", + "file": "src/bin/completion_port.rs", + "expect": "caught", + "why": "The census runs the probe, so a run that prints no row changes the set it reports.", + "find": [ + " raw_row(&support, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.12: the control publishes the associated read", + "file": "src/completion_port/raw_row.rs", + "expect": "caught", + "why": "The unassociated read is the negative control every other case is judged against; filling it from the test case erases the comparison.", + "find": [ + " (\"control_unassociated\", read(finding.control_unassociated))," + ], + "replace": [ + " (\"control_unassociated\", read(finding.after_iocp_association))," + ] + }, + { + "name": "M8.12: the byte count and the first byte trade places", + "file": "src/completion_port/raw_row.rs", + "expect": "caught", + "why": "The probe's first version read a clean failure as a success by trusting one of these three fields alone; each must be published as itself.", + "find": [ + " (\"bytes\", Value::from(attempt.bytes)),", + " (\"first_byte\", Value::from(attempt.first_byte))," + ], + "replace": [ + " (\"bytes\", Value::from(attempt.first_byte)),", + " (\"first_byte\", Value::from(attempt.bytes))," + ] + }, + { + "name": "M8.12: the port control is not taken from the run", + "file": "src/completion_port/raw_row.rs", + "expect": "caught", + "why": "It separates 'the IoRing path is refused' from 'the handle is broken'.", + "find": [ + " .with(\"port_still_works\", finding.port_still_works)" + ], + "replace": [ + " .with(\"port_still_works\", true)" + ] + }, + { + "name": "M8.12: a host without a ring loses the reason", + "file": "src/completion_port/raw_row.rs", + "expect": "caught", + "why": "A declined row that says null for why is indistinguishable from a measured one in that field.", + "find": [ + " return row.with(\"declined\", NO_USABLE_IORING);" + ], + "replace": [ + " return row.with(\"declined\", Value::Null);" + ] + }, + { + "name": "M8.12 control: the row names its build before its architecture", + "file": "src/completion_port/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.13: an attempt the watchdog abandoned is published as a cancel", + "file": "src/cancel_io/raw_row.rs", + "expect": "caught", + "why": "The wedge is the finding, and the item asks for exactly this case to reach the row.", + "find": [ + " CancelOutcome::Wedged => (\"wedged\", None)," + ], + "replace": [ + " CancelOutcome::Wedged => (\"cancelled\", None)," + ] + }, + { + "name": "M8.13: a call that found nothing loses its code", + "file": "src/cancel_io/raw_row.rs", + "expect": "caught", + "why": "The raw code is what keeps a different failure from being read as ERROR_NOT_FOUND.", + "find": [ + " CancelOutcome::NotFound { code } => (\"not_found\", Some(code))," + ], + "replace": [ + " CancelOutcome::NotFound { code: _ } => (\"not_found\", None::)," + ] + }, + { + "name": "M8.13: the busy attempts are published in reverse", + "file": "src/cancel_io/raw_row.rs", + "expect": "caught", + "why": "Which attempt wedged, and what followed it, is read by position.", + "find": [ + " .with(\"busy\", busy.iter().copied().map(outcome).collect::())" + ], + "replace": [ + " .with(\"busy\", busy.iter().rev().copied().map(outcome).collect::())" + ] + }, + { + "name": "M8.13 control: the row names its build before its architecture", + "file": "src/cancel_io/raw_row.rs", + "expect": "survives", + "why": "A control: a row is read by key, so the order its keys are written in is not something the tests claim.", + "find": [ + " .with(\"arch\", std::env::consts::ARCH)", + " .with(\"build\", build)" + ], + "replace": [ + " .with(\"build\", build)", + " .with(\"arch\", std::env::consts::ARCH)" + ] + }, + { + "name": "M8.14: the cancel-io probe stops emitting its row", + "file": "src/bin/cancel_io.rs", + "expect": "caught", + "why": "Before M8.14 the census did not run this probe and checked only that its sources carried no emission literal, so it survived; the census now runs every registered probe and requires a row from each.", + "find": [ + " raw_row(idle, &busy, &this_build().to_string()).render()" + ], + "replace": [ + " \"\"" + ] + }, + { + "name": "M8.14: a report that never finishes is accepted when its time runs out", + "file": "tests/the_probes_that_emit_a_machine_readable_row.rs", + "expect": "caught", + "why": "The deadline is what keeps a probe whose subject can fail to return from hanging the suite; reading a timeout as a finished report would pass a probe that never wrote its row.", + "find": [ + " panic!(\"{name} did not finish its report within {limit:?}\");" + ], + "replace": [ + " return row;" + ] + }, + { + "name": "M8.14: an unsuccessful exit is accepted", + "file": "tests/the_probes_that_emit_a_machine_readable_row.rs", + "expect": "caught", + "why": "A probe that failed part-way may still have written a row; a run that failed is not one the census can stand behind.", + "find": [ + " status.success()," + ], + "replace": [ + " true," + ] + }, + { + "name": "M8.14: a probe excused from exiting is waited on anyway", + "file": "tests/the_probes_that_emit_a_machine_readable_row.rs", + "expect": "caught", + "why": "The excuse is what lets the census run the cancel-io probe at all; waiting on a process whose canceller may never return reintroduces the hang.", + "find": [ + " if may_not_exit {" + ], + "replace": [ + " if may_not_exit && false {" + ] + }, + { + "name": "M8.14 control: the deadline is a second shorter", + "file": "tests/the_probes_that_emit_a_machine_readable_row.rs", + "expect": "survives", + "why": "A control: the deadline's size is margin over the slowest probe, not a claim the tests make.", + "find": [ + "const DEADLINE: Duration = Duration::from_secs(120);" + ], + "replace": [ + "const DEADLINE: Duration = Duration::from_secs(119);" + ] + }, + { + "name": "M8.1 control: the short run takes two pushes fewer", + "file": "tests/the_probes_that_emit_a_machine_readable_row.rs", + "expect": "survives", + "why": "A control: how much a short run measures is incidental to which lines its report carries.", + "find": [ + " \"1000\"," + ], + "replace": [ + " \"998\"," + ] + }, + { + "name": "M4.8: the capture script ignores the build", + "file": "captures/2026-10-19-interleaved-order/summarise.js", + "expect": "caught", + "why": "Runs of two builds would agree on every other attribution line and have their medians combined as though one procedure produced both -- the defect M4.8 exists to refuse.", + "find": [ + " build === undefined ? \"build: (not stamped)\" : build.trim()," + ], + "replace": [ + " \"build: (ignored)\"," + ] + }, + { + "name": "M4.8: the queue report stops naming its build", + "file": "src/bin/queue_contention/main.rs", + "expect": "caught", + "why": "A report without the line would read as unstamped, which the capture scripts accept beside other unstamped runs -- so the build check would silently stop applying to new captures.", + "find": [ + " \"build: {}\"," + ], + "replace": [ + " \"built: {}\"," + ] + }, + { + "name": "M4.8: the build script stops stamping this crate", + "file": "build.rs", + "expect": "refused-by-build", + "buildError": "PLATFORM_PROBES_COMMIT_OUT", + "why": "The identity is read with env!, so a build script that stamps nothing cannot produce a binary that reports a made-up identity: the compiler refuses it. Kept to show that rung holds.", + "find": [ + " stamp_build_identity(\"PLATFORM_PROBES\");" + ], + "replace": [ + " let _ = stamp_build_identity;" + ] + }, + { + "name": "M4.8 control: the script test's scratch directory renamed", + "file": "tests/the_capture_scripts_refuse_runs_of_different_builds.rs", + "expect": "survives", + "why": "A control: the directory's name is incidental to what the scripts are given.", + "find": [ + " \"capture-scripts-{}-{}\"," + ], + "replace": [ + " \"capture-script-runs-{}-{}\"," + ] + }, + { + "name": "M4.4: every run takes the sequential order", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "The default would be back to back again while the report said interleaved, so the control would be measured apart under a line claiming it was not.", + "find": [ + " if sampling.order == Order::Sequential {" + ], + "replace": [ + " if true {" + ] + }, + { + "name": "M4.4: interleaved rounds stop rotating", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "Without the rotation the same configuration is always measured first in a round, so position in the round can align with whatever the first pass after the previous round's last absorbs. The balance the item asked for is the rotation.", + "find": [ + " let index = (round + position) % count;" + ], + "replace": [ + " let index = position;" + ] + }, + { + "name": "M4.4: --order is accepted and dropped", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "A run asked for the sequential order would measure interleaved while the command line said otherwise, and the published figures could not be retaken.", + "find": [ + " if let Some(value) = given.get(\"order\") {" + ], + "replace": [ + " if let Some(value) = given.get(\"order\").filter(|_| false) {" + ] + }, + { + "name": "M4.4: the sequential order is no longer the published one", + "file": "src/queue_contention/measure.rs", + "expect": "caught", + "why": "Swapping two configurations changes no row and no table, so only the test pinning the list notices -- and without it --order sequential would quietly stop retaking the figures it exists to retake.", + "find": [ + " (", + " Regime::Isolated,", + " shapes::SLOTWISE_MPSC,", + " timer(time_isolated_mpsc),", + " ),", + " (", + " Regime::Isolated,", + " shapes::RESERVING_MPSC,", + " timer(time_isolated_reserving),", + " )," + ], + "replace": [ + " (", + " Regime::Isolated,", + " shapes::RESERVING_MPSC,", + " timer(time_isolated_reserving),", + " ),", + " (", + " Regime::Isolated,", + " shapes::SLOTWISE_MPSC,", + " timer(time_isolated_mpsc),", + " )," + ] + }, + { + "name": "M4.4: the report omits the order", + "file": "src/bin/queue_contention/main.rs", + "expect": "caught", + "why": "The order is a capture parameter: figures taken interleaved and back to back are measurements of different procedures, and a captured report that does not say which cannot be compared with anything.", + "find": [ + " sampling.order.described()," + ], + "replace": [ + " \"\"," + ] + }, + { + "name": "M4.4 control: the configuration count read another way", + "file": "src/queue_contention.rs", + "expect": "survives", + "why": "A control: the same count by iteration, which no test can tell apart.", + "find": [ + " let count = timers.len();" + ], + "replace": [ + " let count = timers.iter().count();" + ] + }, + { + "name": "review: the capacity has no minimum, so the smallest run panics", + "file": "src/queue_contention.rs", + "expect": "caught", + "why": "One producer making one push sized a queue of one, which the slotwise queue refuses; the probe panicked after its report had begun. The unit test asks each shape's own constructor, and the binary-level test runs the smallest run.", + "find": [ + " .max(MINIMUM_QUEUE_CAPACITY)\n" ], "replace": [ - " timings.push(time_loop(\"share_handle\", ITERATIONS, || Clone::clone(&shared)));" + " .max(1)\n" ] } ] diff --git a/crates/windows-platform-probes/src/bin/cancel_io.rs b/crates/windows-platform-probes/src/bin/cancel_io.rs index 8a17570fa..fc9f98114 100644 --- a/crates/windows-platform-probes/src/bin/cancel_io.rs +++ b/crates/windows-platform-probes/src/bin/cancel_io.rs @@ -11,8 +11,10 @@ //! is a call that can fail to return, so every case here runs behind a //! watchdog; a wedged `#[test]` would take the whole suite with it. +use windows_platform_probes::build_identity::this_build; use windows_platform_probes::cancel_io::{ - CancelOutcome, WATCHDOG, cancel_against_busy_thread, cancel_against_idle_thread, + BUSY_ATTEMPTS, CancelOutcome, WATCHDOG, cancel_against_busy_thread, cancel_against_idle_thread, + raw_row, }; use windows_platform_probes::report::emit_report; @@ -65,7 +67,7 @@ fn render(out: &mut dyn std::fmt::Write) { out, "\ncase 2: cancel against a thread hammering synchronous reads" ); - let busy = cancel_against_busy_thread(4); + let busy = cancel_against_busy_thread(BUSY_ATTEMPTS); for (attempt, outcome) in busy.iter().enumerate() { let _ = writeln!(out, " attempt {}: {}", attempt + 1, describe(*outcome)); } @@ -114,4 +116,12 @@ fn render(out: &mut dyn std::fmt::Write) { " Re-run, and vary the hammer loop, before concluding otherwise." ); } + + // The raw data, last, as every probe's normal run emits it (`M8.13`) -- + // including an attempt the watchdog abandoned, which is the finding. + let _ = writeln!( + out, + "{}", + raw_row(idle, &busy, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/completion_port.rs b/crates/windows-platform-probes/src/bin/completion_port.rs index 7168f85a6..7536c96cd 100644 --- a/crates/windows-platform-probes/src/bin/completion_port.rs +++ b/crates/windows-platform-probes/src/bin/completion_port.rs @@ -13,8 +13,11 @@ //! completion arrived, and so read a clean failure -- result code //! `ERROR_INVALID_PARAMETER`, zero bytes -- as a success. -use windows_platform_probes::completion_port::{CompletionPortFinding, ReadAttempt, measure}; -use windows_platform_probes::ioring::IoRingSupport; +use windows_platform_probes::build_identity::this_build; +use windows_platform_probes::completion_port::{ + CompletionPortFinding, ReadAttempt, measure, raw_row, +}; +use windows_platform_probes::ioring::{IoRingSupport, NO_USABLE_IORING}; use windows_platform_probes::report::emit_report; fn describe(out: &mut dyn std::fmt::Write, label: &str, attempt: ReadAttempt) { @@ -166,14 +169,20 @@ fn render(out: &mut dyn std::fmt::Write) { ); let _ = writeln!(out, "== IOCP association vs IoRing, on one handle ==\n"); - match measure() { + let support = measure(); + match &support { IoRingSupport::Unavailable => { - let _ = writeln!( - out, - "this host has no usable IoRing, so nothing was measured." - ); + let _ = writeln!(out, "{NO_USABLE_IORING}, so nothing was measured."); let _ = writeln!(out, "('cannot ask' is not 'the answer is no'.)"); } - IoRingSupport::Measured(finding) => report(out, finding), + IoRingSupport::Measured(finding) => report(out, *finding), } + // The raw data, last, as every probe's normal run emits it (`M8.12`), and + // after `report` rather than inside it: `report` returns early when a + // control failed, and the row is owed anyway. + let _ = writeln!( + out, + "{}", + raw_row(&support, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/core_affinity.rs b/crates/windows-platform-probes/src/bin/core_affinity.rs index 9202c0e35..d03413265 100644 --- a/crates/windows-platform-probes/src/bin/core_affinity.rs +++ b/crates/windows-platform-probes/src/bin/core_affinity.rs @@ -4,6 +4,8 @@ use windows_placement_probe::core_affinity::{Observation, Placement, measure}; use windows_placement_probe::peer_index_cache::Strategy; +use windows_platform_probes::build_identity::this_build; +use windows_platform_probes::core_affinity::{declined_row, raw_row}; use windows_platform_probes::report::emit_report; use windows_topology_sys::Observed; @@ -62,6 +64,11 @@ fn render(out: &mut dyn std::fmt::Write) { "\nNothing below could be measured, so nothing below is reported. This is\n\ a failure to observe the host, not a finding about it." ); + let _ = writeln!( + out, + "{}", + declined_row(&error, announced.as_ref().ok(), &this_build().to_string()).render() + ); return; } }; @@ -120,6 +127,25 @@ fn render(out: &mut dyn std::fmt::Write) { ); } + render_observation(out, observation); + // The raw data, last, as every probe's normal run emits it (`M8.4`), and + // after the interpretation rather than inside it: the interpretation + // returns early on a host with one placement, and the row is owed anyway. + let _ = writeln!( + out, + "{}", + raw_row( + observation, + announced.as_ref().ok(), + settled.as_ref().ok(), + &this_build().to_string() + ) + .render() + ); +} + +/// The processors, the tables and their interpretation. +fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) { let _ = writeln!(out, "\nprocessors, as discovered:"); let _ = writeln!( out, diff --git a/crates/windows-platform-probes/src/bin/device_map.rs b/crates/windows-platform-probes/src/bin/device_map.rs index 332a95a5b..0c996f123 100644 --- a/crates/windows-platform-probes/src/bin/device_map.rs +++ b/crates/windows-platform-probes/src/bin/device_map.rs @@ -13,7 +13,10 @@ //! path resolved on a submitting thread and opened on a worker under a captured //! token can name a different device. -use windows_platform_probes::device_map::{SubstDrive, measure_with_subst}; +use windows_platform_probes::build_identity::this_build; +use windows_platform_probes::device_map::{ + NO_FREE_DRIVE_LETTER, SubstDrive, declined_row, measure_with_subst, raw_row, +}; use windows_platform_probes::report::emit_report; fn main() { @@ -37,17 +40,19 @@ fn render(out: &mut dyn std::fmt::Write) { let _ = writeln!(out, "== does impersonation change the DOS device map? ==\n"); let Some(drive) = SubstDrive::claim("binary") else { + let _ = writeln!(out, "{NO_FREE_DRIVE_LETTER}, so the probe cannot run."); let _ = writeln!( out, - "no free drive letter on this host, so the probe cannot run." + "(Reported rather than measured: a probe that cannot set up its" ); let _ = writeln!( out, - "(Reported rather than measured: a probe that cannot set up its" + "fixture must say so instead of producing a misleading negative.)" ); let _ = writeln!( out, - "fixture must say so instead of producing a misleading negative.)" + "{}", + declined_row(NO_FREE_DRIVE_LETTER, &this_build().to_string()).render() ); return; }; @@ -136,4 +141,11 @@ fn render(out: &mut dyn std::fmt::Write) { " check the control above before believing the former." ); } + + // The raw data, last, as every probe's normal run emits it (`M8.10`). + let _ = writeln!( + out, + "{}", + raw_row(&finding, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/doorbell_cost.rs b/crates/windows-platform-probes/src/bin/doorbell_cost.rs index b0ba13155..f62fde15a 100644 --- a/crates/windows-platform-probes/src/bin/doorbell_cost.rs +++ b/crates/windows-platform-probes/src/bin/doorbell_cost.rs @@ -15,20 +15,30 @@ use std::fmt::Write as _; use windows_platform_probes::doorbell_cost::{ - EVERY_LABEL, Presence, json_key, measure, measure_park_and_wake, + EVERY_LABEL, LONGEST_LOOP, PARK_ROUNDS, Presence, json_key, measure, measure_park_and_wake, }; -use windows_platform_probes::report::emit_report; +use windows_platform_probes::probe_args::{IterationMultiplier, usage}; +use windows_platform_probes::report::{emit_report, refuse_arguments}; fn main() { + // Read before anything is measured, so a command line the probe cannot honour + // is refused rather than half-run. See `M4.2`. + let multiplier = IterationMultiplier::from_args(std::env::args().skip(1), LONGEST_LOOP) + .unwrap_or_else(|error| { + refuse_arguments( + &error, + &usage("probe-doorbell-cost", &IterationMultiplier::FLAGS), + ) + }); // The probe's whole output policy, and it is one line: hand the renderer to // the sink. Nothing here or below names a stream -- that is chosen once, in // `report`, so retargeting a probe is not a rewrite. - emit_report(render); + emit_report(|out| render(out, multiplier)); } /// The probe's whole report, as text. -fn render(out: &mut dyn std::fmt::Write) { +fn render(out: &mut dyn std::fmt::Write, multiplier: IterationMultiplier) { // First line of the report, and part of the returned text rather than // written out here: a captured report must carry the line naming the // machine that produced it, and the taint marker with it. Without it a @@ -38,6 +48,9 @@ fn render(out: &mut dyn std::fmt::Write) { "{}", windows_placement_probe::fingerprint::banner_line() ); + // Beside the banner, because a figure is only interpretable with what + // produced it, and the span is settable (M4.2). + let _ = writeln!(out, "{}", multiplier.report_line()); let _ = writeln!( out, "== what does a doorbell cost, against the syscall it guards? ==\n" @@ -58,8 +71,8 @@ fn render(out: &mut dyn std::fmt::Write) { // larger than when this `main` was written -- adding loud failures to a // probe while leaving it outside the mechanism that reports them is half a // change. - let observation = &measure(); - let park = measure_park_and_wake(20_000); + let observation = &measure(multiplier); + let park = measure_park_and_wake(multiplier.scale(PARK_ROUNDS)); let _ = writeln!(out, "{:<30} {:>12}", "operation", "ns/op"); for timing in &observation.timings { @@ -367,10 +380,11 @@ fn render(out: &mut dyn std::fmt::Write) { let _ = writeln!( out, concat!( - r#"{{"reason":"x-probe-doorbell-cost","arch":"{}",{}"#, + r#"{{"reason":"x-probe-doorbell-cost","arch":"{}","iteration_multiplier":{},{}"#, r#""park_and_wake_round_trip_ns":{},"doorbell_over_empty_submit":{}}}"# ), std::env::consts::ARCH, + multiplier.get(), fields, park.map_or("null".to_string(), |n| format!("{n:.1}")), observation diff --git a/crates/windows-platform-probes/src/bin/error_mode.rs b/crates/windows-platform-probes/src/bin/error_mode.rs index fc8409ee5..b0d0f5b23 100644 --- a/crates/windows-platform-probes/src/bin/error_mode.rs +++ b/crates/windows-platform-probes/src/bin/error_mode.rs @@ -20,9 +20,11 @@ //! two-host comparison is in this crate's DESIGN-NOTES.md; a disagreement on a //! third host is exactly what this binary is for. +use windows_platform_probes::build_identity::this_build; use windows_platform_probes::error_mode::{ - alignment_bit_is_sticky_at_process_scope, bits, combined_invalid_installs_nothing, probe_bit, - settable_bits, thread_mode_independent_of_process, + Observation, PROBED_BITS, alignment_bit_is_sticky_at_process_scope, bits, + combined_invalid_installs_nothing, probe_bit, raw_row, settable_bits, + thread_mode_independent_of_process, }; use windows_platform_probes::report::emit_report; @@ -62,13 +64,12 @@ fn render(out: &mut dyn std::fmt::Write) { windows_placement_probe::fingerprint::banner_line() ); let _ = writeln!(out, "--- each bit on its own, set then read back ---"); - for bit in [ - bits::FAIL_CRITICAL_ERRORS, - bits::NO_GP_FAULT_ERROR_BOX, - bits::NO_ALIGNMENT_FAULT_EXCEPT, - bits::NO_OPEN_FILE_ERROR_BOX, - ] { + // Every observation is kept as it is taken, so the row (`M8.7`) carries + // exactly what the prose reported, in the order it was measured. + let mut outcomes = Vec::new(); + for bit in PROBED_BITS { let outcome = probe_bit(bit); + outcomes.push(outcome); let verdict = if outcome.is_settable() { "SETTABLE" } else if outcome.is_silently_dropped() { @@ -132,4 +133,17 @@ fn render(out: &mut dyn std::fmt::Write) { "clearable" } ); + + // The raw data, last, as every probe's normal run emits it. + let observed = Observation { + bits: outcomes, + combined_read_back: read_back, + process_versus_thread: observation, + alignment: (before, after), + }; + let _ = writeln!( + out, + "{}", + raw_row(&observed, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/handle_state.rs b/crates/windows-platform-probes/src/bin/handle_state.rs index a3608ebce..814374db4 100644 --- a/crates/windows-platform-probes/src/bin/handle_state.rs +++ b/crates/windows-platform-probes/src/bin/handle_state.rs @@ -11,9 +11,11 @@ //! observations themselves -- the actual file names each handle returned -- //! which is what makes a surprising result diagnosable rather than merely red. +use windows_platform_probes::build_identity::this_build; use windows_platform_probes::handle_state::{ - Fixture, SingleShot, closing_duplicate_preserves_source, duplicate_shares_cursor, ground_truth, - query_disturbs_cursor, separate_opens_are_independent, + Fixture, Observation, QUERIES, QueryObservation, closing_duplicate_preserves_source, + duplicate_shares_cursor, ground_truth, interleave_query, raw_row, + separate_opens_are_independent, }; use windows_platform_probes::report::emit_report; @@ -71,10 +73,11 @@ fn render(out: &mut dyn std::fmt::Write) { ); let _ = writeln!(out, "--- does closing the duplicate break the source? ---"); + let source_survived_close = closing_duplicate_preserves_source(&fixture); let _ = writeln!( out, " -> {}\n", - if closing_duplicate_preserves_source(&fixture) { + if source_survived_close { "no: the source kept enumerating" } else { "YES: the source was broken, so owning a duplicate is unsafe to drop" @@ -85,13 +88,10 @@ fn render(out: &mut dyn std::fmt::Write) { out, "--- does an interleaved single-shot query move the cursor? ---" ); - for (query, on_duplicate) in [ - (SingleShot::BasicInfo, false), - (SingleShot::IdInfo, false), - (SingleShot::NonEx, false), - (SingleShot::BasicInfo, true), - ] { - let (succeeded, disturbed) = query_disturbs_cursor(&fixture, query, on_duplicate, &truth); + let mut queries = Vec::new(); + for (query, on_duplicate) in QUERIES { + let (succeeded, cursor) = interleave_query(&fixture, query, on_duplicate); + let disturbed = !cursor.continued(&truth); let _ = writeln!( out, "{query:?}{} succeeded={succeeded} -> {}", @@ -106,5 +106,25 @@ fn render(out: &mut dyn std::fmt::Write) { "undisturbed" } ); + queries.push(QueryObservation { + query, + on_duplicate, + succeeded, + cursor, + }); } + + // The raw data, last, as every probe's normal run emits it (`M8.8`). + let observed = Observation { + ground_truth: truth, + duplicate: observation, + separate_opens: control, + source_survived_close, + queries, + }; + let _ = writeln!( + out, + "{}", + raw_row(&observed, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/ioring.rs b/crates/windows-platform-probes/src/bin/ioring.rs index b9e3041d5..6c2931b96 100644 --- a/crates/windows-platform-probes/src/bin/ioring.rs +++ b/crates/windows-platform-probes/src/bin/ioring.rs @@ -11,8 +11,10 @@ //! this reports "cannot measure" rather than a false negative on a host that //! has no ring. +use windows_platform_probes::build_identity::this_build; use windows_platform_probes::ioring::{ - IoRingSupport, is_available, measure_registration, measure_thread_agnosticism, + IoRingSupport, NO_USABLE_IORING, declined_row, is_available, measure_registration, + measure_thread_agnosticism, raw_row, }; use windows_platform_probes::report::emit_report; @@ -37,10 +39,7 @@ fn render(out: &mut dyn std::fmt::Write) { let _ = writeln!(out, "== IoRing registration and thread agnosticism ==\n"); if !is_available() { - let _ = writeln!( - out, - "this host has no usable IoRing, so nothing was measured." - ); + let _ = writeln!(out, "{NO_USABLE_IORING}, so nothing was measured."); let _ = writeln!( out, "(Reported rather than measured: 'we could not ask' and 'the answer" @@ -50,11 +49,17 @@ fn render(out: &mut dyn std::fmt::Write) { "is no' are different facts, and conflating them is how a design note" ); let _ = writeln!(out, "ends up citing a measurement that never ran.)"); + let _ = writeln!( + out, + "{}", + declined_row(NO_USABLE_IORING, &this_build().to_string()).render() + ); return; } let _ = writeln!(out, "registration semantics:"); - match measure_registration() { + let registration = measure_registration(); + match ®istration { IoRingSupport::Unavailable => { let _ = writeln!(out, " (no ring)"); } @@ -87,7 +92,8 @@ fn render(out: &mut dyn std::fmt::Write) { } let _ = writeln!(out, "\nthread agnosticism:"); - match measure_thread_agnosticism() { + let agnosticism = measure_thread_agnosticism(); + match &agnosticism { IoRingSupport::Unavailable => { let _ = writeln!(out, " (no ring)"); } @@ -130,4 +136,11 @@ fn render(out: &mut dyn std::fmt::Write) { } } } + + // The raw data, last, as every probe's normal run emits it (`M8.11`). + let _ = writeln!( + out, + "{}", + raw_row(®istration, &agnosticism, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/peer_index_cache.rs b/crates/windows-platform-probes/src/bin/peer_index_cache.rs index 069f96608..c99ef03d8 100644 --- a/crates/windows-platform-probes/src/bin/peer_index_cache.rs +++ b/crates/windows-platform-probes/src/bin/peer_index_cache.rs @@ -6,7 +6,9 @@ //! and are not for production use. Do not call them from production code, and //! do not lift a technique out of here. See this crate's DESIGN-NOTES.md. -use windows_placement_probe::peer_index_cache::{CAPACITY, ITEMS, Strategy, measure}; +use windows_placement_probe::peer_index_cache::{CAPACITY, ITEMS, Observation, Strategy, measure}; +use windows_platform_probes::build_identity::this_build; +use windows_platform_probes::peer_index_cache::{declined_row, raw_row}; use windows_platform_probes::report::emit_report; fn main() { @@ -54,10 +56,27 @@ fn render(out: &mut dyn std::fmt::Write) { "Nothing below could be measured, so nothing below is reported. This is\n\ a refusal to measure the host, not a finding about it." ); + let _ = writeln!( + out, + "{}", + declined_row(&error, &this_build().to_string()).render() + ); return; } }; + render_observation(out, &observation); + // The raw data, last, as every probe's normal run emits it (`M8.3`), and + // after the interpretation rather than inside it: the interpretation + // returns early when a strategy is missing, and the row is owed anyway. + let _ = writeln!( + out, + "{}", + raw_row(&observation, &this_build().to_string()).render() + ); +} +/// The table and its interpretation. +fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) { let _ = writeln!( out, "{:<24} {:>10} {:>14} {:>14} {:>14}", diff --git a/crates/windows-platform-probes/src/bin/pool_growth.rs b/crates/windows-platform-probes/src/bin/pool_growth.rs index c99646045..e71723e05 100644 --- a/crates/windows-platform-probes/src/bin/pool_growth.rs +++ b/crates/windows-platform-probes/src/bin/pool_growth.rs @@ -11,7 +11,11 @@ //! exceed it, and gets there promptly); this binary prints the numbers, which //! is what a new architecture actually needs to be re-measured against. -use windows_platform_probes::pool_growth::{measure_growth, measure_raise_while_saturated}; +use windows_platform_probes::build_identity::this_build; +use windows_platform_probes::pool_growth::{ + GROWTH_RUNS, GrowthObservation, GrowthRun, RAISE_RUN, measure_growth, + measure_raise_while_saturated, raw_row, +}; use windows_platform_probes::report::emit_report; /// Measure one configuration and append its block to `out`. @@ -19,16 +23,10 @@ use windows_platform_probes::report::emit_report; /// Takes the buffer rather than printing: a helper that wrote to stdout while /// its caller composed a string would emit its lines *before* the caller's, /// reordering the report even though every line still appeared. -fn report( - out: &mut dyn std::fmt::Write, - label: &str, - maximum: u32, - submissions: usize, - runs_long: bool, -) { - let observed = measure_growth(maximum, submissions, runs_long); +fn report(out: &mut dyn std::fmt::Write, run: GrowthRun) -> GrowthObservation { + let observed = measure_growth(run.maximum, run.submissions, run.runs_long); - let _ = writeln!(out, "{label}"); + let _ = writeln!(out, "{}", run.label); let _ = writeln!( out, " max {} / submitted {} -> started while blocked {}, distinct threads {}", @@ -45,6 +43,7 @@ fn report( observed.slowest_arrival() ); let _ = writeln!(out, " arrivals (us): {:?}", observed.arrivals_us); + observed } fn main() { @@ -67,15 +66,24 @@ fn render(out: &mut dyn std::fmt::Write) { ); let _ = writeln!(out, "== how a blocked pool grows ==\n"); - report(out, "P1 growth curve, max 4:", 4, 8, false); - let _ = writeln!(out); - report(out, "P1 growth curve, max 8:", 8, 16, false); - let _ = writeln!(out); - report(out, "P2 the same, runs-long:", 4, 8, true); - let _ = writeln!(out); + // One list drives both the prose and the row (`M8.5`), so they cannot + // describe different runs. + let mut growth = Vec::new(); + for run in GROWTH_RUNS { + growth.push((run, report(out, run))); + let _ = writeln!(out); + } - let raise = measure_raise_while_saturated(2, 6, 8); - let _ = writeln!(out, "P3 raise while saturated (2 -> 6):"); + let raise = measure_raise_while_saturated( + RAISE_RUN.base_max, + RAISE_RUN.raised_max, + RAISE_RUN.submissions, + ); + let _ = writeln!( + out, + "P3 raise while saturated ({} -> {}):", + RAISE_RUN.base_max, RAISE_RUN.raised_max + ); if raise.saturated_before_raise() { let _ = writeln!( out, @@ -109,4 +117,10 @@ fn render(out: &mut dyn std::fmt::Write) { out, "Re-run on another architecture rather than assuming they carry over." ); + // The raw data, last, as every probe's normal run emits it. + let _ = writeln!( + out, + "{}", + raw_row(&growth, (RAISE_RUN, raise), &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/bin/queue_contention/corpus.json b/crates/windows-platform-probes/src/bin/queue_contention/corpus.json index f3e312f12..e599bdfd1 100644 --- a/crates/windows-platform-probes/src/bin/queue_contention/corpus.json +++ b/crates/windows-platform-probes/src/bin/queue_contention/corpus.json @@ -28,6 +28,7 @@ "why": "A plausible run. Establishes that the tables render and line up at all, so the stressed cases below are testing something. It also carries measured `reserving(64/64)` rows in both regimes: that layout is cfg-gated behind a native 128-bit exchange, so without them the renderer's 64/64 columns were only ever exercised by the did-not-run path, and a width or alignment fault in a real Wide measurement would have rendered unseen.", "observation": { "available_parallelism": 8, + "sampling": {"pushes_per_producer": 50000, "repetitions": 5, "producer_counts": [1, 2, 4, 8, 16, 32], "order": "interleaved"}, "isolated": [ [ "baseline_fetch_add", @@ -312,7 +313,9 @@ }, "contains": [ "processors available to this process: 8", - "reserving(64/64)" + "reserving(64/64)", + "sampling: 50000 pushes per producer, median of 5 repetitions, one untimed warmup pass, passes interleaved across configurations, producer counts 1, 2, 4, 8, 16, 32", + "(the defaults)" ], "absent": [] } @@ -322,6 +325,7 @@ "why": "A cell's width is a function of measured data: `format_ratio_bounded` sizes its interval from the observed span, so no constant is provably sufficient. These values are chosen to exceed a fixed width rather than to depict a likely run -- and the margin matters, because the width of 22 that two tables carried was NOT overrun by an ordinary outlier. A 300ms repetition against a 4ns one renders 21 characters. Reaching 23 needs a hundredfold ratio as well. The argument for deriving the width is that the bound cannot be established, not that 22 was obviously too small.", "observation": { "available_parallelism": 8, + "sampling": {"pushes_per_producer": 50000, "repetitions": 5, "producer_counts": [1, 2, 4, 8, 16, 32], "order": "interleaved"}, "isolated": [ [ "baseline_fetch_add", @@ -579,6 +583,7 @@ "why": "A row present but carrying the did-not-run sentinel. Zero is not an obviously broken value in any of this report's columns, so the rendered cells must be marked rather than published: `0.0` ns/op reads as immeasurably fast and `0.00x` as a ratio of one.", "observation": { "available_parallelism": 4, + "sampling": {"pushes_per_producer": 50000, "repetitions": 5, "producer_counts": [1, 2, 4, 8, 16, 32], "order": "interleaved"}, "isolated": [ [ "baseline_fetch_add", @@ -719,11 +724,37 @@ ] } }, + { + "name": "a_run_sampled_other_than_the_defaults", + "why": "M4.2 made the sampling settable, and M4.4 made the order of the passes a setting. The report must print what the run used -- the pushes, the repetitions, the order and the producer counts, in the order given -- say that it was not the defaults, and draw its tables over the producer counts the run measured rather than the compiled-in ones.", + "observation": { + "available_parallelism": 8, + "sampling": {"pushes_per_producer": 1000, "repetitions": 3, "producer_counts": [1, 3], "order": "sequential"}, + "isolated": [], + "drained": [] + }, + "expect": { + "aligned_tables": { + "16/48 vs": 2, + "reserving/slotwise": 1, + "atomic floor": 1 + }, + "contains": [ + "sampling: 1000 pushes per producer, median of 3 repetitions, one untimed warmup pass, each configuration's passes back to back, producer counts 1, 3", + "(NOT the defaults: set on the command line, so these figures are not the default run's)" + ], + "absent": [ + "(the defaults)", + "producer counts 1, 2, 4" + ] + } + }, { "name": "nothing_measured_at_all", "why": "Every shape absent, which is what a `cfg`-elided target or a failed run looks like from the renderer's side. It must still produce a report with its headers rather than panicking or printing figures it does not have.", "observation": { "available_parallelism": null, + "sampling": {"pushes_per_producer": 50000, "repetitions": 5, "producer_counts": [1, 2, 4, 8, 16, 32], "order": "interleaved"}, "isolated": [], "drained": [] }, @@ -749,6 +780,7 @@ "why": "A median is not the only field a row publishes. `render_table` prints ops/sec and both range endpoints directly once `is_measured` says the row counts, so a plausible median beside a poisoned endpoint reached the report as an ordinary-looking cell. These rows keep an ordinary median and spoil one other field each: a zero slowest endpoint, a negative fastest one, a zero ops/sec. JSON cannot express infinity, so the non-finite half of the guard is not reachable from a corpus case and is covered by the same predicate.", "observation": { "available_parallelism": 4, + "sampling": {"pushes_per_producer": 50000, "repetitions": 5, "producer_counts": [1, 2, 4, 8, 16, 32], "order": "interleaved"}, "isolated": [ [ "baseline_fetch_add", diff --git a/crates/windows-platform-probes/src/bin/queue_contention/main.rs b/crates/windows-platform-probes/src/bin/queue_contention/main.rs index a34602898..b23a6ae52 100644 --- a/crates/windows-platform-probes/src/bin/queue_contention/main.rs +++ b/crates/windows-platform-probes/src/bin/queue_contention/main.rs @@ -13,18 +13,23 @@ //! either -- see `queue_contention`'s module docs for what the regimes can and //! cannot separate. +use windows_platform_probes::probe_args::usage; use windows_platform_probes::queue_contention::{ - DRAINED_CAPACITY, Observation, PRODUCER_COUNTS, PUSHES_PER_PRODUCER, REPETITIONS, column_width, - format_nanos, format_ratio_bounded, format_scaling_bounded, measure, ratio_column_width, - render_table, shapes, + DRAINED_CAPACITY, Observation, Sampling, column_width, format_nanos, format_ratio_bounded, + format_scaling_bounded, measure, ratio_column_width, raw_row, render_table, shapes, }; -use windows_platform_probes::report::emit_report; +use windows_platform_probes::report::{emit_report, refuse_arguments}; fn main() { + // Read before anything is measured, so a command line the probe cannot honour + // is refused rather than half-run. See `M4.2`. + let sampling = Sampling::from_args(std::env::args().skip(1)).unwrap_or_else(|error| { + refuse_arguments(&error, &usage("probe-queue-contention", &Sampling::FLAGS)) + }); // The probe's whole output policy, and it is one line: hand the renderer to // the sink. Nothing here or below names a stream -- that is chosen once, in // `report`, so retargeting a probe is not a rewrite. - emit_report(render); + emit_report(|out| render(out, &sampling)); } #[cfg(test)] @@ -40,7 +45,7 @@ mod tests; /// two tables hard-coded a column width for formatters whose output has no /// fixed maximum, and the drained footer printed a seven-run result beneath a /// table produced by one invocation. See `M4.7`. -fn render(out: &mut dyn std::fmt::Write) { +fn render(out: &mut dyn std::fmt::Write, sampling: &Sampling) { // The banner is the one line that is not a function of the observation -- // it is a fresh topology read -- so it is written here and // `render_observation` stays a pure function of what was measured. That @@ -55,7 +60,30 @@ fn render(out: &mut dyn std::fmt::Write) { "{}", windows_placement_probe::fingerprint::banner_line() ); - render_observation(out, &measure()); + // The build that produced the figures, beside the banner and for the same + // reason: a capture's runs are comparable only when one build produced them, + // and the capture scripts refuse a set whose runs name different builds + // (`M4.8`). Written here rather than in `render_observation` because it is a + // fact about this binary, not about what was measured. + let _ = writeln!( + out, + "build: {}", + windows_platform_probes::build_identity::this_build() + ); + let observation = measure(sampling); + render_observation(out, &observation); + // The raw data, last, as every probe's normal run emits it (`M8.2`). The + // build comes from the same `this_build()` as the report's `build:` line, + // so the two cannot disagree. + let _ = writeln!( + out, + "{}", + raw_row( + &observation, + &windows_platform_probes::build_identity::this_build().to_string() + ) + .render() + ); } /// Everything the report says about an observation. @@ -80,9 +108,12 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) // interpretable with them -- see D-observations-not-verdicts. The build // profile is one of them too: a captured report has to be able to show it // was produced by a build that can measure, not merely stay silent when it - // was. The dispersion belongs here as well, and now is: each row carries the - // range across its repetitions and the resulting spread. What M4.2 still - // covers is making the sampling parameters settable rather than fixed. + // was. The dispersion belongs here as well, and is: each row carries the + // range across its repetitions and the resulting spread. The sampling is + // settable (M4.2), so it is read from the observation -- what produced these + // figures -- and a run that did not use the defaults says so. The order is + // printed too (M4.4): the figures published before it were taken + // sequentially, and the default is now interleaved. let _ = writeln!( out, "profile: {}", @@ -92,11 +123,29 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) "release" } ); + let sampling = &observation.sampling; + let counts: Vec = sampling + .producer_counts + .iter() + .map(ToString::to_string) + .collect(); let _ = writeln!( out, "sampling: {} pushes per producer, median of {} repetitions, one untimed \ - warmup pass\n", - PUSHES_PER_PRODUCER, REPETITIONS + warmup pass, {}, producer counts {}", + sampling.pushes_per_producer, + sampling.repetitions, + sampling.order.described(), + counts.join(", "), + ); + let _ = writeln!( + out, + "{}\n", + if sampling.is_default() { + " (the defaults)" + } else { + " (NOT the defaults: set on the command line, so these figures are not the default run's)" + } ); // A debug build does not merely lose precision here: the un-inlined overhead // swamps the cache-coherence effects that ARE the finding, and the two MPSC @@ -144,7 +193,9 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) // reason: `format_scaling_bounded` renders a point plus a measured interval, // whose width follows the data and has no fixed maximum. See // `ratio_column_width`. - let scaling_rows: Vec<(usize, [String; 4])> = PRODUCER_COUNTS + let scaling_rows: Vec<(usize, [String; 4])> = observation + .sampling + .producer_counts .iter() .map(|&producers| { let cell = |shape: &str| { @@ -232,7 +283,9 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) // whose width follows the measured span. The header labels join the // derivation rather than being assumed to fit, so the column is correct by // construction instead of by the floor happening to exceed them. - let drained_rows: Vec<(usize, String, String, String, String, String)> = PRODUCER_COUNTS + let drained_rows: Vec<(usize, String, String, String, String, String)> = observation + .sampling + .producer_counts .iter() .map(|&producers| { let plain = observation.find(&observation.drained, shapes::SLOTWISE_MPSC, producers); @@ -355,16 +408,24 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) // present while carrying the did-not-run sentinel, which `render_table` // marks `--` and this sentence would otherwise still count. See // `Observation::count_measured`. - let layouts_measured = observation.count_measured( - &observation.isolated, - &[ - shapes::CLAIM_NARROW, - shapes::CLAIM_DEEP, - shapes::CLAIM_PERPETUAL, - shapes::CLAIM_WIDE, - ], - PRODUCER_COUNTS[0], - ); + // At the first producer count measured; an observation that measured none + // counts none, rather than indexing past an empty list. + let layouts_measured = observation + .sampling + .producer_counts + .first() + .map_or(0, |&producers| { + observation.count_measured( + &observation.isolated, + &[ + shapes::CLAIM_NARROW, + shapes::CLAIM_DEEP, + shapes::CLAIM_PERPETUAL, + shapes::CLAIM_WIDE, + ], + producers, + ) + }); let _ = writeln!( out, " {layouts_measured} apportionments of reserving_mpsc's claim word, measured on" @@ -440,7 +501,9 @@ fn render_observation(out: &mut dyn std::fmt::Write, observation: &Observation) // width is a minimum, so sizing the header first and discovering a wider // cell later does not truncate that cell -- it silently pushes the two // columns after it out of line. See `ratio_column_width`. - let rows: Vec<(usize, [String; 4], [String; 3])> = PRODUCER_COUNTS + let rows: Vec<(usize, [String; 4], [String; 3])> = observation + .sampling + .producer_counts .iter() .map(|&producers| { let narrow = observation.find(regime, shapes::CLAIM_NARROW, producers); diff --git a/crates/windows-platform-probes/src/bin/queue_contention/tests.rs b/crates/windows-platform-probes/src/bin/queue_contention/tests.rs index 6eb6992d3..98ea9f373 100644 --- a/crates/windows-platform-probes/src/bin/queue_contention/tests.rs +++ b/crates/windows-platform-probes/src/bin/queue_contention/tests.rs @@ -17,7 +17,7 @@ use super::render_observation; use serde_json::Value; use std::panic::AssertUnwindSafe; -use windows_platform_probes::queue_contention::{Observation, Run}; +use windows_platform_probes::queue_contention::{Observation, Order, Run, Sampling}; /// Asserts every line of a table shares its header's width. /// @@ -134,6 +134,38 @@ fn observation_from(value: &Value) -> Observation { Some(usize::try_from(count).expect("a processor count fits a usize")) } }, + sampling: sampling_from(value.get("sampling").unwrap_or_else(|| { + panic!("a case states the `sampling` its run used, as the report prints it") + })), + } +} + +/// A case's `sampling`, every field stated. Required rather than defaulted, for +/// the reason `available_parallelism` is: the report prints it, so a case that +/// left it out would test a line nobody chose. +fn sampling_from(value: &Value) -> Sampling { + let whole = |key: &str| -> usize { + let number = value[key] + .as_u64() + .unwrap_or_else(|| panic!("`sampling.{key}` is a whole number")); + usize::try_from(number).expect("a sampling count fits a usize") + }; + Sampling { + pushes_per_producer: whole("pushes_per_producer"), + repetitions: whole("repetitions"), + producer_counts: value["producer_counts"] + .as_array() + .expect("`sampling.producer_counts` is an array") + .iter() + .map(|count| { + usize::try_from(count.as_u64().expect("a producer count is a whole number")) + .expect("a producer count fits a usize") + }) + .collect(), + order: value["order"] + .as_str() + .and_then(Order::from_name) + .expect("`sampling.order` is `interleaved` or `sequential`"), } } diff --git a/crates/windows-platform-probes/src/bin/request_cost.rs b/crates/windows-platform-probes/src/bin/request_cost.rs index 888fa2fca..19e416b83 100644 --- a/crates/windows-platform-probes/src/bin/request_cost.rs +++ b/crates/windows-platform-probes/src/bin/request_cost.rs @@ -12,8 +12,9 @@ use std::fmt::Write as _; -use windows_platform_probes::report::emit_report; -use windows_platform_probes::request_cost::{EVERY_LABEL, json_key, measure}; +use windows_platform_probes::probe_args::{IterationMultiplier, usage}; +use windows_platform_probes::report::{emit_report, refuse_arguments}; +use windows_platform_probes::request_cost::{EVERY_LABEL, LONGEST_LOOP, json_key, measure}; /// Measured by `probe-doorbell-cost` on a **Snapdragon X2 (ARM64)** machine, /// and recorded in [the 2026-08-30 design session]. Restated here only to @@ -43,14 +44,23 @@ const DOORBELL_NS_REFERENCE: f64 = 164.9; const ATOMIC_NS_REFERENCE: f64 = 7.2; fn main() { + // Read before anything is measured, so a command line the probe cannot honour + // is refused rather than half-run. See `M4.2`. + let multiplier = IterationMultiplier::from_args(std::env::args().skip(1), LONGEST_LOOP) + .unwrap_or_else(|error| { + refuse_arguments( + &error, + &usage("probe-request-cost", &IterationMultiplier::FLAGS), + ) + }); // The probe's whole output policy, and it is one line: hand the renderer to // the sink. Nothing here or below names a stream -- that is chosen once, in // `report`, so retargeting a probe is not a rewrite. - emit_report(render); + emit_report(|out| render(out, multiplier)); } /// The probe's whole report, as text. -fn render(out: &mut dyn std::fmt::Write) { +fn render(out: &mut dyn std::fmt::Write, multiplier: IterationMultiplier) { // First line of the report, and part of the returned text rather than // written out here: a captured report must carry the line naming the // machine that produced it, and the taint marker with it. Without it a @@ -60,9 +70,12 @@ fn render(out: &mut dyn std::fmt::Write) { "{}", windows_placement_probe::fingerprint::banner_line() ); + // Beside the banner, because a figure is only interpretable with what + // produced it, and the span is settable (M4.2). + let _ = writeln!(out, "{}", multiplier.report_line()); let _ = writeln!(out, "== what does a namespace request cost to build? ==\n"); - let observation = measure(); + let observation = measure(multiplier); let _ = writeln!( out, @@ -380,7 +393,8 @@ fn render(out: &mut dyn std::fmt::Write) { let _ = writeln!( out, - r#"{{"reason":"x-probe-request-cost","arch":"{}",{fields}}}"#, + r#"{{"reason":"x-probe-request-cost","arch":"{}","iteration_multiplier":{},{fields}}}"#, std::env::consts::ARCH, + multiplier.get(), ); } diff --git a/crates/windows-platform-probes/src/bin/worker_context.rs b/crates/windows-platform-probes/src/bin/worker_context.rs index 24509332b..c2453cb47 100644 --- a/crates/windows-platform-probes/src/bin/worker_context.rs +++ b/crates/windows-platform-probes/src/bin/worker_context.rs @@ -12,9 +12,10 @@ //! measurement can be eyeballed on a new host or a new Windows build without //! reading a test's output. +use windows_platform_probes::build_identity::this_build; use windows_platform_probes::report::emit_report; use windows_platform_probes::worker_context::{ - observe_on_worker, observe_on_worker_while_impersonating, + observe_on_worker, observe_on_worker_while_impersonating, raw_row, }; fn main() { @@ -103,4 +104,11 @@ conclusion:" " UNEXPECTED: a worker starts with the handler suppressed." ); } + + // The raw data, last, as every probe's normal run emits it (`M8.9`). + let _ = writeln!( + out, + "{}", + raw_row(plain, impersonating, &this_build().to_string()).render() + ); } diff --git a/crates/windows-platform-probes/src/build_identity.rs b/crates/windows-platform-probes/src/build_identity.rs new file mode 100644 index 000000000..437e72a88 --- /dev/null +++ b/crates/windows-platform-probes/src/build_identity.rs @@ -0,0 +1,29 @@ +// Copyright (c) Mike Grier. + +//! Which build of these probes produced a report (`M4.8`). +//! +//! The identity is `windows-placement-probe`'s type, stamped by the same +//! build-script code under this crate's own names, so the two crates' reports +//! name their builds the same way. A capture's runs are only comparable when the +//! same build produced them, and nothing but the report itself can say which +//! build that was. + +pub use windows_placement_probe::build_identity::{BuildIdentity, BuildSource}; + +/// This binary's identity, as this crate's build script stamped it. +/// +/// **A dirty build names its commit, not its source.** Two dirty builds at one +/// commit render alike while their trees may differ, so a capture taken from one +/// still has to say in its README what was built. +#[must_use] +pub fn this_build() -> BuildIdentity { + BuildIdentity::from_stamps( + env!("CARGO_PKG_VERSION"), + env!("PLATFORM_PROBES_COMMIT_OUT"), + env!("PLATFORM_PROBES_DIRTY_OUT"), + env!("PLATFORM_PROBES_SOURCE_OUT"), + ) +} + +#[cfg(test)] +mod tests; diff --git a/crates/windows-platform-probes/src/build_identity/tests.rs b/crates/windows-platform-probes/src/build_identity/tests.rs new file mode 100644 index 000000000..f233a9f00 --- /dev/null +++ b/crates/windows-platform-probes/src/build_identity/tests.rs @@ -0,0 +1,48 @@ +// Copyright (c) Mike Grier. +//! Tests for this crate's build identity. + +use super::*; + +#[test] +fn this_build_names_this_crates_version() { + assert_eq!(this_build().crate_version, env!("CARGO_PKG_VERSION")); +} + +#[test] +fn the_build_script_stamped_a_commit_exactly_when_a_repository_was_there() { + // Both directions, because each failure is real: a repository with no + // commit stamped is the build script failing silently, and a commit with no + // repository is a stamp invented from nothing. A scratch copy without `.git` + // -- which is how cargo-mutants builds -- is the honest second case, so this + // asserts what the build could determine rather than that a commit exists. + let repository = !env!("PLATFORM_PROBES_REPOSITORY_OUT").is_empty(); + let stamped_by_ci = !option_env!("PLATFORM_PROBES_COMMIT") + .unwrap_or_default() + .trim() + .is_empty(); + let identity = this_build(); + if repository || stamped_by_ci { + assert!( + identity.commit.is_some(), + "a repository was there (or CI named the commit), and no commit was stamped: \ + {identity}" + ); + } else { + assert_eq!(identity.commit, None, "{identity}"); + assert_eq!(identity.source, BuildSource::Unknown, "{identity}"); + } +} + +#[test] +fn this_crate_reads_its_own_stamps_not_the_placement_probes() { + // The two crates are stamped by the same code under different names, so + // reading the wrong crate's stamp would compile and report a plausible + // identity. The version is what tells them apart. + let placement = windows_placement_probe::build_identity::BuildIdentity::current(); + assert_ne!( + this_build().crate_version, + placement.crate_version, + "the two crates' versions coincide, so this test cannot tell them apart" + ); + assert_eq!(this_build().crate_version, env!("CARGO_PKG_VERSION")); +} diff --git a/crates/windows-platform-probes/src/cancel_io.rs b/crates/windows-platform-probes/src/cancel_io.rs index 2702bd866..fec0af8a1 100644 --- a/crates/windows-platform-probes/src/cancel_io.rs +++ b/crates/windows-platform-probes/src/cancel_io.rs @@ -59,6 +59,13 @@ use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetCurrentThread} /// only question is whether it returns at all. pub const WATCHDOG: Duration = Duration::from_secs(5); +/// How many cancels the probe binary aims at the busy thread. +pub const BUSY_ATTEMPTS: usize = 4; + +mod raw_row; + +pub use self::raw_row::*; + /// What one cancellation attempt did. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CancelOutcome { diff --git a/crates/windows-platform-probes/src/cancel_io/raw_row.rs b/crates/windows-platform-probes/src/cancel_io/raw_row.rs new file mode 100644 index 000000000..1881e845c --- /dev/null +++ b/crates/windows-platform-probes/src/cancel_io/raw_row.rs @@ -0,0 +1,51 @@ +// Copyright (c) Mike Grier. + +//! The cancel-io probe's machine-readable row (`M8.13`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-cancel-io"; + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: the +/// watchdog each attempt ran under, in milliseconds, then the idle case's +/// outcome and every busy-case attempt's, in order. +/// +/// Each outcome is `cancelled`, `not_found` with the error code the call +/// reported, or **`wedged`** -- a call the watchdog abandoned because it had +/// not returned. A wedge is published like any other outcome rather than +/// dropped, because it is the finding; its `code` is `null`, since a call that +/// never returned reported nothing. +/// +/// Not whether any attempt wedged, nor the conclusion; both read the outcomes +/// here. +#[must_use] +pub fn raw_row(idle: CancelOutcome, busy: &[CancelOutcome], build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with( + "watchdog_ms", + usize::try_from(WATCHDOG.as_millis()).unwrap_or(usize::MAX), + ) + .with("idle", outcome(idle)) + .with("busy", busy.iter().copied().map(outcome).collect::()) +} + +fn outcome(outcome: CancelOutcome) -> Value { + let (name, code) = match outcome { + CancelOutcome::Cancelled => ("cancelled", None), + CancelOutcome::NotFound { code } => ("not_found", Some(code)), + CancelOutcome::Wedged => ("wedged", None), + }; + Value::Object(vec![ + ("outcome", Value::from(name)), + ("code", Value::from(code)), + ]) +} diff --git a/crates/windows-platform-probes/src/cancel_io/raw_row/tests.rs b/crates/windows-platform-probes/src/cancel_io/raw_row/tests.rs new file mode 100644 index 000000000..71bbaac0e --- /dev/null +++ b/crates/windows-platform-probes/src/cancel_io/raw_row/tests.rs @@ -0,0 +1,64 @@ +// Copyright (c) Mike Grier. +//! Tests for the cancel-io probe's row. + +use super::*; + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_watchdog_and_the_build() { + let row = parsed(&raw_row( + CancelOutcome::NotFound { code: 1168 }, + &[], + "v1 abc [ci]", + )); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-cancel-io"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert_eq!(row["watchdog_ms"], 5000); +} + +#[test] +fn the_idle_case_publishes_the_code_the_call_reported() { + let row = parsed(&raw_row(CancelOutcome::NotFound { code: 1168 }, &[], "b")); + assert_eq!( + row["idle"], + serde_json::json!({"outcome": "not_found", "code": 1168}) + ); +} + +#[test] +fn every_busy_attempt_is_published_in_order_including_one_the_watchdog_abandoned() { + let row = parsed(&raw_row( + CancelOutcome::Cancelled, + &[ + CancelOutcome::Cancelled, + CancelOutcome::Wedged, + CancelOutcome::NotFound { code: 6 }, + ], + "b", + )); + assert_eq!( + row["idle"], + serde_json::json!({"outcome": "cancelled", "code": null}) + ); + assert_eq!( + row["busy"], + serde_json::json!([ + {"outcome": "cancelled", "code": null}, + {"outcome": "wedged", "code": null}, + {"outcome": "not_found", "code": 6}, + ]) + ); +} + +#[test] +fn a_run_that_wedged_on_every_attempt_still_publishes_every_attempt() { + let busy = [CancelOutcome::Wedged; BUSY_ATTEMPTS]; + let row = parsed(&raw_row(CancelOutcome::Wedged, &busy, "b")); + assert_eq!(row["busy"].as_array().map(Vec::len), Some(BUSY_ATTEMPTS)); + assert_eq!(row["idle"]["outcome"], "wedged"); +} diff --git a/crates/windows-platform-probes/src/completion_port.rs b/crates/windows-platform-probes/src/completion_port.rs index fecd6e7bd..dbff99f68 100644 --- a/crates/windows-platform-probes/src/completion_port.rs +++ b/crates/windows-platform-probes/src/completion_port.rs @@ -66,6 +66,10 @@ use windows_sys::Win32::System::Threading::{ use crate::ioring::{FILL, FIXTURE_LEN, Fixture, IoRingSupport, Ring, read_raw_handle}; +mod raw_row; + +pub use self::raw_row::*; + /// The completion key the port is created with. Named rather than written as a /// bare literal, since it is checked on the way back out. const COMPLETION_KEY: usize = 0xD; diff --git a/crates/windows-platform-probes/src/completion_port/raw_row.rs b/crates/windows-platform-probes/src/completion_port/raw_row.rs new file mode 100644 index 000000000..6d1be6dbc --- /dev/null +++ b/crates/windows-platform-probes/src/completion_port/raw_row.rs @@ -0,0 +1,72 @@ +// Copyright (c) Mike Grier. + +//! The completion-port probe's machine-readable row (`M8.12`). + +use super::*; +use crate::ioring::NO_USABLE_IORING; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-completion-port"; + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: the fill +/// byte and the length every read asks for, then each read case by name with +/// its result code, the byte count its completion reported and the first byte +/// that landed, and whether the associated handle still completed a read +/// through the port. The result code is an HRESULT, published as `0x` +/// hexadecimal text, as the `IoRing` probe's row does. +/// +/// A host with no usable `IoRing` emits the same row with `declined` naming +/// why and no reads, following topology's `report_unmeasured`. +/// +/// Not pass or fail, valid, forecloses or coexists; each is a comparison over +/// the figures here. +#[must_use] +pub fn raw_row(support: &IoRingSupport, build: &str) -> Row { + let row = Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("fill", FILL) + .with("read_len", FIXTURE_LEN); + let IoRingSupport::Measured(finding) = support else { + return row.with("declined", NO_USABLE_IORING); + }; + row.with("declined", Value::Null) + .with( + "reads", + Value::Object(vec![ + ("control_unassociated", read(finding.control_unassociated)), + ( + "after_iocp_association", + read(finding.after_iocp_association), + ), + ( + "before_late_association", + read(finding.before_late_association), + ), + ( + "after_late_association", + read(finding.after_late_association), + ), + ("before_threadpool_io", read(finding.before_threadpool_io)), + ("after_threadpool_io", read(finding.after_threadpool_io)), + ]), + ) + .with("port_still_works", finding.port_still_works) +} + +fn read(attempt: ReadAttempt) -> Value { + Value::Object(vec![ + ( + "result_code", + Value::from(format!("{:#010x}", attempt.result_code)), + ), + ("bytes", Value::from(attempt.bytes)), + ("first_byte", Value::from(attempt.first_byte)), + ]) +} diff --git a/crates/windows-platform-probes/src/completion_port/raw_row/tests.rs b/crates/windows-platform-probes/src/completion_port/raw_row/tests.rs new file mode 100644 index 000000000..ed72e6467 --- /dev/null +++ b/crates/windows-platform-probes/src/completion_port/raw_row/tests.rs @@ -0,0 +1,77 @@ +// Copyright (c) Mike Grier. +//! Tests for the completion-port probe's row. + +use super::*; + +fn attempt(result_code: u32, bytes: usize, first_byte: u8) -> ReadAttempt { + ReadAttempt { + result_code: i32::from_ne_bytes(result_code.to_ne_bytes()), + bytes, + first_byte, + } +} + +/// Distinct values in every case, so no two can be swapped unnoticed. +fn finding() -> CompletionPortFinding { + CompletionPortFinding { + control_unassociated: attempt(0, 4096, 0xAB), + after_iocp_association: attempt(0x8007_0057, 0, 0), + before_late_association: attempt(0, 4095, 0xAB), + after_late_association: attempt(0x8007_0006, 1, 2), + port_still_works: true, + before_threadpool_io: attempt(0, 4094, 0xAB), + after_threadpool_io: attempt(0x8007_0005, 3, 4), + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_fixture_and_the_build() { + let row = parsed(&raw_row(&IoRingSupport::Measured(finding()), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-completion-port"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert_eq!(row["fill"], FILL); + assert_eq!(row["read_len"], FIXTURE_LEN); + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); +} + +#[test] +fn every_read_case_is_published_under_its_own_name() { + let row = parsed(&raw_row(&IoRingSupport::Measured(finding()), "b")); + let read = |code: &str, bytes: usize, first: u8| serde_json::json!({"result_code": code, "bytes": bytes, "first_byte": first}); + assert_eq!( + row["reads"], + serde_json::json!({ + "control_unassociated": read("0x00000000", 4096, 0xAB), + "after_iocp_association": read("0x80070057", 0, 0), + "before_late_association": read("0x00000000", 4095, 0xAB), + "after_late_association": read("0x80070006", 1, 2), + "before_threadpool_io": read("0x00000000", 4094, 0xAB), + "after_threadpool_io": read("0x80070005", 3, 4), + }) + ); + assert_eq!(row["port_still_works"], true); +} + +#[test] +fn a_port_that_stopped_working_is_published_as_false() { + let mut broken = finding(); + broken.port_still_works = false; + let row = parsed(&raw_row(&IoRingSupport::Measured(broken), "b")); + assert_eq!(row["port_still_works"], false); +} + +#[test] +fn a_host_without_a_ring_declines_and_publishes_no_reads() { + let row = parsed(&raw_row(&IoRingSupport::Unavailable, "b")); + assert_eq!(row["declined"], NO_USABLE_IORING); + assert_eq!(row["fill"], FILL); + for absent in ["reads", "port_still_works"] { + assert!(row.get(absent).is_none(), "{absent}"); + } +} diff --git a/crates/windows-platform-probes/src/core_affinity.rs b/crates/windows-platform-probes/src/core_affinity.rs new file mode 100644 index 000000000..1cef53dfe --- /dev/null +++ b/crates/windows-platform-probes/src/core_affinity.rs @@ -0,0 +1,125 @@ +// Copyright (c) Mike Grier. + +//! The core-affinity probe's machine-readable row (`M8.4`). +//! +//! The measurement is `windows_placement_probe::core_affinity`'s; this is only +//! what `probe-core-affinity` publishes of it. + +use windows_placement_probe::core_affinity::{Measurement, Observation, REPETITIONS}; +use windows_placement_probe::fingerprint::{Fingerprint, ProcessorPlace}; +use windows_placement_probe::peer_index_cache::{CAPACITY, ITEMS}; +use windows_topology_sys::Observed; + +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-core-affinity"; + +/// The row a measured report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: +/// +/// - **The three readings of the host** that bracket the run -- `host_before` +/// (the banner's), `host` (the one the measurement took) and `host_after` -- +/// each as its fingerprint string, or `null` where that reading failed. +/// Whether they agree is the reader's comparison; the prose makes it too. +/// - **Every processor as discovered**: group, number, core, efficiency class, +/// cache domain and NUMA node. A cache domain is a number when the topology +/// named one, `"absent"` when no level partitions the machine, and +/// `"not_observed"` when this processor was left out of the level that does +/// -- three facts the prose keeps apart, so the row does too. +/// - **Every timed pair**, in the order measured, in `placements`, `by_class` +/// and `node_pairs`: the placement and strategy by record name, the two +/// processors, the ring's requested and achieved NUMA nodes (`null` where +/// none was requested, or the achieved one is unknown), and the median of +/// [`REPETITIONS`] passes as whole elapsed nanoseconds with that pass's +/// consumer and producer shared-read counts. +/// +/// Not the per-item costs, batch depths, spreads, confounding or verdicts the +/// prose derives; a consumer wanting a per-item figure divides by `items`, and +/// one wanting a batch depth divides `items` by a read count. +#[must_use] +pub fn raw_row( + observation: &Observation, + before: Option<&Fingerprint>, + after: Option<&Fingerprint>, + build: &str, +) -> Row { + let processors: Value = observation.processors.iter().map(processor).collect(); + let pairs = |rows: &[Measurement]| -> Value { rows.iter().map(measurement).collect() }; + preamble(before, build) + .with("declined", Value::Null) + .with("host", observation.host.to_string()) + .with("host_after", after.map(ToString::to_string)) + .with("processors", processors) + .with("placements", pairs(&observation.measurements)) + .with("by_class", pairs(&observation.by_class)) + .with("node_pairs", pairs(&observation.by_node_pair)) +} + +/// The row a report carries when the host could not be measured. +/// +/// Follows topology's `report_unmeasured`: the same row, naming why, rather +/// than no row. `declined` carries the error's text -- a topology that could +/// not be read, or a processor that could not be pinned -- and is `null` in a +/// measured row. `host_before` is kept, since the banner's reading was taken +/// before the measurement failed; nothing the measurement would have produced +/// is present. +#[must_use] +pub fn declined_row(error: &std::io::Error, before: Option<&Fingerprint>, build: &str) -> Row { + preamble(before, build).with("declined", error.to_string()) +} + +/// What both forms carry: where the run happened and what it was set to do. +fn preamble(before: Option<&Fingerprint>, build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("items", ITEMS) + .with("capacity", CAPACITY) + .with("repetitions", REPETITIONS) + .with("host_before", before.map(ToString::to_string)) +} + +fn processor(place: &ProcessorPlace) -> Value { + Value::Object(vec![ + ("group", Value::from(place.group)), + ("number", Value::from(place.number)), + ("core", Value::from(place.core)), + ("efficiency_class", Value::from(place.efficiency_class)), + ( + "cache_domain", + match place.cache_domain { + Observed::Known(domain) => Value::from(domain), + Observed::Absent => Value::from("absent"), + Observed::NotObserved => Value::from("not_observed"), + }, + ), + ("numa_node", Value::from(place.numa_node)), + ]) +} + +fn measurement(row: &Measurement) -> Value { + Value::Object(vec![ + ("placement", Value::from(row.placement.name())), + ("strategy", Value::from(row.strategy.name())), + ("producer_group", Value::from(row.producer.group)), + ("producer_number", Value::from(row.producer.number)), + ("consumer_group", Value::from(row.consumer.group)), + ("consumer_number", Value::from(row.consumer.number)), + ( + "requested_memory_node", + Value::from(row.requested_memory_node), + ), + ("memory_node", Value::from(row.memory_node)), + ( + "median_elapsed_ns", + Value::from(row.elapsed_nanos.round() as usize), + ), + ("consumer_refreshes", Value::from(row.consumer_refreshes)), + ("producer_refreshes", Value::from(row.producer_refreshes)), + ]) +} diff --git a/crates/windows-platform-probes/src/core_affinity/tests.rs b/crates/windows-platform-probes/src/core_affinity/tests.rs new file mode 100644 index 000000000..7015fa198 --- /dev/null +++ b/crates/windows-platform-probes/src/core_affinity/tests.rs @@ -0,0 +1,288 @@ +// Copyright (c) Mike Grier. +//! Tests for the core-affinity probe's row. + +use windows_placement_probe::core_affinity::Placement; +use windows_placement_probe::fingerprint::Slice; +use windows_placement_probe::peer_index_cache::Strategy; +use windows_topology_sys::Provenance; + +use super::*; + +fn host(processors: usize) -> Fingerprint { + Fingerprint { + arch: "x86_64", + processors, + cores: processors / 2, + smt: true, + partitioning_cache_level: Some(3), + cache_domain_sizes: vec![processors], + efficiency_classes: vec![(0, processors)], + numa_node_sizes: vec![processors], + provenance: Provenance::Measured, + } +} + +fn place(group: u16, number: u8, cache_domain: Observed) -> ProcessorPlace { + ProcessorPlace { + group, + number, + core: u32::from(number) / 2, + efficiency_class: number % 2, + cache_domain, + numa_node: u32::from(group), + } +} + +/// A measurement with distinct figures in every raw field. +fn measured( + producer: ProcessorPlace, + consumer: ProcessorPlace, + placement: Placement, + strategy: Strategy, + elapsed: f64, + reads: (u64, u64), + nodes: (Option, Option), +) -> Measurement { + Measurement { + slice: Slice::pair(producer, consumer), + producer, + consumer, + placement, + strategy, + nanos_per_item: elapsed / ITEMS as f64, + elapsed_nanos: elapsed, + consumer_refreshes: reads.0, + producer_refreshes: reads.1, + consumer_batch: ITEMS as f64 / reads.0.max(1) as f64, + producer_batch: ITEMS as f64 / reads.1.max(1) as f64, + requested_memory_node: nodes.0, + memory_node: nodes.1, + } +} + +fn observation() -> Observation { + let a = place(0, 0, Observed::Known(4)); + let b = place(0, 1, Observed::Absent); + let c = place(1, 2, Observed::NotObserved); + Observation { + host: host(3), + processors: vec![a, b, c], + measurements: vec![ + measured( + a, + b, + Placement::SameCoreSiblings, + Strategy::Baseline, + 40_000_000.0, + (1_900_000, 1_800_000), + (None, None), + ), + measured( + a, + b, + Placement::SameCoreSiblings, + Strategy::Cached, + 20_000_000.0, + (7_000, 300), + (None, Some(0)), + ), + ], + by_class: vec![measured( + a, + b, + Placement::SameCacheSameClass, + Strategy::Cached, + 30_000_001.0, + (11, 13), + (None, None), + )], + by_node_pair: vec![measured( + a, + c, + Placement::CrossNumaNode, + Strategy::Baseline, + 90_000_000.0, + (2_000_017, 2_000_019), + (Some(1), Some(0)), + )], + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_settings_and_the_build() { + let before = host(3); + let row = parsed(&raw_row( + &observation(), + Some(&before), + Some(&before), + "v1 abc [ci]", + )); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-core-affinity"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert_eq!(row["items"], ITEMS); + assert_eq!(row["capacity"], CAPACITY); + assert_eq!(row["repetitions"], REPETITIONS); + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); +} + +#[test] +fn the_three_host_readings_are_published_as_taken() { + let before = host(4); + let after = host(5); + let row = parsed(&raw_row(&observation(), Some(&before), Some(&after), "b")); + assert_eq!(row["host_before"], before.to_string()); + assert_eq!(row["host"], host(3).to_string()); + assert_eq!(row["host_after"], after.to_string()); + // Three different machines here, so a swap of any two is visible. + assert_ne!(row["host_before"], row["host"]); + assert_ne!(row["host"], row["host_after"]); +} + +#[test] +fn a_failed_reading_is_null_rather_than_absent() { + let row = parsed(&raw_row(&observation(), None, None, "b")); + assert!( + row.get("host_before") + .is_some_and(serde_json::Value::is_null) + ); + assert!( + row.get("host_after") + .is_some_and(serde_json::Value::is_null) + ); + assert_eq!(row["host"], host(3).to_string()); +} + +#[test] +fn every_processor_is_published_with_its_three_kinds_of_cache_answer() { + let row = parsed(&raw_row(&observation(), None, None, "b")); + assert_eq!( + row["processors"], + serde_json::json!([ + {"group": 0, "number": 0, "core": 0, "efficiency_class": 0, + "cache_domain": 4, "numa_node": 0}, + {"group": 0, "number": 1, "core": 0, "efficiency_class": 1, + "cache_domain": "absent", "numa_node": 0}, + {"group": 1, "number": 2, "core": 1, "efficiency_class": 0, + "cache_domain": "not_observed", "numa_node": 1}, + ]) + ); +} + +#[test] +fn each_list_publishes_its_own_pairs_with_their_raw_figures() { + let row = parsed(&raw_row(&observation(), None, None, "b")); + assert_eq!( + row["placements"], + serde_json::json!([ + {"placement": "same_core_siblings", "strategy": "baseline", + "producer_group": 0, "producer_number": 0, + "consumer_group": 0, "consumer_number": 1, + "requested_memory_node": null, "memory_node": null, + "median_elapsed_ns": 40_000_000, + "consumer_refreshes": 1_900_000, "producer_refreshes": 1_800_000}, + {"placement": "same_core_siblings", "strategy": "cached", + "producer_group": 0, "producer_number": 0, + "consumer_group": 0, "consumer_number": 1, + "requested_memory_node": null, "memory_node": 0, + "median_elapsed_ns": 20_000_000, + "consumer_refreshes": 7_000, "producer_refreshes": 300}, + ]) + ); + assert_eq!( + row["by_class"], + serde_json::json!([ + {"placement": "same_cache_same_class", "strategy": "cached", + "producer_group": 0, "producer_number": 0, + "consumer_group": 0, "consumer_number": 1, + "requested_memory_node": null, "memory_node": null, + "median_elapsed_ns": 30_000_001, + "consumer_refreshes": 11, "producer_refreshes": 13}, + ]) + ); + assert_eq!( + row["node_pairs"], + serde_json::json!([ + {"placement": "cross_numa_node", "strategy": "baseline", + "producer_group": 0, "producer_number": 0, + "consumer_group": 1, "consumer_number": 2, + "requested_memory_node": 1, "memory_node": 0, + "median_elapsed_ns": 90_000_000, + "consumer_refreshes": 2_000_017, "producer_refreshes": 2_000_019}, + ]) + ); +} + +#[test] +fn a_host_with_nothing_to_measure_publishes_empty_lists() { + let mut empty = observation(); + empty.measurements.clear(); + empty.by_class.clear(); + empty.by_node_pair.clear(); + let row = parsed(&raw_row(&empty, None, None, "b")); + for key in ["placements", "by_class", "node_pairs"] { + assert_eq!(row[key], serde_json::json!([]), "{key}"); + } +} + +#[test] +fn a_declined_run_says_why_keeps_the_banner_reading_and_publishes_no_figures() { + let error = std::io::Error::other("pin refused {\"reason\":\"x\"}\nsecond line"); + let before = host(4); + let row = parsed(&declined_row(&error, Some(&before), "b")); + assert_eq!(row["reason"], REASON); + assert_eq!( + row["declined"], + "pin refused {\"reason\":\"x\"}\nsecond line" + ); + assert_eq!(row["host_before"], before.to_string()); + for absent in [ + "host", + "host_after", + "processors", + "placements", + "by_class", + "node_pairs", + ] { + assert!(row.get(absent).is_none(), "{absent}"); + } +} + +#[test] +fn the_two_forms_share_every_key_but_the_measurement() { + let keys = |row: &serde_json::Value| -> Vec { + row.as_object() + .expect("an object") + .keys() + .cloned() + .collect() + }; + let measured = keys(&parsed(&raw_row(&observation(), None, None, "b"))); + let declined = keys(&parsed(&declined_row( + &std::io::Error::other("e"), + None, + "b", + ))); + let mut extra: Vec<&String> = measured + .iter() + .filter(|key| !declined.contains(key)) + .collect(); + extra.sort(); + assert_eq!( + extra, + [ + "by_class", + "host", + "host_after", + "node_pairs", + "placements", + "processors" + ] + ); + assert!(declined.iter().all(|key| measured.contains(key))); +} diff --git a/crates/windows-platform-probes/src/device_map.rs b/crates/windows-platform-probes/src/device_map.rs index d81bc7592..569f89c93 100644 --- a/crates/windows-platform-probes/src/device_map.rs +++ b/crates/windows-platform-probes/src/device_map.rs @@ -54,6 +54,10 @@ use windows_sys::Win32::System::Threading::{ GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken, }; +mod raw_row; + +pub use self::raw_row::*; + /// What one observation of a drive letter found. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MapObservation { diff --git a/crates/windows-platform-probes/src/device_map/raw_row.rs b/crates/windows-platform-probes/src/device_map/raw_row.rs new file mode 100644 index 000000000..ec1c38353 --- /dev/null +++ b/crates/windows-platform-probes/src/device_map/raw_row.rs @@ -0,0 +1,74 @@ +// Copyright (c) Mike Grier. + +//! The device-map probe's machine-readable row (`M8.10`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-device-map"; + +/// Why a run declines: the one condition the probe checks before measuring. +pub const NO_FREE_DRIVE_LETTER: &str = "no free drive letter on this host"; + +/// The row a measured report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: the +/// target this run defined the letter as, and for the process's own session +/// and the anonymous one, the letter, every target it resolved to (`null` when +/// it resolved to nothing) and the logon session of the token in effect. +/// +/// The logon session is a LUID, published as the `high:low` hexadecimal text +/// the prose prints: it is an identifier, its high half is signed, and the row +/// carries no signed numbers. `null` when it could not be read. +/// +/// Not whether impersonation changed the map, whether the sessions differ, or +/// whether the claim was exclusive; each compares the figures here. +#[must_use] +pub fn raw_row(finding: &DeviceMapFinding, build: &str) -> Row { + preamble(build) + .with("declined", Value::Null) + .with("target", finding.target.as_str()) + .with("own_session", session(&finding.own_session)) + .with("anonymous_session", session(&finding.anonymous_session)) +} + +/// The row a report carries when the probe could not set up its fixture. +/// +/// Follows topology's `report_unmeasured`: the same row with `declined` naming +/// why, and nothing the measurement would have produced. +#[must_use] +pub fn declined_row(reason: &str, build: &str) -> Row { + preamble(build).with("declined", reason) +} + +fn preamble(build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) +} + +fn session(observation: &MapObservation) -> Value { + Value::Object(vec![ + ("letter", Value::from(observation.letter.as_str())), + ( + "targets", + if observation.is_found() { + observation.entries().into_iter().collect() + } else { + Value::Null + }, + ), + ( + "logon_session", + Value::from( + observation + .logon_session + .map(|(low, high)| format!("{high:08x}:{low:08x}")), + ), + ), + ]) +} diff --git a/crates/windows-platform-probes/src/device_map/raw_row/tests.rs b/crates/windows-platform-probes/src/device_map/raw_row/tests.rs new file mode 100644 index 000000000..93028d0d3 --- /dev/null +++ b/crates/windows-platform-probes/src/device_map/raw_row/tests.rs @@ -0,0 +1,79 @@ +// Copyright (c) Mike Grier. +//! Tests for the device-map probe's row. + +use super::*; + +fn finding() -> DeviceMapFinding { + DeviceMapFinding { + own_session: MapObservation { + letter: "Q:".to_owned(), + target: Some("\\??\\C:\\probe-a\0\\??\\C:\\other\0".to_owned()), + logon_session: Some((0x0001_2345, 0)), + }, + anonymous_session: MapObservation { + letter: "Q:".to_owned(), + target: None, + logon_session: Some((0x3e6, -2)), + }, + target: "\\??\\C:\\probe-a".to_owned(), + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_build() { + let row = parsed(&raw_row(&finding(), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-device-map"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); + assert_eq!(row["target"], "\\??\\C:\\probe-a"); +} + +#[test] +fn each_session_publishes_every_target_and_its_logon_session() { + let row = parsed(&raw_row(&finding(), "b")); + assert_eq!( + row["own_session"], + serde_json::json!({ + "letter": "Q:", + "targets": ["\\??\\C:\\probe-a", "\\??\\C:\\other"], + "logon_session": "00000000:00012345", + }) + ); +} + +#[test] +fn a_letter_that_resolved_to_nothing_is_null_and_a_signed_high_half_keeps_its_bits() { + let row = parsed(&raw_row(&finding(), "b")); + assert_eq!( + row["anonymous_session"], + serde_json::json!({"letter": "Q:", "targets": null, "logon_session": "fffffffe:000003e6"}) + ); +} + +#[test] +fn an_unread_logon_session_is_null() { + let mut unread = finding(); + unread.own_session.logon_session = None; + let row = parsed(&raw_row(&unread, "b")); + assert!( + row["own_session"] + .get("logon_session") + .is_some_and(serde_json::Value::is_null) + ); +} + +#[test] +fn a_declined_run_says_why_and_publishes_no_sessions() { + let row = parsed(&declined_row(NO_FREE_DRIVE_LETTER, "b")); + assert_eq!(row["reason"], REASON); + assert_eq!(row["declined"], NO_FREE_DRIVE_LETTER); + for absent in ["target", "own_session", "anonymous_session"] { + assert!(row.get(absent).is_none(), "{absent}"); + } +} diff --git a/crates/windows-platform-probes/src/doorbell_cost.rs b/crates/windows-platform-probes/src/doorbell_cost.rs index 489d26528..338a3aba1 100644 --- a/crates/windows-platform-probes/src/doorbell_cost.rs +++ b/crates/windows-platform-probes/src/doorbell_cost.rs @@ -77,6 +77,7 @@ use windows_sys::Win32::System::Threading::{ }; use crate::ioring; +use crate::probe_args::IterationMultiplier; /// The machine-readable name for a timing's label. /// @@ -209,6 +210,20 @@ impl Observation { } } +/// Iterations of each user-mode and event loop, at the default multiplier. +const ITERATIONS: u32 = 200_000; + +/// Iterations of the empty submit. Far fewer: it is a real kernel transition. +const SUBMIT_ITERATIONS: u32 = 20_000; + +/// Round trips of the park-and-wake handshake the binary times, at the default +/// multiplier. +pub const PARK_ROUNDS: u32 = 20_000; + +/// The longest loop the binary runs at the default multiplier, which bounds the +/// multiplier a command line may give (see [`IterationMultiplier::from_args`]). +pub const LONGEST_LOOP: u32 = ITERATIONS; + fn time_loop(label: &'static str, iterations: u32, mut body: impl FnMut()) -> Timing { // Warm the path first: the first call through a syscall stub pays for // resolution and page faults that a steady-state cost should not include. @@ -261,9 +276,11 @@ fn describe_wait(waited: WAIT_EVENT) -> String { /// measurable transition when it fails, so a probe that swallowed the error /// would report a plausible nanosecond figure for an operation that did not /// happen, which is worse than reporting nothing. +/// +/// Each loop runs `multiplier` times its usual count (`M4.2`). #[must_use] -pub fn measure() -> Observation { - const ITERATIONS: u32 = 200_000; +pub fn measure(multiplier: IterationMultiplier) -> Observation { + let iterations = multiplier.scale(ITERATIONS); // SAFETY: a manual-reset, initially-unsignalled, unnamed event. let event: HANDLE = unsafe { CreateEventW(std::ptr::null(), 1, 0, std::ptr::null()) }; @@ -276,7 +293,7 @@ pub fn measure() -> Observation { let counter = AtomicU64::new(0); let mut timings = Vec::new(); - timings.push(time_loop("atomic_fetch_add", ITERATIONS, || { + timings.push(time_loop("atomic_fetch_add", iterations, || { counter.fetch_add(1, Ordering::Relaxed); })); @@ -332,7 +349,7 @@ pub fn measure() -> Observation { "SetEvent failed: {}", std::io::Error::last_os_error() ); - timings.push(time_loop("set_event_already_signalled", ITERATIONS, || { + timings.push(time_loop("set_event_already_signalled", iterations, || { assert!( unsafe { SetEvent(event) } != 0, "SetEvent on an already-signalled event failed: {}", @@ -345,7 +362,7 @@ pub fn measure() -> Observation { "ResetEvent failed: {}", std::io::Error::last_os_error() ); - timings.push(time_loop("set_reset_event", ITERATIONS, || unsafe { + timings.push(time_loop("set_reset_event", iterations, || unsafe { assert!( SetEvent(event) != 0, "SetEvent failed mid-cycle: {}", @@ -363,7 +380,7 @@ pub fn measure() -> Observation { "SetEvent failed: {}", std::io::Error::last_os_error() ); - timings.push(time_loop("wait_zero_signalled", ITERATIONS, || { + timings.push(time_loop("wait_zero_signalled", iterations, || { // Checked against `WAIT_OBJECT_0`, not merely "did not fail". The label // says *satisfied* wait, and `WAIT_TIMEOUT` is a successful return that // times a different path -- an unsatisfied poll, which is the cheaper @@ -412,29 +429,32 @@ pub fn measure() -> Observation { // asks for no completions, so it returns without blocking and measures the // transition rather than any I/O. let submit_nanos = ioring::Ring::new().map(|ring| { - const SUBMIT_ITERATIONS: u32 = 20_000; - let timing = time_loop("submit_io_ring_empty", SUBMIT_ITERATIONS, || { - // Both halves of the answer are checked, inside the timed region. - // Discarding them let a host where `SubmitIoRing` fails produce a - // perfectly plausible timing -- a failing call still costs a - // measurable transition -- which the report then read as the cost of - // a successful empty submission. That is the failure mode this whole - // crate exists to avoid: a number that looks like evidence and is - // not. The `submitted` count is checked too, because a call that - // succeeded while submitting entries did not measure what the label - // says it measured. - // - // Inside the loop, not outside it: a check after the fact would let - // the timing be taken before anything established it was valid. - // - // Every loop above now does the same, under the flat rule stated - // there. This note came first and for a while was the only one, - // which is the whole reason the rule is now flat rather than - // argued per site. - let (hr, submitted) = ring.submit_and_wait(0); - assert!(hr >= 0, "SubmitIoRing(0) failed: {hr:#010x}"); - assert_eq!(submitted, 0, "SubmitIoRing(0) submitted entries"); - }); + let timing = time_loop( + "submit_io_ring_empty", + multiplier.scale(SUBMIT_ITERATIONS), + || { + // Both halves of the answer are checked, inside the timed region. + // Discarding them let a host where `SubmitIoRing` fails produce a + // perfectly plausible timing -- a failing call still costs a + // measurable transition -- which the report then read as the cost of + // a successful empty submission. That is the failure mode this whole + // crate exists to avoid: a number that looks like evidence and is + // not. The `submitted` count is checked too, because a call that + // succeeded while submitting entries did not measure what the label + // says it measured. + // + // Inside the loop, not outside it: a check after the fact would let + // the timing be taken before anything established it was valid. + // + // Every loop above now does the same, under the flat rule stated + // there. This note came first and for a while was the only one, + // which is the whole reason the rule is now flat rather than + // argued per site. + let (hr, submitted) = ring.submit_and_wait(0); + assert!(hr >= 0, "SubmitIoRing(0) failed: {hr:#010x}"); + assert_eq!(submitted, 0, "SubmitIoRing(0) submitted entries"); + }, + ); timings.push(timing); timing.nanos_per_op }); diff --git a/crates/windows-platform-probes/src/error_mode.rs b/crates/windows-platform-probes/src/error_mode.rs index 76a4f0996..afaee32d0 100644 --- a/crates/windows-platform-probes/src/error_mode.rs +++ b/crates/windows-platform-probes/src/error_mode.rs @@ -34,6 +34,25 @@ pub mod bits { pub const NO_OPEN_FILE_ERROR_BOX: u32 = 0x8000; } +/// Every bit probed on its own, in the order the report lists them. +pub const PROBED_BITS: [u32; 4] = [ + bits::FAIL_CRITICAL_ERRORS, + bits::NO_GP_FAULT_ERROR_BOX, + bits::NO_ALIGNMENT_FAULT_EXCEPT, + bits::NO_OPEN_FILE_ERROR_BOX, +]; + +/// The valid bits [`combined_invalid_installs_nothing`] sets beside an invalid +/// one. +pub const COMBINED_VALID: u32 = bits::FAIL_CRITICAL_ERRORS | bits::NO_GP_FAULT_ERROR_BOX; + +/// The whole mask [`combined_invalid_installs_nothing`] attempts. +pub const COMBINED_ATTEMPT: u32 = COMBINED_VALID | bits::NO_ALIGNMENT_FAULT_EXCEPT; + +mod raw_row; + +pub use self::raw_row::*; + /// What happened when one bit was set on its own. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BitOutcome { @@ -174,15 +193,10 @@ pub fn probe_bit(bit: u32) -> BitOutcome { /// Every bit this platform accepts per thread, as a mask. #[must_use] pub fn settable_bits() -> u32 { - [ - bits::FAIL_CRITICAL_ERRORS, - bits::NO_GP_FAULT_ERROR_BOX, - bits::NO_ALIGNMENT_FAULT_EXCEPT, - bits::NO_OPEN_FILE_ERROR_BOX, - ] - .into_iter() - .filter(|bit| probe_bit(*bit).is_settable()) - .fold(0, |mask, bit| mask | bit) + PROBED_BITS + .into_iter() + .filter(|bit| probe_bit(*bit).is_settable()) + .fold(0, |mask, bit| mask | bit) } /// Does one invalid bit cost the caller the whole call, or only that bit? @@ -193,8 +207,8 @@ pub fn settable_bits() -> u32 { /// combining it with valid bits silently loses the entire change. #[must_use] pub fn combined_invalid_installs_nothing() -> (bool, u32) { - let valid = bits::FAIL_CRITICAL_ERRORS | bits::NO_GP_FAULT_ERROR_BOX; - let combined = valid | bits::NO_ALIGNMENT_FAULT_EXCEPT; + let valid = COMBINED_VALID; + let combined = COMBINED_ATTEMPT; // The whole finding is read out of `read_back`, and the call under test is // expected to *fail* -- so `read_back` is whatever the thread was already diff --git a/crates/windows-platform-probes/src/error_mode/raw_row.rs b/crates/windows-platform-probes/src/error_mode/raw_row.rs new file mode 100644 index 000000000..7b1536305 --- /dev/null +++ b/crates/windows-platform-probes/src/error_mode/raw_row.rs @@ -0,0 +1,92 @@ +// Copyright (c) Mike Grier. + +//! The error-mode probe's machine-readable row (`M8.7`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-error-mode"; + +/// What one run of the probe observed, gathered in the order it was measured. +/// +/// The order is part of the measurement -- the last observation permanently +/// alters the process -- so the binary fills this as it renders rather than +/// measuring ahead of the report. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Observation { + /// Each bit in [`PROBED_BITS`] attempted on its own. + pub bits: Vec, + /// The thread mode read back after attempting [`COMBINED_ATTEMPT`] from a + /// zero baseline. + pub combined_read_back: u32, + /// The process mode against the thread mode, with a process bit installed. + pub process_versus_thread: ProcessVersusThread, + /// The process mode before setting the alignment bit, and after trying to + /// restore it. + pub alignment: (u32, u32), +} + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md, every mode +/// as the number Windows returned: for each bit on its own, whether +/// `SetThreadErrorMode` succeeded, `GetLastError` when it did not (`null` when +/// it succeeded, since none was read) and the mode read back; the mask the +/// combined call attempted and what was read back after it; the process bit, +/// process mode and thread mode read together; and the process mode before +/// and after the alignment bit's restore attempt. +/// +/// Not the verdicts -- settable, silently dropped, independent, sticky -- nor +/// the settable mask, each a comparison over the figures here. The probe has +/// no way to decline: every call either answers or the probe panics, and a +/// panic prints no report. +#[must_use] +pub fn raw_row(observed: &Observation, build: &str) -> Row { + let bits: Value = observed + .bits + .iter() + .map(|outcome| { + Value::Object(vec![ + ("bit", Value::from(outcome.bit)), + ("set_ok", Value::from(outcome.set_ok)), + ( + "last_error", + Value::from((!outcome.set_ok).then_some(outcome.last_error)), + ), + ("read_back", Value::from(outcome.read_back)), + ]) + }) + .collect(); + let versus = observed.process_versus_thread; + let (before, after) = observed.alignment; + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("bits", bits) + .with( + "combined", + Value::Object(vec![ + ("attempted", Value::from(COMBINED_ATTEMPT)), + ("read_back", Value::from(observed.combined_read_back)), + ]), + ) + .with( + "process_versus_thread", + Value::Object(vec![ + ("process_bit", Value::from(versus.process_bit)), + ("process_mode", Value::from(versus.process_mode)), + ("thread_mode", Value::from(versus.thread_mode)), + ]), + ) + .with( + "alignment_at_process_scope", + Value::Object(vec![ + ("before", Value::from(before)), + ("after_restore", Value::from(after)), + ]), + ) +} diff --git a/crates/windows-platform-probes/src/error_mode/raw_row/tests.rs b/crates/windows-platform-probes/src/error_mode/raw_row/tests.rs new file mode 100644 index 000000000..9a15484ef --- /dev/null +++ b/crates/windows-platform-probes/src/error_mode/raw_row/tests.rs @@ -0,0 +1,88 @@ +// Copyright (c) Mike Grier. +//! Tests for the error-mode probe's row. + +use super::*; + +/// Distinct values in every field, so no two can be swapped unnoticed. +fn observed() -> Observation { + Observation { + bits: vec![ + BitOutcome { + bit: bits::FAIL_CRITICAL_ERRORS, + set_ok: true, + last_error: 0, + read_back: 0x0001, + }, + BitOutcome { + bit: bits::NO_ALIGNMENT_FAULT_EXCEPT, + set_ok: false, + last_error: 87, + read_back: 0x0010, + }, + ], + combined_read_back: 0x0020, + process_versus_thread: ProcessVersusThread { + process_bit: bits::FAIL_CRITICAL_ERRORS, + process_mode: 0x0041, + thread_mode: 0x0080, + }, + alignment: (0x0100, 0x0104), + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_build() { + let row = parsed(&raw_row(&observed(), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-error-mode"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); +} + +#[test] +fn each_bit_is_published_with_the_error_only_when_the_call_failed() { + let row = parsed(&raw_row(&observed(), "b")); + assert_eq!( + row["bits"], + serde_json::json!([ + {"bit": 1, "set_ok": true, "last_error": null, "read_back": 1}, + {"bit": 4, "set_ok": false, "last_error": 87, "read_back": 16}, + ]) + ); +} + +#[test] +fn the_combined_call_publishes_what_it_attempted_and_what_it_read_back() { + let row = parsed(&raw_row(&observed(), "b")); + assert_eq!( + row["combined"], + serde_json::json!({"attempted": COMBINED_ATTEMPT, "read_back": 32}) + ); + // The attempt is two valid bits and one invalid one, as the module states. + assert_eq!(COMBINED_ATTEMPT, 0x0007); + assert_eq!(COMBINED_VALID, 0x0003); +} + +#[test] +fn the_two_scopes_and_the_alignment_restore_are_published_as_read() { + let row = parsed(&raw_row(&observed(), "b")); + assert_eq!( + row["process_versus_thread"], + serde_json::json!({"process_bit": 1, "process_mode": 65, "thread_mode": 128}) + ); + assert_eq!( + row["alignment_at_process_scope"], + serde_json::json!({"before": 256, "after_restore": 260}) + ); +} + +#[test] +fn the_probed_bits_are_the_four_sem_bits_once_each() { + let mut sorted = PROBED_BITS; + sorted.sort_unstable(); + assert_eq!(sorted, [0x0001, 0x0002, 0x0004, 0x8000]); +} diff --git a/crates/windows-platform-probes/src/handle_state.rs b/crates/windows-platform-probes/src/handle_state.rs index 7ca195bae..8d687f865 100644 --- a/crates/windows-platform-probes/src/handle_state.rs +++ b/crates/windows-platform-probes/src/handle_state.rs @@ -35,10 +35,14 @@ use windows_sys::Win32::System::Threading::GetCurrentProcess; /// /// A buffer large enough to drain the fixture in one call would answer none of /// these questions, because the cursor would never be left mid-directory. -const BUFFER_BYTES: usize = 320; +pub const BUFFER_BYTES: usize = 320; /// Enough entries that the small buffer needs several calls. -const FIXTURE_FILES: usize = 14; +pub const FIXTURE_FILES: usize = 14; + +mod raw_row; + +pub use self::raw_row::*; /// A temporary directory that removes itself. /// @@ -225,6 +229,16 @@ pub enum SingleShot { } impl SingleShot { + /// A stable identifier for a record. + #[must_use] + pub fn name(self) -> &'static str { + match self { + Self::BasicInfo => "basic_info", + Self::IdInfo => "id_info", + Self::NonEx => "non_ex", + } + } + /// Perform the query. Returns whether it succeeded. fn run(self, handle: &DirHandle) -> bool { match self { @@ -394,6 +408,30 @@ pub fn query_disturbs_cursor( on_duplicate: bool, truth: &[String], ) -> (bool, bool) { + let (succeeded, observation) = interleave_query(fixture, query, on_duplicate); + (succeeded, !observation.continued(truth)) +} + +/// Every query the probe binary interleaves, and whether it goes to a +/// duplicate, in the order the report lists them. +pub const QUERIES: [(SingleShot, bool); 4] = [ + (SingleShot::BasicInfo, false), + (SingleShot::IdInfo, false), + (SingleShot::NonEx, false), + (SingleShot::BasicInfo, true), +]; + +/// Interleave `query` with an enumeration, and return whether it succeeded +/// with the names the handle produced before and after it. +/// +/// What [`query_disturbs_cursor`] reads its verdict from, kept whole so a +/// record can publish the names rather than the verdict. +#[must_use] +pub fn interleave_query( + fixture: &Fixture, + query: SingleShot, + on_duplicate: bool, +) -> (bool, CursorObservation) { let handle = DirHandle::open(fixture.path()); let before = handle.enumerate(true).expect("the restart call"); let succeeded = if on_duplicate { @@ -402,9 +440,11 @@ pub fn query_disturbs_cursor( query.run(&handle) }; let after = handle.enumerate(false); - let observation = CursorObservation { - source_first: before, - other_next: after, - }; - (succeeded, !observation.continued(truth)) + ( + succeeded, + CursorObservation { + source_first: before, + other_next: after, + }, + ) } diff --git a/crates/windows-platform-probes/src/handle_state/raw_row.rs b/crates/windows-platform-probes/src/handle_state/raw_row.rs new file mode 100644 index 000000000..1678318da --- /dev/null +++ b/crates/windows-platform-probes/src/handle_state/raw_row.rs @@ -0,0 +1,99 @@ +// Copyright (c) Mike Grier. + +//! The handle-state probe's machine-readable row (`M8.8`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-handle-state"; + +/// One interleaved query, as the probe binary ran it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct QueryObservation { + /// The query. + pub query: SingleShot, + /// Whether it was sent to a duplicate of the enumerating handle. + pub on_duplicate: bool, + /// Whether the query itself succeeded. + pub succeeded: bool, + /// The names the handle produced before and after it. + pub cursor: CursorObservation, +} + +/// What one run of the probe observed. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Observation { + /// Every name, read start to finish on one handle. + pub ground_truth: Vec, + /// The source's first call, then its duplicate's continuation. + pub duplicate: CursorObservation, + /// The control: one open's first call, then a second open's. + pub separate_opens: CursorObservation, + /// Whether the source's next call succeeded after its duplicate closed. + pub source_survived_close: bool, + /// Each query in [`QUERIES`]. + pub queries: Vec, +} + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: the +/// buffer size and fixture size that make a cursor stop part-way, the ground +/// truth's names, and for each two-call observation the first call's names and +/// the second's -- or, where the second call failed, `null` names and the Win32 +/// error. For each query, whether it succeeded beside the names around it. +/// +/// Not shared, independent, disturbed or any other verdict; each compares the +/// names here against the ground truth. The probe has no way to decline: a +/// fixture or handle it cannot create stops it with a panic, and a panic prints +/// no report. +#[must_use] +pub fn raw_row(observation: &Observation, build: &str) -> Row { + let queries: Value = observation + .queries + .iter() + .map(|query| { + let mut members = vec![ + ("query", Value::from(query.query.name())), + ("on_duplicate", Value::from(query.on_duplicate)), + ("succeeded", Value::from(query.succeeded)), + ]; + members.extend(cursor(&query.cursor)); + Value::Object(members) + }) + .collect(); + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("buffer_bytes", BUFFER_BYTES) + .with("fixture_files", FIXTURE_FILES) + .with("ground_truth", names(&observation.ground_truth)) + .with("duplicate", Value::Object(cursor(&observation.duplicate))) + .with( + "separate_opens", + Value::Object(cursor(&observation.separate_opens)), + ) + .with("source_survived_close", observation.source_survived_close) + .with("queries", queries) +} + +fn names(names: &[String]) -> Value { + names.iter().map(String::as_str).collect() +} + +/// The two calls of one observation, as members of the object holding them. +fn cursor(observation: &CursorObservation) -> Vec<(&'static str, Value)> { + let (next, error) = match &observation.other_next { + Ok(next) => (names(next), Value::Null), + Err(code) => (Value::Null, Value::from(*code)), + }; + vec![ + ("first", names(&observation.source_first)), + ("next", next), + ("next_error", error), + ] +} diff --git a/crates/windows-platform-probes/src/handle_state/raw_row/tests.rs b/crates/windows-platform-probes/src/handle_state/raw_row/tests.rs new file mode 100644 index 000000000..a8a4a03f7 --- /dev/null +++ b/crates/windows-platform-probes/src/handle_state/raw_row/tests.rs @@ -0,0 +1,109 @@ +// Copyright (c) Mike Grier. +//! Tests for the handle-state probe's row. + +use super::*; + +fn list(names: &[&str]) -> Vec { + names.iter().map(|name| (*name).to_owned()).collect() +} + +fn observation() -> Observation { + Observation { + ground_truth: list(&[".", "..", "f00.t", "f01.t", "f02.t"]), + duplicate: CursorObservation { + source_first: list(&[".", ".."]), + other_next: Ok(list(&["f00.t"])), + }, + separate_opens: CursorObservation { + source_first: list(&["."]), + other_next: Ok(list(&[".", ".."])), + }, + source_survived_close: true, + queries: vec![ + QueryObservation { + query: SingleShot::IdInfo, + on_duplicate: false, + succeeded: true, + cursor: CursorObservation { + source_first: list(&["f01.t"]), + other_next: Ok(list(&["f02.t"])), + }, + }, + QueryObservation { + query: SingleShot::BasicInfo, + on_duplicate: true, + succeeded: false, + cursor: CursorObservation { + source_first: list(&["f00.t"]), + other_next: Err(18), + }, + }, + ], + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_apparatus_sizes_and_the_build() { + let row = parsed(&raw_row(&observation(), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-handle-state"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert_eq!(row["buffer_bytes"], BUFFER_BYTES); + assert_eq!(row["fixture_files"], FIXTURE_FILES); +} + +#[test] +fn the_ground_truth_and_both_two_call_observations_are_published_as_names() { + let row = parsed(&raw_row(&observation(), "b")); + assert_eq!( + row["ground_truth"], + serde_json::json!([".", "..", "f00.t", "f01.t", "f02.t"]) + ); + assert_eq!( + row["duplicate"], + serde_json::json!({"first": [".", ".."], "next": ["f00.t"], "next_error": null}) + ); + assert_eq!( + row["separate_opens"], + serde_json::json!({"first": ["."], "next": [".", ".."], "next_error": null}) + ); + assert_eq!(row["source_survived_close"], true); +} + +#[test] +fn each_query_is_published_with_its_outcome_and_the_names_around_it() { + let row = parsed(&raw_row(&observation(), "b")); + assert_eq!( + row["queries"], + serde_json::json!([ + {"query": "id_info", "on_duplicate": false, "succeeded": true, + "first": ["f01.t"], "next": ["f02.t"], "next_error": null}, + {"query": "basic_info", "on_duplicate": true, "succeeded": false, + "first": ["f00.t"], "next": null, "next_error": 18}, + ]) + ); +} + +#[test] +fn a_source_broken_by_the_close_is_published_as_false() { + let mut broken = observation(); + broken.source_survived_close = false; + assert_eq!( + parsed(&raw_row(&broken, "b"))["source_survived_close"], + false + ); +} + +#[test] +fn every_query_has_a_distinct_record_name() { + let all = [SingleShot::BasicInfo, SingleShot::IdInfo, SingleShot::NonEx]; + let mut names: Vec<&str> = all.iter().map(|query| query.name()).collect(); + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), all.len()); +} diff --git a/crates/windows-platform-probes/src/ioring.rs b/crates/windows-platform-probes/src/ioring.rs index 433a2392b..af824c1b0 100644 --- a/crates/windows-platform-probes/src/ioring.rs +++ b/crates/windows-platform-probes/src/ioring.rs @@ -43,21 +43,22 @@ use std::ffi::c_void; use std::io::Write; -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::{AsRawHandle, OwnedHandle}; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; -use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, S_OK}; +use win_pipes_sys::{NamedPipeOptions, PipeMode, PipeOpenMode, create_named_pipe}; +use windows_sys::Win32::Foundation::{HANDLE, S_OK}; use windows_sys::Win32::Storage::FileSystem::{ BuildIoRingReadFile, BuildIoRingRegisterFileHandles, CloseIoRing, CreateIoRing, HIORING, IORING_BUFFER_REF, IORING_BUFFER_REF_0, IORING_CQE, IORING_CREATE_FLAGS, IORING_HANDLE_REF, IORING_HANDLE_REF_0, IORING_REF_RAW, IORING_REF_REGISTERED, IORING_VERSION_3, PopIoRingCompletion, SubmitIoRing, }; -use windows_sys::Win32::Storage::FileSystem::{FILE_FLAG_OVERLAPPED, PIPE_ACCESS_INBOUND}; -use windows_sys::Win32::System::Pipes::{ - CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_TYPE_BYTE, PIPE_WAIT, -}; + +mod raw_row; + +pub use self::raw_row::*; /// How long a submit may wait for its completions. const SUBMIT_TIMEOUT_MS: u32 = 5_000; @@ -301,33 +302,16 @@ impl PipePair { r"\\.\pipe\windows-platform-probes-{}-{label}-{unique}", std::process::id() ); - let wide: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - // SAFETY: `wide` is NUL-terminated and every other argument is a plain - // value; a null security descriptor requests the default. - let raw = unsafe { - CreateNamedPipeW( - wide.as_ptr(), - PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, - READ_LEN, - READ_LEN, - 0, - std::ptr::null(), - ) + // No security attributes are passed, which requests the default descriptor. + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_INBOUND | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE | PipeMode::READMODE_BYTE | PipeMode::WAIT, + max_instances: 1, + out_buffer_size: READ_LEN, + in_buffer_size: READ_LEN, + default_timeout_ms: 0, }; - // Both halves of the condition are ways `CreateNamedPipeW` reports - // failure, and it is the only call between here and the code being - // read, so the code belongs to it either way. - assert!( - !raw.is_null() && raw != INVALID_HANDLE_VALUE, - "create the probe pipe: {}", - std::io::Error::last_os_error() - ); - - // SAFETY: `raw` is a fresh, valid handle this type now owns solely. - let server = unsafe { OwnedHandle::from_raw_handle(raw.cast()) }; + let server = create_named_pipe(&name, &options, None).expect("create the probe pipe"); // Opening the client end is what connects it, so no ConnectNamedPipe // is needed. diff --git a/crates/windows-platform-probes/src/ioring/raw_row.rs b/crates/windows-platform-probes/src/ioring/raw_row.rs new file mode 100644 index 000000000..16c0b0de1 --- /dev/null +++ b/crates/windows-platform-probes/src/ioring/raw_row.rs @@ -0,0 +1,84 @@ +// Copyright (c) Mike Grier. + +//! The `IoRing` probe's machine-readable row (`M8.11`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-ioring"; + +/// Why a run declines: the one condition the probe checks before measuring. +pub const NO_USABLE_IORING: &str = "this host has no usable IoRing"; + +/// The row a measured report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md. For +/// registration: whether reads through index 0 and index 1 succeeded after a +/// second registration of one handle. For thread agnosticism: whether the read +/// was still outstanding when its submitter ended, its result code, and whether +/// it transferred the fill byte. Either is `null` when that measurement found +/// no ring after the host had reported one. +/// +/// The result code is an HRESULT, published as the `0x` hexadecimal text the +/// prose prints: it is a code rather than a quantity, and it is signed, which +/// the row carries no number for. +/// +/// Not replaces, appends or survives; each is a comparison over the figures +/// here. +#[must_use] +pub fn raw_row( + registration: &IoRingSupport, + agnosticism: &IoRingSupport, + build: &str, +) -> Row { + let registration = match registration { + IoRingSupport::Unavailable => Value::Null, + IoRingSupport::Measured(observed) => Value::Object(vec![ + ( + "index_zero_usable_after_second", + Value::from(observed.index_zero_usable_after_second), + ), + ( + "index_one_usable_after_second", + Value::from(observed.index_one_usable_after_second), + ), + ]), + }; + let agnosticism = match agnosticism { + IoRingSupport::Unavailable => Value::Null, + IoRingSupport::Measured(observed) => Value::Object(vec![ + ( + "pending_at_submitter_exit", + Value::from(observed.pending_at_submitter_exit), + ), + ( + "result_code", + Value::from(format!("{:#010x}", observed.result_code)), + ), + ("filled", Value::from(observed.filled)), + ]), + }; + preamble(build) + .with("declined", Value::Null) + .with("registration", registration) + .with("thread_agnosticism", agnosticism) +} + +/// The row a report carries when the host has no ring to measure. +/// +/// Follows topology's `report_unmeasured`: the same row with `declined` naming +/// why, and nothing the measurement would have produced. +#[must_use] +pub fn declined_row(reason: &str, build: &str) -> Row { + preamble(build).with("declined", reason) +} + +fn preamble(build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) +} diff --git a/crates/windows-platform-probes/src/ioring/raw_row/tests.rs b/crates/windows-platform-probes/src/ioring/raw_row/tests.rs new file mode 100644 index 000000000..42467560b --- /dev/null +++ b/crates/windows-platform-probes/src/ioring/raw_row/tests.rs @@ -0,0 +1,79 @@ +// Copyright (c) Mike Grier. +//! Tests for the `IoRing` probe's row. + +use super::*; + +fn registration() -> IoRingSupport { + IoRingSupport::Measured(RegistrationObservation { + index_one_usable_after_second: false, + index_zero_usable_after_second: true, + }) +} + +fn agnosticism(result_code: i32) -> IoRingSupport { + IoRingSupport::Measured(ThreadAgnosticism { + pending_at_submitter_exit: true, + result_code, + filled: false, + }) +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_build() { + let row = parsed(&raw_row(®istration(), &agnosticism(0), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-ioring"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); +} + +#[test] +fn both_measurements_are_published_as_observed() { + let row = parsed(&raw_row(®istration(), &agnosticism(0x0012_3456), "b")); + assert_eq!( + row["registration"], + serde_json::json!({"index_zero_usable_after_second": true, "index_one_usable_after_second": false}) + ); + assert_eq!( + row["thread_agnosticism"], + serde_json::json!({"pending_at_submitter_exit": true, "result_code": "0x00123456", "filled": false}) + ); +} + +#[test] +fn a_failing_result_code_keeps_its_bits() { + // An HRESULT failure has the high bit set; as an `i32` it is negative, and + // the row must publish the code a reader would look up, not a sign. + let code = i32::from_ne_bytes(0x8007_0005_u32.to_ne_bytes()); + let row = parsed(&raw_row(®istration(), &agnosticism(code), "b")); + assert_eq!(row["thread_agnosticism"]["result_code"], "0x80070005"); +} + +#[test] +fn a_measurement_that_found_no_ring_is_null_rather_than_absent() { + let row = parsed(&raw_row( + &IoRingSupport::Unavailable, + &IoRingSupport::Unavailable, + "b", + )); + for key in ["registration", "thread_agnosticism"] { + assert!( + row.get(key).is_some_and(serde_json::Value::is_null), + "{key}" + ); + } +} + +#[test] +fn a_declined_run_says_why_and_publishes_no_measurement() { + let row = parsed(&declined_row(NO_USABLE_IORING, "b")); + assert_eq!(row["declined"], NO_USABLE_IORING); + for absent in ["registration", "thread_agnosticism"] { + assert!(row.get(absent).is_none(), "{absent}"); + } +} diff --git a/crates/windows-platform-probes/src/lib.rs b/crates/windows-platform-probes/src/lib.rs index 00ca432f5..0e576f201 100644 --- a/crates/windows-platform-probes/src/lib.rs +++ b/crates/windows-platform-probes/src/lib.rs @@ -144,8 +144,10 @@ #![forbid(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] +pub mod build_identity; pub mod cancel_io; pub mod completion_port; +pub mod core_affinity; pub mod device_map; pub mod doorbell_cost; pub mod error_mode; @@ -153,7 +155,9 @@ pub mod handle_state; pub mod ioring; pub mod long_path; pub mod long_path_report; +pub mod peer_index_cache; pub mod pool_growth; +pub mod probe_args; pub mod queue_contention; pub mod report; /// The report oracle. **Test-support: present only where it is used.** diff --git a/crates/windows-platform-probes/src/long_path.rs b/crates/windows-platform-probes/src/long_path.rs index 646d510c6..cddd0bd6c 100644 --- a/crates/windows-platform-probes/src/long_path.rs +++ b/crates/windows-platform-probes/src/long_path.rs @@ -117,6 +117,17 @@ pub enum Shape { } impl Shape { + /// A stable identifier for a record, separate from the prose + /// [`label`](Self::label) so the table can be reworded freely. + #[must_use] + pub fn name(self) -> &'static str { + match self { + Self::Plain => "plain", + Self::DotDot => "dot_dot", + Self::ForwardSlash => "forward_slash", + } + } + /// A short word for a table. #[must_use] pub fn label(self) -> &'static str { @@ -702,5 +713,9 @@ pub fn is_refusal(attempt: &Attempt) -> bool { !attempt.opened && matches!(attempt.error, ERROR_PATH_NOT_FOUND | ERROR_FILE_NOT_FOUND) } +mod raw_row; + +pub use self::raw_row::*; + #[cfg(test)] mod tests; diff --git a/crates/windows-platform-probes/src/long_path/raw_row.rs b/crates/windows-platform-probes/src/long_path/raw_row.rs new file mode 100644 index 000000000..1dfa0ec57 --- /dev/null +++ b/crates/windows-platform-probes/src/long_path/raw_row.rs @@ -0,0 +1,58 @@ +// Copyright (c) Mike Grier. + +//! The long-path probes' machine-readable row (`M8.6`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself -- **one name for both binaries**. Their finding +/// is the difference between two runs, so a reader pairs two rows of one shape +/// told apart by `manifest_aware`, rather than two shapes. +pub const REASON: &str = "x-probe-long-path"; + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: whether +/// this binary declares `longPathAware`, the machine's `LongPathsEnabled` +/// (`null` when the run was refused before it could be read), the content +/// ceiling the attempts are compared against, and every attempt -- its shape by +/// record name, the resolved length in UTF-16 code units, whether +/// `CreateFileW` opened it, and the Win32 error when it did not (`null` when it +/// opened, since none was read). +/// +/// `declined` carries the apparatus error when there is one -- an over-long +/// temporary directory refused before anything ran, or a tree that could not be +/// built -- and is `null` otherwise. When it is set, the attempts say nothing +/// about the machine, which is what the prose says too. +/// +/// Not whether an attempt was over the ceiling, which `resolved_len` and +/// `ceiling` give, nor the verdict the prose draws. +#[must_use] +pub fn raw_row(observation: &Observation, build: &str) -> Row { + let attempts: Value = observation + .attempts + .iter() + .map(|attempt| { + Value::Object(vec![ + ("shape", Value::from(attempt.shape.name())), + ("resolved_len", Value::from(attempt.resolved_len)), + ("opened", Value::from(attempt.opened)), + ( + "error", + Value::from((!attempt.opened).then_some(attempt.error)), + ), + ]) + }) + .collect(); + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("manifest_aware", observation.manifest_aware) + .with("registry_enabled", observation.registry_enabled) + .with("declined", observation.apparatus_error.clone()) + .with("ceiling", MAX_PATH_CONTENT) + .with("attempts", attempts) +} diff --git a/crates/windows-platform-probes/src/long_path/raw_row/tests.rs b/crates/windows-platform-probes/src/long_path/raw_row/tests.rs new file mode 100644 index 000000000..faf766a0b --- /dev/null +++ b/crates/windows-platform-probes/src/long_path/raw_row/tests.rs @@ -0,0 +1,118 @@ +// Copyright (c) Mike Grier. +//! Tests for the long-path probes' row. + +use super::*; + +fn attempt(shape: Shape, resolved_len: usize, opened: bool, error: u32) -> Attempt { + Attempt { + shape, + resolved_len, + over_max_path: resolved_len > MAX_PATH_CONTENT, + opened, + error, + } +} + +fn observation(manifest_aware: bool) -> Observation { + Observation { + manifest_aware, + registry_enabled: Some(true), + attempts: vec![ + attempt(Shape::Plain, 120, true, 0), + attempt(Shape::DotDot, 125, true, 0), + attempt(Shape::ForwardSlash, 121, true, 0), + attempt(Shape::Plain, 300, true, 0), + attempt(Shape::DotDot, 305, false, ERROR_PATH_NOT_FOUND), + attempt(Shape::ForwardSlash, 301, false, ERROR_FILE_NOT_FOUND), + ], + apparatus_error: None, + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_both_halves_of_the_opt_in() { + let row = parsed(&raw_row(&observation(true), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-long-path"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); + assert_eq!(row["manifest_aware"], true); + assert_eq!(row["registry_enabled"], true); + assert_eq!(row["ceiling"], 259); + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); +} + +#[test] +fn the_two_binaries_differ_in_the_manifest_field_and_nowhere_else() { + // The finding is the difference between the two runs, so the row must let + // a reader tell them apart by one field, with nothing else moving. + let aware = parsed(&raw_row(&observation(true), "b")); + let unaware = parsed(&raw_row(&observation(false), "b")); + assert_eq!(aware["manifest_aware"], true); + assert_eq!(unaware["manifest_aware"], false); + let mut aware = aware; + aware["manifest_aware"] = false.into(); + assert_eq!(aware, unaware); +} + +#[test] +fn every_attempt_is_published_with_its_error_only_when_it_failed() { + let row = parsed(&raw_row(&observation(false), "b")); + assert_eq!( + row["attempts"], + serde_json::json!([ + {"shape": "plain", "resolved_len": 120, "opened": true, "error": null}, + {"shape": "dot_dot", "resolved_len": 125, "opened": true, "error": null}, + {"shape": "forward_slash", "resolved_len": 121, "opened": true, "error": null}, + {"shape": "plain", "resolved_len": 300, "opened": true, "error": null}, + {"shape": "dot_dot", "resolved_len": 305, "opened": false, "error": ERROR_PATH_NOT_FOUND}, + {"shape": "forward_slash", "resolved_len": 301, "opened": false, "error": ERROR_FILE_NOT_FOUND}, + ]) + ); +} + +#[test] +fn a_registry_read_that_was_off_is_false_and_one_never_made_is_null() { + let mut off = observation(true); + off.registry_enabled = Some(false); + assert_eq!(parsed(&raw_row(&off, "b"))["registry_enabled"], false); + + let mut unread = observation(true); + unread.registry_enabled = None; + let row = parsed(&raw_row(&unread, "b")); + assert!( + row.get("registry_enabled") + .is_some_and(serde_json::Value::is_null) + ); +} + +#[test] +fn a_refused_run_says_why_and_publishes_no_attempts() { + let refused = Observation { + manifest_aware: true, + registry_enabled: None, + attempts: Vec::new(), + apparatus_error: Some("TEMP is \"too\" long\nsecond line".to_owned()), + }; + let row = parsed(&raw_row(&refused, "b")); + assert_eq!(row["declined"], "TEMP is \"too\" long\nsecond line"); + assert_eq!(row["attempts"], serde_json::json!([])); + assert!(row["registry_enabled"].is_null()); +} + +#[test] +fn every_shape_has_a_distinct_record_name_that_is_not_its_label() { + let all = [Shape::Plain, Shape::DotDot, Shape::ForwardSlash]; + let mut names: Vec<&str> = all.iter().map(|shape| shape.name()).collect(); + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), all.len()); + // `plain` is both, legitimately; the other two must differ, since their + // labels carry punctuation a record key should not. + assert_ne!(Shape::DotDot.name(), Shape::DotDot.label()); + assert_ne!(Shape::ForwardSlash.name(), Shape::ForwardSlash.label()); +} diff --git a/crates/windows-platform-probes/src/long_path_report.rs b/crates/windows-platform-probes/src/long_path_report.rs index 6d772eba2..24b9a7370 100644 --- a/crates/windows-platform-probes/src/long_path_report.rs +++ b/crates/windows-platform-probes/src/long_path_report.rs @@ -28,7 +28,20 @@ pub fn render(out: &mut dyn std::fmt::Write, manifest_aware: bool) { preamble(out); // Everything above is already in `out`, so the report survives whatever this // does. - body(out, &crate::long_path::measure(manifest_aware)); + let observation = crate::long_path::measure(manifest_aware); + body(out, &observation); + // The raw data, last, as every probe's normal run emits it (`M8.6`), and + // after `body` rather than inside it: `body` returns early on an apparatus + // failure, and the row is owed anyway -- it says why. + let _ = writeln!( + out, + "{}", + crate::long_path::raw_row( + &observation, + &crate::build_identity::this_build().to_string() + ) + .render() + ); } /// Everything that can be written before the measurement runs. diff --git a/crates/windows-platform-probes/src/peer_index_cache.rs b/crates/windows-platform-probes/src/peer_index_cache.rs new file mode 100644 index 000000000..41cba3856 --- /dev/null +++ b/crates/windows-platform-probes/src/peer_index_cache.rs @@ -0,0 +1,100 @@ +// Copyright (c) Mike Grier. + +//! The peer-index probe's machine-readable row (`M8.3`). +//! +//! The measurement is `windows_placement_probe::peer_index_cache`'s; this is +//! only what `probe-peer-index-cache` publishes of it. + +use windows_placement_probe::peer_index_cache::{ + CAPACITY, ITEMS, Observation, REPETITIONS, Run, Strategy, +}; + +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-peer-index-cache"; + +/// The strategies in the order the row lists them. +const STRATEGIES: [Strategy; 3] = [Strategy::Baseline, Strategy::Cached, Strategy::Warmed]; + +/// The row a measured report carries: the raw data `observation` holds, and +/// the build that produced it. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md. For the +/// shipping queue and for each model strategy, the median of [`REPETITIONS`] +/// timed passes as whole elapsed nanoseconds, and -- for the strategies -- how +/// many times that pass's consumer and producer read the other side's shared +/// position. Not the per-item cost, the speedups, the batch depths or the +/// verdicts the prose derives from them; a consumer that wants a per-item +/// figure divides by `items`. +/// +/// **The shipping queue's read counts are `null`.** It has no counter: the +/// probe fills those two fields with [`ITEMS`] as a floor, which the report's +/// table prints and which is not an observation. Publishing it would put a +/// constant in the row under a name that says it was counted. +/// +/// **The elapsed time is recovered from [`Run::nanos_per_item`]**, which is the +/// median pass's elapsed time divided by [`ITEMS`]. A pass is a whole number of +/// nanoseconds far below 2^53, so multiplying back and rounding returns it +/// exactly, which a test checks. +/// +/// A strategy absent from `observation` is absent from the row. `measure` +/// always produces all three; an `Observation` built by hand need not. +#[must_use] +pub fn raw_row(observation: &Observation, build: &str) -> Row { + let strategies: Value = STRATEGIES + .into_iter() + .filter_map(|strategy| { + observation.get(strategy).map(|run| { + Value::Object(vec![ + ("strategy", Value::from(strategy.name())), + ("median_elapsed_ns", elapsed(&run)), + ("consumer_refreshes", Value::from(run.consumer_refreshes)), + ("producer_refreshes", Value::from(run.producer_refreshes)), + ]) + }) + }) + .collect(); + preamble(build) + .with("declined", Value::Null) + .with( + "calibration", + Value::Object(vec![ + ("median_elapsed_ns", elapsed(&observation.calibration)), + ("consumer_refreshes", Value::Null), + ("producer_refreshes", Value::Null), + ]), + ) + .with("strategies", strategies) +} + +/// The row a report carries when the host declined to be measured. +/// +/// Follows topology's `report_unmeasured`: the same row, naming why, rather +/// than no row. `declined` carries the error's text, which is `null` in a +/// measured row; there is no `calibration` or `strategies`, because nothing +/// was timed. `measure` cannot return an error today -- it pins nothing -- so +/// this is the row for a path the type requires rather than one a host +/// reaches. +#[must_use] +pub fn declined_row(error: &std::io::Error, build: &str) -> Row { + preamble(build).with("declined", error.to_string()) +} + +/// What both forms carry: where the run happened and what it was set to do. +fn preamble(build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("items", ITEMS) + .with("capacity", CAPACITY) + .with("repetitions", REPETITIONS) +} + +/// The median pass's elapsed time, in whole nanoseconds. +fn elapsed(run: &Run) -> Value { + Value::from((run.nanos_per_item * ITEMS as f64).round() as usize) +} diff --git a/crates/windows-platform-probes/src/peer_index_cache/tests.rs b/crates/windows-platform-probes/src/peer_index_cache/tests.rs new file mode 100644 index 000000000..bc875342a --- /dev/null +++ b/crates/windows-platform-probes/src/peer_index_cache/tests.rs @@ -0,0 +1,189 @@ +// Copyright (c) Mike Grier. +//! Tests for the peer-index probe's row. + +use super::*; + +/// A run whose median pass took `elapsed` nanoseconds. +fn run(label: &'static str, elapsed: f64, consumer: u64, producer: u64) -> Run { + Run { + label, + nanos_per_item: elapsed / ITEMS as f64, + items_per_second: ITEMS as f64 / (elapsed / 1e9), + consumer_refreshes: consumer, + producer_refreshes: producer, + } +} + +/// An observation shaped as `measure` produces one, with distinct figures in +/// every field so no two can be swapped unnoticed. +fn observation() -> Observation { + Observation { + calibration: run("shipping spsc", 9_000_000.0, ITEMS as u64, ITEMS as u64), + strategies: vec![ + run( + Strategy::Baseline.label(), + 10_000_000.0, + 1_900_000, + 1_800_000, + ), + run(Strategy::Cached.label(), 4_000_000.0, 7_000, 300), + run(Strategy::Warmed.label(), 11_000_000.0, 1_950_000, 1_850_000), + ], + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_settings_and_the_build() { + let row = parsed(&raw_row(&observation(), "v1.2.3 0123456789ab [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-peer-index-cache"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1.2.3 0123456789ab [ci]"); + assert_eq!(row["items"], ITEMS); + assert_eq!(row["capacity"], CAPACITY); + assert_eq!(row["repetitions"], REPETITIONS); + // Present and null, so a reader can tell a measured row from a declined + // one by the field rather than by which other fields happen to exist. + assert!(row.get("declined").is_some_and(serde_json::Value::is_null)); +} + +#[test] +fn each_strategy_is_published_under_its_record_name_with_its_own_figures() { + let row = parsed(&raw_row(&observation(), "b")); + assert_eq!( + row["strategies"], + serde_json::json!([ + {"strategy": "baseline", "median_elapsed_ns": 10_000_000, + "consumer_refreshes": 1_900_000, "producer_refreshes": 1_800_000}, + {"strategy": "cached", "median_elapsed_ns": 4_000_000, + "consumer_refreshes": 7_000, "producer_refreshes": 300}, + {"strategy": "warmed", "median_elapsed_ns": 11_000_000, + "consumer_refreshes": 1_950_000, "producer_refreshes": 1_850_000}, + ]) + ); +} + +#[test] +fn the_strategies_are_listed_in_a_fixed_order_whatever_order_they_were_measured_in() { + let mut reversed = observation(); + reversed.strategies.reverse(); + let names: Vec = parsed(&raw_row(&reversed, "b"))["strategies"] + .as_array() + .expect("a list") + .iter() + .map(|entry| entry["strategy"].as_str().expect("a name").to_owned()) + .collect(); + assert_eq!(names, ["baseline", "cached", "warmed"]); +} + +#[test] +fn a_strategy_that_was_not_measured_is_absent_rather_than_invented() { + let mut partial = observation(); + partial + .strategies + .retain(|run| run.label != Strategy::Cached.label()); + let row = parsed(&raw_row(&partial, "b")); + let names: Vec<&str> = row["strategies"] + .as_array() + .expect("a list") + .iter() + .map(|entry| entry["strategy"].as_str().expect("a name")) + .collect(); + assert_eq!(names, ["baseline", "warmed"]); + + let mut none = observation(); + none.strategies.clear(); + assert_eq!( + parsed(&raw_row(&none, "b"))["strategies"], + serde_json::json!([]) + ); +} + +#[test] +fn the_shipping_queue_publishes_its_time_but_not_the_floor_it_was_given_as_counts() { + let row = parsed(&raw_row(&observation(), "b")); + assert_eq!( + row["calibration"], + serde_json::json!({ + "median_elapsed_ns": 9_000_000, + "consumer_refreshes": null, + "producer_refreshes": null, + }) + ); +} + +#[test] +fn every_elapsed_time_is_recovered_exactly_from_the_per_item_figure() { + // The claim the elapsed fields rest on: dividing by the items and + // multiplying back loses nothing that rounding does not restore. Across + // times from one nanosecond to well past any real pass, including ones the + // item count does not divide. 249 and 507 are among the times whose round + // trip lands just BELOW the whole nanosecond, found by searching, so they + // are what tells rounding from truncation. + for elapsed in [ + 1.0, + 7.0, + 249.0, + 507.0, + 1_999_999.0, + 2_000_001.0, + 999_999_937.0, + 65_000_000_123.0, + (1u64 << 50) as f64 + 3.0, + ] { + let mut sample = observation(); + sample.calibration = run("shipping spsc", elapsed, 0, 0); + sample.strategies = vec![run(Strategy::Cached.label(), elapsed, 0, 0)]; + let row = parsed(&raw_row(&sample, "b")); + assert_eq!( + row["calibration"]["median_elapsed_ns"].as_u64(), + Some(elapsed as u64), + "calibration: {elapsed} ns" + ); + assert_eq!( + row["strategies"][0]["median_elapsed_ns"].as_u64(), + Some(elapsed as u64), + "strategy: {elapsed} ns" + ); + } +} + +#[test] +fn a_declined_run_says_why_and_publishes_no_figures() { + // Quotes, a brace and a newline: the error text is the OS's, and has to + // stay one field of one row whatever it contains. + let error = std::io::Error::other("refused {\"reason\":\"x\"}\nsecond line"); + let row = parsed(&declined_row(&error, "b")); + assert_eq!(row["reason"], REASON); + assert_eq!(row["declined"], "refused {\"reason\":\"x\"}\nsecond line"); + assert_eq!(row["items"], ITEMS); + assert_eq!(row["repetitions"], REPETITIONS); + assert!(row.get("calibration").is_none()); + assert!(row.get("strategies").is_none()); +} + +#[test] +fn the_two_forms_share_every_key_but_the_figures() { + // Both directions: the declined form carries nothing the measured form + // lacks, and the measured form adds exactly the figures. + let keys = |row: &serde_json::Value| -> Vec { + row.as_object() + .expect("an object") + .keys() + .cloned() + .collect() + }; + let measured = keys(&parsed(&raw_row(&observation(), "b"))); + let declined = keys(&parsed(&declined_row(&std::io::Error::other("e"), "b"))); + let mut extra: Vec<&String> = measured + .iter() + .filter(|key| !declined.contains(key)) + .collect(); + extra.sort(); + assert_eq!(extra, ["calibration", "strategies"]); + assert!(declined.iter().all(|key| measured.contains(key))); +} diff --git a/crates/windows-platform-probes/src/pool_growth.rs b/crates/windows-platform-probes/src/pool_growth.rs index e33441728..4f5d2fe09 100644 --- a/crates/windows-platform-probes/src/pool_growth.rs +++ b/crates/windows-platform-probes/src/pool_growth.rs @@ -38,6 +38,70 @@ use windows_threadpool_sys::callback_env::CallbackEnviron; use windows_threadpool_sys::pool::ThreadpoolPool; use windows_threadpool_sys::work::ThreadpoolWork; +mod raw_row; + +pub use self::raw_row::*; + +/// One growth run the probe binary makes: how the pool is configured, and the +/// names it is reported under. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct GrowthRun { + /// The heading the report prints over the run. Prose; may be reworded. + pub label: &'static str, + /// A stable identifier for a record. + pub name: &'static str, + /// The pool maximum. + pub maximum: u32, + /// How many callbacks are submitted. + pub submissions: usize, + /// Whether the callbacks are flagged as running long. + pub runs_long: bool, +} + +/// The growth runs the probe binary makes, in order. One list, so the report +/// and its row cannot describe different runs. +pub const GROWTH_RUNS: [GrowthRun; 3] = [ + GrowthRun { + label: "P1 growth curve, max 4:", + name: "max_4", + maximum: 4, + submissions: 8, + runs_long: false, + }, + GrowthRun { + label: "P1 growth curve, max 8:", + name: "max_8", + maximum: 8, + submissions: 16, + runs_long: false, + }, + GrowthRun { + label: "P2 the same, runs-long:", + name: "max_4_runs_long", + maximum: 4, + submissions: 8, + runs_long: true, + }, +]; + +/// The raise the probe binary measures: saturate at `base_max`, then raise. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RaiseRun { + /// The maximum the pool is saturated at. + pub base_max: u32, + /// The maximum it is raised to. + pub raised_max: u32, + /// How many callbacks are submitted. + pub submissions: usize, +} + +/// The raise the probe binary measures. +pub const RAISE_RUN: RaiseRun = RaiseRun { + base_max: 2, + raised_max: 6, + submissions: 8, +}; + /// A manual-reset event every callback parks on, so "blocked" means blocked. struct Gate(HANDLE); diff --git a/crates/windows-platform-probes/src/pool_growth/raw_row.rs b/crates/windows-platform-probes/src/pool_growth/raw_row.rs new file mode 100644 index 000000000..290d74bb7 --- /dev/null +++ b/crates/windows-platform-probes/src/pool_growth/raw_row.rs @@ -0,0 +1,82 @@ +// Copyright (c) Mike Grier. + +//! The pool-growth probe's machine-readable row (`M8.5`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-pool-growth"; + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md. For each +/// growth run, under its record name: the configuration (maximum, submitted, +/// runs-long) and what was observed -- callbacks started while blocked, the +/// distinct threads they ran on, and every arrival in microseconds from +/// submission. For the raise: the two maxima, the submissions, how many +/// callbacks had started when the maximum was raised, whether an extra one +/// started within the settle window, and the elapsed nanoseconds from the +/// raise to that start -- or to the window's end, when `took_effect` is false. +/// +/// Not the verdicts the prose prints (saturated, one thread each) nor the +/// slowest arrival or largest gap; each is a comparison or a maximum over the +/// figures here. +/// +/// The probe has no way to decline: every measurement either completes or +/// panics, and a panic prints no report. +#[must_use] +pub fn raw_row( + growth: &[(GrowthRun, GrowthObservation)], + raise: (RaiseRun, RaiseObservation), + build: &str, +) -> Row { + let growth: Value = growth + .iter() + .map(|(run, observed)| { + Value::Object(vec![ + ("run", Value::from(run.name)), + ("maximum", Value::from(observed.maximum)), + ("submitted", Value::from(observed.submitted)), + ("runs_long", Value::from(run.runs_long)), + ( + "started_while_blocked", + Value::from(observed.started_while_blocked), + ), + ("distinct_threads", Value::from(observed.distinct_threads)), + ( + "arrivals_us", + observed.arrivals_us.iter().copied().map(wide).collect(), + ), + ]) + }) + .collect(); + let (plan, observed) = raise; + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("growth", growth) + .with( + "raise", + Value::Object(vec![ + ("base_max", Value::from(plan.base_max)), + ("raised_max", Value::from(plan.raised_max)), + ("submitted", Value::from(plan.submissions)), + ( + "started_before_raise", + Value::from(observed.started_before_raise), + ), + ("took_effect", Value::from(observed.took_effect)), + ("elapsed_ns", wide(observed.delay.as_nanos())), + ]), + ) +} + +/// A `u128` time, as a count. Saturates past `u64::MAX` nanoseconds -- about +/// 584 years -- and then as `From` does. +fn wide(value: u128) -> Value { + Value::from(u64::try_from(value).unwrap_or(u64::MAX)) +} diff --git a/crates/windows-platform-probes/src/pool_growth/raw_row/tests.rs b/crates/windows-platform-probes/src/pool_growth/raw_row/tests.rs new file mode 100644 index 000000000..268ed8a9b --- /dev/null +++ b/crates/windows-platform-probes/src/pool_growth/raw_row/tests.rs @@ -0,0 +1,118 @@ +// Copyright (c) Mike Grier. +//! Tests for the pool-growth probe's row. + +use super::*; + +fn grew(run: GrowthRun, started: usize, threads: usize, arrivals: Vec) -> GrowthObservation { + GrowthObservation { + maximum: run.maximum, + submitted: run.submissions, + started_while_blocked: started, + distinct_threads: threads, + arrivals_us: arrivals, + } +} + +fn raised(started: usize, took_effect: bool, nanos: u64) -> (RaiseRun, RaiseObservation) { + ( + RAISE_RUN, + RaiseObservation { + base_max: RAISE_RUN.base_max, + started_before_raise: started, + delay: Duration::from_nanos(nanos), + took_effect, + }, + ) +} + +fn growth() -> Vec<(GrowthRun, GrowthObservation)> { + vec![ + ( + GROWTH_RUNS[0], + grew(GROWTH_RUNS[0], 4, 4, vec![12, 15, 17, 503_001]), + ), + (GROWTH_RUNS[1], grew(GROWTH_RUNS[1], 7, 6, vec![3])), + (GROWTH_RUNS[2], grew(GROWTH_RUNS[2], 0, 0, Vec::new())), + ] +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_build() { + let row = parsed(&raw_row(&growth(), raised(2, true, 1), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-pool-growth"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); +} + +#[test] +fn each_growth_run_is_published_under_its_name_with_every_arrival() { + let row = parsed(&raw_row(&growth(), raised(2, true, 1), "b")); + assert_eq!( + row["growth"], + serde_json::json!([ + {"run": "max_4", "maximum": 4, "submitted": 8, "runs_long": false, + "started_while_blocked": 4, "distinct_threads": 4, + "arrivals_us": [12, 15, 17, 503_001]}, + {"run": "max_8", "maximum": 8, "submitted": 16, "runs_long": false, + "started_while_blocked": 7, "distinct_threads": 6, + "arrivals_us": [3]}, + {"run": "max_4_runs_long", "maximum": 4, "submitted": 8, "runs_long": true, + "started_while_blocked": 0, "distinct_threads": 0, + "arrivals_us": []}, + ]) + ); +} + +#[test] +fn the_raise_is_published_with_its_plan_and_what_happened() { + let row = parsed(&raw_row(&growth(), raised(2, true, 1_234_567), "b")); + assert_eq!( + row["raise"], + serde_json::json!({ + "base_max": 2, "raised_max": 6, "submitted": 8, + "started_before_raise": 2, "took_effect": true, + "elapsed_ns": 1_234_567, + }) + ); +} + +#[test] +fn a_raise_that_did_not_take_effect_still_publishes_the_window_it_waited() { + // Both halves: the boolean says the extra callback never started, and the + // elapsed time is then the settle window, published as measured rather + // than hidden behind a null. + let row = parsed(&raw_row(&growth(), raised(1, false, 2_000_000_123), "b")); + assert_eq!(row["raise"]["took_effect"], false); + assert_eq!(row["raise"]["started_before_raise"], 1); + assert_eq!(row["raise"]["elapsed_ns"], 2_000_000_123_u64); +} + +#[test] +fn the_plan_names_every_run_distinctly() { + let mut names: Vec<&str> = GROWTH_RUNS.iter().map(|run| run.name).collect(); + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), GROWTH_RUNS.len()); + for run in GROWTH_RUNS { + assert_ne!(run.name, run.label, "a record name is not the prose label"); + } +} + +#[test] +fn an_arrival_past_u64_saturates_rather_than_wrapping() { + let run = GROWTH_RUNS[0]; + let row = parsed(&raw_row( + &[(run, grew(run, 1, 1, vec![u128::from(u64::MAX) + 5]))], + raised(2, true, 1), + "b", + )); + assert_eq!( + row["growth"][0]["arrivals_us"], + serde_json::json!([usize::MAX]) + ); +} diff --git a/crates/windows-platform-probes/src/probe_args.rs b/crates/windows-platform-probes/src/probe_args.rs new file mode 100644 index 000000000..af6d062c6 --- /dev/null +++ b/crates/windows-platform-probes/src/probe_args.rs @@ -0,0 +1,306 @@ +// Copyright (c) Mike Grier. + +//! The settings a measurement probe takes on its command line, and the one parser for them +//! (`M4.2`). +//! +//! **An experiment, not a component.** These probes measure platform behaviour and are not for +//! production use. Do not call them from production code, and do not lift a technique out of +//! here. See this crate's DESIGN-NOTES.md. +//! +//! A probe declares its settings as [`Flag`]s and reads what was given through [`parse`]. Every +//! flag takes one value, written `--name value` or `--name=value`, and may appear once. An +//! argument that is not one of the probe's flags, a flag given twice, a flag with no value and a +//! value the probe cannot use are all refused, and the binary hands the refusal and the +//! [`usage`] text to [`refuse_arguments`](crate::report::refuse_arguments), which prints both and +//! exits with status 2. A run that measured something other than what was asked would be read +//! as a record of what was asked. +//! +//! **The defaults belong to the settings types**, `queue_contention::Sampling` for the queue +//! probe and [`IterationMultiplier`] below for the two cost probes, and a run given no arguments +//! measures exactly what it measured before the settings existed, so a published figure stays +//! reproducible. +//! +//! Only the axes a stated diagnostic step varies are reachable: the length of a timed span, and, +//! for the probe that repeats, the repetitions and the producer counts. The cost probes neither +//! repeat nor vary concurrency, so their one setting is the span. + +use std::fmt; + +#[cfg(test)] +mod tests; + +/// One setting a probe accepts. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Flag { + /// The name, without the leading `--`. + pub name: &'static str, + /// What the value looks like, for the usage text: `N`, or `N,N,...`. + pub value: &'static str, + /// What the setting does and what it is when not given, for the usage text. + pub help: &'static str, +} + +/// Why a probe's arguments were refused. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ArgError { + /// An argument that is not one of the probe's flags. + Unknown(String), + /// A flag with no value after it. + MissingValue(&'static str), + /// A flag given more than once. + Repeated(&'static str), + /// A value the probe cannot use. + Invalid { + /// The flag the value was given for. + flag: &'static str, + /// The value as it was given. + value: String, + /// What is wrong with it. + reason: String, + }, +} + +impl fmt::Display for ArgError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unknown(argument) => write!(f, "`{argument}` is not a setting this probe takes"), + Self::MissingValue(flag) => write!(f, "--{flag} needs a value after it"), + Self::Repeated(flag) => write!(f, "--{flag} was given more than once"), + Self::Invalid { + flag, + value, + reason, + } => write!(f, "--{flag} {value}: {reason}"), + } + } +} + +impl std::error::Error for ArgError {} + +/// What a command line gave, for the flags it was parsed against. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Given { + flags: Vec, + values: Vec>, +} + +impl Given { + /// The value given for the flag named `name`, or `None` when it was not given. + /// + /// # Panics + /// + /// Panics if `name` is not one of the flags the arguments were parsed against, which is a + /// mistake in the probe and not in the command line: a lookup under a name nothing declared + /// would otherwise read as a setting that was never given. + #[must_use] + pub fn get(&self, name: &str) -> Option<&str> { + let index = self + .flags + .iter() + .position(|flag| flag.name == name) + .unwrap_or_else(|| panic!("`{name}` is not a flag this probe declared")); + self.values[index].as_deref() + } +} + +/// Read `args`, the command line without the program's name, against `flags`. +/// +/// # Errors +/// +/// [`ArgError::Unknown`] for an argument that is not `--` and one of the flags, +/// [`ArgError::MissingValue`] for a flag at the end with nothing after it, and +/// [`ArgError::Repeated`] for a flag given twice. A value is taken as given; whether the probe +/// can use it is the probe's to say. +pub fn parse(args: impl IntoIterator, flags: &[Flag]) -> Result { + let mut values: Vec> = vec![None; flags.len()]; + let mut args = args.into_iter(); + while let Some(argument) = args.next() { + let Some(named) = argument.strip_prefix("--") else { + return Err(ArgError::Unknown(argument)); + }; + let (name, inline) = match named.split_once('=') { + Some((name, value)) => (name, Some(value.to_owned())), + None => (named, None), + }; + let Some(index) = flags.iter().position(|flag| flag.name == name) else { + return Err(ArgError::Unknown(argument)); + }; + let flag = flags[index].name; + let value = match inline { + Some(value) => value, + None => args.next().ok_or(ArgError::MissingValue(flag))?, + }; + if values[index].is_some() { + return Err(ArgError::Repeated(flag)); + } + values[index] = Some(value); + } + Ok(Given { + flags: flags.to_vec(), + values, + }) +} + +/// How to call `probe`, one line per flag. +#[must_use] +pub fn usage(probe: &str, flags: &[Flag]) -> String { + let mut text = format!("usage: {probe}"); + for flag in flags { + text.push_str(&format!(" [--{} {}]", flag.name, flag.value)); + } + for flag in flags { + text.push_str(&format!( + "\n --{} {}\n {}", + flag.name, flag.value, flag.help + )); + } + text +} + +/// How many times its usual count each timed loop of a cost probe runs: the length of the span +/// (`M4.2`). +/// +/// One multiplier for every loop, rather than a count per loop, so the loops keep the proportions +/// they were given -- those that make a kernel call run fewer iterations than those that do not -- +/// and lengthening the span is one decision. The default is 1, the run the published figures came +/// from. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IterationMultiplier(u32); + +impl Default for IterationMultiplier { + fn default() -> Self { + Self(1) + } +} + +impl IterationMultiplier { + /// The one setting the cost probes take. + pub const FLAGS: [Flag; 1] = [Flag { + name: "iteration-multiplier", + value: "N", + help: "each timed loop runs N times its usual count, lengthening the span; 1 when not given", + }]; + + /// The multiplier a command line asks for, given that the probe's longest loop runs + /// `largest` iterations at the default. + /// + /// # Errors + /// + /// What [`parse`] refuses, a value that is not a whole number of at least one, and a + /// multiplier that would take the longest loop past `u32::MAX` iterations, which is what a + /// loop counts in. + pub fn from_args( + args: impl IntoIterator, + largest: u32, + ) -> Result { + let given = parse(args, &Self::FLAGS)?; + let Some(value) = given.get("iteration-multiplier") else { + return Ok(Self::default()); + }; + let multiplier = u32::try_from(positive("iteration-multiplier", value)?).ok(); + match multiplier { + Some(multiplier) if largest.checked_mul(multiplier).is_some() => Ok(Self(multiplier)), + _ => Err(ArgError::Invalid { + flag: "iteration-multiplier", + value: value.to_owned(), + reason: format!( + "takes the longest loop, {largest} iterations at the default, past {} iterations", + u32::MAX + ), + }), + } + } + + /// The multiplier. + #[must_use] + pub const fn get(self) -> u32 { + self.0 + } + + /// `iterations` times the multiplier. + /// + /// # Panics + /// + /// Panics if the product passes `u32::MAX`, which [`from_args`](Self::from_args) refuses for + /// any loop no longer than the `largest` it was given: a loop longer than that is the probe's + /// mistake. + #[must_use] + pub fn scale(self, iterations: u32) -> u32 { + iterations.checked_mul(self.0).unwrap_or_else(|| { + panic!( + "{iterations} iterations times {} passes u32::MAX; the probe gave \ + `from_args` a smaller longest loop than it runs", + self.0 + ) + }) + } + + /// Whether this is the multiplier a run given no settings uses. + #[must_use] + pub fn is_default(self) -> bool { + self == Self::default() + } + + /// The line a cost probe's report carries beside its banner, saying what the run used. + /// + /// Its second word is not a number, so a reader of the probe's table that takes a line + /// starting with a word and then a number as a row does not take this for one. + #[must_use] + pub fn report_line(self) -> String { + if self.is_default() { + format!("iteration multiplier: {} (the default)", self.0) + } else { + format!( + "iteration multiplier: {} (NOT the default: set on the command line, so these \ + figures are not the published ones)", + self.0 + ) + } + } +} + +/// `value` as a whole number of at least one. +/// +/// # Errors +/// +/// [`ArgError::Invalid`] when `value` is not a whole number, or is zero. +pub fn positive(flag: &'static str, value: &str) -> Result { + let invalid = |reason: &str| ArgError::Invalid { + flag, + value: value.to_owned(), + reason: reason.to_owned(), + }; + match value.parse::() { + Ok(0) => Err(invalid("must be at least 1")), + Ok(number) => Ok(number), + Err(_) => Err(invalid("is not a whole number")), + } +} + +/// `value` as a comma-separated list of whole numbers of at least one, none repeated, in the +/// order given. +/// +/// # Errors +/// +/// [`ArgError::Invalid`] when the list is empty, an entry is not a whole number of at least one, +/// or an entry is repeated: a repeated entry would measure the same configuration twice under +/// one name. +pub fn positive_list(flag: &'static str, value: &str) -> Result, ArgError> { + let mut list: Vec = Vec::new(); + for entry in value.split(',') { + let number = positive(flag, entry.trim()).map_err(|_| ArgError::Invalid { + flag, + value: value.to_owned(), + reason: format!("`{entry}` is not a whole number of at least 1"), + })?; + if list.contains(&number) { + return Err(ArgError::Invalid { + flag, + value: value.to_owned(), + reason: format!("{number} is listed more than once"), + }); + } + list.push(number); + } + Ok(list) +} diff --git a/crates/windows-platform-probes/src/probe_args/tests.rs b/crates/windows-platform-probes/src/probe_args/tests.rs new file mode 100644 index 000000000..ea4c42d0e --- /dev/null +++ b/crates/windows-platform-probes/src/probe_args/tests.rs @@ -0,0 +1,254 @@ +// Copyright (c) Mike Grier. +//! Tests for the probes' argument parser. + +use super::{ArgError, Flag, IterationMultiplier, parse, positive, positive_list, usage}; + +const FLAGS: [Flag; 2] = [ + Flag { + name: "repetitions", + value: "N", + help: "how many times; 5 when not given", + }, + Flag { + name: "producers", + value: "N,N,...", + help: "which producer counts; 1,2 when not given", + }, +]; + +fn args(list: &[&str]) -> Vec { + list.iter().map(|arg| (*arg).to_owned()).collect() +} + +#[test] +fn no_arguments_give_no_values() { + let given = parse(args(&[]), &FLAGS).expect("nothing to refuse"); + assert_eq!(given.get("repetitions"), None); + assert_eq!(given.get("producers"), None); +} + +#[test] +fn a_flag_takes_the_argument_after_it_or_the_text_after_an_equals_sign() { + let given = parse(args(&["--repetitions", "7", "--producers=1,3"]), &FLAGS).expect("valid"); + assert_eq!(given.get("repetitions"), Some("7")); + assert_eq!(given.get("producers"), Some("1,3")); +} + +#[test] +fn the_flags_may_come_in_any_order_and_only_some_may_be_given() { + let given = parse(args(&["--producers", "4"]), &FLAGS).expect("valid"); + assert_eq!(given.get("producers"), Some("4")); + assert_eq!(given.get("repetitions"), None); +} + +#[test] +fn an_unknown_flag_a_bare_word_and_help_are_refused_as_unknown() { + for argument in ["--pushes", "repetitions", "--help", "-r", "--"] { + assert_eq!( + parse(args(&[argument, "1"]), &FLAGS), + Err(ArgError::Unknown(argument.to_owned())), + "{argument}" + ); + } +} + +#[test] +fn a_flag_with_nothing_after_it_is_refused() { + assert_eq!( + parse(args(&["--repetitions"]), &FLAGS), + Err(ArgError::MissingValue("repetitions")) + ); +} + +#[test] +fn a_flag_given_twice_is_refused_in_either_spelling() { + assert_eq!( + parse(args(&["--repetitions", "3", "--repetitions=5"]), &FLAGS), + Err(ArgError::Repeated("repetitions")) + ); +} + +#[test] +fn an_empty_value_after_an_equals_sign_is_a_value_for_the_probe_to_judge() { + let given = parse(args(&["--repetitions="]), &FLAGS).expect("parsed"); + assert_eq!(given.get("repetitions"), Some("")); + assert!(positive("repetitions", "").is_err()); +} + +#[test] +#[should_panic(expected = "is not a flag this probe declared")] +fn asking_for_a_flag_nothing_declared_is_the_probes_mistake() { + let given = parse(args(&[]), &FLAGS).expect("valid"); + let _ = given.get("pushes"); +} + +#[test] +fn a_positive_number_is_a_whole_number_of_at_least_one() { + assert_eq!(positive("repetitions", "1"), Ok(1)); + assert_eq!(positive("repetitions", "50000"), Ok(50_000)); + for refused in ["0", "-1", "1.5", "x", "", " 3"] { + let error = positive("repetitions", refused).expect_err(refused); + assert!( + matches!(&error, ArgError::Invalid { flag: "repetitions", value, .. } if value == refused), + "{refused}: {error:?}" + ); + } +} + +#[test] +fn a_list_keeps_its_order_and_tolerates_spaces_after_commas() { + assert_eq!(positive_list("producers", "1,2,4"), Ok(vec![1, 2, 4])); + assert_eq!(positive_list("producers", "8, 1"), Ok(vec![8, 1])); + assert_eq!(positive_list("producers", "3"), Ok(vec![3])); +} + +#[test] +fn a_list_that_is_empty_repeats_an_entry_or_holds_a_bad_one_is_refused() { + for refused in ["", "1,,2", "1,0", "1,2,1", "1,x", "1,"] { + let error = positive_list("producers", refused).expect_err(refused); + assert!( + matches!(&error, ArgError::Invalid { flag: "producers", value, .. } if value == refused), + "{refused}: {error:?}" + ); + } + let repeated = positive_list("producers", "1,2,1").expect_err("repeated"); + assert!( + repeated.to_string().contains("1 is listed more than once"), + "{repeated}" + ); +} + +#[test] +fn the_usage_names_every_flag_its_value_and_its_help() { + let text = usage("probe-example", &FLAGS); + assert!(text.starts_with("usage: probe-example [--repetitions N] [--producers N,N,...]")); + for flag in FLAGS { + assert!( + text.contains(&format!("--{} {}", flag.name, flag.value)), + "{text}" + ); + assert!(text.contains(flag.help), "{text}"); + } +} + +#[test] +fn each_refusal_says_which_flag_and_why() { + assert_eq!( + ArgError::Unknown("--x".into()).to_string(), + "`--x` is not a setting this probe takes" + ); + assert_eq!( + ArgError::MissingValue("repetitions").to_string(), + "--repetitions needs a value after it" + ); + assert_eq!( + ArgError::Repeated("repetitions").to_string(), + "--repetitions was given more than once" + ); + assert_eq!( + positive("repetitions", "0").expect_err("zero").to_string(), + "--repetitions 0: must be at least 1" + ); +} + +// --- The cost probes' one setting --------------------------------------------------------------- + +#[test] +fn the_multiplier_is_one_unless_a_command_line_says_otherwise() { + let multiplier = IterationMultiplier::from_args(args(&[]), 100_000).expect("valid"); + assert_eq!(multiplier, IterationMultiplier::default()); + assert_eq!(multiplier.get(), 1); + assert!(multiplier.is_default()); + assert_eq!(multiplier.scale(100_000), 100_000); +} + +#[test] +fn a_multiplier_scales_every_loop_by_the_same_factor() { + let multiplier = + IterationMultiplier::from_args(args(&["--iteration-multiplier", "4"]), 100_000) + .expect("valid"); + assert_eq!(multiplier.get(), 4); + assert!(!multiplier.is_default()); + assert_eq!(multiplier.scale(100_000), 400_000); + assert_eq!(multiplier.scale(20_000), 80_000); + assert_eq!(multiplier.scale(1), 4); +} + +#[test] +fn a_multiplier_is_refused_when_it_takes_the_longest_loop_past_what_a_loop_counts() { + let largest = 200_000; + let ceiling = u32::MAX / largest; + let fits = ceiling.to_string(); + let over = (ceiling + 1).to_string(); + assert_eq!( + IterationMultiplier::from_args(args(&["--iteration-multiplier", &fits]), largest) + .expect("the largest that fits") + .get(), + ceiling + ); + let error = IterationMultiplier::from_args(args(&["--iteration-multiplier", &over]), largest) + .expect_err("one past it"); + assert!( + matches!( + &error, + ArgError::Invalid { + flag: "iteration-multiplier", + .. + } + ), + "{error:?}" + ); + assert!( + error + .to_string() + .contains("200000 iterations at the default"), + "{error}" + ); + + let beyond_u32 = (u64::from(u32::MAX) + 1).to_string(); + assert!( + IterationMultiplier::from_args(args(&["--iteration-multiplier", &beyond_u32]), 1).is_err(), + "a multiplier that is not even a u32 is refused, not truncated" + ); +} + +#[test] +fn a_multiplier_of_zero_or_text_is_refused_and_another_probes_setting_is_unknown() { + for refused in ["0", "two", "-3"] { + assert!( + IterationMultiplier::from_args(args(&["--iteration-multiplier", refused]), 10).is_err(), + "{refused}" + ); + } + assert_eq!( + IterationMultiplier::from_args(args(&["--repetitions", "3"]), 10), + Err(ArgError::Unknown("--repetitions".to_owned())) + ); +} + +#[test] +#[should_panic(expected = "passes u32::MAX")] +fn scaling_a_loop_longer_than_the_one_the_multiplier_was_checked_against_is_the_probes_mistake() { + let multiplier = + IterationMultiplier::from_args(args(&["--iteration-multiplier", "3"]), 1).expect("valid"); + let _ = multiplier.scale(u32::MAX); +} + +#[test] +fn the_report_line_says_the_multiplier_and_whether_it_is_the_default() { + let default = IterationMultiplier::default().report_line(); + assert_eq!(default, "iteration multiplier: 1 (the default)"); + let set = IterationMultiplier::from_args(args(&["--iteration-multiplier=8"]), 10) + .expect("valid") + .report_line(); + assert!( + set.starts_with("iteration multiplier: 8 (NOT the default"), + "{set}" + ); + // The cost probes' table readers take a line that starts with a word and then + // a number for a row; this line must not look like one. + for line in [default, set] { + let second = line.split_whitespace().nth(1).expect("two words"); + assert!(second.parse::().is_err(), "{line}"); + } +} diff --git a/crates/windows-platform-probes/src/queue_contention.rs b/crates/windows-platform-probes/src/queue_contention.rs index 9fe291a3c..831e3b89d 100644 --- a/crates/windows-platform-probes/src/queue_contention.rs +++ b/crates/windows-platform-probes/src/queue_contention.rs @@ -86,6 +86,8 @@ use std::time::Instant; use windows_waitable_queues::{permit_mpsc, reserving_mpsc, slotwise_mpsc}; +use crate::probe_args::{self, ArgError, Flag}; + use windows_waitable_queues::reserving_mpsc::{Balanced, ClaimLayout, Enduring, Perpetual}; /// The 128-bit layout exists only where a 128-bit exchange is native. @@ -102,26 +104,214 @@ use windows_waitable_queues::reserving_mpsc::{Balanced, ClaimLayout, Enduring, P ))] use windows_waitable_queues::reserving_mpsc::Wide; +#[cfg(test)] +mod sampling_tests; #[cfg(test)] mod tests; -/// How many pushes each producer thread performs in one timed run. +mod measure; +mod raw_row; +mod render; + +pub use self::measure::*; +pub use self::raw_row::*; +pub use self::render::*; + +/// How many pushes each producer thread performs in one timed run, unless +/// [`Sampling`] says otherwise. pub const PUSHES_PER_PRODUCER: usize = 50_000; -/// How many times each configuration is repeated; the median is reported. +/// How many times each configuration is repeated, unless [`Sampling`] says +/// otherwise; the median is reported. /// /// Odd, so the median is an observed value rather than an average of two. Five /// because these probes run on a virtual machine, where a single run can be /// perturbed by something entirely outside the process. pub const REPETITIONS: usize = 5; -/// The producer counts measured, in order. +/// The producer counts measured, in order, unless [`Sampling`] says otherwise. /// /// Fixed rather than derived from the host's processor count, so two runs on /// different machines produce comparable rows. The host's own count is reported /// alongside, since the interesting region is around and beyond it. pub const PRODUCER_COUNTS: &[usize] = &[1, 2, 4, 8, 16, 32]; +/// How a run samples: how long each timed pass is, how many are taken, and at +/// which producer counts (`M4.2`). +/// +/// Settable so that the first step the design notes prescribe on a wide control +/// -- lengthen the span and raise the repetitions on the unchanged configuration +/// -- is a command-line argument and not a source edit. The +/// [default](Sampling::default) is the three constants above, so a run given no +/// settings takes the published counts. What a run used travels with its +/// [`Observation`] and is printed in its report. +/// +/// **The order passes are taken in is a setting too, since `M4.4`**, and its +/// default changed there: see [`Order`] for what the figures measured before it. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Sampling { + /// Pushes each producer performs in one timed pass: the length of the span. + pub pushes_per_producer: usize, + /// Timed passes per configuration, of which the median is reported. With an + /// even count the median taken is the upper of the two middle passes, so it + /// is still one that was observed. + pub repetitions: usize, + /// The producer counts measured, in the order given. Scaling is reported + /// against the one-producer row, so a list without 1 reports no scaling. + pub producer_counts: Vec, + /// The order the timed passes are taken in. + pub order: Order, +} + +impl Default for Sampling { + fn default() -> Self { + Self { + pushes_per_producer: PUSHES_PER_PRODUCER, + repetitions: REPETITIONS, + producer_counts: PRODUCER_COUNTS.to_vec(), + order: Order::Interleaved, + } + } +} + +impl Sampling { + /// The settings `probe-queue-contention` takes, which are this type's + /// fields. + pub const FLAGS: [Flag; 4] = [ + Flag { + name: "pushes-per-producer", + value: "N", + help: "pushes each producer performs in one timed pass; 50000 when not given", + }, + Flag { + name: "repetitions", + value: "N", + help: "timed passes per configuration, the median reported; 5 when not given", + }, + Flag { + name: "producers", + value: "N,N,...", + help: "producer counts to measure, in order; 1,2,4,8,16,32 when not given", + }, + Flag { + name: "order", + value: "interleaved|sequential", + help: "how passes are ordered; interleaved when not given, sequential being the \ + order of the figures published before M4.4", + }, + ]; + + /// The sampling a command line asks for: the defaults, with each setting it + /// gives in place of its default. + /// + /// # Errors + /// + /// What [`probe_args::parse`] refuses, a value that is not a whole number of + /// at least one or a producer list [`probe_args::positive_list`] refuses, and + /// a combination whose largest run would not fit the queue's capacity + /// arithmetic: the isolated regime sizes its queue to the next power of two + /// at or above producers times pushes, and a total with none is refused + /// rather than overflowing. + pub fn from_args(args: impl IntoIterator) -> Result { + let given = probe_args::parse(args, &Self::FLAGS)?; + let mut sampling = Self::default(); + if let Some(value) = given.get("pushes-per-producer") { + sampling.pushes_per_producer = probe_args::positive("pushes-per-producer", value)?; + } + if let Some(value) = given.get("repetitions") { + sampling.repetitions = probe_args::positive("repetitions", value)?; + } + if let Some(value) = given.get("producers") { + sampling.producer_counts = probe_args::positive_list("producers", value)?; + } + if let Some(value) = given.get("order") { + sampling.order = Order::from_name(value).ok_or_else(|| ArgError::Invalid { + flag: "order", + value: value.to_owned(), + reason: "is neither `interleaved` nor `sequential`".to_owned(), + })?; + } + let largest = sampling + .producer_counts + .iter() + .max() + .copied() + .expect("a parsed producer list is never empty"); + if largest + .checked_mul(sampling.pushes_per_producer) + .and_then(usize::checked_next_power_of_two) + .is_none() + { + return Err(ArgError::Invalid { + flag: "pushes-per-producer", + value: sampling.pushes_per_producer.to_string(), + reason: format!( + "{largest} producers times this many pushes is too large to size a queue for" + ), + }); + } + Ok(sampling) + } + + /// Whether this is the sampling a run given no settings uses. + #[must_use] + pub fn is_default(&self) -> bool { + *self == Self::default() + } +} + +/// The order a run takes its timed passes in (`M4.4`). +/// +/// **Interleaved by default, because the same-code control is two rows.** The +/// control is `reserving_mpsc` against `reserving(32/32)`: the same code measured +/// twice in one run. Taken back to back, each configuration's passes fill their +/// own stretch of the run, so the control's two halves were measured several +/// configurations apart, and drift across that interval -- frequency, thermal, +/// the scheduler -- landed inside the control and inside every comparison judged +/// against it. Interleaved, every configuration at a producer count takes one pass +/// per round, so each row's passes are spread across the same rounds. +/// +/// **Sequential is kept, not replaced.** It is the order every figure published +/// before `M4.4` was measured in, so it is what retakes them, and running both on +/// one host is the measurement that says whether interleaving narrowed the +/// control there. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Order { + /// One pass of every configuration per round, the first position moving on + /// by one each round so that no configuration is always measured first. + Interleaved, + /// Every pass of one configuration before the next configuration starts. + Sequential, +} + +impl Order { + /// The name the command line and the test corpus give this order. + #[must_use] + pub const fn name(self) -> &'static str { + match self { + Self::Interleaved => "interleaved", + Self::Sequential => "sequential", + } + } + + /// The order a [`name`](Self::name) names, if it names one. + #[must_use] + pub fn from_name(name: &str) -> Option { + [Self::Interleaved, Self::Sequential] + .into_iter() + .find(|order| order.name() == name) + } + + /// How the report's sampling line describes this order. + #[must_use] + pub const fn described(self) -> &'static str { + match self { + Self::Interleaved => "passes interleaved across configurations", + Self::Sequential => "each configuration's passes back to back", + } + } +} + /// The names a run is filed under. /// /// **Named once because a lookup by string literal is a rename waiting to @@ -183,20 +373,20 @@ pub struct Run { /// rather than by the claim, which is a fact about the measurement and not /// about the queue. pub refusals: u64, - /// Fastest of the [`REPETITIONS`] timed repetitions, in nanoseconds per - /// operation. + /// Fastest of the run's timed repetitions ([`Sampling::repetitions`]), in + /// nanoseconds per operation. /// /// Carried because [`d-observations-not-verdicts`] obliges every published /// figure to arrive with its run count *and its dispersion*: a median alone /// is an anecdote a reader cannot compare against their own hardware. The - /// four repetitions the median discards are the only evidence this probe has + /// repetitions the median discards are the only evidence this probe has /// about its own stability within a run, and discarding them silently was /// the crate publishing a figure its own contract forbids. /// /// [`d-observations-not-verdicts`]: ../DESIGN-NOTES.md#d-observations-not-verdicts pub fastest_nanos_per_op: f64, - /// Slowest of the [`REPETITIONS`] timed repetitions, in nanoseconds per - /// operation. See [`Run::fastest_nanos_per_op`]. + /// Slowest of the run's timed repetitions, in nanoseconds per operation. See + /// [`Run::fastest_nanos_per_op`]. pub slowest_nanos_per_op: f64, } @@ -300,6 +490,8 @@ pub struct Observation { /// which the report then printed as a zero-processor host -- a value no host /// has, presented with the same confidence as a measured one. pub available_parallelism: Option, + /// How the run sampled, so its report states what produced its figures. + pub sampling: Sampling, } impl Observation { @@ -408,388 +600,15 @@ impl Observation { } } -/// The interval the cost ratio `numerator / denominator` could occupy, given -/// each row's observed span. -/// -/// Both arguments are rows whose costs are nanoseconds per operation, and the -/// result is in those same terms: `0.20x` means the numerator cost a fifth of -/// what the denominator cost. A caller wanting a *rate* ratio -- "how much -/// faster" -- passes the two rows the other way round, which is what -/// [`Observation::scaling_bounds`] does. -/// -/// See [`Observation::scaling_bounds`] for why this is a bound rather than a -/// sample. -/// -/// **An earlier version of this function documented a cost ratio and returned -/// the inverse**, leaving both internal callers to compensate by swapping their -/// arguments. That works until somebody calls it directly, which is the defect a -/// review caught: a public contract that is only correct if you read the -/// implementation is not a contract. -/// -/// `None` when either span touches zero, which cannot happen for a real run and -/// is reported rather than divided by. -#[must_use] -pub fn ratio_bounds(numerator: Run, denominator: Run) -> Option<(f64, f64)> { - // **A `<= 0.0` test does not reject `NaN`.** IEEE comparison against NaN is - // false whichever way it is written, so every one of these four disjuncts is - // false for a NaN endpoint and the guard falls through to the division, - // which then renders `[NaN-NaN]`. Testing `is_finite()` first is what - // actually excludes it, and it excludes infinities in the same move. - let endpoints = [ - numerator.fastest_nanos_per_op, - numerator.slowest_nanos_per_op, - denominator.fastest_nanos_per_op, - denominator.slowest_nanos_per_op, - ]; - if endpoints - .iter() - .any(|endpoint| !endpoint.is_finite() || *endpoint <= 0.0) - { - return None; - } - // Widest is the numerator at its worst over the denominator at its best; - // narrowest is the reverse. - let low = numerator.fastest_nanos_per_op / denominator.slowest_nanos_per_op; - let high = numerator.slowest_nanos_per_op / denominator.fastest_nanos_per_op; - Some((low, high)) -} - -/// Renders a ratio together with the interval it could occupy, or `--`. -/// -/// The bound is printed in square brackets to mark it as *not* a sampled range: -/// the row ranges above it are observed spans, this is arithmetic over two of -/// them. -/// -/// **Both rows must have measured something.** A shape that failed to run -/// reports zero nanoseconds, and zero is the sentinel for "no measurement here" -/// on either side of the division -- a zero numerator would render `0.00x`, -/// which is a number a reader takes for a result rather than for the absence of -/// one. An earlier version guarded only the denominator, on the reasoning that -/// division is what breaks; publishing a plausible figure from a row that never -/// ran is the worse failure of the two. -#[must_use] -pub fn format_ratio_bounded(numerator: Option, denominator: Option) -> String { - match (numerator, denominator) { - (Some(numerator), Some(denominator)) - if numerator.is_measured() && denominator.is_measured() => - { - let point = numerator.nanos_per_op / denominator.nanos_per_op; - match ratio_bounds(numerator, denominator) { - Some((low, high)) => format!("{point:.2}x [{low:.2}-{high:.2}]"), - None => format!("{point:.2}x"), - } - } - _ => "--".to_owned(), - } -} - -/// The **minimum** width the report gives a bounded ratio column. -/// -/// Named here, beside the formatter, rather than written as a literal in the -/// report's format string. [`format_ratio_bounded`] emits a point estimate *and* -/// its interval -- `1.00x [1.00-1.00]` is 17 characters, not the 5 a bare -/// `1.00x` would take -- and a Rust width is a minimum rather than a maximum, so -/// a field narrower than the value does not truncate it, it pushes every later -/// column out of line with its header. The report had been allocating 10. -/// -/// **This is a floor, not a bound, because the formatter has no bound.** The -/// interval's endpoints come from measured spans, so a slow outlier -- an -/// ordinary event on a loaded or virtualized host, and the reason `median_run` -/// takes a median at all -- widens the cell without limit: a 300 ms repetition -/// against a 4 ns one renders `10.00x [6.67-1500.00]`, which is 21. Use -/// [`ratio_column_width`] to size the column against the values it must actually -/// hold; this constant only stops a table of narrow values from looking cramped. -pub const RATIO_COLUMN_WIDTH: usize = 20; - -/// The width a ratio column must take to keep its rows aligned with its header. -/// -/// The widest cell the column has to hold, or [`RATIO_COLUMN_WIDTH`] when that -/// is wider. Derived from the rendered cells rather than assumed, because -/// [`format_ratio_bounded`]'s output length is a function of measured data and -/// therefore has no compile-time bound -- see [`RATIO_COLUMN_WIDTH`] for the -/// case that overruns it. A caller must render every cell of the column before -/// emitting the header, which is the only ordering that can get this right. -#[must_use] -pub fn ratio_column_width<'a>(cells: impl IntoIterator) -> usize { - column_width(cells, RATIO_COLUMN_WIDTH) -} - -/// The width a column must take to keep its rows aligned with its header. -/// -/// The widest cell, or `floor` when that is wider. Every column in this report -/// holds rendered measurements, whose length is a function of the data rather -/// than a constant, so a fixed field silently shifts everything to its right the -/// first time a value outgrows it. `floor` only stops a table of narrow values -/// from looking cramped; it is never an upper bound. -#[must_use] -pub fn column_width<'a>(cells: impl IntoIterator, floor: usize) -> usize { - cells - .into_iter() - .map(str::len) - .max() - .map_or(floor, |widest| widest.max(floor)) -} - -/// Renders a scaling factor together with the interval it could occupy. -/// -/// See [`Observation::scaling_bounds`]: the bracketed interval is a bound over -/// two unpaired spans, not a distribution. -#[must_use] -pub fn format_scaling_bounded(point: Option, bounds: Option<(f64, f64)>) -> String { - match (point, bounds) { - // A scaling of zero is the sentinel, not a measurement: it means the - // many-producer row reported no throughput at all. Guarded here as well - // as against non-finite values, because zero is the half that renders - // plausibly -- `0.00x` looks like a contended queue, `infx` does not. - (Some(point), _) if !point.is_finite() || point <= 0.0 => "--".to_owned(), - (Some(point), Some((low, high))) if low.is_finite() && high.is_finite() => { - format!("{point:.2}x [{low:.2}-{high:.2}]") - } - (Some(point), _) => format!("{point:.2}x"), - (None, _) => "--".to_owned(), - } -} - -/// Renders one regime's rows as the report's table body. -/// -/// Here rather than in the binary so it can be tested without running the -/// measurement. It takes a sink rather than writing to stdout for the reason -/// [`crate::report`] records: a helper writing to stdout while its caller -/// composes a string emits its lines first, reordering the report without losing -/// any of it. -pub fn render_table(out: &mut dyn fmt::Write, runs: &[Run]) { - const HEADERS: [&str; 7] = [ - "shape", - "producers", - "ns/op", - "ops/sec", - "refusals", - "ns/op range", - "spread", - ]; - // The widths this table has always used, kept as floors so an ordinary - // report is unchanged. Every column is a measurement rendered at its natural - // width, and `spread` and `ns/op range` are quotients and pairs of measured - // endpoints with no upper bound -- a long pause, the very outlier - // `median_run` exists to tolerate, renders wider than any fixed field and - // pushes every column after it out of line. See `ratio_column_width`, which - // is the same argument for the ratio tables. - const FLOOR: [usize; 7] = [18, 10, 14, 16, 14, 18, 9]; - - let mut rows: Vec<[String; 7]> = Vec::with_capacity(runs.len()); - for run in runs { - // `shape` and `producers` are configuration and always mean something. - // Every other column is a measurement, so a row that did not run has - // nothing to put in any of them -- including `refusals`, whose zero - // would otherwise read as "nothing was refused" rather than "nothing - // was attempted". See `Run::is_measured`. - let (nanos, ops, refusals, range, spread) = if run.is_measured() { - ( - format!("{:.1}", run.nanos_per_op), - format!("{:.0}", run.ops_per_second), - run.refusals.to_string(), - format!( - "{:.1}-{:.1}", - run.fastest_nanos_per_op, run.slowest_nanos_per_op - ), - format_scaling(run.spread()), - ) - } else { - ( - "--".to_owned(), - "--".to_owned(), - "--".to_owned(), - "--".to_owned(), - "--".to_owned(), - ) - }; - rows.push([ - run.shape.to_owned(), - run.producers.to_string(), - nanos, - ops, - refusals, - range, - spread, - ]); - } - - let mut width = FLOOR; - for row in &rows { - for (column, cell) in row.iter().enumerate() { - width[column] = width[column].max(cell.len()); - } - } - - let _ = writeln!( - out, - "{:b$} {:>c$} {:>d$} {:>e$} {:>f$} {:>g$}", - HEADERS[0], - HEADERS[1], - HEADERS[2], - HEADERS[3], - HEADERS[4], - HEADERS[5], - HEADERS[6], - a = width[0], - b = width[1], - c = width[2], - d = width[3], - e = width[4], - f = width[5], - g = width[6], - ); - for row in &rows { - let _ = writeln!( - out, - "{:b$} {:>c$} {:>d$} {:>e$} {:>f$} {:>g$}", - row[0], - row[1], - row[2], - row[3], - row[4], - row[5], - row[6], - a = width[0], - b = width[1], - c = width[2], - d = width[3], - e = width[4], - f = width[5], - g = width[6], - ); - } -} - -/// A scaling factor, or `--` when it is missing or not a number. -/// -/// Guards non-finite values for the same reason [`format_ratio`] guards its -/// denominator, and the guard belongs here rather than in [`Observation::scaling`]: -/// a shape whose one-producer row reports zero makes the quotient infinite, and -/// `infx` in a column of measurements reads as a measurement. `scaling` is -/// deliberately allowed to return the non-finite value -- it is arithmetic, not -/// a renderer -- so the display layer is where it has to be caught. -#[must_use] -pub fn format_scaling(scaling: Option) -> String { - match scaling { - Some(value) if value.is_finite() && value > 0.0 => format!("{value:.2}x"), - _ => "--".to_owned(), - } -} - -/// `numerator / denominator` as a cost ratio, or `--` when either is missing. -/// -/// Guards both rows rather than trusting them: a shape that failed to run -/// reports zero, so a zero denominator would print `inf` or `NaN` and a zero -/// numerator would print `0.00x` -- and of those two the second is the more -/// dangerous, because it looks like a measurement rather than like a failure. -#[must_use] -pub fn format_ratio(numerator: Option, denominator: Option) -> String { - match (numerator, denominator) { - (Some(numerator), Some(denominator)) - if numerator.is_measured() && denominator.is_measured() => - { - format!("{:.2}x", numerator.nanos_per_op / denominator.nanos_per_op) - } - _ => "--".to_owned(), - } -} - -/// One row's nanoseconds per operation, or `--` when the row is missing or did -/// not run. -/// -/// Guards the sentinel for the reason [`Run::is_measured`] records: this column -/// is the report's most-read number, and `0.0` in it reads as a shape too fast -/// to time rather than as one that never ran. -#[must_use] -pub fn format_nanos(run: Option) -> String { - match run { - Some(run) if run.is_measured() => format!("{:.1}", run.nanos_per_op), - _ => "--".to_owned(), - } -} - -/// Time every configuration. -#[must_use] -pub fn measure() -> Observation { - let mut isolated = Vec::new(); - let mut drained = Vec::new(); - - for &producers in PRODUCER_COUNTS { - isolated.push(median_run(shapes::BASELINE_FETCH_ADD, producers, |count| { - time_contended_atomic(count) - })); - isolated.push(median_run(shapes::SLOTWISE_MPSC, producers, |count| { - time_isolated_mpsc(count) - })); - isolated.push(median_run(shapes::RESERVING_MPSC, producers, |count| { - time_isolated_reserving(count) - })); - isolated.push(median_run(shapes::PERMIT_MPSC, producers, |count| { - time_isolated_permit(count) - })); - - drained.push(median_run(shapes::SLOTWISE_MPSC, producers, |count| { - time_drained_mpsc(count) - })); - drained.push(median_run(shapes::RESERVING_MPSC, producers, |count| { - time_drained_reserving(count) - })); - drained.push(median_run(shapes::PERMIT_MPSC, producers, |count| { - time_drained_permit(count) - })); - - isolated.push(median_run(shapes::CLAIM_NARROW, producers, |count| { - time_isolated_layout::(count) - })); - isolated.push(median_run(shapes::CLAIM_DEEP, producers, |count| { - time_isolated_layout::(count) - })); - isolated.push(median_run(shapes::CLAIM_PERPETUAL, producers, |count| { - time_isolated_layout::(count) - })); - // Gated on the architectures where a 128-bit exchange is native; see the - // `Wide` import above. `#[cfg]` governs only the statement that follows - // it, so each of the two pushes carries its own. - #[cfg(any( - all(target_arch = "x86_64", target_feature = "cmpxchg16b"), - target_arch = "aarch64" - ))] - isolated.push(median_run(shapes::CLAIM_WIDE, producers, |count| { - time_isolated_layout::(count) - })); - - drained.push(median_run(shapes::CLAIM_NARROW, producers, |count| { - time_drained_layout::(count) - })); - drained.push(median_run(shapes::CLAIM_DEEP, producers, |count| { - time_drained_layout::(count) - })); - drained.push(median_run(shapes::CLAIM_PERPETUAL, producers, |count| { - time_drained_layout::(count) - })); - #[cfg(any( - all(target_arch = "x86_64", target_feature = "cmpxchg16b"), - target_arch = "aarch64" - ))] - drained.push(median_run(shapes::CLAIM_WIDE, producers, |count| { - time_drained_layout::(count) - })); - } - - Observation { - isolated, - drained, - available_parallelism: thread::available_parallelism() - .ok() - .map(std::num::NonZeroUsize::get), - } -} - /// Raw result of one timed repetition: elapsed nanoseconds and refusals. type Repetition = (f64, u64); -/// Run one configuration [`REPETITIONS`] times and keep the median. +/// Run one configuration `sampling.repetitions` times and keep the median. +/// +/// The timer is handed the producer count and `sampling.pushes_per_producer`, +/// and the per-operation figures divide by that same value, so the pushes a +/// pass performed and the pushes its time is divided by cannot come from two +/// places. /// /// The median rather than the mean, because on a virtual machine the failure /// mode is one run being hugely slower rather than a spread around a centre, @@ -797,8 +616,10 @@ type Repetition = (f64, u64); fn median_run( shape: &'static str, producers: usize, - mut timer: impl FnMut(usize) -> Repetition, + sampling: &Sampling, + mut timer: impl FnMut(usize, usize) -> Repetition, ) -> Run { + let pushes_per_producer = sampling.pushes_per_producer; // One untimed pass first. Be exact about what this does and does not warm: // for the queue timers, every call to `timer` builds and drops its OWN // queue, so this does not pre-touch the allocation any timed repetition will @@ -811,18 +632,75 @@ fn median_run( // which is not true of an allocation made fresh each pass, and a later one // said every timer builds a queue, which is not true of the baseline. // Both found by review. - let _ = timer(producers); + let _ = timer(producers, pushes_per_producer); + + let results: Vec = (0..sampling.repetitions) + .map(|_| timer(producers, pushes_per_producer)) + .collect(); + summarise(shape, producers, pushes_per_producer, results) +} + +/// A configuration's timer, boxed so a run's configurations can sit in one list. +type Timer<'a> = Box Repetition + 'a>; - let mut results: Vec = (0..REPETITIONS).map(|_| timer(producers)).collect(); +/// Time every configuration in `timers` at `producers`, in the order +/// `sampling.order` says, and return one [`Run`] per timer in the order given. +/// +/// [`Order::Sequential`] is [`median_run`] once per timer. [`Order::Interleaved`] +/// gives every timer its untimed pass first -- the same pass, for the reason +/// `median_run` records -- and then takes `sampling.repetitions` rounds of one +/// pass each, starting each round one position later than the last. So all the +/// timed passes of every configuration fall within the same rounds, and the +/// median of each is taken over passes spread across the same interval. +fn runs( + producers: usize, + sampling: &Sampling, + timers: &mut [(&'static str, Timer<'_>)], +) -> Vec { + let pushes_per_producer = sampling.pushes_per_producer; + if sampling.order == Order::Sequential { + return timers + .iter_mut() + .map(|(shape, timer)| median_run(shape, producers, sampling, &mut **timer)) + .collect(); + } + + for (_, timer) in timers.iter_mut() { + let _ = timer(producers, pushes_per_producer); + } + let count = timers.len(); + let mut results: Vec> = vec![Vec::with_capacity(sampling.repetitions); count]; + for round in 0..sampling.repetitions { + for position in 0..count { + let index = (round + position) % count; + results[index].push((timers[index].1)(producers, pushes_per_producer)); + } + } + timers + .iter() + .zip(results) + .map(|((shape, _), results)| summarise(shape, producers, pushes_per_producer, results)) + .collect() +} + +/// The [`Run`] a configuration's timed passes make: the median, with the extremes +/// beside it, each divided by the pushes every pass performed. +fn summarise( + shape: &'static str, + producers: usize, + pushes_per_producer: usize, + mut results: Vec, +) -> Run { + let repetitions = results.len(); results.sort_by(|left, right| left.0.total_cmp(&right.0)); - let (elapsed_nanos, refusals) = results[REPETITIONS / 2]; + let (elapsed_nanos, refusals) = results[repetitions / 2]; // The sort is ascending by elapsed time, so the extremes are the ends. They // are carried rather than discarded because a median without its dispersion // is what `d-observations-not-verdicts` forbids publishing. let (fastest_nanos, _) = results[0]; - let (slowest_nanos, _) = results[REPETITIONS - 1]; + let (slowest_nanos, _) = results[repetitions - 1]; - let pushes = (producers * PUSHES_PER_PRODUCER) as f64; + let pushes = (producers * pushes_per_producer) as f64; Run { shape, producers, @@ -834,49 +712,18 @@ fn median_run( } } -/// The floor: N threads incrementing one shared counter. -/// -/// Not a queue, and not trying to be. It is the cheapest possible operation on -/// a contended line, so it says how much of a queue's scaling curve is the -/// queue and how much is simply what this processor does when N cores fight -/// over one cache line. -fn time_contended_atomic(producers: usize) -> Repetition { - let counter = Arc::new(AtomicU64::new(0)); - // One party per worker plus this thread. Every worker is created, then waits - // here; the clock starts as the barrier releases, so neither thread creation - // nor a solo head start by an early worker is inside the measurement. See - // `start_barrier`'s note for why that matters at these producer counts. - let gate = start_gate(producers); - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for _ in 0..producers { - let counter = Arc::clone(&counter); - let gate = Arc::clone(&gate); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - let began = Instant::now(); - for _ in 0..PUSHES_PER_PRODUCER { - counter.fetch_add(1, Ordering::Relaxed); - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - (measured_span(&spans), 0) -} +/// The smallest capacity every timed queue accepts, which `M4.2`'s settable push count can fall below: one +/// producer making one push needs room for one item, and the slotwise queue refuses a capacity under two. +/// Larger than the run needs is harmless, since nothing is refused either way, so the minimum raises the +/// capacity and leaves the workload as it was asked for. +const MINIMUM_QUEUE_CAPACITY: usize = 2; -/// Capacity big enough that a whole run fits, so nothing is ever refused. -fn capacity_for(producers: usize) -> usize { - (producers * PUSHES_PER_PRODUCER).next_power_of_two() +/// Capacity big enough that a whole run fits, so nothing is ever refused, and no smaller than a timed queue +/// accepts. +fn capacity_for(producers: usize, pushes_per_producer: usize) -> usize { + (producers * pushes_per_producer) + .next_power_of_two() + .max(MINIMUM_QUEUE_CAPACITY) } /// A gate holding every participant until all of them exist, or until the @@ -1045,123 +892,6 @@ fn measured_span(spans: &[(Instant, Instant)]) -> f64 { ended.duration_since(began).as_nanos() as f64 } -fn time_isolated_mpsc(producers: usize) -> Repetition { - let (tx, rx) = - slotwise_mpsc::bounded::(capacity_for(producers)).expect("a valid capacity"); - let gate = start_gate(producers); - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - tx.push((producer * PUSHES_PER_PRODUCER + index) as u64) - .expect("the run fits in the capacity"); - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - let refusals = tx.refused(); - // Drain before dropping: teardown would otherwise walk every slot, and that - // is not part of what is being timed. - while rx.pop().is_ok() {} - (elapsed, refusals) -} - -fn time_isolated_reserving(producers: usize) -> Repetition { - let (tx, rx) = - reserving_mpsc::bounded::(capacity_for(producers)).expect("a valid capacity"); - let gate = start_gate(producers); - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - tx.push((producer * PUSHES_PER_PRODUCER + index) as u64) - .expect("the run fits in the capacity"); - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - let refusals = tx.refused(); - while rx.pop().is_ok() {} - (elapsed, refusals) -} - -/// The experimental permit claim, with no consumer and no possibility of refusal. -/// -/// Not "the regime that isolates the claim", which an earlier wording said: this -/// times the whole push path, including slot metadata, the item write, -/// publication and the doorbell's fence. See the module header. -/// -/// A line-for-line twin of [`time_isolated_reserving`] with one shape -/// substituted. Deliberately not factored into a generic over the two, which -/// would need a trait both implement and would put a dynamic or monomorphised -/// indirection inside the timed region -- in a measurement whose whole output is -/// a difference of a few nanoseconds per push. -fn time_isolated_permit(producers: usize) -> Repetition { - let (tx, rx) = permit_mpsc::bounded::(capacity_for(producers)).expect("a valid capacity"); - let gate = start_gate(producers); - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - tx.push((producer * PUSHES_PER_PRODUCER + index) as u64) - .expect("the run fits in the capacity"); - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - let refusals = tx.refused(); - while rx.pop().is_ok() {} - (elapsed, refusals) -} - /// A capacity a real system would choose, so the drained regime exercises /// backpressure the way a real one would. pub const DRAINED_CAPACITY: usize = 1024; @@ -1243,444 +973,3 @@ fn drain_then_announce(pop_once: impl FnOnce(), ready: &AtomicBool) { pop_once(); ready.store(true, Ordering::Release); } - -fn time_drained_mpsc(producers: usize) -> Repetition { - let (tx, rx) = slotwise_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); - let done = Arc::new(AtomicBool::new(false)); - let consumer_done = Arc::clone(&done); - // Set on every exit path, not just the one that returns. See StopOnDrop. - let stop = StopOnDrop(done); - // Closes the undrained opening the gate alone leaves. See await_consumer. - let ready = Arc::new(AtomicBool::new(false)); - let consumer_ready = Arc::clone(&ready); - // The consumer is a gate participant, not merely spawned: spawning is not - // readiness, and a consumer still in thread start-up while producers push - // turns the opening of the run into an undrained regime. - // - // The gate alone does not finish the job, which is why `await_consumer` - // exists. Arriving proves the consumer exists, is scheduled and is past - // start-up; it does not prove the consumer has reached its first `pop`, and - // the gate releases every party together. The handshake narrows that - // remainder to a stated guarantee: no producer begins timing until the - // consumer has executed its pop path at least once. Continuous draining is - // not guaranteed and cannot be by a flag. This is the M4.3 change, and it - // MOVED the drained numbers -- the figures taken before it are kept beside - // the ones taken after rather than replaced, because they measure two - // different pieces of code. - let gate = start_gate(producers + 1); - let consumer_gate = Arc::clone(&gate); - - let consumer = thread::spawn(move || { - if !consumer_gate.arrive_and_wait() { - return rx.refused(); - } - // **One drain attempt BEFORE announcing, not merely reaching the loop.** - // Publishing first proves only that the consumer is about to drain: it - // can be descheduled between the store and its first `pop`, which is the - // same undrained opening in a narrower form. Popping first makes the - // announcement mean `this consumer has executed the pop path`, which is a - // fact rather than an intention. The queue is empty here, so it costs one - // failed pop, and it happens before any producer has started timing. - drain_then_announce( - || { - let _ = rx.pop(); - }, - &consumer_ready, - ); - // Spin rather than park: the doorbell's cost is `doorbell_cost`'s - // question, and parking here would measure that instead of the claim. - while !consumer_done.load(Ordering::Relaxed) { - while rx.pop().is_ok() {} - std::hint::spin_loop(); - } - while rx.pop().is_ok() {} - rx.refused() - }); - - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - let ready = Arc::clone(&ready); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - await_consumer(&ready); - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - let mut item = (producer * PUSHES_PER_PRODUCER + index) as u64; - // Retry a FULL queue, which is what a real producer does; - // the refusal count is what makes that visible. Anything - // else is not retryable -- a disconnected queue never - // drains -- and retrying it is an infinite spin that - // presents as a hung probe rather than as the consumer - // failure it actually is. The queue crate says so itself: - // "retrying the first is sensible and retrying the second - // is a spin". - while let Err(error) = tx.push(item) { - assert!( - error.is_retryable(), - "the consumer is gone, so this push can never \ - succeed: {error}" - ); - item = error.into_inner(); - std::hint::spin_loop(); - } - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - - drop(stop); - drop(tx); - let refusals = consumer.join().expect("the consumer must not panic"); - (elapsed, refusals) -} - -fn time_drained_reserving(producers: usize) -> Repetition { - // **Defaults on both sides, and that is a correction.** This row previously - // enabled high-water tracking here and nowhere else, to "also price the - // switch M31.4 made opt-in". But the number it feeds is presented as the - // cost of *reservation*, and tracking adds work to this shape's push path - // alone, so the ratio measured reservation plus a handicap with no way for a - // reader to separate them. - // - // **What the handicap actually is, corrected:** an earlier version of this - // comment said tracking adds a load of the consumer's position. It does not. - // `reserving_mpsc::publish` loads `head` **unconditionally** -- the slot - // write needs that acquire edge whether or not anything is measured, as the - // comment at that load says in as many words. What the switch adds is the - // depth arithmetic and the metric update on the far side of a branch that is - // taken either way. Smaller than claimed, and still not part of what this row - // is presented as measuring. - // - // Nothing consumes the high-water figure here either, so the tracking was - // paying a cost to produce a number nobody read. Pricing that switch is a - // worthwhile measurement and needs its own row, with both shapes tracking, - // rather than being folded into this comparison. - let (tx, rx) = reserving_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); - let done = Arc::new(AtomicBool::new(false)); - let consumer_done = Arc::clone(&done); - // Set on every exit path, not just the one that returns. See StopOnDrop. - let stop = StopOnDrop(done); - // Closes the undrained opening the gate alone leaves. See await_consumer. - let ready = Arc::new(AtomicBool::new(false)); - let consumer_ready = Arc::clone(&ready); - // The consumer joins the gate here for the reason it does in the slotwise - // twin: a run whose opening is undrained is not the regime being measured. - let gate = start_gate(producers + 1); - let consumer_gate = Arc::clone(&gate); - - let consumer = thread::spawn(move || { - if !consumer_gate.arrive_and_wait() { - return rx.refused(); - } - // **One drain attempt BEFORE announcing, not merely reaching the loop.** - // Publishing first proves only that the consumer is about to drain: it - // can be descheduled between the store and its first `pop`, which is the - // same undrained opening in a narrower form. Popping first makes the - // announcement mean `this consumer has executed the pop path`, which is a - // fact rather than an intention. The queue is empty here, so it costs one - // failed pop, and it happens before any producer has started timing. - drain_then_announce( - || { - let _ = rx.pop(); - }, - &consumer_ready, - ); - while !consumer_done.load(Ordering::Relaxed) { - while rx.pop().is_ok() {} - std::hint::spin_loop(); - } - while rx.pop().is_ok() {} - rx.refused() - }); - - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - let ready = Arc::clone(&ready); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - await_consumer(&ready); - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - let mut item = (producer * PUSHES_PER_PRODUCER + index) as u64; - while let Err(error) = tx.push(item) { - // Only a FULL queue is retryable; see the note on the - // first of these loops. - assert!( - error.is_retryable(), - "the consumer is gone, so this push can never \ - succeed: {error}" - ); - item = error.into_inner(); - std::hint::spin_loop(); - } - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - - drop(stop); - drop(tx); - let refusals = consumer.join().expect("the consumer must not panic"); - (elapsed, refusals) -} - -/// The experimental permit claim, against a consumer looping on `pop`. -/// -/// The regime that can price the claim honestly, for the same reason the -/// reserving twin needs it: the shared line a producer touches is only -/// expensive when a consumer is writing it. Measured in isolation, an -/// uncontended line looks free -- which would be a confident wrong answer, and -/// this shape has more riding on that answer than the others, because it trades -/// `reserving_mpsc`'s *load* of the consumer's position for a read-modify-write -/// on a count the consumer also writes. -fn time_drained_permit(producers: usize) -> Repetition { - let (tx, rx) = permit_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); - let done = Arc::new(AtomicBool::new(false)); - let consumer_done = Arc::clone(&done); - // Set on every exit path, not just the one that returns. See StopOnDrop. - let stop = StopOnDrop(done); - // Closes the undrained opening the gate alone leaves. See await_consumer. - let ready = Arc::new(AtomicBool::new(false)); - let consumer_ready = Arc::clone(&ready); - let gate = start_gate(producers + 1); - let consumer_gate = Arc::clone(&gate); - - let consumer = thread::spawn(move || { - if !consumer_gate.arrive_and_wait() { - return rx.refused(); - } - // **One drain attempt BEFORE announcing, not merely reaching the loop.** - // Publishing first proves only that the consumer is about to drain: it - // can be descheduled between the store and its first `pop`, which is the - // same undrained opening in a narrower form. Popping first makes the - // announcement mean `this consumer has executed the pop path`, which is a - // fact rather than an intention. The queue is empty here, so it costs one - // failed pop, and it happens before any producer has started timing. - drain_then_announce( - || { - let _ = rx.pop(); - }, - &consumer_ready, - ); - while !consumer_done.load(Ordering::Relaxed) { - while rx.pop().is_ok() {} - std::hint::spin_loop(); - } - while rx.pop().is_ok() {} - rx.refused() - }); - - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - let ready = Arc::clone(&ready); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - await_consumer(&ready); - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - let mut item = (producer * PUSHES_PER_PRODUCER + index) as u64; - while let Err(error) = tx.push(item) { - // Only a FULL queue is retryable; see the note on the - // first of these loops. - assert!( - error.is_retryable(), - "the consumer is gone, so this push can never \ - succeed: {error}" - ); - item = error.into_inner(); - std::hint::spin_loop(); - } - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - - drop(stop); - drop(tx); - let refusals = consumer.join().expect("the consumer must not panic"); - (elapsed, refusals) -} - -/// One claim-word layout, with no consumer and no possibility of refusal. -/// -/// As with the other isolated timers, this is the whole push path and not the -/// claim word alone; only the layout differs between these rows, so a difference -/// is still attributable to the layout, but its magnitude is a share of total -/// push cost rather than of the exchange. -/// -/// **Generic over the layout, where [`time_isolated_permit`] is deliberately -/// duplicated, and the difference is the point.** That twin compares two -/// *different types*, which a generic could only unify behind a trait, putting -/// an indirection that might not inline identically inside the timed region. -/// These are the *same type* at different layout parameters, so this -/// monomorphises to exactly the code a hand-written copy would produce -- there -/// is nothing left to dispatch. -/// -/// Measures `reserving_mpsc` itself rather than a stand-in. An earlier form of -/// this probe carried its own duplicated implementation of the claim protocol, -/// built so the layouts could be compared before the shipping crate had them; -/// it drifted from the original twice while doing so. The shipping type takes -/// the layout as a parameter now, so the duplicate is gone. -fn time_isolated_layout(producers: usize) -> Repetition { - let (tx, rx) = - reserving_mpsc::bounded_as::(capacity_for(producers)).expect("a valid capacity"); - let gate = start_gate(producers); - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - tx.push((producer * PUSHES_PER_PRODUCER + index) as u64) - .expect("the run fits in the capacity"); - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - let refusals = tx.refused(); - while rx.pop().is_ok() {} - (elapsed, refusals) -} - -/// One claim-word layout, against a consumer looping on `pop`. -/// -/// Generic for [`time_isolated_layout`]'s reason. -fn time_drained_layout(producers: usize) -> Repetition { - let (tx, rx) = - reserving_mpsc::bounded_as::(DRAINED_CAPACITY).expect("a valid capacity"); - let done = Arc::new(AtomicBool::new(false)); - let consumer_done = Arc::clone(&done); - // Set on every exit path, not just the one that returns. See StopOnDrop. - let stop = StopOnDrop(done); - // Closes the undrained opening the gate alone leaves. See await_consumer. - let ready = Arc::new(AtomicBool::new(false)); - let consumer_ready = Arc::clone(&ready); - // The consumer joins the gate for the reason its twins do: a run whose - // opening is undrained is not the regime being measured. - let gate = start_gate(producers + 1); - let consumer_gate = Arc::clone(&gate); - - let consumer = thread::spawn(move || { - if !consumer_gate.arrive_and_wait() { - return rx.refused(); - } - // **One drain attempt BEFORE announcing, not merely reaching the loop.** - // Publishing first proves only that the consumer is about to drain: it - // can be descheduled between the store and its first `pop`, which is the - // same undrained opening in a narrower form. Popping first makes the - // announcement mean `this consumer has executed the pop path`, which is a - // fact rather than an intention. The queue is empty here, so it costs one - // failed pop, and it happens before any producer has started timing. - drain_then_announce( - || { - let _ = rx.pop(); - }, - &consumer_ready, - ); - while !consumer_done.load(Ordering::Relaxed) { - while rx.pop().is_ok() {} - std::hint::spin_loop(); - } - while rx.pop().is_ok() {} - rx.refused() - }); - - let spans = thread::scope(|scope| { - let _release = ReleaseOnDrop(Arc::clone(&gate)); - let mut workers = Vec::with_capacity(producers); - for producer in 0..producers { - let tx = tx.clone(); - let gate = Arc::clone(&gate); - let ready = Arc::clone(&ready); - workers.push(scope.spawn(move || { - if !gate.arrive_and_wait() { - // Abandoned before the party completed; see `StartGate`. - return (Instant::now(), Instant::now()); - } - await_consumer(&ready); - let began = Instant::now(); - for index in 0..PUSHES_PER_PRODUCER { - let mut item = (producer * PUSHES_PER_PRODUCER + index) as u64; - while let Err(error) = tx.push(item) { - // Only a FULL queue is retryable; see the note on the - // first of these loops. - assert!( - error.is_retryable(), - "the consumer is gone, so this push can never \ - succeed: {error}" - ); - item = error.into_inner(); - std::hint::spin_loop(); - } - } - (began, Instant::now()) - })); - } - let _ = gate.arrive_and_wait(); - workers - .into_iter() - .map(|worker| worker.join().expect("a producer must not panic")) - .collect::>() - }); - let elapsed = measured_span(&spans); - drop(stop); - drop(tx); - let refusals = consumer.join().expect("the consumer must not panic"); - (elapsed, refusals) -} diff --git a/crates/windows-platform-probes/src/queue_contention/measure.rs b/crates/windows-platform-probes/src/queue_contention/measure.rs new file mode 100644 index 000000000..7e777c285 --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/measure.rs @@ -0,0 +1,757 @@ +// Copyright (c) Mike Grier. +// Split from queue_contention.rs at bf9786ef. + +//! The measurement: `measure` and the timer for each shape and regime it runs. + +use super::*; + +#[cfg(test)] +mod tests; + +/// Time every configuration, sampled as `sampling` says. +/// +/// [`Sampling::default`] takes the published counts, interleaved; with +/// [`Order::Sequential`] it is the procedure the figures published before `M4.4` +/// came from. +#[must_use] +pub fn measure(sampling: &Sampling) -> Observation { + let mut isolated = Vec::new(); + let mut drained = Vec::new(); + + for &producers in &sampling.producer_counts { + let (regimes, mut timers): (Vec, Vec<(&'static str, Timer<'static>)>) = + configurations() + .into_iter() + .map(|(regime, shape, timer)| (regime, (shape, timer))) + .unzip(); + for (regime, run) in regimes + .into_iter() + .zip(runs(producers, sampling, &mut timers)) + { + match regime { + Regime::Isolated => isolated.push(run), + Regime::Drained => drained.push(run), + } + } + } + + Observation { + isolated, + drained, + available_parallelism: thread::available_parallelism() + .ok() + .map(std::num::NonZeroUsize::get), + sampling: sampling.clone(), + } +} + +/// Which table a configuration's row belongs in. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Regime { + Isolated, + Drained, +} + +/// Boxes a timer, so the configurations can be listed together. +fn timer(timer: fn(usize, usize) -> Repetition) -> Timer<'static> { + Box::new(timer) +} + +/// Every configuration timed at one producer count. +/// +/// **In the order the measurement took before `M4.4`, and that order is load +/// bearing**: [`Order::Sequential`] walks this list, so it is what lets the +/// published figures be retaken. The two regimes are interleaved in it -- the +/// comparison shapes in both, then the layouts in both -- because that is how +/// they were measured, and in each regime the same-code control's two halves, +/// `reserving_mpsc` and `reserving(32/32)`, sit several entries apart. +fn configurations() -> Vec<(Regime, &'static str, Timer<'static>)> { + let mut list = vec![ + ( + Regime::Isolated, + shapes::BASELINE_FETCH_ADD, + timer(time_contended_atomic), + ), + ( + Regime::Isolated, + shapes::SLOTWISE_MPSC, + timer(time_isolated_mpsc), + ), + ( + Regime::Isolated, + shapes::RESERVING_MPSC, + timer(time_isolated_reserving), + ), + ( + Regime::Isolated, + shapes::PERMIT_MPSC, + timer(time_isolated_permit), + ), + ( + Regime::Drained, + shapes::SLOTWISE_MPSC, + timer(time_drained_mpsc), + ), + ( + Regime::Drained, + shapes::RESERVING_MPSC, + timer(time_drained_reserving), + ), + ( + Regime::Drained, + shapes::PERMIT_MPSC, + timer(time_drained_permit), + ), + ( + Regime::Isolated, + shapes::CLAIM_NARROW, + timer(time_isolated_layout::), + ), + ( + Regime::Isolated, + shapes::CLAIM_DEEP, + timer(time_isolated_layout::), + ), + ( + Regime::Isolated, + shapes::CLAIM_PERPETUAL, + timer(time_isolated_layout::), + ), + ]; + // Gated on the architectures where a 128-bit exchange is native; see the + // `Wide` import in the facade. `#[cfg]` governs only the statement that + // follows it, so each of the two pushes carries its own. + #[cfg(any( + all(target_arch = "x86_64", target_feature = "cmpxchg16b"), + target_arch = "aarch64" + ))] + list.push(( + Regime::Isolated, + shapes::CLAIM_WIDE, + timer(time_isolated_layout::), + )); + list.push(( + Regime::Drained, + shapes::CLAIM_NARROW, + timer(time_drained_layout::), + )); + list.push(( + Regime::Drained, + shapes::CLAIM_DEEP, + timer(time_drained_layout::), + )); + list.push(( + Regime::Drained, + shapes::CLAIM_PERPETUAL, + timer(time_drained_layout::), + )); + #[cfg(any( + all(target_arch = "x86_64", target_feature = "cmpxchg16b"), + target_arch = "aarch64" + ))] + list.push(( + Regime::Drained, + shapes::CLAIM_WIDE, + timer(time_drained_layout::), + )); + list +} + +/// The floor: N threads incrementing one shared counter. +/// +/// Not a queue, and not trying to be. It is the cheapest possible operation on +/// a contended line, so it says how much of a queue's scaling curve is the +/// queue and how much is simply what this processor does when N cores fight +/// over one cache line. +fn time_contended_atomic(producers: usize, pushes: usize) -> Repetition { + let counter = Arc::new(AtomicU64::new(0)); + // One party per worker plus this thread. Every worker is created, then waits + // here; the clock starts as the barrier releases, so neither thread creation + // nor a solo head start by an early worker is inside the measurement. See + // `start_barrier`'s note for why that matters at these producer counts. + let gate = start_gate(producers); + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for _ in 0..producers { + let counter = Arc::clone(&counter); + let gate = Arc::clone(&gate); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + let began = Instant::now(); + for _ in 0..pushes { + counter.fetch_add(1, Ordering::Relaxed); + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + (measured_span(&spans), 0) +} + +fn time_isolated_mpsc(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = + slotwise_mpsc::bounded::(capacity_for(producers, pushes)).expect("a valid capacity"); + let gate = start_gate(producers); + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + let began = Instant::now(); + for index in 0..pushes { + tx.push((producer * pushes + index) as u64) + .expect("the run fits in the capacity"); + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + let refusals = tx.refused(); + // Drain before dropping: teardown would otherwise walk every slot, and that + // is not part of what is being timed. + while rx.pop().is_ok() {} + (elapsed, refusals) +} + +fn time_isolated_reserving(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = + reserving_mpsc::bounded::(capacity_for(producers, pushes)).expect("a valid capacity"); + let gate = start_gate(producers); + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + let began = Instant::now(); + for index in 0..pushes { + tx.push((producer * pushes + index) as u64) + .expect("the run fits in the capacity"); + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + let refusals = tx.refused(); + while rx.pop().is_ok() {} + (elapsed, refusals) +} + +/// The experimental permit claim, with no consumer and no possibility of refusal. +/// +/// Not "the regime that isolates the claim", which an earlier wording said: this +/// times the whole push path, including slot metadata, the item write, +/// publication and the doorbell's fence. See the module header. +/// +/// A line-for-line twin of [`time_isolated_reserving`] with one shape +/// substituted. Deliberately not factored into a generic over the two, which +/// would need a trait both implement and would put a dynamic or monomorphised +/// indirection inside the timed region -- in a measurement whose whole output is +/// a difference of a few nanoseconds per push. +fn time_isolated_permit(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = + permit_mpsc::bounded::(capacity_for(producers, pushes)).expect("a valid capacity"); + let gate = start_gate(producers); + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + let began = Instant::now(); + for index in 0..pushes { + tx.push((producer * pushes + index) as u64) + .expect("the run fits in the capacity"); + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + let refusals = tx.refused(); + while rx.pop().is_ok() {} + (elapsed, refusals) +} + +fn time_drained_mpsc(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = slotwise_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); + let done = Arc::new(AtomicBool::new(false)); + let consumer_done = Arc::clone(&done); + // Set on every exit path, not just the one that returns. See StopOnDrop. + let stop = StopOnDrop(done); + // Closes the undrained opening the gate alone leaves. See await_consumer. + let ready = Arc::new(AtomicBool::new(false)); + let consumer_ready = Arc::clone(&ready); + // The consumer is a gate participant, not merely spawned: spawning is not + // readiness, and a consumer still in thread start-up while producers push + // turns the opening of the run into an undrained regime. + // + // The gate alone does not finish the job, which is why `await_consumer` + // exists. Arriving proves the consumer exists, is scheduled and is past + // start-up; it does not prove the consumer has reached its first `pop`, and + // the gate releases every party together. The handshake narrows that + // remainder to a stated guarantee: no producer begins timing until the + // consumer has executed its pop path at least once. Continuous draining is + // not guaranteed and cannot be by a flag. This is the M4.3 change, and it + // MOVED the drained numbers -- the figures taken before it are kept beside + // the ones taken after rather than replaced, because they measure two + // different pieces of code. + let gate = start_gate(producers + 1); + let consumer_gate = Arc::clone(&gate); + + let consumer = thread::spawn(move || { + if !consumer_gate.arrive_and_wait() { + return rx.refused(); + } + // **One drain attempt BEFORE announcing, not merely reaching the loop.** + // Publishing first proves only that the consumer is about to drain: it + // can be descheduled between the store and its first `pop`, which is the + // same undrained opening in a narrower form. Popping first makes the + // announcement mean `this consumer has executed the pop path`, which is a + // fact rather than an intention. The queue is empty here, so it costs one + // failed pop, and it happens before any producer has started timing. + drain_then_announce( + || { + let _ = rx.pop(); + }, + &consumer_ready, + ); + // Spin rather than park: the doorbell's cost is `doorbell_cost`'s + // question, and parking here would measure that instead of the claim. + while !consumer_done.load(Ordering::Relaxed) { + while rx.pop().is_ok() {} + std::hint::spin_loop(); + } + while rx.pop().is_ok() {} + rx.refused() + }); + + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + let ready = Arc::clone(&ready); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + await_consumer(&ready); + let began = Instant::now(); + for index in 0..pushes { + let mut item = (producer * pushes + index) as u64; + // Retry a FULL queue, which is what a real producer does; + // the refusal count is what makes that visible. Anything + // else is not retryable -- a disconnected queue never + // drains -- and retrying it is an infinite spin that + // presents as a hung probe rather than as the consumer + // failure it actually is. The queue crate says so itself: + // "retrying the first is sensible and retrying the second + // is a spin". + while let Err(error) = tx.push(item) { + assert!( + error.is_retryable(), + "the consumer is gone, so this push can never \ + succeed: {error}" + ); + item = error.into_inner(); + std::hint::spin_loop(); + } + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + + drop(stop); + drop(tx); + let refusals = consumer.join().expect("the consumer must not panic"); + (elapsed, refusals) +} + +fn time_drained_reserving(producers: usize, pushes: usize) -> Repetition { + // **Defaults on both sides, and that is a correction.** This row previously + // enabled high-water tracking here and nowhere else, to "also price the + // switch M31.4 made opt-in". But the number it feeds is presented as the + // cost of *reservation*, and tracking adds work to this shape's push path + // alone, so the ratio measured reservation plus a handicap with no way for a + // reader to separate them. + // + // **What the handicap actually is, corrected:** an earlier version of this + // comment said tracking adds a load of the consumer's position. It does not. + // `reserving_mpsc::publish` loads `head` **unconditionally** -- the slot + // write needs that acquire edge whether or not anything is measured, as the + // comment at that load says in as many words. What the switch adds is the + // depth arithmetic and the metric update on the far side of a branch that is + // taken either way. Smaller than claimed, and still not part of what this row + // is presented as measuring. + // + // Nothing consumes the high-water figure here either, so the tracking was + // paying a cost to produce a number nobody read. Pricing that switch is a + // worthwhile measurement and needs its own row, with both shapes tracking, + // rather than being folded into this comparison. + let (tx, rx) = reserving_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); + let done = Arc::new(AtomicBool::new(false)); + let consumer_done = Arc::clone(&done); + // Set on every exit path, not just the one that returns. See StopOnDrop. + let stop = StopOnDrop(done); + // Closes the undrained opening the gate alone leaves. See await_consumer. + let ready = Arc::new(AtomicBool::new(false)); + let consumer_ready = Arc::clone(&ready); + // The consumer joins the gate here for the reason it does in the slotwise + // twin: a run whose opening is undrained is not the regime being measured. + let gate = start_gate(producers + 1); + let consumer_gate = Arc::clone(&gate); + + let consumer = thread::spawn(move || { + if !consumer_gate.arrive_and_wait() { + return rx.refused(); + } + // **One drain attempt BEFORE announcing, not merely reaching the loop.** + // Publishing first proves only that the consumer is about to drain: it + // can be descheduled between the store and its first `pop`, which is the + // same undrained opening in a narrower form. Popping first makes the + // announcement mean `this consumer has executed the pop path`, which is a + // fact rather than an intention. The queue is empty here, so it costs one + // failed pop, and it happens before any producer has started timing. + drain_then_announce( + || { + let _ = rx.pop(); + }, + &consumer_ready, + ); + while !consumer_done.load(Ordering::Relaxed) { + while rx.pop().is_ok() {} + std::hint::spin_loop(); + } + while rx.pop().is_ok() {} + rx.refused() + }); + + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + let ready = Arc::clone(&ready); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + await_consumer(&ready); + let began = Instant::now(); + for index in 0..pushes { + let mut item = (producer * pushes + index) as u64; + while let Err(error) = tx.push(item) { + // Only a FULL queue is retryable; see the note on the + // first of these loops. + assert!( + error.is_retryable(), + "the consumer is gone, so this push can never \ + succeed: {error}" + ); + item = error.into_inner(); + std::hint::spin_loop(); + } + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + + drop(stop); + drop(tx); + let refusals = consumer.join().expect("the consumer must not panic"); + (elapsed, refusals) +} + +/// The experimental permit claim, against a consumer looping on `pop`. +/// +/// The regime that can price the claim honestly, for the same reason the +/// reserving twin needs it: the shared line a producer touches is only +/// expensive when a consumer is writing it. Measured in isolation, an +/// uncontended line looks free -- which would be a confident wrong answer, and +/// this shape has more riding on that answer than the others, because it trades +/// `reserving_mpsc`'s *load* of the consumer's position for a read-modify-write +/// on a count the consumer also writes. +fn time_drained_permit(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = permit_mpsc::bounded::(DRAINED_CAPACITY).expect("a valid capacity"); + let done = Arc::new(AtomicBool::new(false)); + let consumer_done = Arc::clone(&done); + // Set on every exit path, not just the one that returns. See StopOnDrop. + let stop = StopOnDrop(done); + // Closes the undrained opening the gate alone leaves. See await_consumer. + let ready = Arc::new(AtomicBool::new(false)); + let consumer_ready = Arc::clone(&ready); + let gate = start_gate(producers + 1); + let consumer_gate = Arc::clone(&gate); + + let consumer = thread::spawn(move || { + if !consumer_gate.arrive_and_wait() { + return rx.refused(); + } + // **One drain attempt BEFORE announcing, not merely reaching the loop.** + // Publishing first proves only that the consumer is about to drain: it + // can be descheduled between the store and its first `pop`, which is the + // same undrained opening in a narrower form. Popping first makes the + // announcement mean `this consumer has executed the pop path`, which is a + // fact rather than an intention. The queue is empty here, so it costs one + // failed pop, and it happens before any producer has started timing. + drain_then_announce( + || { + let _ = rx.pop(); + }, + &consumer_ready, + ); + while !consumer_done.load(Ordering::Relaxed) { + while rx.pop().is_ok() {} + std::hint::spin_loop(); + } + while rx.pop().is_ok() {} + rx.refused() + }); + + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + let ready = Arc::clone(&ready); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + await_consumer(&ready); + let began = Instant::now(); + for index in 0..pushes { + let mut item = (producer * pushes + index) as u64; + while let Err(error) = tx.push(item) { + // Only a FULL queue is retryable; see the note on the + // first of these loops. + assert!( + error.is_retryable(), + "the consumer is gone, so this push can never \ + succeed: {error}" + ); + item = error.into_inner(); + std::hint::spin_loop(); + } + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + + drop(stop); + drop(tx); + let refusals = consumer.join().expect("the consumer must not panic"); + (elapsed, refusals) +} + +/// One claim-word layout, with no consumer and no possibility of refusal. +/// +/// As with the other isolated timers, this is the whole push path and not the +/// claim word alone; only the layout differs between these rows, so a difference +/// is still attributable to the layout, but its magnitude is a share of total +/// push cost rather than of the exchange. +/// +/// **Generic over the layout, where [`time_isolated_permit`] is deliberately +/// duplicated, and the difference is the point.** That twin compares two +/// *different types*, which a generic could only unify behind a trait, putting +/// an indirection that might not inline identically inside the timed region. +/// These are the *same type* at different layout parameters, so this +/// monomorphises to exactly the code a hand-written copy would produce -- there +/// is nothing left to dispatch. +/// +/// Measures `reserving_mpsc` itself rather than a stand-in. An earlier form of +/// this probe carried its own duplicated implementation of the claim protocol, +/// built so the layouts could be compared before the shipping crate had them; +/// it drifted from the original twice while doing so. The shipping type takes +/// the layout as a parameter now, so the duplicate is gone. +fn time_isolated_layout(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = reserving_mpsc::bounded_as::(capacity_for(producers, pushes)) + .expect("a valid capacity"); + let gate = start_gate(producers); + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + let began = Instant::now(); + for index in 0..pushes { + tx.push((producer * pushes + index) as u64) + .expect("the run fits in the capacity"); + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + let refusals = tx.refused(); + while rx.pop().is_ok() {} + (elapsed, refusals) +} + +/// One claim-word layout, against a consumer looping on `pop`. +/// +/// Generic for [`time_isolated_layout`]'s reason. +fn time_drained_layout(producers: usize, pushes: usize) -> Repetition { + let (tx, rx) = + reserving_mpsc::bounded_as::(DRAINED_CAPACITY).expect("a valid capacity"); + let done = Arc::new(AtomicBool::new(false)); + let consumer_done = Arc::clone(&done); + // Set on every exit path, not just the one that returns. See StopOnDrop. + let stop = StopOnDrop(done); + // Closes the undrained opening the gate alone leaves. See await_consumer. + let ready = Arc::new(AtomicBool::new(false)); + let consumer_ready = Arc::clone(&ready); + // The consumer joins the gate for the reason its twins do: a run whose + // opening is undrained is not the regime being measured. + let gate = start_gate(producers + 1); + let consumer_gate = Arc::clone(&gate); + + let consumer = thread::spawn(move || { + if !consumer_gate.arrive_and_wait() { + return rx.refused(); + } + // **One drain attempt BEFORE announcing, not merely reaching the loop.** + // Publishing first proves only that the consumer is about to drain: it + // can be descheduled between the store and its first `pop`, which is the + // same undrained opening in a narrower form. Popping first makes the + // announcement mean `this consumer has executed the pop path`, which is a + // fact rather than an intention. The queue is empty here, so it costs one + // failed pop, and it happens before any producer has started timing. + drain_then_announce( + || { + let _ = rx.pop(); + }, + &consumer_ready, + ); + while !consumer_done.load(Ordering::Relaxed) { + while rx.pop().is_ok() {} + std::hint::spin_loop(); + } + while rx.pop().is_ok() {} + rx.refused() + }); + + let spans = thread::scope(|scope| { + let _release = ReleaseOnDrop(Arc::clone(&gate)); + let mut workers = Vec::with_capacity(producers); + for producer in 0..producers { + let tx = tx.clone(); + let gate = Arc::clone(&gate); + let ready = Arc::clone(&ready); + workers.push(scope.spawn(move || { + if !gate.arrive_and_wait() { + // Abandoned before the party completed; see `StartGate`. + return (Instant::now(), Instant::now()); + } + await_consumer(&ready); + let began = Instant::now(); + for index in 0..pushes { + let mut item = (producer * pushes + index) as u64; + while let Err(error) = tx.push(item) { + // Only a FULL queue is retryable; see the note on the + // first of these loops. + assert!( + error.is_retryable(), + "the consumer is gone, so this push can never \ + succeed: {error}" + ); + item = error.into_inner(); + std::hint::spin_loop(); + } + } + (began, Instant::now()) + })); + } + let _ = gate.arrive_and_wait(); + workers + .into_iter() + .map(|worker| worker.join().expect("a producer must not panic")) + .collect::>() + }); + let elapsed = measured_span(&spans); + drop(stop); + drop(tx); + let refusals = consumer.join().expect("the consumer must not panic"); + (elapsed, refusals) +} diff --git a/crates/windows-platform-probes/src/queue_contention/measure/tests.rs b/crates/windows-platform-probes/src/queue_contention/measure/tests.rs new file mode 100644 index 000000000..c5effdfc2 --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/measure/tests.rs @@ -0,0 +1,46 @@ +// Copyright (c) Mike Grier. +//! Tests for the list of configurations `measure` times. + +use super::*; + +#[test] +fn the_sequential_order_is_the_order_the_published_figures_were_taken_in() { + // **Written out rather than derived, because it is a record of a procedure + // that has already run.** Every figure published before `M4.4` was measured + // in this order, and `--order sequential` is how they are retaken -- which is + // true only while `configurations()` lists them in it. Nothing else depends + // on the order, so nothing else would notice it changing. + let mut published = vec![ + (Regime::Isolated, shapes::BASELINE_FETCH_ADD), + (Regime::Isolated, shapes::SLOTWISE_MPSC), + (Regime::Isolated, shapes::RESERVING_MPSC), + (Regime::Isolated, shapes::PERMIT_MPSC), + (Regime::Drained, shapes::SLOTWISE_MPSC), + (Regime::Drained, shapes::RESERVING_MPSC), + (Regime::Drained, shapes::PERMIT_MPSC), + (Regime::Isolated, shapes::CLAIM_NARROW), + (Regime::Isolated, shapes::CLAIM_DEEP), + (Regime::Isolated, shapes::CLAIM_PERPETUAL), + ]; + let wide = cfg!(any( + all(target_arch = "x86_64", target_feature = "cmpxchg16b"), + target_arch = "aarch64" + )); + if wide { + published.push((Regime::Isolated, shapes::CLAIM_WIDE)); + } + published.extend([ + (Regime::Drained, shapes::CLAIM_NARROW), + (Regime::Drained, shapes::CLAIM_DEEP), + (Regime::Drained, shapes::CLAIM_PERPETUAL), + ]); + if wide { + published.push((Regime::Drained, shapes::CLAIM_WIDE)); + } + + let listed: Vec<(Regime, &'static str)> = configurations() + .into_iter() + .map(|(regime, shape, _)| (regime, shape)) + .collect(); + assert_eq!(listed, published); +} diff --git a/crates/windows-platform-probes/src/queue_contention/raw_row.rs b/crates/windows-platform-probes/src/queue_contention/raw_row.rs new file mode 100644 index 000000000..cc4494d5f --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/raw_row.rs @@ -0,0 +1,74 @@ +// Copyright (c) Mike Grier. + +//! The queue probe's machine-readable row (`M8.2`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// The row a report carries: the raw data `observation` holds, and the build +/// that produced it. +/// +/// **Raw data**, per the decision that every probe's normal run emits one +/// (`d-every-probe-emits-a-row` in DESIGN-NOTES.md): what each configuration's +/// timed passes took, in whole nanoseconds -- the median pass, the fastest and +/// the slowest -- with the median pass's refusals, the sampling and the build. +/// The median is the upper of the two middle passes when the count is even, so +/// it is always a pass that happened rather than an average of two. Not the +/// per-operation figures, ratios or scaling the prose derives from them; a +/// consumer that wants those divides by `producers * pushes_per_producer`. +/// +/// **The elapsed times are recovered from [`Run`]'s per-operation figures**, +/// each of which is an elapsed time divided by the run's total pushes. A timed +/// pass is a whole number of nanoseconds far below 2^53, so multiplying back and +/// rounding returns it exactly, which a test checks. A configuration that did +/// not measure ([`Run::is_measured`]) publishes `null` for all three. +/// +/// A refused command line exits before anything is measured and prints no +/// report, so there is no refusal for this row to carry. +#[must_use] +pub fn raw_row(observation: &Observation, build: &str) -> Row { + let pushes_per_producer = observation.sampling.pushes_per_producer; + let runs = |runs: &[Run]| -> Value { + runs.iter() + .map(|run| { + let total = run.producers * pushes_per_producer; + let elapsed = |per_op: f64| -> Value { + if run.is_measured() { + Value::from((per_op * total as f64).round() as usize) + } else { + Value::Null + } + }; + Value::Object(vec![ + ("shape", Value::from(run.shape)), + ("producers", Value::from(run.producers)), + ("median_elapsed_ns", elapsed(run.nanos_per_op)), + ("fastest_elapsed_ns", elapsed(run.fastest_nanos_per_op)), + ("slowest_elapsed_ns", elapsed(run.slowest_nanos_per_op)), + ("refusals", Value::from(run.refusals)), + ]) + }) + .collect() + }; + Row::new("x-probe-queue-contention") + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("pushes_per_producer", pushes_per_producer) + .with("repetitions", observation.sampling.repetitions) + .with("order", observation.sampling.order.name()) + .with( + "producer_counts", + observation + .sampling + .producer_counts + .iter() + .copied() + .collect::(), + ) + .with("available_parallelism", observation.available_parallelism) + .with("isolated", runs(&observation.isolated)) + .with("drained", runs(&observation.drained)) +} diff --git a/crates/windows-platform-probes/src/queue_contention/raw_row/tests.rs b/crates/windows-platform-probes/src/queue_contention/raw_row/tests.rs new file mode 100644 index 000000000..a3d00970d --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/raw_row/tests.rs @@ -0,0 +1,178 @@ +// Copyright (c) Mike Grier. +//! Tests for the queue probe's row. + +use super::*; +use crate::queue_contention::summarise; + +/// A configuration whose passes all took `elapsed` nanoseconds. +fn run_taking(shape: &'static str, producers: usize, pushes: usize, elapsed: f64) -> Run { + summarise(shape, producers, pushes, vec![(elapsed, 3)]) +} + +fn observation(isolated: Vec, drained: Vec, pushes: usize) -> Observation { + Observation { + isolated, + drained, + available_parallelism: Some(16), + sampling: Sampling { + pushes_per_producer: pushes, + ..Sampling::default() + }, + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_sampling_and_the_build() { + let row = parsed(&raw_row( + &observation(Vec::new(), Vec::new(), 50_000), + "v1.2.3 0123456789ab [ci]", + )); + assert_eq!(row["reason"], "x-probe-queue-contention"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1.2.3 0123456789ab [ci]"); + assert_eq!(row["pushes_per_producer"], 50_000); + assert_eq!(row["repetitions"], REPETITIONS); + assert_eq!(row["order"], "interleaved"); + assert_eq!(row["producer_counts"], serde_json::json!(PRODUCER_COUNTS)); + assert_eq!(row["available_parallelism"], 16); + assert_eq!(row["isolated"], serde_json::json!([])); + assert_eq!(row["drained"], serde_json::json!([])); +} + +#[test] +fn an_unknown_processor_count_is_null_rather_than_absent() { + let mut unknown = observation(Vec::new(), Vec::new(), 1); + unknown.available_parallelism = None; + let row = parsed(&raw_row(&unknown, "b")); + assert!(row["available_parallelism"].is_null()); + assert!(row.get("available_parallelism").is_some()); +} + +#[test] +fn every_elapsed_time_is_recovered_exactly_from_the_per_operation_figures() { + // The claim the row's elapsed fields rest on: dividing by the total pushes + // and multiplying back loses nothing that rounding does not restore. Across + // small and large times, push counts that do and do not divide them, and + // producer counts from one to thirty-two. + for elapsed in [ + 1.0, + 7.0, + 999_999_937.0, + 65_000_000_123.0, + (1u64 << 50) as f64 + 3.0, + ] { + for (producers, pushes) in [(1, 1), (1, 1_000), (3, 49_999), (32, 50_000), (16, 7)] { + let row = parsed(&raw_row( + &observation( + vec![run_taking( + shapes::SLOTWISE_MPSC, + producers, + pushes, + elapsed, + )], + Vec::new(), + pushes, + ), + "b", + )); + let entry = &row["isolated"][0]; + for field in [ + "median_elapsed_ns", + "fastest_elapsed_ns", + "slowest_elapsed_ns", + ] { + assert_eq!( + entry[field].as_u64(), + Some(elapsed as u64), + "{field}: {elapsed} ns over {producers} x {pushes} pushes" + ); + } + } + } +} + +#[test] +fn each_regime_lists_its_runs_in_order_with_their_refusals() { + let isolated = vec![ + run_taking(shapes::BASELINE_FETCH_ADD, 1, 10, 100.0), + run_taking(shapes::RESERVING_MPSC, 2, 10, 300.0), + ]; + let drained = vec![run_taking(shapes::PERMIT_MPSC, 4, 10, 900.0)]; + let row = parsed(&raw_row(&observation(isolated, drained, 10), "b")); + + let shapes_in = |key: &str| -> Vec<(String, u64)> { + row[key] + .as_array() + .expect("a list") + .iter() + .map(|entry| { + ( + entry["shape"].as_str().expect("a shape").to_owned(), + entry["producers"].as_u64().expect("a count"), + ) + }) + .collect() + }; + assert_eq!( + shapes_in("isolated"), + vec![ + (shapes::BASELINE_FETCH_ADD.to_owned(), 1), + (shapes::RESERVING_MPSC.to_owned(), 2) + ] + ); + assert_eq!( + shapes_in("drained"), + vec![(shapes::PERMIT_MPSC.to_owned(), 4)] + ); + assert_eq!(row["drained"][0]["refusals"], 3); + assert_eq!(row["drained"][0]["median_elapsed_ns"], 900); +} + +#[test] +fn a_configuration_that_did_not_measure_publishes_null_not_zero() { + // Zero would read as an immeasurably fast pass; null says there was none. + let unmeasured = Run { + shape: shapes::CLAIM_WIDE, + producers: 2, + nanos_per_op: 0.0, + ops_per_second: 0.0, + refusals: 0, + fastest_nanos_per_op: 0.0, + slowest_nanos_per_op: 0.0, + }; + let row = parsed(&raw_row( + &observation(vec![unmeasured], Vec::new(), 10), + "b", + )); + for field in [ + "median_elapsed_ns", + "fastest_elapsed_ns", + "slowest_elapsed_ns", + ] { + assert!(row["isolated"][0][field].is_null(), "{field}"); + } + assert_eq!(row["isolated"][0]["shape"], shapes::CLAIM_WIDE); +} + +#[test] +fn an_even_number_of_passes_publishes_three_distinct_passes_that_happened() { + // Four passes, deliberately out of order, each with its own refusal count. + // The median is the upper middle pass (300 ns, 7 refusals), never the 250 + // an average would give; the extremes are the first and last after sorting. + let run = summarise( + shapes::PERMIT_MPSC, + 2, + 10, + vec![(400.0, 9), (100.0, 1), (300.0, 7), (200.0, 5)], + ); + let row = parsed(&raw_row(&observation(Vec::new(), vec![run], 10), "b")); + let entry = &row["drained"][0]; + assert_eq!(entry["median_elapsed_ns"], 300); + assert_eq!(entry["fastest_elapsed_ns"], 100); + assert_eq!(entry["slowest_elapsed_ns"], 400); + assert_eq!(entry["refusals"], 7); +} diff --git a/crates/windows-platform-probes/src/queue_contention/render.rs b/crates/windows-platform-probes/src/queue_contention/render.rs new file mode 100644 index 000000000..e52ccc0bb --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/render.rs @@ -0,0 +1,306 @@ +// Copyright (c) Mike Grier. +// Split from queue_contention.rs at bf9786ef. + +//! How a run is turned into text: the ratio bounds and the table and cell formatters the report uses. + +use super::*; +/// The interval the cost ratio `numerator / denominator` could occupy, given +/// each row's observed span. +/// +/// Both arguments are rows whose costs are nanoseconds per operation, and the +/// result is in those same terms: `0.20x` means the numerator cost a fifth of +/// what the denominator cost. A caller wanting a *rate* ratio -- "how much +/// faster" -- passes the two rows the other way round, which is what +/// [`Observation::scaling_bounds`] does. +/// +/// See [`Observation::scaling_bounds`] for why this is a bound rather than a +/// sample. +/// +/// **An earlier version of this function documented a cost ratio and returned +/// the inverse**, leaving both internal callers to compensate by swapping their +/// arguments. That works until somebody calls it directly, which is the defect a +/// review caught: a public contract that is only correct if you read the +/// implementation is not a contract. +/// +/// `None` when either span touches zero, which cannot happen for a real run and +/// is reported rather than divided by. +#[must_use] +pub fn ratio_bounds(numerator: Run, denominator: Run) -> Option<(f64, f64)> { + // **A `<= 0.0` test does not reject `NaN`.** IEEE comparison against NaN is + // false whichever way it is written, so every one of these four disjuncts is + // false for a NaN endpoint and the guard falls through to the division, + // which then renders `[NaN-NaN]`. Testing `is_finite()` first is what + // actually excludes it, and it excludes infinities in the same move. + let endpoints = [ + numerator.fastest_nanos_per_op, + numerator.slowest_nanos_per_op, + denominator.fastest_nanos_per_op, + denominator.slowest_nanos_per_op, + ]; + if endpoints + .iter() + .any(|endpoint| !endpoint.is_finite() || *endpoint <= 0.0) + { + return None; + } + // Widest is the numerator at its worst over the denominator at its best; + // narrowest is the reverse. + let low = numerator.fastest_nanos_per_op / denominator.slowest_nanos_per_op; + let high = numerator.slowest_nanos_per_op / denominator.fastest_nanos_per_op; + Some((low, high)) +} + +/// Renders a ratio together with the interval it could occupy, or `--`. +/// +/// The bound is printed in square brackets to mark it as *not* a sampled range: +/// the row ranges above it are observed spans, this is arithmetic over two of +/// them. +/// +/// **Both rows must have measured something.** A shape that failed to run +/// reports zero nanoseconds, and zero is the sentinel for "no measurement here" +/// on either side of the division -- a zero numerator would render `0.00x`, +/// which is a number a reader takes for a result rather than for the absence of +/// one. An earlier version guarded only the denominator, on the reasoning that +/// division is what breaks; publishing a plausible figure from a row that never +/// ran is the worse failure of the two. +#[must_use] +pub fn format_ratio_bounded(numerator: Option, denominator: Option) -> String { + match (numerator, denominator) { + (Some(numerator), Some(denominator)) + if numerator.is_measured() && denominator.is_measured() => + { + let point = numerator.nanos_per_op / denominator.nanos_per_op; + match ratio_bounds(numerator, denominator) { + Some((low, high)) => format!("{point:.2}x [{low:.2}-{high:.2}]"), + None => format!("{point:.2}x"), + } + } + _ => "--".to_owned(), + } +} + +/// The **minimum** width the report gives a bounded ratio column. +/// +/// Named here, beside the formatter, rather than written as a literal in the +/// report's format string. [`format_ratio_bounded`] emits a point estimate *and* +/// its interval -- `1.00x [1.00-1.00]` is 17 characters, not the 5 a bare +/// `1.00x` would take -- and a Rust width is a minimum rather than a maximum, so +/// a field narrower than the value does not truncate it, it pushes every later +/// column out of line with its header. The report had been allocating 10. +/// +/// **This is a floor, not a bound, because the formatter has no bound.** The +/// interval's endpoints come from measured spans, so a slow outlier -- an +/// ordinary event on a loaded or virtualized host, and the reason `median_run` +/// takes a median at all -- widens the cell without limit: a 300 ms repetition +/// against a 4 ns one renders `10.00x [6.67-1500.00]`, which is 21. Use +/// [`ratio_column_width`] to size the column against the values it must actually +/// hold; this constant only stops a table of narrow values from looking cramped. +pub const RATIO_COLUMN_WIDTH: usize = 20; + +/// The width a ratio column must take to keep its rows aligned with its header. +/// +/// The widest cell the column has to hold, or [`RATIO_COLUMN_WIDTH`] when that +/// is wider. Derived from the rendered cells rather than assumed, because +/// [`format_ratio_bounded`]'s output length is a function of measured data and +/// therefore has no compile-time bound -- see [`RATIO_COLUMN_WIDTH`] for the +/// case that overruns it. A caller must render every cell of the column before +/// emitting the header, which is the only ordering that can get this right. +#[must_use] +pub fn ratio_column_width<'a>(cells: impl IntoIterator) -> usize { + column_width(cells, RATIO_COLUMN_WIDTH) +} + +/// The width a column must take to keep its rows aligned with its header. +/// +/// The widest cell, or `floor` when that is wider. Every column in this report +/// holds rendered measurements, whose length is a function of the data rather +/// than a constant, so a fixed field silently shifts everything to its right the +/// first time a value outgrows it. `floor` only stops a table of narrow values +/// from looking cramped; it is never an upper bound. +#[must_use] +pub fn column_width<'a>(cells: impl IntoIterator, floor: usize) -> usize { + cells + .into_iter() + .map(str::len) + .max() + .map_or(floor, |widest| widest.max(floor)) +} + +/// Renders a scaling factor together with the interval it could occupy. +/// +/// See [`Observation::scaling_bounds`]: the bracketed interval is a bound over +/// two unpaired spans, not a distribution. +#[must_use] +pub fn format_scaling_bounded(point: Option, bounds: Option<(f64, f64)>) -> String { + match (point, bounds) { + // A scaling of zero is the sentinel, not a measurement: it means the + // many-producer row reported no throughput at all. Guarded here as well + // as against non-finite values, because zero is the half that renders + // plausibly -- `0.00x` looks like a contended queue, `infx` does not. + (Some(point), _) if !point.is_finite() || point <= 0.0 => "--".to_owned(), + (Some(point), Some((low, high))) if low.is_finite() && high.is_finite() => { + format!("{point:.2}x [{low:.2}-{high:.2}]") + } + (Some(point), _) => format!("{point:.2}x"), + (None, _) => "--".to_owned(), + } +} + +/// Renders one regime's rows as the report's table body. +/// +/// Here rather than in the binary so it can be tested without running the +/// measurement. It takes a sink rather than writing to stdout for the reason +/// [`crate::report`] records: a helper writing to stdout while its caller +/// composes a string emits its lines first, reordering the report without losing +/// any of it. +pub fn render_table(out: &mut dyn fmt::Write, runs: &[Run]) { + const HEADERS: [&str; 7] = [ + "shape", + "producers", + "ns/op", + "ops/sec", + "refusals", + "ns/op range", + "spread", + ]; + // The widths this table has always used, kept as floors so an ordinary + // report is unchanged. Every column is a measurement rendered at its natural + // width, and `spread` and `ns/op range` are quotients and pairs of measured + // endpoints with no upper bound -- a long pause, the very outlier + // `median_run` exists to tolerate, renders wider than any fixed field and + // pushes every column after it out of line. See `ratio_column_width`, which + // is the same argument for the ratio tables. + const FLOOR: [usize; 7] = [18, 10, 14, 16, 14, 18, 9]; + + let mut rows: Vec<[String; 7]> = Vec::with_capacity(runs.len()); + for run in runs { + // `shape` and `producers` are configuration and always mean something. + // Every other column is a measurement, so a row that did not run has + // nothing to put in any of them -- including `refusals`, whose zero + // would otherwise read as "nothing was refused" rather than "nothing + // was attempted". See `Run::is_measured`. + let (nanos, ops, refusals, range, spread) = if run.is_measured() { + ( + format!("{:.1}", run.nanos_per_op), + format!("{:.0}", run.ops_per_second), + run.refusals.to_string(), + format!( + "{:.1}-{:.1}", + run.fastest_nanos_per_op, run.slowest_nanos_per_op + ), + format_scaling(run.spread()), + ) + } else { + ( + "--".to_owned(), + "--".to_owned(), + "--".to_owned(), + "--".to_owned(), + "--".to_owned(), + ) + }; + rows.push([ + run.shape.to_owned(), + run.producers.to_string(), + nanos, + ops, + refusals, + range, + spread, + ]); + } + + let mut width = FLOOR; + for row in &rows { + for (column, cell) in row.iter().enumerate() { + width[column] = width[column].max(cell.len()); + } + } + + let _ = writeln!( + out, + "{:b$} {:>c$} {:>d$} {:>e$} {:>f$} {:>g$}", + HEADERS[0], + HEADERS[1], + HEADERS[2], + HEADERS[3], + HEADERS[4], + HEADERS[5], + HEADERS[6], + a = width[0], + b = width[1], + c = width[2], + d = width[3], + e = width[4], + f = width[5], + g = width[6], + ); + for row in &rows { + let _ = writeln!( + out, + "{:b$} {:>c$} {:>d$} {:>e$} {:>f$} {:>g$}", + row[0], + row[1], + row[2], + row[3], + row[4], + row[5], + row[6], + a = width[0], + b = width[1], + c = width[2], + d = width[3], + e = width[4], + f = width[5], + g = width[6], + ); + } +} + +/// A scaling factor, or `--` when it is missing or not a number. +/// +/// Guards non-finite values for the same reason [`format_ratio`] guards its +/// denominator, and the guard belongs here rather than in [`Observation::scaling`]: +/// a shape whose one-producer row reports zero makes the quotient infinite, and +/// `infx` in a column of measurements reads as a measurement. `scaling` is +/// deliberately allowed to return the non-finite value -- it is arithmetic, not +/// a renderer -- so the display layer is where it has to be caught. +#[must_use] +pub fn format_scaling(scaling: Option) -> String { + match scaling { + Some(value) if value.is_finite() && value > 0.0 => format!("{value:.2}x"), + _ => "--".to_owned(), + } +} + +/// `numerator / denominator` as a cost ratio, or `--` when either is missing. +/// +/// Guards both rows rather than trusting them: a shape that failed to run +/// reports zero, so a zero denominator would print `inf` or `NaN` and a zero +/// numerator would print `0.00x` -- and of those two the second is the more +/// dangerous, because it looks like a measurement rather than like a failure. +#[must_use] +pub fn format_ratio(numerator: Option, denominator: Option) -> String { + match (numerator, denominator) { + (Some(numerator), Some(denominator)) + if numerator.is_measured() && denominator.is_measured() => + { + format!("{:.2}x", numerator.nanos_per_op / denominator.nanos_per_op) + } + _ => "--".to_owned(), + } +} + +/// One row's nanoseconds per operation, or `--` when the row is missing or did +/// not run. +/// +/// Guards the sentinel for the reason [`Run::is_measured`] records: this column +/// is the report's most-read number, and `0.0` in it reads as a shape too fast +/// to time rather than as one that never ran. +#[must_use] +pub fn format_nanos(run: Option) -> String { + match run { + Some(run) if run.is_measured() => format!("{:.1}", run.nanos_per_op), + _ => "--".to_owned(), + } +} diff --git a/crates/windows-platform-probes/src/queue_contention/sampling_tests.rs b/crates/windows-platform-probes/src/queue_contention/sampling_tests.rs new file mode 100644 index 000000000..93bd4d224 --- /dev/null +++ b/crates/windows-platform-probes/src/queue_contention/sampling_tests.rs @@ -0,0 +1,361 @@ +// Copyright (c) Mike Grier. +//! Tests for the queue probe's settings (`M4.2`, and `M4.4`'s order): what a command line asks +//! for, what is refused, and that the median, the per-operation figures and the order of the +//! passes follow the sampling a run was given. + +use super::*; + +fn args(list: &[&str]) -> Vec { + list.iter().map(|arg| (*arg).to_owned()).collect() +} + +/// A timer that hands back `values` in order, the first to the untimed warmup pass. +fn scripted(values: Vec) -> impl FnMut(usize, usize) -> Repetition { + let mut values = values.into_iter(); + move |_producers, _pushes| values.next().expect("a scripted pass for every call") +} + +#[test] +fn no_settings_take_the_published_counts_interleaved() { + let sampling = Sampling::from_args(args(&[])).expect("nothing to refuse"); + assert_eq!(sampling, Sampling::default()); + assert!(sampling.is_default()); + assert_eq!(sampling.pushes_per_producer, PUSHES_PER_PRODUCER); + assert_eq!(sampling.repetitions, REPETITIONS); + assert_eq!(sampling.producer_counts, PRODUCER_COUNTS); + assert_eq!(sampling.order, Order::Interleaved); +} + +#[test] +fn the_order_is_set_by_name_and_anything_else_is_refused() { + for order in [Order::Interleaved, Order::Sequential] { + let sampling = Sampling::from_args(args(&["--order", order.name()])).expect("valid"); + assert_eq!(sampling.order, order); + assert_eq!(sampling.is_default(), order == Order::Interleaved); + assert_eq!(Order::from_name(order.name()), Some(order)); + } + for refused in ["random", "Interleaved", ""] { + let error = Sampling::from_args(args(&["--order", refused])).expect_err("refused"); + assert!( + matches!(&error, ArgError::Invalid { flag: "order", .. }), + "{refused:?}: {error:?}" + ); + } +} + +#[test] +fn each_setting_replaces_its_default_and_leaves_the_others() { + let sampling = Sampling::from_args(args(&["--repetitions", "9"])).expect("valid"); + assert_eq!(sampling.repetitions, 9); + assert_eq!(sampling.pushes_per_producer, PUSHES_PER_PRODUCER); + assert_eq!(sampling.producer_counts, PRODUCER_COUNTS); + assert!(!sampling.is_default()); + + let sampling = Sampling::from_args(args(&[ + "--pushes-per-producer=200000", + "--producers", + "4,1", + ])) + .expect("valid"); + assert_eq!(sampling.pushes_per_producer, 200_000); + assert_eq!(sampling.repetitions, REPETITIONS); + assert_eq!( + sampling.producer_counts, + vec![4, 1], + "the order given is kept" + ); +} + +#[test] +fn a_setting_given_its_default_value_is_still_the_default_run() { + let sampling = Sampling::from_args(args(&["--repetitions", "5"])).expect("valid"); + assert!(sampling.is_default()); +} + +#[test] +fn a_setting_the_probe_cannot_use_is_refused_with_its_flag() { + for (arguments, flag) in [ + (&["--repetitions", "0"][..], "repetitions"), + (&["--pushes-per-producer", "x"][..], "pushes-per-producer"), + (&["--producers", "1,1"][..], "producers"), + (&["--producers", ""][..], "producers"), + ] { + let error = Sampling::from_args(args(arguments)).expect_err("refused"); + assert!( + matches!(&error, ArgError::Invalid { flag: refused, .. } if *refused == flag), + "{arguments:?}: {error:?}" + ); + } + assert_eq!( + Sampling::from_args(args(&["--iteration-multiplier", "2"])), + Err(ArgError::Unknown("--iteration-multiplier".to_owned())), + "the cost probes' setting is not this probe's" + ); +} + +#[test] +fn a_run_too_large_to_size_a_queue_for_is_refused_before_it_starts() { + let pushes = (usize::MAX / 2).to_string(); + let error = Sampling::from_args(args(&[ + "--pushes-per-producer", + &pushes, + "--producers", + "1,4", + ])) + .expect_err("too large"); + assert!( + error + .to_string() + .contains("4 producers times this many pushes"), + "{error}" + ); + + // The largest producer count decides, not the first: the same order of pushes + // with one producer sizes a queue. + let fits = (usize::MAX / 4).to_string(); + assert!( + Sampling::from_args(args(&["--pushes-per-producer", &fits, "--producers", "1"])).is_ok() + ); +} + +#[test] +fn the_median_follows_the_repetition_count_it_was_given() { + // A warmup pass and then three timed passes of 3, 1 and 2 ms: the median is the + // middle one and the extremes are the ends -- of three, not the five the + // default takes. + let three = Sampling { + repetitions: 3, + ..Sampling::default() + }; + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &three, + scripted(vec![(0.0, 0), (3e6, 0), (1e6, 0), (2e6, 0)]), + ); + let pushes = PUSHES_PER_PRODUCER as f64; + assert_eq!(measured.nanos_per_op, 2e6 / pushes); + assert_eq!(measured.fastest_nanos_per_op, 1e6 / pushes); + assert_eq!(measured.slowest_nanos_per_op, 3e6 / pushes); +} + +#[test] +fn with_an_even_count_the_median_is_the_upper_of_the_two_middle_passes() { + let four = Sampling { + repetitions: 4, + ..Sampling::default() + }; + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &four, + scripted(vec![(0.0, 0), (4e6, 0), (1e6, 0), (3e6, 0), (2e6, 0)]), + ); + assert_eq!(measured.nanos_per_op, 3e6 / PUSHES_PER_PRODUCER as f64); +} + +#[test] +fn every_pass_is_asked_for_the_producers_and_the_pushes_the_run_was_given() { + let sampling = Sampling { + pushes_per_producer: 777, + repetitions: 3, + ..Sampling::default() + }; + let mut asked = Vec::new(); + let _ = median_run(shapes::SLOTWISE_MPSC, 4, &sampling, |producers, pushes| { + asked.push((producers, pushes)); + (1e6, 0) + }); + assert_eq!( + asked, + vec![(4, 777); 4], + "a warmup pass and three timed ones" + ); +} + +#[test] +fn the_per_operation_figures_divide_by_the_pushes_the_run_used() { + let short = Sampling { + pushes_per_producer: 1_000, + repetitions: 1, + ..Sampling::default() + }; + let measured = median_run( + shapes::RESERVING_MPSC, + 2, + &short, + scripted(vec![(0.0, 0), (4e6, 0)]), + ); + assert_eq!(measured.nanos_per_op, 4e6 / 2_000.0); + assert_eq!(measured.ops_per_second, 2_000.0 / (4e6 / 1e9)); +} + +/// Timers that log which of them was called, in call order, and hand back what +/// `elapsed` says for that call. +fn logging_timers<'a>( + count: usize, + log: &'a std::cell::RefCell>, + elapsed: impl Fn(usize) -> f64 + Copy + 'a, +) -> Vec<(&'static str, Timer<'a>)> { + (0..count) + .map(|index| { + let timer: Timer<'a> = Box::new(move |_producers, _pushes| { + let call = log.borrow().len(); + log.borrow_mut().push(index); + (elapsed(call), 0) + }); + (shapes::RESERVING_MPSC, timer) + }) + .collect() +} + +#[test] +fn interleaved_takes_every_warmup_then_rotates_the_first_position_each_round() { + let sampling = Sampling { + repetitions: 3, + ..Sampling::default() + }; + let log = std::cell::RefCell::new(Vec::new()); + let mut timers = logging_timers(3, &log, |_| 1e6); + let measured = runs(1, &sampling, &mut timers); + + assert_eq!( + *log.borrow(), + vec![0, 1, 2, 0, 1, 2, 1, 2, 0, 2, 0, 1], + "three warmups in list order, then rounds starting at 0, 1 and 2" + ); + assert_eq!(measured.len(), 3, "one run per configuration"); +} + +#[test] +fn sequential_takes_each_configurations_passes_back_to_back() { + let sampling = Sampling { + repetitions: 2, + order: Order::Sequential, + ..Sampling::default() + }; + let log = std::cell::RefCell::new(Vec::new()); + let mut timers = logging_timers(3, &log, |_| 1e6); + let _ = runs(1, &sampling, &mut timers); + + assert_eq!( + *log.borrow(), + vec![0, 0, 0, 1, 1, 1, 2, 2, 2], + "a warmup and two passes each, one configuration after another" + ); +} + +#[test] +fn each_run_is_the_median_of_its_own_passes_whatever_the_order() { + // Configuration `k`'s passes take `k` milliseconds plus the pass number in + // nanoseconds, so a run that took another configuration's passes would show it. + for order in [Order::Interleaved, Order::Sequential] { + let sampling = Sampling { + repetitions: 3, + pushes_per_producer: 1, + order, + ..Sampling::default() + }; + let calls = std::cell::RefCell::new(vec![0usize; 4]); + let mut timers: Vec<(&'static str, Timer<'_>)> = (0..4) + .map(|k| { + let calls = &calls; + let timer: Timer<'_> = Box::new(move |_producers, _pushes| { + let pass = calls.borrow()[k]; + calls.borrow_mut()[k] += 1; + ((k as f64) * 1e6 + pass as f64, 0) + }); + (shapes::RESERVING_MPSC, timer) + }) + .collect(); + let measured = runs(1, &sampling, &mut timers); + let medians: Vec = measured.iter().map(|run| run.nanos_per_op).collect(); + // Pass 0 is the warmup, so the timed passes are 1, 2 and 3 and the median 2. + assert_eq!( + medians, + vec![2.0, 1e6 + 2.0, 2e6 + 2.0, 3e6 + 2.0], + "{order:?}" + ); + } +} + +#[test] +fn interleaving_puts_two_halves_of_a_control_inside_one_interval_under_drift() { + // **The property `M4.4` exists for, under a drift model rather than on a + // machine.** Every pass takes as long as the number of passes before it, so + // the run slows steadily. Two configurations measuring the same code then + // differ only by when they were measured -- which is the control's job to + // show as nothing at all. + // + // Fifteen configurations with the halves at positions 2 and 7, as in the + // isolated regime. Back to back, the halves' medians sit five configurations + // of (warmup + passes) apart; interleaved, they sit less than one round apart. + let count = 15; + let (first, second) = (2, 7); + let median_gap = |order: Order| { + let sampling = Sampling { + repetitions: 5, + pushes_per_producer: 1, + order, + ..Sampling::default() + }; + let log = std::cell::RefCell::new(Vec::new()); + let mut timers = logging_timers(count, &log, |call| call as f64); + let measured = runs(1, &sampling, &mut timers); + measured[second].nanos_per_op - measured[first].nanos_per_op + }; + + let sequential = median_gap(Order::Sequential); + let interleaved = median_gap(Order::Interleaved); + assert_eq!( + sequential, + ((second - first) * (1 + 5)) as f64, + "back to back, the gap is every pass taken between the two" + ); + assert!( + interleaved.abs() < count as f64, + "interleaved, the gap is less than one round: {interleaved}" + ); +} + +#[test] +fn a_short_run_measures_every_shape_at_every_count_it_was_given() { + // A real measurement, kept tiny: one repetition of a few pushes at two + // producer counts. It shows `measure` walks the counts it was given, in that + // order, and records the sampling it used -- not that any figure means + // anything. + let sampling = Sampling { + pushes_per_producer: 64, + repetitions: 1, + producer_counts: vec![2, 1], + order: Order::Interleaved, + }; + let observation = measure(&sampling); + assert_eq!(observation.sampling, sampling); + let counts: Vec = observation + .isolated + .iter() + .filter(|run| run.shape == shapes::RESERVING_MPSC) + .map(|run| run.producers) + .collect(); + assert_eq!(counts, vec![2, 1]); + assert!( + observation + .isolated + .iter() + .chain(&observation.drained) + .all(|run| run.producers == 1 || run.producers == 2) + ); + + // The order changes when passes are taken, never which rows a run reports or + // the table each lands in. + let sequential = measure(&Sampling { + order: Order::Sequential, + ..sampling + }); + let rows = |runs: &[Run]| -> Vec<(&'static str, usize)> { + runs.iter().map(|run| (run.shape, run.producers)).collect() + }; + assert_eq!(rows(&sequential.isolated), rows(&observation.isolated)); + assert_eq!(rows(&sequential.drained), rows(&observation.drained)); +} diff --git a/crates/windows-platform-probes/src/queue_contention/tests.rs b/crates/windows-platform-probes/src/queue_contention/tests.rs index 76f2b04e1..3ea3e1aec 100644 --- a/crates/windows-platform-probes/src/queue_contention/tests.rs +++ b/crates/windows-platform-probes/src/queue_contention/tests.rs @@ -38,6 +38,7 @@ fn observation(isolated: Vec, drained: Vec) -> Observation { isolated, drained, available_parallelism: Some(8), + sampling: Sampling::default(), } } @@ -263,24 +264,53 @@ fn every_shape_name_is_distinct() { /// refuse and stop being the regime it claims to be. /// /// `>=` rather than `>`: a `bounded(n)` queue accepts exactly `n` items -/// (measured, not assumed), so an exact fit is sufficient. Today the product is -/// never a power of two, so the distinction is unreachable -- but M4.2 makes the -/// push count settable, and a stricter assertion than the contract requires would -/// reject a valid configuration then. +/// (measured, not assumed), so an exact fit is sufficient. The default product is +/// never a power of two, but `M4.2` made the push count settable, and 1024 pushes +/// at a power-of-two producer count is an exact fit: a stricter assertion than the +/// contract requires would reject that valid configuration. #[test] fn capacity_for_leaves_room_for_every_push_at_every_producer_count() { - for &producers in PRODUCER_COUNTS { - let capacity = capacity_for(producers); - let pushes = producers * PUSHES_PER_PRODUCER; - assert!( - capacity >= pushes, - "{producers} producers push {pushes} but capacity is {capacity}" - ); - assert!( - capacity.is_power_of_two(), - "{capacity} must be a power of two" - ); + for pushes_per_producer in [PUSHES_PER_PRODUCER, 1, 1024, 1000] { + for &producers in PRODUCER_COUNTS { + let capacity = capacity_for(producers, pushes_per_producer); + let pushes = producers * pushes_per_producer; + assert!( + capacity >= pushes, + "{producers} producers push {pushes} but capacity is {capacity}" + ); + assert!( + capacity.is_power_of_two(), + "{capacity} must be a power of two" + ); + } } + assert_eq!( + capacity_for(2, 1024), + 2048, + "an exact fit is not rounded up" + ); +} + +/// The smallest run the settings accept, one producer making one push, must size a queue every timed +/// shape accepts: the slotwise queue refuses a capacity of one, and the probe panicked on it after +/// starting its report. Each shape's own constructor is asked, so the minimum is theirs and not this +/// test's. +#[test] +fn the_smallest_run_the_settings_accept_sizes_a_queue_every_timed_shape_accepts() { + let capacity = capacity_for(1, 1); + assert!( + capacity >= 1, + "capacity {capacity} cannot hold the one push" + ); + assert!( + slotwise_mpsc::bounded::(capacity).is_ok(), + "{capacity}" + ); + assert!( + reserving_mpsc::bounded::(capacity).is_ok(), + "{capacity}" + ); + assert!(permit_mpsc::bounded::(capacity).is_ok(), "{capacity}"); } /// `measured_span` is the correction that round two of this branch's review @@ -439,9 +469,9 @@ fn render_table_of_nothing_still_writes_its_header() { /// A timer that replays a scripted sequence instead of measuring anything, so /// `median_run`'s selection can be checked exactly. The first value is consumed /// by the untimed warmup pass. -fn scripted(values: Vec) -> impl FnMut(usize) -> Repetition { +fn scripted(values: Vec) -> impl FnMut(usize, usize) -> Repetition { let mut next = 0usize; - move |_producers| { + move |_producers, _pushes| { let value = values[next]; next += 1; value @@ -475,7 +505,12 @@ fn scripted_repetitions() -> Vec { #[test] fn median_run_reports_the_median_repetition_rather_than_the_first_or_last() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); // 3e6 ns over 1 * 50,000 pushes is 60 ns per op. assert!( (measured.nanos_per_op - 60.0).abs() < 1e-9, @@ -492,7 +527,12 @@ fn median_run_reports_the_median_repetition_rather_than_the_first_or_last() { /// would misattribute the cause while leaving the timing plausible. #[test] fn median_run_pairs_the_refusal_count_with_the_median_repetition() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); assert_eq!( measured.refusals, 20, "3e6 ns is the median and its repetition refused 20; got {}", @@ -506,7 +546,12 @@ fn median_run_pairs_the_refusal_count_with_the_median_repetition() { /// is visible. #[test] fn median_run_discards_the_warmup_pass() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); assert_ne!( measured.refusals, 9_999, "the warmup's refusals were reported" @@ -524,7 +569,12 @@ fn median_run_discards_the_warmup_pass() { /// different runs. #[test] fn median_run_derives_both_rates_from_the_one_chosen_repetition() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); let round_trip = 1_000_000_000.0 / measured.nanos_per_op; assert!( (measured.ops_per_second - round_trip).abs() < 1e-3, @@ -538,8 +588,18 @@ fn median_run_derives_both_rates_from_the_one_chosen_repetition() { /// durations must report a lower per-push cost when more producers shared them. #[test] fn median_run_divides_the_median_by_every_producers_pushes() { - let one = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); - let four = median_run(shapes::RESERVING_MPSC, 4, scripted(scripted_repetitions())); + let one = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); + let four = median_run( + shapes::RESERVING_MPSC, + 4, + &Sampling::default(), + scripted(scripted_repetitions()), + ); assert!( (one.nanos_per_op / four.nanos_per_op - 4.0).abs() < 1e-9, "four producers push four times as many items in the same span: {} vs {}", @@ -576,7 +636,12 @@ fn render_table_shows_a_nonzero_refusal_count() { /// median repeated or two adjacent repetitions. #[test] fn median_run_carries_the_fastest_and_slowest_repetitions() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); // Scripted timed repetitions are 1e6..5e6 ns over 50,000 pushes: 20..100 ns/op. assert!( (measured.fastest_nanos_per_op - 20.0).abs() < 1e-9, @@ -595,7 +660,12 @@ fn median_run_carries_the_fastest_and_slowest_repetitions() { /// wrong vector or from an unsorted one. #[test] fn median_run_brackets_its_median_with_the_range() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); assert!( measured.fastest_nanos_per_op <= measured.nanos_per_op, "fastest {} must not exceed the median {}", @@ -615,7 +685,12 @@ fn median_run_brackets_its_median_with_the_range() { /// wildly unstable. #[test] fn median_run_excludes_the_warmup_from_the_range() { - let measured = median_run(shapes::RESERVING_MPSC, 1, scripted(scripted_repetitions())); + let measured = median_run( + shapes::RESERVING_MPSC, + 1, + &Sampling::default(), + scripted(scripted_repetitions()), + ); assert!( measured.slowest_nanos_per_op < 1_000.0, "the warmup's 999e6 ns reached the range as {} ns/op", @@ -821,6 +896,7 @@ fn scaling_bounds_reads_the_one_and_many_producer_rows() { ], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; let (low, high) = observation .scaling_bounds(&observation.isolated, shapes::RESERVING_MPSC, 8) @@ -840,6 +916,7 @@ fn scaling_bounds_is_none_when_a_row_is_missing() { isolated: vec![run_spanning(shapes::RESERVING_MPSC, 8, 40.0, 50.0, 60.0)], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert!( observation @@ -1025,6 +1102,7 @@ fn a_many_producer_row_that_did_not_run_scales_to_the_marker() { ], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; let point = observation.scaling(&observation.isolated, shapes::RESERVING_MPSC, 8); assert_eq!( @@ -1125,6 +1203,7 @@ fn count_measured_counts_rows_that_ran_rather_than_rows_that_exist() { ], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert_eq!( observation.count_measured(&observation.isolated, &layouts, 1), @@ -1245,6 +1324,7 @@ fn scaling_bounds_at_one_producer_is_exactly_one() { isolated: vec![run_spanning(shapes::RESERVING_MPSC, 1, 4.0, 5.0, 6.0)], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert_eq!( observation.scaling(&observation.isolated, shapes::RESERVING_MPSC, 1), @@ -1266,6 +1346,7 @@ fn scaling_bounds_at_one_producer_is_exactly_one() { ], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; let (low, high) = observation .scaling_bounds(&observation.isolated, shapes::RESERVING_MPSC, 8) @@ -1286,6 +1367,7 @@ fn scaling_bounds_at_one_producer_is_exactly_one() { isolated: vec![row], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert_eq!( observation.scaling_bounds(&observation.isolated, shapes::RESERVING_MPSC, 1), @@ -1311,6 +1393,7 @@ fn scaling_bounds_needs_both_rows_measured_not_merely_one() { isolated: vec![measured, absent], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert_eq!( one_ran.scaling_bounds(&one_ran.isolated, shapes::RESERVING_MPSC, 8), @@ -1327,6 +1410,7 @@ fn scaling_bounds_needs_both_rows_measured_not_merely_one() { ], drained: Vec::new(), available_parallelism: Some(8), + sampling: Sampling::default(), }; assert_eq!( other_ran.scaling_bounds(&other_ran.isolated, shapes::RESERVING_MPSC, 8), diff --git a/crates/windows-platform-probes/src/report.rs b/crates/windows-platform-probes/src/report.rs index fbbec13b0..f1d57ff78 100644 --- a/crates/windows-platform-probes/src/report.rs +++ b/crates/windows-platform-probes/src/report.rs @@ -34,6 +34,11 @@ //! measurement, and stderr already separates it. Routing it through this sink //! would mix it into the evidence stdout carries. //! +//! A refused command line ([`refuse_arguments`]) goes to stderr for the same +//! reason: nothing was measured, so there is no report for it to be part of, and +//! a probe that wrote it to stdout would leave a capture holding a usage message +//! where a reader expects findings. +//! //! # Every probe routes through this //! //! Every probe in this crate, with no exceptions -- stated without a count on @@ -239,5 +244,17 @@ pub fn emit_report_to(report: &mut impl Report, render: impl FnOnce(&mut dyn std // `render` panics, which costs at most that fragment -- see `emit_report`. sink.finish(); } + +/// Refuse a probe's command line: write why, and how to call the probe, to +/// stderr, and exit with status 2 before anything is measured. +/// +/// The one place a refusal is written, so every probe that takes settings +/// refuses the same way. Status 2 and not 1, so a refusal is told apart from a +/// probe that ran and failed. +pub fn refuse_arguments(error: &dyn std::fmt::Display, usage: &str) -> ! { + eprintln!("{error}\n\n{usage}"); + std::process::exit(2) +} + #[cfg(test)] mod tests; diff --git a/crates/windows-platform-probes/src/request_cost.rs b/crates/windows-platform-probes/src/request_cost.rs index 5e76d35a2..86a7c037c 100644 --- a/crates/windows-platform-probes/src/request_cost.rs +++ b/crates/windows-platform-probes/src/request_cost.rs @@ -97,6 +97,8 @@ use wtf_string::Wtf16String; use std::os::windows::io::{AsHandle, OwnedHandle}; use win_shared_os_owned_handle::SharedHandle; + +use crate::probe_args::IterationMultiplier; use windows_namespace_request_sys::OpenFile; use windows_sys::Win32::Foundation::GENERIC_READ; use windows_sys::Win32::Storage::FileSystem::{FILE_SHARE_READ, OPEN_EXISTING}; @@ -252,20 +254,31 @@ fn open_request(path: &Wtf16String) -> OpenFile { .with_creation_disposition(OPEN_EXISTING) } +/// Iterations of each loop that only allocates, at the default multiplier. +const ITERATIONS: u32 = 100_000; +/// An open and a close are kernel calls, so these are timed over fewer +/// iterations than the loops that only allocate. +const PERFORM_ITERATIONS: u32 = 20_000; +/// The duplication and close loops, also kernel calls. +const HANDLE_ITERATIONS: u32 = 50_000; + +/// The longest loop [`measure`] runs at the default multiplier, which bounds the +/// multiplier a command line may give (see [`IterationMultiplier::from_args`]). +pub const LONGEST_LOOP: u32 = ITERATIONS; + /// Time request construction, request cloning, performing an open, sharing a -/// handle with a request, and duplicating a borrowed handle outside one. +/// handle with a request, and duplicating a borrowed handle outside one, each +/// loop run `multiplier` times its usual count (`M4.2`). /// /// # Panics /// /// Panics if a fixed sample path cannot build a request, or `kernel32.dll` /// cannot be opened, which would mean nothing here is meaningful. #[must_use] -pub fn measure() -> Observation { - const ITERATIONS: u32 = 100_000; - // An open and a close are kernel calls, so these are timed over fewer - // iterations than the loops that only allocate. - const PERFORM_ITERATIONS: u32 = 20_000; - const HANDLE_ITERATIONS: u32 = 50_000; +pub fn measure(multiplier: IterationMultiplier) -> Observation { + let iterations = multiplier.scale(ITERATIONS); + let perform_iterations = multiplier.scale(PERFORM_ITERATIONS); + let handle_iterations = multiplier.scale(HANDLE_ITERATIONS); // Resolved, not assumed. Windows is not always on `C:` -- a valid // installation can sit on any volume -- and hard-coding it made this probe @@ -305,20 +318,20 @@ pub fn measure() -> Observation { // The owned string a build is handed, on its own. Every build below // includes this clone, so it is what to subtract to read the rest. - timings.push(time_loop("clone_path_string", ITERATIONS, || kept.clone())); + timings.push(time_loop("clone_path_string", iterations, || kept.clone())); // A request whose path is kept as given: an absolute path, at two lengths. // No directory is read. - timings.push(time_loop("build_request_kept", ITERATIONS, || { + timings.push(time_loop("build_request_kept", iterations, || { open_request(&kept) })); - timings.push(time_loop("build_request_kept_long", ITERATIONS, || { + timings.push(time_loop("build_request_kept_long", iterations, || { open_request(&kept_long) })); // A request whose path is grafted onto the current directory, which the // build reads. - timings.push(time_loop("build_request_grafted", ITERATIONS, || { + timings.push(time_loop("build_request_grafted", iterations, || { open_request(&relative) })); @@ -348,12 +361,12 @@ pub fn measure() -> Observation { "the relative sample is meant to be grafted onto the current directory" ); - timings.push(time_loop("clone_request_kept", ITERATIONS, || { + timings.push(time_loop("clone_request_kept", iterations, || { kept_request .try_clone() .expect("a request with no handle or security copies") })); - timings.push(time_loop("clone_request_grafted", ITERATIONS, || { + timings.push(time_loop("clone_request_grafted", iterations, || { grafted_request .try_clone() .expect("a request with no handle or security copies") @@ -362,14 +375,14 @@ pub fn measure() -> Observation { // The call a request carries, against a real file: a built request // performed, and a request built and performed. The returned handle is // dropped inside the timed region, so each is an open-and-close cycle. - timings.push(time_loop("perform_open", PERFORM_ITERATIONS, || { + timings.push(time_loop("perform_open", perform_iterations, || { kept_request .perform() .expect("kernel32.dll opens for reading") })); timings.push(time_loop( "build_and_perform_open", - PERFORM_ITERATIONS, + perform_iterations, || { open_request(&kept) .perform() @@ -386,11 +399,11 @@ pub fn measure() -> Observation { // A clone and its drop. `shared` outlives the loop, so a dropped clone only // counts one holder fewer and closes nothing: this is the whole cost of // giving a request a handle the caller already shares. - timings.push(time_loop("share_handle", ITERATIONS, || shared.clone())); + timings.push(time_loop("share_handle", iterations, || shared.clone())); // The kept-path build again, now also carrying a template handle, to read // against `build_request_kept`. It is only built, never performed. - timings.push(time_loop("build_request_with_handle", ITERATIONS, || { + timings.push(time_loop("build_request_with_handle", iterations, || { open_request(&kept).with_template(Some(shared.clone())) })); @@ -418,9 +431,9 @@ pub fn measure() -> Observation { let _ = std::hint::black_box(borrowed.try_clone_to_owned().expect("duplicating a handle")); } - let mut duplicates: Vec = Vec::with_capacity(HANDLE_ITERATIONS as usize); + let mut duplicates: Vec = Vec::with_capacity(handle_iterations as usize); let start = Instant::now(); - for _ in 0..HANDLE_ITERATIONS { + for _ in 0..handle_iterations { duplicates.push( borrowed .try_clone_to_owned() @@ -430,8 +443,8 @@ pub fn measure() -> Observation { let duplicate_elapsed = start.elapsed(); timings.push(Timing { label: "duplicate_borrowed_handle", - iterations: HANDLE_ITERATIONS, - nanos_per_op: duplicate_elapsed.as_nanos() as f64 / f64::from(HANDLE_ITERATIONS), + iterations: handle_iterations, + nanos_per_op: duplicate_elapsed.as_nanos() as f64 / f64::from(handle_iterations), }); let start = Instant::now(); @@ -439,8 +452,8 @@ pub fn measure() -> Observation { let close_elapsed = start.elapsed(); timings.push(Timing { label: "close_duplicate", - iterations: HANDLE_ITERATIONS, - nanos_per_op: close_elapsed.as_nanos() as f64 / f64::from(HANDLE_ITERATIONS), + iterations: handle_iterations, + nanos_per_op: close_elapsed.as_nanos() as f64 / f64::from(handle_iterations), }); Observation { timings } diff --git a/crates/windows-platform-probes/src/row.rs b/crates/windows-platform-probes/src/row.rs index ea6018d82..7055042f4 100644 --- a/crates/windows-platform-probes/src/row.rs +++ b/crates/windows-platform-probes/src/row.rs @@ -93,15 +93,17 @@ pub enum Shape { /// A value the row can carry. /// /// Deliberately not every JSON shape: there is no floating point, because every -/// quantity this crate publishes is a count, an identifier or a list of them, -/// and a float in a mined artifact invites a consumer to compare values that -/// were never measured that precisely. +/// quantity this crate publishes is a count, an identifier, a yes-or-no or a +/// list of them, and a float in a mined artifact invites a consumer to compare +/// values that were never measured that precisely. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { /// A string, escaped on the way out. Text(String), /// A count. Number(usize), + /// A yes-or-no the probe observed, such as whether a raise took effect. + Bool(bool), /// The absent case, for a field whose answer is "there is none". /// /// A variant rather than an omitted key, because a consumer can tell `null` @@ -146,7 +148,7 @@ fn assert_unique_names(at: &str, value: &Value) { assert_unique_names(at, entry); } } - Value::Text(_) | Value::Number(_) | Value::Null => {} + Value::Text(_) | Value::Number(_) | Value::Bool(_) | Value::Null => {} } } @@ -174,6 +176,37 @@ impl From for Value { } } +impl From for Value { + fn from(held: bool) -> Self { + Self::Bool(held) + } +} + +impl From for Value { + fn from(count: u16) -> Self { + Self::Number(usize::from(count)) + } +} + +/// Lossless on every target this crate builds for: Windows has no target whose +/// `usize` is narrower than 32 bits. +impl From for Value { + fn from(count: u32) -> Self { + Self::Number(count as usize) + } +} + +/// **Saturates at `usize::MAX` on a 32-bit target**, the one place a count can +/// be published wrong rather than refused. Every caller is a count of events +/// within one run -- refusals, shared reads -- which no measured run has come +/// within orders of magnitude of 2^32; lossless on a 64-bit target. One +/// definition, so the saturation is decided here rather than at each call. +impl From for Value { + fn from(count: u64) -> Self { + Self::Number(usize::try_from(count).unwrap_or(usize::MAX)) + } +} + impl> From> for Value { fn from(value: Option) -> Self { value.map_or(Self::Null, Into::into) @@ -194,6 +227,7 @@ impl Value { Self::Number(count) => { let _ = write!(out, "{count}"); } + Self::Bool(held) => out.push_str(if *held { "true" } else { "false" }), Self::Null => out.push_str("null"), Self::List(items) => { out.push('['); diff --git a/crates/windows-platform-probes/src/row/tests.rs b/crates/windows-platform-probes/src/row/tests.rs index a11624850..cad8bc880 100644 --- a/crates/windows-platform-probes/src/row/tests.rs +++ b/crates/windows-platform-probes/src/row/tests.rs @@ -232,3 +232,38 @@ fn a_key_is_escaped_too() { assert_eq!(row.render(), r#"{"reason":"x","odd\"name":1}"#); } + +#[test] +fn every_unsigned_width_renders_as_the_number_it_holds() { + let row = Row::new("x") + .with("u16", u16::MAX) + .with("u32", u32::MAX) + .with("u64_small", 7_u64) + .with("option_u32", Some(9_u32)) + .with("none_u32", None::); + assert_eq!( + row.render(), + r#"{"reason":"x","u16":65535,"u32":4294967295,"u64_small":7,"option_u32":9,"none_u32":null}"# + ); +} + +#[cfg(target_pointer_width = "64")] +#[test] +fn a_u64_is_lossless_on_a_64_bit_target() { + // The saturation `From` documents is a 32-bit case; here the widest + // value must come through exactly. + let row = Row::new("x").with("u64", u64::MAX); + assert_eq!(row.render(), r#"{"reason":"x","u64":18446744073709551615}"#); +} + +#[test] +fn a_yes_or_no_renders_as_a_json_boolean() { + let row = Row::new("x") + .with("yes", true) + .with("no", false) + .with("unknown", None::); + assert_eq!( + row.render(), + r#"{"reason":"x","yes":true,"no":false,"unknown":null}"# + ); +} diff --git a/crates/windows-platform-probes/src/topology/invariant.rs b/crates/windows-platform-probes/src/topology/invariant.rs index 1e1aa9e97..cf47183dd 100644 --- a/crates/windows-platform-probes/src/topology/invariant.rs +++ b/crates/windows-platform-probes/src/topology/invariant.rs @@ -152,6 +152,28 @@ blocking_states! { CoherenceNotCollected => "coherence between the two enumerations was not collected", /// The bracket did not establish that the machine held still. BracketNotHeld => "the bracket did not establish that the machine held still", + /// A cache level the survey carries has no partitions at all. + CacheLevelsWithoutPartitions => "a cache level has no partitions", + /// A measured topology reports no processors, or no groups. + MeasuredButCountsAbsent => "a measured topology reports no processors or no groups", + /// A NUMA domain was reported only by CPU Sets. + NumaDomainsOnlyInCpuSets => "a NUMA domain was reported only by CPU Sets", + /// A NUMA domain was reported by no source at all. + NumaDomainsUnreported => "a NUMA domain was reported by no source", + /// A NUMA domain carries more than one distinct node number. + NumaDomainsWithConflictingLabels => "a NUMA domain carries conflicting node numbers", + /// A core was reported only by CPU Sets. + CoresOnlyInCpuSets => "a core was reported only by CPU Sets", + /// A core or a package covers no processors. + RelationsWithoutProcessors => "a core or package covers no processors", + /// Two relationship-walk records of one kind claim the same processor. + OverlappingWalkRelations => "two walk records of one kind claim the same processor", + /// A relation was described by a caller rather than reported by the platform. + DescribedRelations => "a relation was described rather than reported", + /// A relation was reported by no source at all. + UnreportedRelations => "a relation was reported by no source", + /// A per-processor attribute carries more than one distinct value. + ProcessorAttributeConflicts => "a processor attribute carries conflicting values", } /// A state that forbids an agreeing verdict, found beside one. @@ -240,21 +262,22 @@ impl fmt::Display for Violation { /// enumerating them separately is that a deleted push site leaves the state /// here and the list empty -- so this is what notices, and the list could not. /// -/// **The converse does NOT hold, and the wording here used to imply it did.** -/// This said the absentees were "derived counts whose only source IS the -/// cross-check's own arithmetic", which would make their exclusion principled. -/// It is false for most of them: `CacheLevelsWithoutPartitions`, -/// `NumaDomainsOnlyInCpuSets`, `CoresOnlyInCpuSets`, `RelationsWithoutProcessors` -/// and the rest read fields that sit on [`Observation`] in plain sight. So -/// deleting one of THOSE push sites is not caught here -- the verdict can reach -/// `agree` with `blocking_states` silent -- and the guarantee above covers the -/// states actually listed below, not every condition `cross_check` can find. -/// Reported by three review rounds against two different variants of the claim. +/// **Every `parse_incomplete` condition `cross_check` can file has a state +/// here**, and `every_parse_incomplete_code_is_answered_by_a_blocking_state` in +/// `tests/a_real_report_agrees_with_itself.rs` fails when a new condition does +/// not -- so the guarantee above covers the whole list rather than whichever +/// part of it somebody remembered to model. /// -/// The tautology argument is still the right test for what belongs here; it -/// simply was not what excluded these. Closing the gap is queued as **M4.1** in -/// [CHECKLIST.md](../../CHECKLIST.md) rather than recorded only here, because a -/// decision written in a comment schedules nothing. +/// It used to cover a part. Eleven conditions were left out on the grounds that +/// they were "derived counts whose only source IS the cross-check's own +/// arithmetic", which would have made leaving them out principled; it was false +/// for all eleven, each of which reads fields that sit on [`Observation`] in +/// plain sight. Reported by three review rounds against two wordings of the +/// claim, and closed by `M4.1`. +/// +/// The tautology test is still what decides membership: a state whose only +/// source is `cross_check`'s own output does not belong, because it restates +/// `verdict()` and goes blind to a deleted push site. None does yet. #[must_use] pub fn blocking_states(observation: &Observation) -> Vec { let mut states = Vec::new(); @@ -325,6 +348,52 @@ pub fn blocking_states(observation: &Observation) -> Vec { states.push(BlockingState::BracketNotHeld); } + if observation.caches.iter().any(|cache| cache.domains() == 0) { + states.push(BlockingState::CacheLevelsWithoutPartitions); + } + + if observation.topology_was_measured + && (observation.online_processors == 0 || observation.groups == 0) + { + states.push(BlockingState::MeasuredButCountsAbsent); + } + + if observation.numa_domains_only_in_cpu_sets > 0 { + states.push(BlockingState::NumaDomainsOnlyInCpuSets); + } + + if observation.numa_domains_unreported > 0 { + states.push(BlockingState::NumaDomainsUnreported); + } + + if observation.numa_domains_with_conflicting_labels > 0 { + states.push(BlockingState::NumaDomainsWithConflictingLabels); + } + + if observation.cores_only_in_cpu_sets > 0 { + states.push(BlockingState::CoresOnlyInCpuSets); + } + + if observation.cores_without_processors > 0 || observation.packages_without_processors > 0 { + states.push(BlockingState::RelationsWithoutProcessors); + } + + if observation.overlapping_walk_relations > 0 { + states.push(BlockingState::OverlappingWalkRelations); + } + + if observation.described_relations > 0 { + states.push(BlockingState::DescribedRelations); + } + + if observation.unreported_relations > 0 { + states.push(BlockingState::UnreportedRelations); + } + + if observation.processor_attribute_conflicts > 0 { + states.push(BlockingState::ProcessorAttributeConflicts); + } + states } diff --git a/crates/windows-platform-probes/src/topology/invariant/tests.rs b/crates/windows-platform-probes/src/topology/invariant/tests.rs index b5fc36149..9abf6e70b 100644 --- a/crates/windows-platform-probes/src/topology/invariant/tests.rs +++ b/crates/windows-platform-probes/src/topology/invariant/tests.rs @@ -173,9 +173,118 @@ fn perturbations() -> Vec { BlockingState::BracketNotHeld, Box::new(|o: &mut Observation| o.bracket = BracketOutcome::Changed), ), + ( + BlockingState::CacheLevelsWithoutPartitions, + Box::new(|o: &mut Observation| { + o.caches = vec![CacheLevel { + level: 1, + processors_per_domain: Vec::new(), + }]; + }), + ), + ( + BlockingState::MeasuredButCountsAbsent, + Box::new(|o: &mut Observation| o.groups = 0), + ), + ( + BlockingState::NumaDomainsOnlyInCpuSets, + Box::new(|o: &mut Observation| o.numa_domains_only_in_cpu_sets = 1), + ), + ( + BlockingState::NumaDomainsUnreported, + Box::new(|o: &mut Observation| o.numa_domains_unreported = 1), + ), + ( + BlockingState::NumaDomainsWithConflictingLabels, + Box::new(|o: &mut Observation| o.numa_domains_with_conflicting_labels = 1), + ), + ( + BlockingState::CoresOnlyInCpuSets, + Box::new(|o: &mut Observation| o.cores_only_in_cpu_sets = 1), + ), + ( + BlockingState::RelationsWithoutProcessors, + Box::new(|o: &mut Observation| o.cores_without_processors = 1), + ), + ( + BlockingState::OverlappingWalkRelations, + Box::new(|o: &mut Observation| o.overlapping_walk_relations = 1), + ), + ( + BlockingState::DescribedRelations, + Box::new(|o: &mut Observation| o.described_relations = 1), + ), + ( + BlockingState::UnreportedRelations, + Box::new(|o: &mut Observation| o.unreported_relations = 1), + ), + ( + BlockingState::ProcessorAttributeConflicts, + Box::new(|o: &mut Observation| o.processor_attribute_conflicts = 1), + ), ] } +#[test] +fn a_state_read_from_two_fields_is_reached_by_each_of_them_alone() { + // **The table above reaches each of these states through ONE field**, so a + // branch that dropped its other input would still pass every test that + // iterates the table. Each input is driven alone here, and the guard on the + // measured-but-absent state is driven both ways: `cross_check` files it only + // for a MEASURED topology, so an unmeasured one with no groups is in + // `NotMeasured` and not in this state. + let reached = |state: BlockingState, mutate: &dyn Fn(&mut Observation)| { + let mut observation = agreeing(); + mutate(&mut observation); + let found = blocking_states(&observation).contains(&state); + let filed = observation.cross_check().verdict() != Verdict::Agree; + (found, filed) + }; + + for (what, mutate) in [ + ( + "a core with no processors", + Box::new(|o: &mut Observation| o.cores_without_processors = 1) + as Box, + ), + ( + "a package with no processors", + Box::new(|o: &mut Observation| o.packages_without_processors = 1), + ), + ] { + assert_eq!( + reached(BlockingState::RelationsWithoutProcessors, &*mutate), + (true, true), + "{what}: the state and `cross_check` must both see it" + ); + } + + for (what, mutate) in [ + ( + "no processors", + Box::new(|o: &mut Observation| o.online_processors = 0) + as Box, + ), + ("no groups", Box::new(|o: &mut Observation| o.groups = 0)), + ] { + assert_eq!( + reached(BlockingState::MeasuredButCountsAbsent, &*mutate), + (true, true), + "{what}: the state and `cross_check` must both see it" + ); + } + + let (found, _) = reached(BlockingState::MeasuredButCountsAbsent, &|o| { + o.topology_was_measured = false; + o.groups = 0; + }); + assert!( + !found, + "an unmeasured topology with no groups is `NotMeasured`, and is not \ + also a measured one missing its counts" + ); +} + #[test] fn every_blocking_state_has_a_perturbation() { // **Compared against `BlockingState::ALL`, not against the table itself.** @@ -584,13 +693,14 @@ fn every_numa_counter_branch_in_the_real_cross_check_is_one_this_module_forbids( // demanding behaviour that does not. // // **Named for the counter on purpose.** This was - // `every_numa_branch_...`, which was false: `cross_check` also pushes - // `NumaDomainsOnlyInCpuSets`, `NumaDomainsUnreported` and - // `NumaDomainsWithConflictingLabels`, none of which has a `BlockingState`, - // so deleting one of those push sites is not caught. A review found the - // name claiming the whole family while the body covered the two branches - // that read `raw_highest_numa_node`. The absentees are queued as M4.1; the - // name now says which half is guarded. + // `every_numa_branch_...`, which was false at the time: `cross_check` also + // pushes `NumaDomainsOnlyInCpuSets`, `NumaDomainsUnreported` and + // `NumaDomainsWithConflictingLabels`, and none of them then had a + // `BlockingState`. A review found the name claiming the whole family while + // the body covered the two branches that read `raw_highest_numa_node`. + // `M4.1` gave those three their states, so they are guarded through the + // perturbation table like every other state; this test still covers only + // the counter half, and its name still says so. // // This is the pairing the other states get from // `every_blocking_state_is_one_the_real_cross_check_already_reports`; NUMA diff --git a/crates/windows-platform-probes/src/worker_context.rs b/crates/windows-platform-probes/src/worker_context.rs index 2280faf85..088e713a1 100644 --- a/crates/windows-platform-probes/src/worker_context.rs +++ b/crates/windows-platform-probes/src/worker_context.rs @@ -36,6 +36,10 @@ use windows_sys::Win32::System::Threading::{ SetThreadToken, TP_CALLBACK_ENVIRON_V3, TrySubmitThreadpoolCallback, }; +mod raw_row; + +pub use self::raw_row::*; + /// What a worker observed about the thread it was handed. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct WorkerContext { diff --git a/crates/windows-platform-probes/src/worker_context/raw_row.rs b/crates/windows-platform-probes/src/worker_context/raw_row.rs new file mode 100644 index 000000000..622cfd6bd --- /dev/null +++ b/crates/windows-platform-probes/src/worker_context/raw_row.rs @@ -0,0 +1,50 @@ +// Copyright (c) Mike Grier. + +//! The worker-context probe's machine-readable row (`M8.9`). + +use super::*; +use crate::row::{Row, Value}; + +#[cfg(test)] +mod tests; + +/// What the row names itself. +pub const REASON: &str = "x-probe-worker-context"; + +/// The row a report carries. +/// +/// **Raw data**, per `d-every-probe-emits-a-row` in DESIGN-NOTES.md: what a +/// worker found when submitted plainly, and what the submitter and its worker +/// each found when submitted while impersonating -- for each, whether +/// `OpenThreadToken` found a token, its `GetLastError` when it did not (`null` +/// when it did, since none was read), and the thread error mode on entry. +/// +/// Not unimpersonated, disagree or handler-enabled; each is a comparison over +/// the figures here. The probe has no way to decline: a callback that never +/// reports stops it with a panic after its timeout, and a panic prints no +/// report. +#[must_use] +pub fn raw_row(plain: WorkerContext, impersonating: IdentityAsymmetry, build: &str) -> Row { + Row::new(REASON) + .with("arch", std::env::consts::ARCH) + .with("build", build) + .with("plain_worker", context(plain)) + .with( + "impersonating", + Value::Object(vec![ + ("submitter", context(impersonating.submitter)), + ("worker", context(impersonating.worker)), + ]), + ) +} + +fn context(seen: WorkerContext) -> Value { + Value::Object(vec![ + ("has_thread_token", Value::from(seen.has_thread_token)), + ( + "open_token_error", + Value::from((!seen.has_thread_token).then_some(seen.open_token_error)), + ), + ("error_mode", Value::from(seen.error_mode)), + ]) +} diff --git a/crates/windows-platform-probes/src/worker_context/raw_row/tests.rs b/crates/windows-platform-probes/src/worker_context/raw_row/tests.rs new file mode 100644 index 000000000..911ec3972 --- /dev/null +++ b/crates/windows-platform-probes/src/worker_context/raw_row/tests.rs @@ -0,0 +1,62 @@ +// Copyright (c) Mike Grier. +//! Tests for the worker-context probe's row. + +use super::*; + +/// Distinct values in every field, so no two can be swapped unnoticed. +fn plain() -> WorkerContext { + WorkerContext { + has_thread_token: false, + open_token_error: ERROR_NO_TOKEN, + error_mode: 0x0010, + } +} + +fn impersonating() -> IdentityAsymmetry { + IdentityAsymmetry { + submitter: WorkerContext { + has_thread_token: true, + open_token_error: 0, + error_mode: 0x0020, + }, + worker: WorkerContext { + has_thread_token: false, + open_token_error: 5, + error_mode: 0x0040, + }, + } +} + +fn parsed(row: &Row) -> serde_json::Value { + serde_json::from_str(&row.render()).expect("the row is JSON") +} + +#[test] +fn the_row_names_itself_and_carries_the_build() { + let row = parsed(&raw_row(plain(), impersonating(), "v1 abc [ci]")); + assert_eq!(row["reason"], REASON); + assert_eq!(REASON, "x-probe-worker-context"); + assert_eq!(row["arch"], std::env::consts::ARCH); + assert_eq!(row["build"], "v1 abc [ci]"); +} + +#[test] +fn the_plain_worker_is_published_as_found() { + let row = parsed(&raw_row(plain(), impersonating(), "b")); + assert_eq!( + row["plain_worker"], + serde_json::json!({"has_thread_token": false, "open_token_error": ERROR_NO_TOKEN, "error_mode": 16}) + ); +} + +#[test] +fn both_sides_of_the_impersonating_submission_are_published_with_the_error_only_without_a_token() { + let row = parsed(&raw_row(plain(), impersonating(), "b")); + assert_eq!( + row["impersonating"], + serde_json::json!({ + "submitter": {"has_thread_token": true, "open_token_error": null, "error_mode": 32}, + "worker": {"has_thread_token": false, "open_token_error": 5, "error_mode": 64}, + }) + ); +} diff --git a/crates/windows-platform-probes/tests/a_probe_takes_its_settings_or_refuses_them.rs b/crates/windows-platform-probes/tests/a_probe_takes_its_settings_or_refuses_them.rs new file mode 100644 index 000000000..7f58edf18 --- /dev/null +++ b/crates/windows-platform-probes/tests/a_probe_takes_its_settings_or_refuses_them.rs @@ -0,0 +1,169 @@ +// Copyright (c) Mike Grier. + +//! The probes that take settings (`M4.2`) take them from a command line, report +//! what they used, and refuse what they cannot use -- on the real binaries, since +//! the refusal is a process exit and the report is what a reader captures. + +use std::process::{Command, Output}; + +const QUEUE: &str = env!("CARGO_BIN_EXE_probe-queue-contention"); +const REQUEST: &str = env!("CARGO_BIN_EXE_probe-request-cost"); +const DOORBELL: &str = env!("CARGO_BIN_EXE_probe-doorbell-cost"); + +fn run(binary: &str, args: &[&str]) -> Output { + Command::new(binary) + .args(args) + .output() + .expect("run the probe") +} + +/// A refusal exits with status 2, says why and how to call the probe on stderr, +/// and puts nothing on stdout: nothing was measured, so there is no report. +fn assert_refused(binary: &str, args: &[&str], why: &str) { + let output = run(binary, args); + let stderr = String::from_utf8_lossy(&output.stderr); + assert_eq!( + output.status.code(), + Some(2), + "{binary} {args:?} was not refused; stderr: {stderr}" + ); + assert!(stderr.contains(why), "{binary} {args:?}: {stderr}"); + assert!( + stderr.contains("usage: probe-"), + "{binary} {args:?}: {stderr}" + ); + assert!( + output.stdout.is_empty(), + "{binary} {args:?} wrote a report it did not measure: {}", + String::from_utf8_lossy(&output.stdout) + ); +} + +#[test] +fn each_probe_refuses_a_setting_it_does_not_take() { + assert_refused(QUEUE, &["--iteration-multiplier", "2"], "is not a setting"); + assert_refused(REQUEST, &["--repetitions", "3"], "is not a setting"); + assert_refused(DOORBELL, &["--producers", "1"], "is not a setting"); + assert_refused(REQUEST, &["--help"], "is not a setting"); +} + +#[test] +fn each_probe_refuses_a_value_it_cannot_use() { + assert_refused(QUEUE, &["--repetitions", "0"], "must be at least 1"); + assert_refused(QUEUE, &["--producers", "1,1"], "listed more than once"); + assert_refused( + REQUEST, + &["--iteration-multiplier", "x"], + "is not a whole number", + ); + assert_refused( + DOORBELL, + &["--iteration-multiplier"], + "needs a value after it", + ); +} + +#[test] +fn a_queue_run_given_settings_reports_them_and_measures_only_the_counts_given() { + let output = run( + QUEUE, + &[ + "--pushes-per-producer", + "64", + "--repetitions", + "1", + "--producers", + "3,1", + "--order", + "sequential", + ], + ); + let stdout = String::from_utf8(output.stdout).expect("a probe's report is UTF-8"); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert!( + stdout.contains( + "sampling: 64 pushes per producer, median of 1 repetitions, one untimed warmup \ + pass, each configuration's passes back to back, producer counts 3, 1" + ), + "{stdout}" + ); + assert!(stdout.contains("NOT the defaults"), "{stdout}"); + // The build line (`M4.8`) names the build this test's own crate was stamped + // with, which is the same build script run. + assert!( + stdout.contains(&format!( + "build: {}", + windows_platform_probes::build_identity::this_build() + )), + "{stdout}" + ); +} + +/// The smallest run the settings accept is measured and not a crash: one producer making one push sizes +/// a queue every shape takes, where the slotwise queue refused a capacity of one and the probe panicked +/// after its report had begun. +#[test] +fn a_queue_run_of_one_producer_making_one_push_is_measured() { + let output = run( + QUEUE, + &[ + "--producers", + "1", + "--pushes-per-producer", + "1", + "--repetitions", + "1", + ], + ); + assert!( + output.status.success(), + "exit {:?}; stderr: {}", + output.status.code(), + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("sampling: 1 pushes per producer"), + "{stdout}" + ); +} + +#[test] +fn a_cost_probe_given_a_multiplier_reports_it_beside_its_banner_and_in_its_row() { + let output = run(REQUEST, &["--iteration-multiplier", "2"]); + let stdout = String::from_utf8(output.stdout).expect("a probe's report is UTF-8"); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + let lines: Vec<&str> = stdout.lines().collect(); + assert!( + lines[1].starts_with("iteration multiplier: 2 (NOT the default"), + "the line after the banner says what the run used: {stdout}" + ); + let row = lines + .iter() + .find(|line| line.starts_with('{')) + .expect("the NDJSON row"); + assert!(row.contains(r#""iteration_multiplier":2,"#), "{row}"); +} + +#[test] +fn a_cost_probe_given_nothing_says_it_used_the_default() { + let output = run(DOORBELL, &[]); + let stdout = String::from_utf8(output.stdout).expect("a probe's report is UTF-8"); + assert!(output.status.success()); + assert!( + stdout + .lines() + .nth(1) + .is_some_and(|line| line == "iteration multiplier: 1 (the default)"), + "{stdout}" + ); + assert!(stdout.contains(r#""iteration_multiplier":1,"#), "{stdout}"); +} diff --git a/crates/windows-platform-probes/tests/a_real_report_agrees_with_itself.rs b/crates/windows-platform-probes/tests/a_real_report_agrees_with_itself.rs index 1432cc730..39ca64a4e 100644 --- a/crates/windows-platform-probes/tests/a_real_report_agrees_with_itself.rs +++ b/crates/windows-platform-probes/tests/a_real_report_agrees_with_itself.rs @@ -105,7 +105,7 @@ use windows_platform_probes::topology::{BracketOutcome, CacheLevel, CoreShape, O /// A host shape, named by the renderer branch it exists to reach. struct Shape { - what: &'static str, + what: String, observation: Observation, } @@ -174,16 +174,15 @@ fn base() -> Observation { } } -/// Representative shapes this renderer can be driven into, by the branch each -/// reaches. +/// Shapes this renderer can be driven into: the hand-written ones, each named for +/// the branch it reaches, then the generated cross product of [`crossed_dimensions`]. /// -/// **Not every shape, and the difference is recorded rather than glossed.** This -/// said "every shape this renderer can be driven into", which describes a closed -/// set. Each entry below varies ONE dimension, so a branch selected by two at -/// once is unreachable here by construction -- the reason the crossed -/// anomaly-and-disagreement case at the end had to be written by hand, and the -/// reason M2.17 exists. Until the cross product is generated, read this as a -/// sample chosen from the renderer's branches, not as a proof of coverage. +/// **Still not every shape, and the difference is recorded rather than glossed.** +/// Each hand-written entry varies ONE dimension, so a branch selected by two at +/// once was unreachable here by construction -- the reason the crossed +/// anomaly-and-disagreement case below had to be written by hand. `M2.17` added +/// the cross product, which reaches every combination of the dimensions it +/// names; a dimension it does not name is still varied only by hand. /// /// **Which combinations are legal is DERIVED from `cross_check`, not assumed.** /// It pushes a `parse_incomplete` entry for an empty cache survey, for a named @@ -194,7 +193,12 @@ fn base() -> Observation { /// cannot produce. fn shapes() -> Vec { let mut shapes = Vec::new(); - let mut push = |what, observation| shapes.push(Shape { what, observation }); + let mut push = |what: &str, observation| { + shapes.push(Shape { + what: what.to_owned(), + observation, + }); + }; push("the level arm, agreeing", base()); @@ -355,6 +359,42 @@ fn shapes() -> Vec { }]; push("a cache level Windows does not number", unnumbered); + // **The states `M4.1` added, one shape each where none already reached + // them.** `numa_domains_only_in_cpu_sets` is reached by the parse-in-doubt + // shape above, and `cores_only_in_cpu_sets`, `numa_domains_unreported` and + // `processor_attribute_conflicts` by the several-at-once shape below. + let mut empty_level = base(); + empty_level.partitioning_cache_level = None; + empty_level.caches = vec![CacheLevel { + level: 1, + processors_per_domain: Vec::new(), + }]; + push("a cache level with no partitions", empty_level); + + let mut no_groups = base(); + no_groups.groups = 0; + push("a measured topology that reports no groups", no_groups); + + let mut empty_core = base(); + empty_core.cores_without_processors = 1; + push("a core that covers no processors", empty_core); + + let mut overlapping = base(); + overlapping.overlapping_walk_relations = 1; + push("two walk records claiming one processor", overlapping); + + let mut described = base(); + described.described_relations = 1; + push("a relation a caller described", described); + + let mut unreported = base(); + unreported.unreported_relations = 1; + push("a relation no source reported", unreported); + + let mut conflicting_labels = base(); + conflicting_labels.numa_domains_with_conflicting_labels = 1; + push("a NUMA domain with two node numbers", conflicting_labels); + let mut anomalies = base(); anomalies.enumeration_anomalies = vec![ windows_topology_sys::EnumerationAnomaly { @@ -384,8 +424,9 @@ fn shapes() -> Vec { // That is how the anomaly-count rule came to be unread on a disagreeing // report while a comment above it said it was read for every verdict: // corrupting the count there raised no violation, and no corpus report - // could show it. Found by a review. This shape is the cross, and it is the - // only one here that varies two dimensions at once. + // could show it. Found by a review. This shape was the cross, written by + // hand; the generated product below now reaches it and every combination + // like it, and it stays as the named case that motivated that. let mut anomalies_while_disagreeing = base(); anomalies_while_disagreeing.raw_active_processors = 8; anomalies_while_disagreeing.enumeration_anomalies = @@ -399,9 +440,9 @@ fn shapes() -> Vec { anomalies_while_disagreeing, ); - // **Several conditions in ONE list, which nothing else here reaches.** Every - // other shape varies one dimension, so each lands at most one entry per - // list -- and a one-element list has no order to get wrong. That made the + // **Several conditions in ONE list, which no other hand-written shape + // reaches.** Each of those varies one dimension, so each lands at most one + // entry per list -- and a one-element list has no order to get wrong. That made the // ordering half of `the_row_lists_exactly_the_conditions_the_cross_check_found` // vacuous: reversing the row's `parse_incomplete` reddened nothing at all. // @@ -415,9 +456,240 @@ fn shapes() -> Vec { several.processor_attribute_conflicts = 1; push("several conditions at once, in one list", several); + shapes.extend(cross(base(), &crossed_dimensions())); + shapes +} + +// --- M2.17: the corpus, crossed ----------------------------------------------- + +/// A change to an observation, named for what it makes the observation. +type Change = (&'static str, fn(&mut Observation)); + +/// The values one dimension takes. The first is always the identity, so `base()` +/// is the shape where every dimension is at its first value. +type Dimension = Vec; + +/// The dimensions the cross product varies, each over every value it takes. +/// +/// **The ones `cross_check`, the verdict and the row branch on**: the partitioning +/// arm, the bracket, coherence, the two kinds of counter result that fill +/// `disagreements` and `not_compared`, anomalies (which fill `parse_incomplete` +/// through their own push), and the efficiency classes, which the renderer gates +/// on the parse being in doubt. The verdict is not a dimension of its own: it is a +/// function of the three lists, so crossing what fills them crosses it. +/// +/// **Each dimension changes fields no other one does**, so a combination is one +/// observation rather than a later change overwriting an earlier one. +/// `the_crossed_dimensions_are_independent_of_each_other` checks that rather than +/// trusting this sentence. +fn crossed_dimensions() -> Vec { + vec![ + vec![ + ("the level arm", |_| {}), + ("no level partitions the machine", |o| { + o.partitioning_cache_level = None; + }), + ("no unique outermost level", |o| { + o.partitioning_cache_level = None; + o.caches = vec![CacheLevel { + level: 2, + processors_per_domain: vec![2, 2], + }]; + }), + ("no cache levels", |o| { + o.partitioning_cache_level = None; + o.caches = Vec::new(); + }), + ("a summary missing", |o| { + o.partitioning_cache_level = Some(9) + }), + ], + vec![ + ("the bracket held", |_| {}), + ("the machine changed", |o| { + o.bracket = BracketOutcome::Changed + }), + ("the bracket left open", |o| { + o.bracket = BracketOutcome::NotEstablished; + }), + ], + vec![ + ("the enumerations agreed", |_| {}), + ("the enumerations disagreed", |o| { + o.coherence = windows_topology_sys::Coherence::Disagreed { + attempts: 2, + walk_only: Vec::new(), + cpu_sets_only: Vec::new(), + }; + }), + ("coherence not collected", |o| { + o.coherence = windows_topology_sys::Coherence::NotCollected; + }), + ], + vec![ + ("the processor counter agrees", |_| {}), + ("the processor counter disagrees", |o| { + o.raw_active_processors = 8; + }), + ], + vec![ + ("the NUMA counter read", |_| {}), + ("the NUMA counter failed", |o| { + o.raw_highest_numa_node = None + }), + ], + vec![ + ("no anomalies", |_| {}), + ("an anomaly", |o| { + o.enumeration_anomalies = vec![windows_topology_sys::EnumerationAnomaly { + source: windows_topology_sys::Source::RelationshipWalk, + offset: 0, + kind: windows_topology_sys::AnomalyKind::TrailingBytes { remaining: 8 }, + }]; + }), + ], + vec![ + ("one efficiency class", |_| {}), + ("two efficiency classes", |o| { + o.cores = vec![ + CoreShape { + simultaneous_multithreading: true, + efficiency_class: 0, + processors: 2, + }, + CoreShape { + simultaneous_multithreading: true, + efficiency_class: 1, + processors: 2, + }, + ]; + }), + ], + ] +} + +/// Every combination of one value from each of `dimensions`, applied to `start` +/// in dimension order, each named for the values it took. +fn cross(start: Observation, dimensions: &[Dimension]) -> Vec { + let mut shapes = vec![Shape { + what: String::new(), + observation: start, + }]; + for dimension in dimensions { + shapes = shapes + .iter() + .flat_map(|shape| { + dimension.iter().map(move |(name, change)| { + let mut observation = shape.observation.clone(); + change(&mut observation); + let what = if shape.what.is_empty() { + (*name).to_owned() + } else { + format!("{}; {name}", shape.what) + }; + Shape { what, observation } + }) + }) + .collect(); + } shapes } +#[test] +fn the_crossed_dimensions_are_independent_of_each_other() { + // **What makes the product a product.** If two dimensions wrote the same + // field, the later would overwrite the earlier and a combination would + // quietly collapse into one of its halves -- reaching fewer states than its + // name claims, with nothing reporting it. So every value other than a + // dimension's first must change the observation, and any two values from + // different dimensions must give the same observation in either order. + // + // `Observation` has no `PartialEq`, so they are compared by their `Debug` + // rendering, which prints every field. + let after = |changes: &[&Change]| { + let mut observation = base(); + for (_, change) in changes { + change(&mut observation); + } + format!("{observation:?}") + }; + let unchanged = after(&[]); + let dimensions = crossed_dimensions(); + + for dimension in &dimensions { + let (first, rest) = dimension.split_first().expect("no dimension is empty"); + assert_eq!( + after(&[first]), + unchanged, + "`{}` is a dimension's first value, which is base()", + first.0 + ); + for value in rest { + assert_ne!(after(&[value]), unchanged, "`{}` changes nothing", value.0); + } + } + + for (index, left) in dimensions.iter().enumerate() { + for right in &dimensions[index + 1..] { + for a in left { + for b in right { + assert_eq!( + after(&[a, b]), + after(&[b, a]), + "`{}` and `{}` touch the same field", + a.0, + b.0 + ); + } + } + } + } +} + +#[test] +fn the_crossed_corpus_reaches_each_verdict_under_each_partitioning_arm() { + // **The crossing that M2.17 was written for, checked for having happened.** A + // product whose combinations collapsed -- say, a counter disagreement that + // a dimension silently cancelled -- would still be a long list of shapes. So + // under every partitioning arm the corpus must reach `incomplete` and + // `disagree`, and `agree` wherever the arm alone does not block it, and + // somewhere a shape must fill all three diagnostic lists at once, which is + // the combination no single-dimension shape can reach. + use windows_platform_probes::topology::Verdict; + + let dimensions = crossed_dimensions(); + let (arms, rest) = dimensions.split_first().expect("there are dimensions"); + for (arm, change) in arms { + let mut alone = base(); + change(&mut alone); + let reached: Vec = cross(alone.clone(), rest) + .iter() + .map(|shape| shape.observation.cross_check().verdict()) + .collect(); + + let mut wanted = vec![Verdict::Incomplete, Verdict::Disagree]; + if alone.cross_check().verdict() == Verdict::Agree { + wanted.push(Verdict::Agree); + } + for verdict in wanted { + assert!( + reached.contains(&verdict), + "under `{arm}` the crossed corpus never reaches {verdict:?}" + ); + } + } + + assert!( + cross(base(), &dimensions).iter().any(|shape| { + let check = shape.observation.cross_check(); + !check.disagreements.is_empty() + && !check.not_compared.is_empty() + && !check.parse_incomplete.is_empty() + }), + "no crossed shape fills all three diagnostic lists at once" + ); +} + #[test] fn every_representative_shape_agrees_with_itself() { // **M2.12.** Every instrument in this file was validated against ONE @@ -629,6 +901,64 @@ fn codes_for(state: invariant::BlockingState) -> &'static [&'static str] { State::EnumerationsDisagreed => &["enumerations_disagreed"], State::CoherenceNotCollected => &["coherence_not_collected"], State::BracketNotHeld => &["machine_changed", "bracket_not_established"], + State::CacheLevelsWithoutPartitions => &["cache_levels_without_partitions"], + State::MeasuredButCountsAbsent => &["measured_but_counts_absent"], + State::NumaDomainsOnlyInCpuSets => &["numa_domains_only_in_cpu_sets"], + State::NumaDomainsUnreported => &["numa_domains_unreported"], + State::NumaDomainsWithConflictingLabels => &["numa_domains_with_conflicting_labels"], + State::CoresOnlyInCpuSets => &["cores_only_in_cpu_sets"], + State::RelationsWithoutProcessors => &["relations_without_processors"], + State::OverlappingWalkRelations => &["overlapping_walk_relations"], + State::DescribedRelations => &["described_relations"], + State::UnreportedRelations => &["unreported_relations"], + State::ProcessorAttributeConflicts => &["processor_attribute_conflicts"], + } +} + +#[test] +fn every_parse_incomplete_code_is_answered_by_a_blocking_state() { + // **The census `M4.1` closed, held closed.** `blocking_states` once modelled + // part of `parse_incomplete` and justified the rest by an argument that was + // false, so a push site deleted from `cross_check` for any of eleven + // conditions left the verdict free to reach `agree` with nothing noticing. + // Both sides of this comparison are generated: `ALL_CODES` from the list + // that defines `ParseIncomplete::code`, and `BlockingState::ALL` from the + // list that defines the enum. So a new condition with no state fails here, + // rather than waiting for a review to find it. + // + // **And the other direction**, because a guard tested one way is half a + // guard: every code a state names must be one the row can publish, or the + // per-state publication rule demands a code nothing emits. + use windows_platform_probes::topology::{Disagreement, NotCompared, ParseIncomplete}; + + let answered: Vec<&str> = invariant::BlockingState::ALL + .iter() + .flat_map(|state| codes_for(*state).iter().copied()) + .collect(); + let unanswered: Vec<&str> = ParseIncomplete::ALL_CODES + .iter() + .copied() + .filter(|code| !answered.contains(code)) + .collect(); + assert!( + unanswered.is_empty(), + "`cross_check` can file {unanswered:?} and no blocking state answers it, so \ + deleting that push site lets the verdict reach `agree` unnoticed" + ); + + let publishable = [ + ParseIncomplete::ALL_CODES, + NotCompared::ALL_CODES, + Disagreement::ALL_CODES, + ] + .concat(); + for state in invariant::BlockingState::ALL { + for code in codes_for(*state) { + assert!( + publishable.contains(code), + "{state:?} names `{code}`, which no diagnostic list can publish" + ); + } } } diff --git a/crates/windows-platform-probes/tests/the_capture_scripts_refuse_runs_of_different_builds.rs b/crates/windows-platform-probes/tests/the_capture_scripts_refuse_runs_of_different_builds.rs new file mode 100644 index 000000000..e5a5d75e8 --- /dev/null +++ b/crates/windows-platform-probes/tests/the_capture_scripts_refuse_runs_of_different_builds.rs @@ -0,0 +1,129 @@ +// Copyright (c) Mike Grier. + +//! The capture scripts refuse to combine runs that different builds produced +//! (`M4.8`), and still accept the runs they were written for. +//! +//! The scripts are JavaScript and nothing else runs them, so this test does, +//! with `node`, against copies of committed runs given a `build:` line. It runs +//! every committed copy of each script, since each capture directory keeps the +//! copy that derived its figures. +//! +//! **It needs `node` on `PATH`**, and fails saying so rather than passing +//! without having run anything. + +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +const SCRIPTS: &[&str] = &["summarise.js", "isolated.js"]; + +fn captures() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("captures") +} + +/// Every capture directory that carries the scripts. +fn script_directories() -> Vec { + let mut found: Vec = std::fs::read_dir(captures()) + .expect("the captures directory exists") + .map(|entry| entry.expect("a readable entry").path()) + .filter(|dir| SCRIPTS.iter().all(|script| dir.join(script).is_file())) + .collect(); + found.sort(); + assert!( + !found.is_empty(), + "no capture directory carries the scripts" + ); + found +} + +/// A committed run, with `build` inserted after its `profile:` line when given. +fn run_with_build(source: &str, build: Option<&str>) -> String { + let text = + std::fs::read_to_string(captures().join("2026-10-19-interleaved-order").join(source)) + .expect("a committed run"); + let Some(build) = build else { + return text; + }; + let mut out = String::new(); + for line in text.lines() { + out.push_str(line); + out.push('\n'); + if line.starts_with("profile:") { + out.push_str(build); + out.push('\n'); + } + } + out +} + +/// Run `script` over two runs, each with the build line given. +fn combine(directory: &Path, script: &str, first: Option<&str>, second: Option<&str>) -> Output { + // A counter as well as the process id, because the tests below run in + // parallel in one process and each calls this many times. + static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + let scratch = std::env::temp_dir().join(format!( + "capture-scripts-{}-{}", + std::process::id(), + NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + )); + std::fs::create_dir_all(&scratch).expect("a scratch directory"); + let a = scratch.join("a.txt"); + let b = scratch.join("b.txt"); + std::fs::write(&a, run_with_build("interleaved-run1.txt", first)).expect("written"); + std::fs::write(&b, run_with_build("interleaved-run2.txt", second)).expect("written"); + + let output = Command::new("node") + .arg(directory.join(script)) + .arg(&a) + .arg(&b) + .output() + .unwrap_or_else(|error| { + panic!("this test runs the capture scripts and needs `node` on PATH: {error}") + }); + let _ = std::fs::remove_dir_all(&scratch); + output +} + +const ONE: &str = "build: !!UNOFFICIAL!! v2026.1016.0 0123456789ab [LOCAL]"; +const OTHER: &str = "build: !!UNOFFICIAL!! v2026.1016.0 ba9876543210 [LOCAL]"; + +#[test] +fn runs_of_different_builds_are_refused() { + for directory in script_directories() { + for script in SCRIPTS { + for (first, second) in [ + (Some(ONE), Some(OTHER)), + (Some(ONE), None), + (None, Some(OTHER)), + ] { + let output = combine(&directory, script, first, second); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success() && stderr.contains("different conditions"), + "{}/{script} combined {first:?} with {second:?}: {:?}\n{stderr}", + directory.display(), + output.status, + ); + } + } + } +} + +#[test] +fn runs_of_one_build_are_accepted_whether_stamped_or_not() { + // The other direction, because a script that refused everything would pass + // the test above. Unstamped runs are what every committed capture holds, so + // refusing them would leave no capture regenerable. + for directory in script_directories() { + for script in SCRIPTS { + for build in [Some(ONE), None] { + let output = combine(&directory, script, build, build); + assert!( + output.status.success(), + "{}/{script} refused two runs of {build:?}: {}", + directory.display(), + String::from_utf8_lossy(&output.stderr), + ); + } + } + } +} diff --git a/crates/windows-platform-probes/tests/the_probes_that_emit_a_machine_readable_row.rs b/crates/windows-platform-probes/tests/the_probes_that_emit_a_machine_readable_row.rs index b8652eb3d..d0a48011e 100644 --- a/crates/windows-platform-probes/tests/the_probes_that_emit_a_machine_readable_row.rs +++ b/crates/windows-platform-probes/tests/the_probes_that_emit_a_machine_readable_row.rs @@ -1,6 +1,7 @@ // Copyright (c) Mike Grier. -//! Which probes carry a machine-readable row, established by running them. +//! Every registered probe emits a machine-readable row, established by running +//! them. //! //! **This replaces a unit test that asked the wrong question and got the right //! answer.** That test walked `src/bin` and grepped each file for the string @@ -18,46 +19,65 @@ //! one that crosses a process boundary, which is this one. //! //! The binary list comes from `Cargo.toml` rather than from a list kept here, so -//! a probe added tomorrow joins the census without anyone remembering to add it. +//! a probe added tomorrow joins the census without anyone remembering to add it +//! -- and, since `M8.14`, fails it unless it emits a row, because the rule +//! (`d-every-probe-emits-a-row` in DESIGN-NOTES.md) is that every probe does. +//! Until then this test kept a list of the probes that emitted one, which a +//! probe without a row could join as easily as one with. +use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Child, Command, Stdio}; +use std::sync::mpsc::{self, RecvTimeoutError}; +use std::time::{Duration, Instant}; -/// The probes whose report carries an `x-probe-*` NDJSON line. +/// What a row looks like as it is written: the start of its `reason` member. +/// Not the bare tag -- a probe that only mentions `x-probe` in prose is not a +/// probe that emits one, and that exact confusion is why this test moved off a +/// source grep. +const ROW: &str = r#""reason":"x-probe"#; + +/// How long one probe may run before the census gives up on it. /// -/// Measured, not recalled. A document that describes the split -- `M4.10` in -/// CHECKLIST.md is the current one -- is corrected by this failing rather than -/// by somebody noticing. -const EMIT_A_ROW: [&str; 3] = [ - "probe-doorbell-cost", - "probe-request-cost", - "probe-topology", -]; - -/// The probes this test does not run, and why each one is excluded. +/// **Every probe runs under it**, because `Command::output` waits on a child +/// without limit, and one probe's subject is a call that can fail to return. A +/// probe that runs out of time fails the census with its name rather than +/// hanging the suite. The longest run is the cancel-io probe's worst case, +/// several five-second watchdogs; this is several times that. +const DEADLINE: Duration = Duration::from_secs(120); + +/// Probes the census does not wait to exit once their row has been read. /// -/// A list of pairs rather than a bare name, because an exclusion without its -/// reason is indistinguishable from an oversight -- and the second entry here -/// was exactly that until a review found it. +/// - `probe-cancel-io` measures a call that **can fail to return**, and its own +/// module doc draws the conclusion: a canceller wedged that way is left +/// running, since nothing can stop it safely. Whether such a thread also holds +/// up the process's exit is not something this census should stake a hang on. +/// Its report streams line by line, so its row is written before `main` +/// returns; reading the row is the census's whole question, and having read +/// it the census ends the process rather than waiting for it. /// -/// - `probe-queue-contention` is the contention benchmark: a host-dependent run -/// its own module doc puts at about sixty-five seconds, with no short mode. -/// Ten seconds for the rest is worth paying every time; seventy-five is not. -/// - `probe-cancel-io` measures a call that **can fail to return**. Its own -/// module doc says so and draws the conclusion: "Binary only, and deliberately -/// not a test ... a wedged `#[test]` would take the whole suite with it." It -/// guards each case with an internal watchdog, but `Command::output` waits on -/// the child without one, so running it here would reintroduce the hazard that -/// probe is shaped to avoid. +/// This is why the probe can be censused at all. Until `M8.14` it was excluded +/// and checked only for an emission literal in its sources -- a question a row +/// built with `Row::new` answers falsely. +const MAY_NOT_EXIT: [&str; 1] = ["probe-cancel-io"]; + +/// Probes run with a command line that keeps them short, and that command line. /// -/// Both are still censused, by the one question a source can answer soundly -- -/// whether the emission literal appears anywhere in their sources at all. That -/// is weaker than running them, and it is stated as weaker rather than blended -/// in with the rest. -const NOT_RUN: [(&str, &str); 2] = [ - ("probe-queue-contention", "src/bin/queue_contention"), - ("probe-cancel-io", "src/bin/cancel_io.rs"), -]; +/// The settings change how much is measured, never which lines the report +/// carries, so a short run answers the census's question as well as a full +/// one. One producer, one repetition and a thousand pushes take a fraction of a +/// second against the default's minute. +const SHORT_RUNS: [(&str, &[&str]); 1] = [( + "probe-queue-contention", + &[ + "--producers", + "1", + "--repetitions", + "1", + "--pushes-per-producer", + "1000", + ], +)]; /// Every `[[bin]]` name the manifest registers. fn registered_binaries(manifest: &str) -> Vec { @@ -95,25 +115,97 @@ fn binary_directory() -> PathBuf { .to_path_buf() } -/// Every `.rs` file under `root`, at any depth. -fn walk(root: &Path) -> Vec { - let mut found = Vec::new(); - let Ok(entries) = std::fs::read_dir(root) else { - return found; - }; - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - found.extend(walk(&path)); - } else if path.extension().is_some_and(|ext| ext == "rs") { - found.push(path); +/// Ends a child the census has given up on, without waiting for it. +/// +/// Waiting is exactly what cannot be relied on for the one probe whose subject +/// can fail to return, so the child is terminated and left. +fn abandon(child: &mut Child) { + let _ = child.kill(); +} + +/// Run one probe under `deadline` and say whether its report carried a row. +/// +/// `may_not_exit` ends the process as soon as a row is read, for a probe in +/// [`MAY_NOT_EXIT`]; every other probe must also exit successfully in time. +/// +/// # Panics +/// +/// Panics, naming the probe, when it cannot be started, does not finish its +/// report in time, or exits unsuccessfully -- a probe that cannot run cannot be +/// censused. +fn emits_a_row(name: &str, mut command: Command, may_not_exit: bool, deadline: Duration) -> bool { + let mut child = command + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .unwrap_or_else(|error| panic!("{name} could not be run: {error}")); + let stdout = child.stdout.take().expect("stdout was piped"); + + // Lines arrive on another thread so the wait below has a deadline; a read + // on the pipe itself has none. + let (send, lines) = mpsc::channel::(); + std::thread::spawn(move || { + for line in BufReader::new(stdout).split(b'\n') { + let Ok(line) = line else { break }; + if send + .send(String::from_utf8_lossy(&line).into_owned()) + .is_err() + { + break; + } + } + }); + + let limit = deadline; + let deadline = Instant::now() + limit; + let mut row = false; + loop { + match lines.recv_timeout(deadline.saturating_duration_since(Instant::now())) { + Ok(line) => { + if line.contains(ROW) { + row = true; + if may_not_exit { + abandon(&mut child); + return true; + } + } + } + Err(RecvTimeoutError::Disconnected) => break, + Err(RecvTimeoutError::Timeout) => { + abandon(&mut child); + panic!("{name} did not finish its report within {limit:?}"); + } + } + } + + // The report is closed, so the probe is exiting; it still has to say how. + loop { + match child.try_wait() { + Ok(Some(status)) => { + assert!( + status.success(), + "{name} exited {:?}; a probe that cannot run cannot be censused", + status.code() + ); + return row; + } + Ok(None) if Instant::now() < deadline => { + std::thread::sleep(Duration::from_millis(10)); + } + // Not reached by a test below: it needs a child that closes its + // own standard output and then keeps running, which no `cmd.exe` + // one-liner does. A probe that freed its console and kept working + // would reach it. + Ok(None) | Err(_) => { + abandon(&mut child); + panic!("{name} closed its report but did not exit within {limit:?}"); + } } } - found } #[test] -fn the_probes_that_emit_a_machine_readable_row_are_the_ones_we_say_they_are() { +fn every_registered_probe_emits_a_machine_readable_row() { let manifest = std::fs::read_to_string(crate_root().join("Cargo.toml")) .expect("the crate's manifest is readable"); let registered = registered_binaries(&manifest); @@ -122,87 +214,144 @@ fn the_probes_that_emit_a_machine_readable_row_are_the_ones_we_say_they_are() { "no `[[bin]]` targets were read from the manifest, so this test checked \ nothing -- the manifest's shape has probably changed" ); - for (name, _) in NOT_RUN { + for name in MAY_NOT_EXIT { + assert!( + registered.iter().any(|registered| registered == name), + "`{name}` is excused from exiting by name, so it must still be a \ + registered target; if it was renamed, the renamed probe would be \ + waited on, which is the hang the excuse exists to avoid" + ); + } + for (name, _) in SHORT_RUNS { assert!( registered.iter().any(|registered| registered == name), - "`{name}` is excluded by name, so it must still be a registered \ - target; if it was renamed, this exclusion is silently covering a \ - probe nobody is censusing" + "`{name}` is given a short command line by name, so it must still be \ + a registered target; if it was renamed, the renamed probe is run at \ + its full length" ); } let directory = binary_directory(); - let mut emitting: Vec = Vec::new(); + let mut without_a_row: Vec<&str> = Vec::new(); for name in ®istered { - if NOT_RUN.iter().any(|(excluded, _)| excluded == name) { - continue; - } let executable = directory.join(format!("{name}{}", std::env::consts::EXE_SUFFIX)); - let output = Command::new(&executable) - .output() - .unwrap_or_else(|error| panic!("{} could not be run: {error}", executable.display())); - assert!( - output.status.success(), - "{name} exited {:?}; a probe that cannot run cannot be censused", - output.status.code() - ); - // The row as it is actually written, not the bare tag: a probe that only - // mentions `x-probe` in prose is not a probe that emits one, and that - // exact confusion is why this test moved off a source grep. - if String::from_utf8_lossy(&output.stdout).contains(r#""reason":"x-probe"#) { - emitting.push(name.clone()); + let arguments = SHORT_RUNS + .iter() + .find(|(short, _)| short == name) + .map_or(&[][..], |(_, arguments)| *arguments); + let mut command = Command::new(&executable); + command.args(arguments); + let may_not_exit = MAY_NOT_EXIT.contains(&name.as_str()); + if !emits_a_row(name, command, may_not_exit, DEADLINE) { + without_a_row.push(name); } } - emitting.sort(); - assert_eq!( - emitting, EMIT_A_ROW, - "the set of probes emitting a machine-readable row has changed; update \ - `EMIT_A_ROW` and every document that describes the split, rather than \ - leaving the two to disagree" + assert!( + without_a_row.is_empty(), + "every probe's normal run emits a row of raw data (`d-every-probe-emits-a-row` \ + in DESIGN-NOTES.md), and these did not: {without_a_row:?}" + ); +} + +/// A `cmd.exe` running `script`, with a stdin it can wait on forever. +/// +/// The census's deadline paths need a child that does not finish, and `set /p` +/// blocks reading a pipe nothing writes to. It is a `cmd` builtin, so ending +/// `cmd` ends it -- no grandchild outlives the test. +fn cmd(script: &str) -> Command { + use std::os::windows::process::CommandExt; + let mut command = Command::new("cmd.exe"); + command + .raw_arg(format!("/d /c {script}")) + .stdin(Stdio::piped()); + command +} + +#[test] +fn a_report_that_never_finishes_fails_the_census_by_its_deadline() { + let started = Instant::now(); + let outcome = std::panic::catch_unwind(|| { + emits_a_row( + "hangs", + cmd("set /p never="), + false, + Duration::from_millis(500), + ) + }); + let message = *outcome + .expect_err("a probe that never finishes must fail the census") + .downcast::() + .expect("a formatted message"); + assert!(message.contains("did not finish its report"), "{message}"); + assert!( + started.elapsed() < Duration::from_secs(30), + "the deadline bounded the wait" ); +} - // The excluded probes, by the weaker question. If an emission literal ever - // appears in their sources this stops being answerable without running - // them, and the assertion says so rather than quietly going stale. - for (name, source_path) in NOT_RUN { - let path = crate_root().join(source_path); - // **One read for both shapes, and it fails loudly.** These were two - // reads with opposite failure behaviour: the file branch panicked on an - // unreadable source, the directory branch mapped it to an empty string - // through `unwrap_or_default`. An empty string contains no emission - // literal, so a source this census could not read passed it -- and the - // non-empty guard below passed too, because the vector still had an - // entry. The weaker half of the census could therefore report a clean - // answer having read nothing, which is the one outcome it must not have. - let read = |file: &Path| -> String { - std::fs::read_to_string(file).unwrap_or_else(|error| { - panic!( - "{name}'s source at {} could not be read: {error} -- the \ - source census cannot stand in for running it if it cannot \ - read it", - file.display() - ) - }) - }; - let sources: Vec = if path.is_dir() { - walk(&path).iter().map(|file| read(file)).collect() - } else { - vec![read(&path)] - }; - assert!( - !sources.is_empty(), - "no sources were found for `{name}` at {}, so its half of the \ - census checked nothing", - path.display() - ); - assert!( - sources - .iter() - .all(|source| !source.contains(r#""reason":"x-probe"#)), - "`{name}` has gained an emission literal, so the source question can \ - no longer stand in for running it -- either run it here and accept \ - the cost, or state plainly that it is uncensused" - ); - } +#[test] +fn a_probe_excused_from_exiting_is_ended_once_its_row_is_read() { + // Both directions: the row is found while the process is still running, and + // the same child, not excused, is not ended on the row -- it runs into the + // deadline instead. + let script = r#"echo {"reason":"x-probe-test"}& set /p never="#; + let started = Instant::now(); + assert!(emits_a_row( + "excused", + cmd(script), + true, + Duration::from_secs(60) + )); + assert!( + started.elapsed() < Duration::from_secs(30), + "the census stopped waiting once it read the row" + ); + + let outcome = std::panic::catch_unwind(|| { + emits_a_row( + "not excused", + cmd(script), + false, + Duration::from_millis(500), + ) + }); + assert!( + outcome.is_err(), + "a probe that is not excused must still finish in time" + ); +} + +#[test] +fn a_probe_that_exits_unsuccessfully_fails_the_census() { + let outcome = std::panic::catch_unwind(|| { + emits_a_row( + "fails", + cmd(r#"echo {"reason":"x-probe-test"}& exit 3"#), + false, + Duration::from_secs(60), + ) + }); + let message = *outcome + .expect_err("a probe that exits unsuccessfully must fail the census") + .downcast::() + .expect("a formatted message"); + assert!(message.contains("exited Some(3)"), "{message}"); +} + +#[test] +fn a_report_is_read_as_written_whether_or_not_it_carries_a_row() { + assert!(emits_a_row( + "row", + cmd(r#"echo prose& echo {"reason":"x-probe-test","n":1}"#), + false, + Duration::from_secs(60), + )); + // A mention of the tag in prose is not a row. + assert!(!emits_a_row( + "prose", + cmd("echo this probe writes x-probe-test in prose only"), + false, + Duration::from_secs(60), + )); } diff --git a/crates/windows-threadpool-sys/CHECKLIST.md b/crates/windows-threadpool-sys/CHECKLIST.md index 1d0b8cbf5..dc7be4c68 100644 --- a/crates/windows-threadpool-sys/CHECKLIST.md +++ b/crates/windows-threadpool-sys/CHECKLIST.md @@ -264,1068 +264,14 @@ component nobody has written. **`M-T7.1` is unrelated and does not gate this.** That item is the pool *stall*; this is pool *placement*. They share a crate and nothing else. -## M-T10 -- The second review round: ordering, serialization, and a test that never ran +## M-T16 -- Follow-ups to the `Event` conversion -Opened 2026-10-02 by a review of everything on this branch, and grown by each review round that -followed it; every item is in `windows-threadpool-sys`. The round's lesson is recorded with -`M-T10.1`: the reviewer named two sites for a rule that lived at eight, and the sweep the house -rules require is what found the third. +Graduated 2026-10-21 from `M-T14+`, whose gate, [M-T14](COMPLETED-CHECKLIST.md#m-t141), is complete. -- [x] **M-T10.1** -- **Record the drain obligation before the arming is published, at one site - rather than eight.** The re-arm paths in `wait` and `timer`, and `work`'s submit, set the - obligation flag *after* the native call that publishes the arming. A dispatch entering in - that window settles the obligation and is then overwritten, so a correctly-drained object - claims it owes a drain -- under `fail-fast` a panic, and an abort if `Drop` was already - unwinding. Fixed by moving the pair into `CloseObligation::record_live_before` and making - the bare store private, so the inverted order is a compile error. Recorded as - [The drain obligation is recorded before the arming is - published](../../DESIGN-NOTES.md#obligation-recorded-before-arming). - -- [x] **M-T10.2** -- **Make `tests/obligation_report.rs` run in CI, then make it pass there.** - - **The defect, and which half matters.** The test deliberately drops objects that owe a drain, so - under `--all-features` -- which arms `fail-fast` -- it exits 101. That is the reported symptom. - The *defect* is why nobody noticed: the test early-returns when no trace filter is set, so on - every CI run it does nothing and reports success. It is inert, and an inert test that reads as - green is worse than an absent one. - - **Target.** Fix the inertness first and confirm the test then fails, which is what shows the - early return was hiding it. Then make it pass under `fail-fast` -- either by catching the - expected panic, or by gating the owing cases off the feature -- without reintroducing a path - where the whole body is skipped silently. - - **Sabotage it**: break the behaviour the test asserts and confirm it goes red *in the - configuration CI actually runs*. A guard that only fires under a locally-set environment - variable has not been shown to guard anything. - -- [x] **M-T10.3** -- **Serialize every entry point that suspends other threads.** Done by a - `Mutex` inside `with_others_suspended` itself rather than a guard at each caller, so the - rule holds for every path into it including ones not yet written. `install_requested`'s - `AtomicBool` stops it running twice, which is idempotence and not mutual exclusion; - `install_by_label` had nothing. - - **The reported hang did not reproduce, and the report should not be relied on.** The review - said two named tests hang at `--test-threads 2` and pass 20 of 20 at `--test-threads 1`. Neither - half holds here: 0 hangs in 40 runs of that pair at `--test-threads 2`, 0 hangs in 25 runs of - the whole `trace::` module at `--test-threads 2`, and the pair *fails* rather than passes at - `--test-threads 1` -- for the unrelated reason that became `M-T10.6`. - - **The fix is justified analytically, not by that measurement.** Two threads inside - `with_others_suspended` can each enumerate the other and then suspend it, leaving neither - running to resume the other; `SuspendThread` is documented as not guaranteeing the suspension - is complete when it returns, which is the window. One `Mutex` removes the state entirely and - costs nothing on a facility that installs hooks once per process. An unreproduced hazard that a - cheap change makes unrepresentable is worth closing; what is not acceptable is recording it as - measured when it was not. - -- [x] **M-T10.4** -- **DECIDED 2026-10-02: hot-patching is exclusive, and the inline hooks spend - that exclusivity knowingly.** Documented as a hazard rather than engineered around, and - recorded as [Hot-patching is - exclusive](../../DESIGN-NOTES.md#hot-patching-is-exclusive). - - **The decision reframed the finding, and the reframing is the useful part.** The review raised - a patch window: suspension does not place a thread's instruction pointer *outside* the fourteen - bytes being overwritten. True, and the narrower half. The larger half is that inline hooking is - **process-exclusive** -- one slot per stub, no way to share or negotiate, and a second patcher - captures the first one's jump as though it were the original. Setting - `WINDOWS_THREADPOOL_TRACE_HOOKS` therefore gives up hot-patching those stubs for the life of - the process, and the crate never undoes it. - - **Why documenting beats fixing.** Relocating trapped instruction pointers would close the - window and would not make two patchers able to share a stub, because the exclusivity belongs to - the technique and not to this implementation. The expensive fix buys the smaller half. The cost - is better spent making the choice visible where it is made, which is what this item did: the - module docs own the statement, the variable's own documentation and the README point at it. - - **The window itself is narrower than reported -- but not for the reason this item first gave.** - Installation runs from the `.CRT$XCU` initialiser before `main`, so no other thread exists to be - inside the range. This item originally asserted that as already true; a later review found it - was not. Installation sat at the end of the public `observe_exceptions`, which `enabled` calls - only when the trace is armed, so a process with `WINDOWS_THREADPOOL_TRACE_HOOKS` set and - `WINDOWS_THREADPOOL_TRACE` unset could reach the installer after its threads existed. Closed by - `M-T10.8`, which moved installation into the initialiser and sealed the window. What remains is - `install_by_label`, which is `#[cfg(test)]`. - -- [x] **M-T10.6** -- **Stop the recogniser's live canary reading a stub a sibling test has - patched.** Found while trying to reproduce `M-T10.3`'s reported hang, which is the only - reason it was found at all. - - **The defect.** `the_stub_recogniser_accepts_the_shape_and_rejects_everything_else` ended by - resolving the `selftest` hook target and asserting the bytes there are a syscall stub -- the - canary that says the recognised shape still describes this machine's Windows. - `a_hooked_stub_records_both_ends_and_still_performs_its_syscall` patches that same stub and, by - design, never removes it. Whichever ran first decided the answer, so the canary was asserting - the planted jump rather than the shape Windows shipped. - - **Measured.** 25 of 25 runs of the `trace::` module failed at `--test-threads 2` before the - fix and 0 of 25 after. It is an order dependency and not a thread-count race: running just the - two tests single-threaded, with the hooking one first, failed too. The full suite passes only - because the default thread count happens to order them the other way -- luck that changes with - the test count, the machine, or a rename. - - **Fixed** by giving the canary `unhookable_stub_entry()`, an `ntdll` export deliberately absent - from `HOOKS` and asserted to be absent, so this module cannot patch it however many hooks a run - installs. - -- [x] **M-T10.8** -- **Enforce the one-thread hook-installation window at the installer.** Found - by the third review round, which falsified a claim `M-T10.4` had written into three - documents one commit earlier. - - **The defect.** Hook installation sat at the end of `observe_exceptions`, which `enabled` calls - only when the trace is armed. A process with `WINDOWS_THREADPOOL_TRACE_HOOKS` set and - `WINDOWS_THREADPOOL_TRACE` unset therefore left the installer's once-flag unconsumed, and - `observe_exceptions` is public -- so a later call from a running process would patch `ntdll` - with the pool's threads alive, the one case the recogniser cannot make safe. - - **The shape is the lesson.** The guarantee was carried by *which function happened to call the - installer*: correct while nobody moved the call, silently false afterwards, and checked by - nothing. Fixed by moving installation into the pre-`main` initialiser, unconditionally and - independent of the trace, then sealing the window; `install_requested` now refuses and records - `refused-after-seal`. Guarded by `the_hook_installation_window_is_shut_before_any_test_runs`, - verified by sabotage. - -- [x] **M-T10.9** -- **Take no process lock while the other threads are suspended.** Found by the - third review round. - - **The defect.** The suspended window took three process-wide locks: the allocator (through - `ntdll_proc`'s owned name, and through the suspend loop consuming its enumeration vector by - value so the `Vec` was freed with threads already stopped), the loader (`GetModuleHandleA`, - `GetProcAddress`), and the memory manager (`VirtualAlloc`, `VirtualProtect`). A thread stopped - holding any of them can never give it back. - - **The shape is the lesson, again.** The window's own comment said "Nothing in here may - allocate", and the enumeration vectors were reserved ahead of time for exactly that reason, with - a control in `sabotage.json` recording it -- while the install the window wrapped called the - loader three lines later. The rule was stated at the allocation somebody noticed rather than at - the window it belongs to. - - **Fixed** by splitting install into prepare / commit / finish: everything that can take a lock - happens while the process is still running, and the window holds a single fourteen-byte store. - -- [x] **M-T10.10** -- **Mark a cancelling group release's repairs after the cancellation, not - before.** Found by the third review round. - - **The defect.** `CleanupGroup::close_members(true)` marked each member's pool as owing a repair - *before* calling `CloseThreadpoolCleanupGroupMembers`, justified in a comment by the claim that - the native release frees the member contexts. It does not -- this crate frees them, in the loop - immediately after. So the justification was false and the ordering it bought was harmful: the - healer could see the mark, find the pool still dispatching, clear it as repaired, and then the - real cancellation would happen with no mark outstanding. An unrepaired wedge from a **single** - cancellation, where the overlapping-cancellation race already recorded in - [`M-T9.2`](COMPLETED-CHECKLIST.md#m-t92) needs two. - - **Guarded.** `a_cancelling_release_marks_its_pool_after_the_cancellation` installs a test-only - hook that clears the mark at the instant before the native release, standing in for the healer's - tick landing there -- which is necessary because both orderings leave a mark outstanding once - the release has returned, so no end-state assertion can tell them apart. Verified by sabotage: - restoring the old ordering fails it. - -- [x] **M-T10.11** -- **Give the worker-factory snapshot named fields.** Found by the third review - round. - - **The defect.** `worker_factory_snapshot` returned `(usize, u32, u32, u32)` and its doc comment - described `(handle, total, waiting, pending)` -- every field after the handle shifted by one - from what the code returns, which is the handle, the thread *maximum*, the total workers and the - waiting workers. A caller who believed the comment read the configured maximum as the number of - workers that exist, so a cold or stalled pool looked fully staffed: the exact inverse of the - reading the data is gathered for. There is no `pending` field at all. - - **Fixed at the build rung, not in the comment.** The return type is now - `WorkerFactorySnapshot` with named fields, so the mismatch is unrepresentable. Nothing could - have caught the tuple version: both halves type-check, and a tuple carries no statement about - which field is which for anything to check against. The consuming tests destructured correctly, - which is why the suite stayed green and the defect lived only in what a reader was told. - -- [x] **M-T10.12** -- **Make the lifecycle trace test run, and expect what teardown actually - calls.** Found by the third review round; the same inertness as `M-T10.2`, which was fixed - without sweeping for siblings. - - **Two defects, one hiding the other.** The test early-returned when the trace filter was unset, - which is every ordinary run and every CI run. Underneath that, it expected - `WaitForThreadpoolWaitCallbacks(cancel)` and `WaitForThreadpoolTimerCallbacks(cancel)` -- forms - that only `try_cancel_pending` makes, and that teardown stopped making when it changed from - cancelling to draining. Run alone with the filter armed it failed; run in a full armed suite it - passed, because sibling tests supplied those records into the one process-wide buffer and - `counted` only asks for a non-zero count. - - **Fixed** by re-executing the one test single-threaded in a child with the filter set, and by - correcting the two expectations. The child exits with a distinctive code, so a filter that - matched nothing -- a rename -- fails loudly instead of exiting 0 and looking like a pass. - Verified by sabotage in both directions: the old labels fail the body, and a wrong test name - fails the parent. - - **The sweep this time:** two tests had the early-return shape; - `the_exception_observer_notes_a_first_chance_exception` passes when armed. - -- [x] **M-T10.13** -- **Restore page protection once per page, not once per stub.** A defect - introduced by `M-T10.9`'s own fix, found by the fourth review round. - - **The defect.** `VirtualProtect` reports the previous protection of the whole **page**, not of - the byte range asked about. Splitting install into prepare / commit / finish moved protection - to one save-and-restore per stub, so two stubs sharing a page had the second save the writable - state the first had just installed -- and the last restore left `ntdll` executable **and - writable** for the rest of the process's life. `park`, `set-info` and `shutdown` share a page on - this host. The single-phase install it replaced did not have this, because it protected and - restored around each store in turn. - - **Guarded** by `stubs_sharing_a_page_are_opened_once_and_the_page_is_restored`, hermetic on a - page the test allocates rather than on `ntdll` -- the real stubs are patched once per process, - so whichever test ran first would decide the answer. Verified by sabotage. - -- [x] **M-T10.14** -- **Read `AlreadySignaled` only on a successful association.** The - `associate` hook read its out-parameter unconditionally after forwarding the call. A failed - `NtAssociateWaitCompletionPacket` need not have written through that pointer, or validated - it, so the instrument could turn an ordinary error return into an access violation raised - by the tracing facility itself. A failed association has nothing to report there anyway: - no packet was associated, and the `-leave` record already carries the status. - -- [x] **M-T10.15** -- **Make the periodic-timer exercise reach its second re-arm entry point.** - The callback switched on a `load` taken *after* its own `fetch_add`, so the second firing - compared 2 against 1 and the `rearm_at` arm was unreachable: the exercise finished a firing - early having driven only `rearm_after`, while its comment claimed both. Both entry points - emit the same event tag, so neither the test nor the trace assertion it feeds could notice. - Now switches on the value `fetch_add` returns. - -- [x] **M-T10.16** -- **Run every trace assertion in a trace-armed child, from one site.** The - sweep for `M-T10.12` asked the wrong question -- it grepped for early `return`, while - `io::tests`' two trace blocks are `if wants(..) { .. }` -- so both were still inert. - - **Fixed at one site rather than a fourth copy.** `trace::in_a_trace_armed_child` now owns the - parent/child protocol; the lifecycle test and both `io::tests` blocks go through it. The first - two hand-written copies had already disagreed about how to distinguish a child that ran from - one whose filter matched nothing. Verified by sabotage: removing the `start-cancelled` record - fails the child on its assertion and the parent on its exit-code check. - - **The real population**, found by grepping for `wants(` rather than for `return`: four sites. - Two were these; one is `the_filter_narrows_to_the_targets_named`, where the gating *is* the - subject; the fourth was the hook records, converted by `M-T10.5`. Re-run after that conversion, - the grep leaves no assertion *silently* skipped on an unset filter: what remains is this crate's - `wants` definition and macros, four sites that **assert** `wants` is true inside an armed child, - the one test whose subject is the gating, and `tests/obligation_report.rs`, which gates but then - exits with a distinct code its parent checks rather than passing quietly. - -- [x] **M-T10.17** -- **Bound the warm-up's teardown on the path where its callback never - arrived.** `prewarm_default_pool` timed out after two seconds and then called - `stop_and_drain`, which waits with no deadline for the very callback whose absence caused - the timeout -- so the bounded check was followed by an unbounded one and the `false` it - exists to return could never arrive. It now cancels the queued invocation first, on that - path only. - - **Not covered by a test**, and stated plainly rather than papered over: reaching the timeout - needs a pool that has stopped dispatching, which is the stall this repository has spent `M-T7` - failing to produce on demand. - -- [x] **M-T10.19** -- **Handle the failures the patch path assumed away, and check the branch the - trampoline does not rebuild.** Three findings from the fifth review round, all in - `trace/hook.rs`, two of them the same recorded rule broken twice. - - **Two unchecked failable calls**, against [A failable call has its failure handled, - always](../../DESIGN-NOTES.md#a-failable-call-has-its-failure-handled-always). The trampoline - page's `VirtualProtect` result was discarded and the pointer published regardless, so a failure - left the hook jumping into a page the processor will not execute. And `with_others_suspended` - skipped past a thread it could not open or suspend and patched anyway -- while that thread was - still running through the bytes being overwritten, which is the precondition the suspension - exists to establish. Both now refuse, with their own `Refusal` variants. - - **The refusal had to learn a distinction, and a test is what taught it.** Refusing on every - failure made `install_by_label` fail in an ordinary test process, because the thread snapshot is - taken while the process runs and a pool starting and finishing workers routinely leaves a listed - thread gone before `OpenThread` reaches it. `ERROR_INVALID_PARAMETER` names that case, and a - thread that does not exist cannot be executing the bytes -- so it is skipped and everything else - refuses. - - **An assumption the module did not admit to.** The recognised stub tests a shared-data byte and - branches past `syscall`; the trampoline rebuilds only the fall-through, so "the same call by - construction" held while that bit is clear and not otherwise. It is clear on ordinary x64, which - is why it went unstated -- an observation about this machine rather than a property of the - technique. `syscall_path_is_direct` now reads it, derived from the recogniser's own bytes rather - than written out a second time, and an install refuses when it is set. - -- [x] **M-T10.20** -- **Stop the fail-fast and the cancellation safety claim promising more than - they detect.** Two findings from the fifth review round that are not defects in the code but - in what it says. - - **The fail-fast** records an outstanding *arming*, not "this `Drop` blocked". A dispatch settles - it for a wait and a one-shot timer while `Drop` still waits for that callback, so dropping - during an executing callback drains without reporting -- and `CleanupGroup` carries no - obligation flag at all. Both now stated at the one site that owns the message. - - **`try_cancel_pending`** is documented as safe because the crate repairs the pool afterwards. - On a pool whose repair item could not be created there is no entry and nothing repairs it, and - `owe_repair` returned in silence. It now records `cancel-untracked`, and both statements of the - claim name the exception. - -- [x] **M-T10.21** -- **Make the thread enumeration fail closed before anything is patched.** The - sixth review round. `with_others_suspended` refused when a thread could not be opened or - suspended, but every way of failing to *find* the threads reported success: a snapshot that - could not be taken, an enumeration that could not be started, and a thread count past `ROOM` - each left `ids` empty or short, which read as "everything is suspended" and patched fourteen - bytes of live code with nothing stopped. Strictly worse than the per-thread case fixed one - block above it in `M-T10.19`. An `enumerated` flag now gates `quiesced`, and the suspend loop - is skipped entirely when it is false, so a refused install perturbs nothing. - -- [x] **M-T10.22** -- **Stop a transient healer-start failure disabling self-heal for the process.** - `ensure_running` cached its result in a `OnceLock>` built with `.ok()?`, so one - failed `ThreadpoolPool::new()` -- a transient condition -- latched `None` permanently and - silently, and no later cancellation anywhere in the process was ever repaired. Now an - `AtomicBool` fast path over a `Mutex>`: a failure records `healer-start-failed` - and leaves the slot empty, so the next cancellation retries. - -- [x] **M-T10.23** -- **Isolate the heal tests from every other tick in the process.** `tick` walks - the whole registry, so a private pool does not isolate a test: another test's tick, or the - background healer's, can clear a mark between the cancellation that sets it and the assertion - about it. A `TICK_GATE` now brackets each critical section. - - **Reproduction, stated honestly.** The review saw this fail on run 19 at 32 test threads. 60 runs - here with the gate disconnected, and 60 more with a thread calling `tick` in a loop, did not - reproduce it -- the window is microseconds wide, so missing it settles nothing. What settles it is - placing a tick in the window by hand: inserting `tick_inner()` between - `a_cancelling_group_release_marks_a_wait_members_pool`'s `close_members_cancelling()` and its - assertion fails that test every time, on the assertion the review named. - -- [x] **M-T10.24** -- **Retry a refused quiesce instead of weakening the refusal.** Found while - verifying `M-T10.23`, not by the review: the full lib suite at 32 test threads failed 13 of 30 - runs in `trace::tests::a_hooked_stub_records_both_ends_and_still_performs_its_syscall` with - `NotQuiesced`. Instrumenting rather than guessing named the cause -- `SuspendThread` returning - `ERROR_ACCESS_DENIED`, enumeration intact -- which is a thread that is *terminating*. - - This was `M-T10.19` half-converted: the benign/dangerous distinction was given to `OpenThread` and - not to `SuspendThread` one line below. The carve-out was **not** extended, because the two cases - differ -- an id that names nothing is gone, whereas a terminating thread may still be running its - exit path, which is the property the refusal establishes. The refusal stands and the whole attempt - is retried outside the suspended window. Measured 13/30 before, 0/30 after. - -- [x] **M-T10.25** -- **Split the cancelling group release out of `close_members(bool)`, and make - posting fabricated packets `unsafe`.** Two findings of the same shape: a hazard gated by the - build for one entry point and by prose for another. - - **`close_members(cancel_pending: bool)`** was safe and ungated in every configuration, while the - per-object `try_cancel_pending` it reaches is `self-heal`-gated and `unsafe` without the feature. - With the feature off, `close_members(true)` cancelled every wait member and landed on a no-op - `owe_repair`. Now `close_members()`, `close_members_cancelling()` (gated), and - `close_members_cancelling_no_heal_tracking()` (ungated, `unsafe`) -- a `bool` cannot be `cfg`-gated, - an item can. - - **`poke_completion_ports`** is now `unsafe fn`. It posts a zero key and a null `OVERLAPPED` to - every completion port in the process above a depth, including ports this crate does not own; the - "**Destructive** ... only for a process that has already failed" rule was enforced by prose alone. - - Both breaking; both recorded in [DESIGN-NOTES.md](../../DESIGN-NOTES.md). - -- [x] **M-T10.18** -- **DECIDED 2026-10-02: a cancellation registers the pool itself, and reports - it when even that fails.** `heal::register` is best-effort, so a pool whose repair item could - not be created had nothing for a later `try_cancel_pending` to mark. - - **The decision.** The cancellation retries the registration at cancel time rather than giving up. - Allocating at the moment of need is what the pre-created repair object exists to avoid, but that - argument does not reach this path: the alternative is not "allocate earlier", it is "never repair - this pool at all". If the retry also fails, `cancel-untracked` is recorded **with the pool key** - and, under `fail-fast`, the call panics. - - **A prior defect surfaced while deciding it, fixed in the same change.** `Registration` held only - `Option>` and discarded the key, so an object whose registration failed could not - find an entry another object later created for the same pool -- the pool was repairable, a healthy - entry existed, and the cancellation still reported it untracked. The same omission made - `cancel-untracked` record `0` rather than naming the pool. `Registration` now keeps its key. - - **Where the fail-fast fires is load-bearing.** `CleanupGroup`'s cancelling release marks members - between the native release and the loop that frees their contexts, so a panic raised there would - unwind past the frees and leak every context. The report is accumulated and acted on after the - frees, matching `fail_fast_if_owed`'s rule that the panic must report a violation and not cause - one. - - **The error edge is now reachable from a test.** It runs only when `CreateThreadpoolWork` fails, - which no test can arrange, so a test-only `FORCE_REPAIR_FAILURE_FOR` forces it -- keyed to one - pool, because a global flag failed an unrelated test's registration on the first run. Both - directions are asserted, and sabotaging the retry fails the recovery test by name. - - Recorded in [DESIGN-NOTES.md](../../DESIGN-NOTES.md#a-cancellation-allocates-its-own-repair-rather-than-giving-up). - -- [x] **M-T10.26** -- **Release a trampoline page when the batch that prepared it is refused.** The - seventh review round, and its only finding. `prepare` allocates a 4 KiB executable page per - hook and publishes it into `TRAMPOLINES[index]` before the batch knows whether it will commit, - because the patch it builds points at that page. Both post-preparation refusals -- `open_pages` - failing, and the quiesce failing after all its attempts -- returned with the pages still - allocated and still published. Bounded at one page per hook and once per process, but a leak. - - **The obvious fix would have introduced a use-after-free, which is why it is not the one taken.** - `prepare` publishes unconditionally, so if an index were already installed its live, patched stub - would be jumping through the slot this preparation just overwrote; freeing there would release a - page a running hook is inside. `discard_prepared` therefore releases only when it found the slot - **empty**, and restores the displaced pointer otherwise. Only the test-only `install_by_label` can - reach the non-empty case -- `install_requested` runs the batch once per process behind `DONE` -- - but the branch exists because the function must be correct for its callers rather than for the one - that happens to exist today. - - **Verified on the real path rather than by reading.** Forcing `open_pages` to return `None` and - running the hook test printed `DISCARD index=5 previous=0 slot_now=0` and reported `NotWritable`: - the cleanup runs, takes the releasing branch, and clears the slot before the free. Neither refusal - branch is reachable from a test without that kind of injection, which is pre-existing and remains - true. - - The macro's `SAFETY` note that the slot is "never cleared" was corrected in the same change; it is - now cleared on exactly one path, and the comment states why that cannot race a call already inside - the hook body. - -- [x] **M-T10.27** -- **Run the exception-observer assertion in a trace-armed child.** The eighth - review round's headline finding, and this branch's own signature defect recurring: - `the_exception_observer_notes_a_first_chance_exception` opened with - `if !wants("exception") { return; }`, so in every ordinary run and in CI it returned having - raised nothing and asserted nothing. `M-T10.12` and `M-T10.16` exist to remove exactly this, - and the remedy they introduced -- `in_a_trace_armed_child` -- was already applied at three - other sites. Measured before the fix: the test passed in 0.00s with the filter unset, and - since its only other exit is a `panic!`, passing was itself the proof it returned early. - Measured after: 0.06s, and sabotaging the recorder fails it by name. - -- [x] **M-T10.28** -- **Stop the healer parking on the one pool it is trying to repair.** `tick` - submits a repair, clears the mark, then calls `retire_idle` in the same pass. An entry whose - objects are gone is then idle, so the last `Arc` drops, `RepairWork::drop` drains the work - object -- and that drain cannot return until the pool dispatches. The pool is by construction - the one suspected of not dispatching, and the healer is built with `set_max_threads(1)`, so - one wedged pool parked the only thread the facility has and no pool in the process would ever - be repaired again. - - `retire_idle` already moved the drop out of the registry lock for this exact reason, and its - comment says so; that protected every *other* pool's registration from the wait but not the - healer's own thread. Retirement now also requires `repair_settled()` -- a submission counter - paired against the existing `runs` -- so an entry is retired only once the pool has given every - repair back. An entry kept alive for a pool that never dispatches is the cheaper failure by a - wide margin. The counter is incremented *before* the submit, because a repair can be dispatched - the instant it is handed over and a count taken afterwards could be overtaken by the run it is - meant to be paired against. - -- [x] **M-T10.29** -- **Bound the writable window to one attempt, not sixteen.** A defect introduced - by `M-T10.24`'s retry. `install_batch` opened the `ntdll` pages, called the retrying - `with_others_suspended`, and restored them only afterwards -- so a contended install held live - code pages `PAGE_EXECUTE_READWRITE`, with every other thread running, across all sixteen - attempts and their snapshots, where one attempt holds them for microseconds. The retry now - lives in `install_batch` and opens and restores per attempt; `quiesce_once` is a single - attempt again and its doc records why the retry cannot wrap it. `NotWritable` is not retried, - because a page this process cannot make writable will not become writable a millisecond - later. Re-verified that the flake `M-T10.24` fixed stays fixed: 0 failures in 30 runs at 32 - test threads. - -- [x] **M-T10.30** -- **Carry the factory handle on the two records the investigation turns on, and - five smaller corrections.** `counts-waiting` and `counts-pending` passed a literal `0` while - `read_one`'s doc promised every record carries the handle -- and those two are precisely what - the facility exists to read. `counts()` reads every factory in the process rather than - guessing which is the default pool's, so without the handle a two-factory capture could only - be attributed by row adjacency. Both now carry it, and the doc names the records that - genuinely spend the slot on a partner value. - - Also: the module doc linked `../../../windows-ioring-sys/STALL-TIMELINE.md`, a path `b4a0a886` - emptied when it re-homed that file here; `commit`'s safety contract credited `prepare` with - opening the page, which `prepare` explicitly does not do (`open_pages` does, per `M-T10.13`); - `fail-fast` appeared in neither the README nor the crate docs, leaving its build-unification - hazard stated only in a `Cargo.toml` comment; the `compile_fail` guarding the feature-off API - shape called a made-up method and so asserted only that `rustc` rejects unknown names -- it now - names the real method under `cfg_attr(not(feature = "self-heal"))`, verified bidirectionally by - forcing the block on with the feature enabled and watching it fail; and `Win32_System_IO` under - `trace` was redundant against the unconditional dependency feature and justified by a comment - calling it test-only, which `poke_completion_ports` being public contradicts. - -- [x] **M-T10.31** -- **Claim a member's repair before the release that can free its pool.** The - ninth review round, and a use-after-free introduced by `M-T10.18`'s cancel-time retry. A wait - whose pool could not be registered holds nothing that keeps that pool alive -- the repair work - object, which would be a bound object deferring `CloseThreadpool`, was never created. A - cancelling group release frees the last bound object and *then* marks, so the retry inside - `owe_repair` could call `CreateThreadpoolWork` against freed memory, reachable through the - safe API. `release_members` now takes each member's claim **before** the native release and - holds it across, which both creates that object and keeps the pool alive. The per-object path - was never exposed: `self` is a live bound object for the whole call. - -- [x] **M-T10.32** -- **Give retirement one predicate instead of two.** `M-T10.28` added the - outstanding-repair condition to `retire_idle` and not to `release`, so the hazard it was added - for -- `RepairWork::drop` draining an undispatched repair on the healer's only thread -- was - still reachable through the other site. Both now call `is_retirable`. This is the fourth - partial fix of this shape on this branch; the answer is one definition, not a third careful - reading. - -- [x] **M-T10.33** -- **Make `close_members_cancelling_no_heal_tracking` actually not track.** It - called `release_members(true)`, whose repair pass was unconditional, so the method named for - *not* tracking marked pools, allocated repair objects, started the process-lifetime healer, - and could panic under `fail-fast` -- a contract its own documentation denies, and the opposite - of what a caller diagnosing a stall asks for. Tracking is now a parameter the safe form passes - and this one does not, which also stops the fail-fast firing for a caller who took the - obligation deliberately. - -- [x] **M-T10.34** -- **Check the patch-window precondition rather than asserting it from - placement.** The module documented the hazard exactly -- a suspended thread's instruction - pointer can be inside the bytes being replaced -- and then claimed the supported path avoids - it "because it patches when the process has no other threads", justified by running from a - `.CRT$XCU` initialiser. Placement does not establish that: an initialiser ordered earlier may - have started threads, and in a DLL the same initialiser runs at attach inside a running - process. No instruction pointer was ever read. - - Each suspended thread's `Rip` is now compared against the ranges about to be written, and the - install refuses when one is inside. Verified by sabotage: forcing the comparison to match refuses - with `NotQuiesced`. The `CONTEXT` buffer is wrapped in a 16-byte-aligned type because - `GetThreadContext` documents that alignment on this architecture while `align_of::()` - from `windows-sys` measures 8 -- it worked without the wrapper, which is incidental behaviour - rather than a contract. - -- [x] **M-T10.35** -- **Stop the hook tests failing whenever hooks are enabled.** The initialiser - installs whatever `WINDOWS_THREADPOOL_TRACE_HOOKS` names before any test runs; the two - installation tests then installed again, met their own patch, and failed on the recogniser's - correct `NotAStub` refusal. The suite was green only because nothing ever set the variable. - Reproduced at `exit 101` before the fix. They now install only when `installed_by_label` - reports the stub is not already patched, which does not weaken rejection of a foreign patch. - - One assertion was wrong in the same configuration for a different reason: it required - `factory_handle()` to be zero, but `FACTORY` is process-wide and the worker-factory hooks - legitimately write it, so with every hook installed a real handle is already learned. The claim is - that *this* call contributes none, so it now compares against the value captured before the call. - Measured after: 299 pass with hooks unset, `*`, `selftest`, and `associate`. - -- [x] **M-T10.7** -- **DECIDED 2026-10-02: yes. CI documents private items.** - `--document-private-items` added to all five `cargo doc` steps, which already denied the link - lints but only ever resolved the public surface. - - **The cost it was weighed against did not materialise.** The open question was whether the flag - would surface links that are useful to a maintainer but meaningless in published docs, forcing a - choice between noise and rewriting prose to satisfy a build nobody reads. It surfaced **nine dead - links across four crates**, and every one was simply wrong -- naming an item that had been - renamed, removed, or gated out of the configuration being documented. None was a judgement call: - - - `heal.rs` x3 -- `record_repair_run` (renamed by [`M-T9.1`](COMPLETED-CHECKLIST.md#m-t91)), - `PoolEntry::arm` (never existed; `arm_repair` is a free function), - `PoolEntry::dispatched_since` (removed by [`M-T9.2`](COMPLETED-CHECKLIST.md#m-t92)). All - three were written during this branch's last two commits. - - `trace/hook.rs` x3 -- `install`, which has been `install_batch` throughout. - - `windows-waitable-queues` x3 -- `reserving_mpsc::ClaimLayout` from two sibling modules needing - a `crate::` path, and `BOUNDS` where the item is `BOUNDS_MAX`. - - Found afterwards, because cargo stops at the first failing crate: `windows-file-watcher` x3 - (`WatcherInner::reopen`, renamed to `install`) and a `windows-platform-probes` link ambiguous - between a function and a macro. - - **Verified the flag is what catches them**, rather than assuming: a dead link planted in a - private item passes `cargo doc -p windows-threadpool-sys --no-deps --all-features` and fails the - same command with `--document-private-items`. The planted link was first placed in a - `self-heal`-gated module and checked under `--no-default-features`, where both forms passed -- - the module is not compiled there, so the first attempt proved nothing. - - **One link was deliberately left unlinked.** `pending.rs` referenced - `Completion::with_injected_failure`, which is gated on `fault-injection`; a link dangles in the - `--no-default-features` build CI also documents. Named in backticks with the reason, matching the - precedent set for `close_members_cancelling`. - -- [x] **M-T10.5** -- **Make the hook tests' trace-record assertions reachable, so a sabotage can - reach them.** Done 2026-10-02. - - **What landed.** `a_hooked_stub_records_both_ends_and_still_performs_its_syscall` now runs its - body in a trace-armed child through `trace::in_a_trace_armed_child`, the one site `M-T10.16` - built for exactly this, and its two record assertions lost the `if wants("wfactory")` guard that - made them inert on every ordinary run and in CI. The child opens by asserting `wants("wfactory")` - is true, so a filter that failed to reach it fails loudly rather than passing vacuously -- the - trap being closed here, arriving one level up. - - **`fired` stays counted rather than traced**, deliberately. It is the half of that test which - needs no armed trace, and keeping it independent of the filter means the two halves cannot fail - for the same reason. - - **Both record sabotages are in `sabotage.json` and are caught by name**, not by a crash: - deleting the `-enter` record fails on "the hook must record entering the call", and deleting - `-leave` on its own message, each surfacing through the parent's exit-code check. Verified - individually before being written down, and the manifest's `notCoveredHere` note -- which had - cited a completed, unrelated decision for this work -- was rewritten to say what is now swept. - - **The note did not simply go away, because the gap it describes did not.** What `M-T10.5` could - fix was assertions that existed and could not run. The hook module's other records have no - assertions at all, which is a different defect; it is now stated precisely in the manifest and - queued as `M-T10.36` rather than left implied by a note that would read as though everything - were covered. - -- [x] **M-T10.36** -- **Assert the hook module's remaining records, which no test checks at all.** - **Dissolved 2026-10-03 by [`M-T12.2`](#m-t122): the hook module was deleted.** The records - it names no longer exist, so there is nothing left to assert. Recorded as dissolved rather - than deleted because the gap it describes was real while the module was. - -- [x] **M-T10.37** -- **Gate the five heal tests that drive an entry with synthetic stamps.** - Found 2026-10-02 by `M-T10.5`'s stress run, and a defect in `M-T9.2`'s own tests. - - **The failure.** `an_entry_owing_a_repair_outlives_its_last_object` failed on run 18 of 25 at - `--test-threads 32`, on its final assertion -- the entry it had just healed was still in the - registry. It took no tick gate. - - **The mechanism, and why `M-T9.2` created it.** These tests stamp an entry by hand with small - values (`1`, `2`, `10`, `12`). A concurrent tick -- the background healer's, or another test's -- - writes `last_submitted` with `now()`, and the repair it submits writes `last_started` the same - way. Both are astronomically larger than the synthetic values, so a single interleaved tick - inverts `repair_in_flight`, `unhealed`, or retirability, whichever the test went on to assert. - The flag this design replaced could not do it: holding no clock value, a tick could only set or - clear it, so these tests were safe when they were written and stopped being safe when the stamps - landed. - - **Fixed at all five sites, not at the one that failed.** Grepping for tests that stamp without - taking the gate found five; fixing only the observed one would have left the identical defect at - four. This is the half-converted-rule shape the house rules name, and it is the fifth time it has - appeared on this branch. - - **Confirmed by placing the interleaving rather than by waiting for it**, since a window this - narrow proves nothing by failing to recur: inserting one `tick_inner()` between the test's - `stamp_cancelled(1)` and `stamp_started(2)` fails it every time, on the same assertion the stress - run hit. 30 runs at 32 threads are clean afterwards. The mechanism is recorded on `gate()`, beside - the first reason it exists. - -## M-T11 -- The tenth review round: a lost trait, two stamp defects, and the hook's ABI - -Opened 2026-10-03 by a review of everything on this branch. Seven findings. Four are this branch's -own regressions and two of those are `M-T9.2`'s, landed the same day; two are long-standing in the -`trace`-gated hook facility. The round's lesson is that **the gate cannot see any of them**: every -one passed `cargo clippy -D warnings` on four configurations, the full test suite on five, 30 -stress runs at 32 threads, and a 27/27 sabotage sweep. Four of the seven are a claim in a doc -comment that the code next to it does not keep. - -- [x] **M-T11.1** -- **Restore `TimerMember`'s `Send` and `Sync`, and put the rule on the build.** - Done 2026-10-03. The two `unsafe impl`s, with `WaitMember`'s SAFETY reasoning, plus a - `const _` block instantiating `needs_send` / `needs_sync` over all four member types. - Verified by sabotage in the direction that matters: with the impls removed the build fails - `E0277` **at lines 942-943 of the assertion itself**, not at some incidental use, so the - guard is what catches it rather than a bystander. - - **The regression.** At the merge base `TimerMember` held only a `PTP_TIMER` and a `PhantomData`, - so it was auto-`Send + Sync`. This branch added `context: *mut c_void` and did not add the - `unsafe impl`s, so a public type stopped being movable or shareable between threads. Verified by - compiling an assertion: `TimerMember` fails both bounds with `E0277` while `WorkMember`, - `PeriodicTimerMember` and `WaitMember` pass. - - **`WaitMember` is the precedent and the tell.** It carries two pointers and has both impls with a - SAFETY note. The same reasoning applies verbatim to the timer's context, so this is one rule - applied at one of two sites -- the sixth time that shape has appeared on this branch. - - **Fix at the build rung, not by adding two lines.** A `const _: () = ...` assertion over all four - member types makes the next pointer-shaped field a compile error rather than a silent API break. - Assert the accepting direction too, so a bound that is accidentally removed from every type is - not reported as success. - -- [x] **M-T11.2** -- **Make the repair predicates say what their doc comments claim.** Two defects - in `M-T9.2`, found together because they are the same block of accessors. Done 2026-10-03: - `outstanding: AtomicU32` answers "is a repair queued", and `last_cancelled` / `last_started` - are published with `fetch_max`. Each has a test that traverses the defect - (`a_retry_leaves_two_repairs_outstanding_until_both_have_started`, - `a_cancellation_published_out_of_order_does_not_lower_the_stamp`), each verified to fail by - name when its fix is reverted, and both are in `sabotage.json`. `last_submitted` stays a - plain store and says why: single-writer, and the tests must be able to move it backwards. - - **A plain store cannot publish a monotonic clock.** `stamp_cancelled` is an unconditional store - whose doc says "a later cancellation can only move the stamp forward", and `now`'s doc goes - further: "the opposite mistake cannot happen at any resolution, because the counter never goes - backwards." The counter does not, but the *store* can land out of order: a thread that reads 10 - and is preempted past a dispatch at 20 and a second cancellation at 30 will write 10 last, and - `unhealed` then reads healthy with a cancellation unanswered. That is exactly the lost - cancellation `M-T9.2` was written to eliminate, re-entering through the publication rather than - through the compare-exchange. `fetch_max` makes the stated property true. - - **The retry broke the outstanding-repair predicate.** `repair_in_flight` is - `last_submitted > last_started`, which was sound only while at most one repair could be - outstanding. `M-T9.2`'s overdue path submits a second before the first has started, so the first - starting makes the predicate false while the second is still queued. `is_retirable` then permits - retirement, and `PoolEntry::drop` drains -- waiting for a callback on a pool that is by - construction suspected of not dispatching, on the healer's only thread. Self-heal stops - process-wide. - - **Count outstanding submissions rather than comparing timestamps**, so the predicate answers the - question it is named for. Timestamps then serve only health (`unhealed`) and retry timing - (`repair_overdue`), which is what they can actually support. - -- [x] **M-T11.3** -- **Update the self-heal feature guide, which documents the design `M-T9.2` - deleted.** Done 2026-10-03. The guide still tells a consumer that every callback this crate dispatches stamps - a per-pool marker, that a busy pool therefore suppresses repair submissions, and that the per - callback cost is an interrupt-time read and a relaxed store. None of that is true: health is now - evidenced only by the crate's own repair dispatching, and that store was removed from every - callback. This is the public-facing document and nothing swept it. - -- [x] **M-T11.4** -- **Stop a failed pre-release recovery claiming to pin a pool it does not.** - Done 2026-10-03. The marking pass now iterates the **claims** rather than the members' - contexts, and `Registration::owe_repair_claimed` reports untracked instead of re-registering. - Losing that retry costs little: the claim *is* a retry, taken at the only moment it is safe, - with the members still live. The `OwnedResource::owe_repair` hook is gone, since the claim - now carries the same fact and two mechanisms could disagree -- which orphaned - `ThreadpoolWait::owe_repair` in the feature-off build and it is gated to match its one - caller. Guarded by a test that drives the *difference*, not the symptom: it lifts the forced - allocation failure in the `before_release` hook, so the removed retry would have succeeded. - `recover_repair` returns `Some(reclaim())` unconditionally, and `reclaim` re-attempts - registration. When that attempt fails the `Registration` holds no entry and therefore no `Arc`, - so it keeps nothing alive -- yet the field's doc says the caller "holds [it] across the native - release so the pool cannot be freed under the marking pass that follows". On that path the - marking pass can reach `owe_repair_untracked`, which registers and arms a work object against a - pool pointer whose last member has just been released. Reachable only under allocation failure, - which is why it is stated rather than assumed. - -- [x] **M-T11.5** -- **Give each hook the arity of the stub it is planted over.** Done 2026-10-03, - and **deliberately only where the arity is establishable** -- read the next paragraph before - treating the remaining five as an oversight. - - **What landed.** The `hooks!` table gained an argument-list column that becomes each hook's own - signature. `selftest` declares three and `associate` eight. Two `const` assertions sit beside the - table: no entry may exceed the forwarding width, and the `associate` entry may not drop below - eight, since it reads `AlreadySignaled` through a `get` whose `None` arm would otherwise make the - record quietly stop being emitted with every test still passing. Both verified by sabotage: each - fails the build with its own message. - - **The other five keep the full width, and that is a decision rather than a gap.** Their stubs are - undocumented and at least `NtWaitForWorkViaWorkerFactory` has been described with different - argument counts on different Windows versions. Declaring too many reads stale bytes from the - caller's frame and forwards values the kernel ignores; declaring too few silently drops a real - argument, and on that stub the result is every pool worker parking with a zeroed parameter -- in - the exact path this facility exists to observe. So the rule is to narrow only where this crate - establishes the arity from something it owns: `call_selftest` calls its stub directly, and - `already_signalled` reads the eighth argument of the other. - - **`HOOKS` no longer stores the replacement function.** With per-stub signatures there is no single - function type for that column, so the address is reached through a generated `hook_address`; a - `const` cannot cast a function to an integer, which is why it is a function. - - > **DECISION TO RAISE: narrowing the remaining five needs an arity source good on every supported - > Windows version.** That is a different kind of work from this item -- acquiring and validating - > an external contract, not editing a table -- and is not queued. Say if it is wanted. - -- [x] **M-T11.7** -- **Make an arity change break something, rather than being discovered by a - crash.** Asked 2026-10-03, done the same day. - - **The build rung is not available here, and that is the finding rather than a limitation to work - around.** Arity is a property of the *running* operating system. A build script could read the - build machine's Windows version, but the binary may run on a different one, so a build-time check - would be a proxy that cannot establish the fact -- the "cheaper rung that looks like enforcement" - the house rules name. Runtime is the honest rung. - - **Two guards already existed without being labelled.** `call_selftest` invokes its stub through a - *three*-argument pointer and the test asserts all three out-parameters are written and ordered, - so a changed arity fails CI; that is now stated at the call, with a note not to relax those - assertions into "the call returned", which is the half that would still pass. And the five wide - hooks are immune to arity *changes* by construction, since twelve is an upper bound -- if - `NtWaitForWorkViaWorkerFactory` were mis-forwarded the pool would stop dispatching, which is the - stall this crate's whole suite detects. - - **The real gap was `associate`**, the only hook that interprets a high-numbered argument rather - than forwarding it. A shifted arity makes its eighth slot some other parameter, and dereferencing - that raises an access violation *inside the instrument*. `already_signalled` now returns a - three-way `Signalled` and checks twice before believing the value: the slot must be a possible - user-mode address, and what it points at must be a `BOOLEAN`. A failure records - `associate-flag-implausible` carrying the raw argument, because the finding is what the slot held - instead. - - **Stated limit:** a plausible-looking address may still be unmapped, and no check affordable on - every wait registration proves otherwise. What the range rejects is the likely shape of a shifted - argument -- a small integer, which is what this call's other parameters hold. - - **Only one half is swept, deliberately.** The value check fails by assertion and is in - `sabotage.json`. Removing the *range* check makes the test dereference address 1 and the process - dies with `STATUS_ACCESS_VIOLATION` -- caught by a crash rather than by a test noticing, which - this repository treats as uncovered, so it is verified by hand and recorded here rather than - written down as a guard it is not. Every replacement - is a twelve-argument `extern "system"` fn, but the stubs take fewer. Forwarding twelve - arguments *to* a smaller callee is fine; *receiving* twelve from a caller that passed three is - not -- the hook reads incoming stack slots the caller never had to supply. On x64 those slots - land inside the caller's own frame, so it reads stale data rather than faulting, which is why - this has never been seen to misbehave and why it is still wrong. The macro already varies - per-hook data; arity is one more column. - -- [x] **M-T11.6** -- **Make the quiesce account for threads created while it is enumerating.** - Done 2026-10-03. After the suspend loop, the thread set is swept again and any thread the - quiesce did not suspend refuses the attempt; `install_batch`'s retry then picks it up in a - fresh snapshot. - - **`NtGetNextThread`, not a second toolhelp snapshot, and that is forced rather than chosen.** - Re-enumerating with `CreateToolhelp32Snapshot` allocates, and an allocation with threads - suspended deadlocks against one suspended while holding the allocator lock -- the hazard this - phase is built around, which an earlier version of this function hit for real. The export is - resolved *before* anything is suspended, because `ntdll_proc` allocates and `GetProcAddress` - takes the loader lock. Unresolvable means refuse, matching the module's posture. - - **The guard needed a test, and finding that out is the point.** Making the sweep blind - (`known = true`) left all 292 lib tests passing, so the `sabotage.json` entry would have claimed - `caught` falsely -- caught only by running it rather than assuming. The race cannot be landed on - demand, so `OMIT_ONE_FROM_SNAPSHOT` produces its *effect*: it drops one id the snapshot did list, - which from the sweep's side is indistinguishable from a thread created after it. - `a_thread_the_snapshot_missed_refuses_the_install` asserts both directions, and the blind sweep - now fails it by name. The - installer snapshots thread IDs, then suspends that snapshot. A listed thread can create - another before it is suspended, and the new thread is neither in the snapshot nor suspended, so - it can execute a stub while its fourteen bytes are being written. The instruction-pointer check - covers only threads that were successfully suspended. Reach a fixed point -- re-enumerate after - suspending and repeat until a pass adds nothing -- or refuse, which is the established posture - for this module when it cannot establish its precondition. - -## M-T12 -- The eleventh review round - -Opened 2026-10-03 by a review of everything on this branch. Four findings; this milestone holds -the one acted on so far. - -- [x] **M-T12.1** -- **Submit the repair inline when the healer cannot be started.** Done - 2026-10-03. - - **The gap.** `owe_repair` stamped the entry, called `ensure_running`, and reported the - cancellation as tracked **whatever that returned**. When the healer could not be started there - was then a pool marked unhealed with nothing scheduled to visit it, while `try_cancel_pending`'s - claim that this crate repairs the pool afterwards silently did not hold -- and the untracked - fail-fast did not fire either, because by its own account nothing had gone wrong. `M-T10.22` - makes a *later* cancellation retry the start; what it does not cover is a process where no later - cancellation arrives. - - **The fix is to do what the tick would have done.** `ensure_running` now reports whether a healer - exists, and when it does not the entry's own pre-created repair is submitted on the spot. That is - the action measured to release the stall, the work object already exists so it allocates nothing, - and the only thing given up is the coalescing the timer provides -- a cost this path pays once - rather than a correctness question. The cancellation is still reported as tracked, which is - honest: its repair has been handed over rather than scheduled, which is the stronger answer. - - **One site, not one of three.** The first attempt fixed `owe_repair` alone and left - `owe_repair_claimed` and `owe_repair_untracked` with the old unconditional `true` -- the - half-converted shape this branch keeps producing. The common tail is now - `mark_and_arrange_repair`, which all three call, and the submit itself is `submit_repair`, shared - with `tick_inner` so the order of stamp, count and call cannot drift between them. - - **Guarded in both directions** by `a_cancellation_submits_its_repair_when_the_healer_will_not_start`, - which holds the tick gate so a submission it observes can only be the inline one: with a healer - available nothing is handed over, and without one the repair is submitted and counted - outstanding. In `sabotage.json`, and caught by name when the fallback is reverted. - - **Extracting `submit_repair` broke two existing sabotage anchors**, which the pre-sweep check - caught before a run was wasted on them. That check is now the habit this branch was missing. - -- [x] **M-T12.2** -- **Delete the inline hooking facility: this crate ships no - undocumented APIs.** Decided and done 2026-10-03 by the engineer. - - **The constraint.** No shipped code may call an undocumented entry point or depend on behaviour - Microsoft does not publish -- names *and* shape: instruction encodings, structure layouts, fixed - addresses, argument counts. Recorded as - [This crate ships no undocumented APIs](../../DESIGN-NOTES.md#no-undocumented-apis), which also - records what rebuilding the observations on documented ground would take. - - **The surface was wholly confined to one module**, which is why the removal is surgical: all 61 - undocumented references were in `trace/hook.rs` and 11 more in its tests. The threadpool - wrappers, the obligation model, the self-heal and the trace *record* layer contained none. - - **What went:** `trace/hook.rs`; the hook tests; four public functions (`worker_factory_counts`, - `completion_port_depths`, `worker_factory_snapshot`, `poke_completion_ports`); the - `WorkerFactorySnapshot` type; fourteen `sabotage.json` entries; and two integration tests. - - **A real reduction, stated rather than tidied away.** One of those tests, - `prewarming_makes_the_default_pool_create_a_worker`, was the only external evidence that - `prewarm_default_pool` causes a worker to be created. That claim now rests on the function's own - documented contract -- a callback having run is the proof, because it ran on a worker -- which is - self-evidencing. **I had told the engineer the hook layer's only consumers were its own tests; - that was wrong**, and the compiler found the two integration tests I had missed. - - **The manifest lost both of its `expect: "survives"` controls**, since both lived in the deleted - module. A sweep with no control can only say the tests are sensitive, never that they are - sensitive to the right things, so one was added: strengthening a `Relaxed` load to `SeqCst` - cannot change observable behaviour, so a suite reporting it CAUGHT is failing for some other - reason and the sweep's other results cannot be trusted until that is explained. - - **Two of the open review findings are dissolved with the code** -- the quiesce sweep's fail-open - and the recogniser's 16-byte validation. The third, the trace buffer's `Vec::remove(0)` eviction, - is in the record layer and survives; see `M-T12.3`. - -- [x] **M-T12.3** -- **Make the trace buffer evict in constant time.** Found by the eleventh review - round; the one finding of that round that the hook removal does not dissolve. Done - 2026-10-03. - - **What landed.** `Buffer` keeps a `head` and overwrites in place, so eviction is one indexed - write and a modulus. The vector is no longer in logical order after the first wrap, so every - reader goes through `iter`, which yields `records[head..]` then `records[..head]`; before the - first wrap `head` is zero and that is simply the vector. `dump` and the tests' `held` helper - both moved onto it. - - **The modulus is of the vector's own length, not of `capacity`.** The two can differ: a buffer - that reached a larger capacity keeps that length if a later push passes a smaller one, exactly - as the remove-and-push it replaces did. Taking `capacity` would index outside the vector. - - **The risk this fix introduces is a rotation, not a loss**, which is why the guard is shaped the - way it is. An off-by-one head, or an iterator that splits the wrong way, returns the right - records in the right number and the wrong order -- a capture of a sequence that never happened, - and nothing about it looks wrong. `the_window_stays_in_order_across_repeated_wraps` pins the - exact window after every push through several full turns; both failure shapes were verified to - fail it by name, and the reversed split is in `sabotage.json`. - - **What is deliberately not swept:** restoring `Vec::remove(0)`. It would survive, and correctly - -- it is slower, not wrong, and no test can assert the absence of a memmove without timing. The - case against it is analytic and is recorded above rather than measured. - - **The defect.** `Buffer::push` calls `self.records.remove(0)` once the buffer is full, which - shifts every remaining record. `Record` is about 72 bytes and `CAPACITY` is 262,144, so each - push past the limit memmoves roughly 18 MB, under the trace mutex. - - **Why it matters more here than the figure suggests.** This is an instrument for observing - timing-sensitive behaviour. A buffer that fills mid-capture starts perturbing -- and plausibly - manufacturing -- the very stall it is being used to study. The buffer is deliberately sized not - to fill in an ordinary capture, which is what has kept this invisible. - - **Shape.** A ring: a head index and wraparound, so eviction and insertion are both constant - time. `dump` reads from the head rather than from index zero; `dropped` and the - overflow announcement are unaffected. Watch that the existing buffer tests pin *order* as well - as contents, since a ring is where that can silently invert. - -- [x] **M-T12.4** -- **Unfuse eight doc blocks, and check the half that is checkable.** Found by - the twelfth review round, which found them by reading; done 2026-10-03. - - **The defect, and why ten rounds missed it.** An edit that replaces an item's documentation by - anchoring on its signature and prepending new prose leaves the previous block above the new one. - The result compiles, renders, and passes every gate: rustdoc validates link *syntax* rather than - prose, clippy does not read doc semantics, and `missing_docs` is satisfied by the presence of *a* - comment rather than by its being the right one. All eight are mine, all from this branch. - - **Five were a false claim repeated.** The `registration` field on every context type said it sat - there "so the trampoline can reach it to stamp a dispatch (`M-T6.2`)" -- a mechanism `M-T9.2` - deleted. In four of them the true text sat directly underneath, so each field stated two - contradictory things; `wait.rs` had only the false one. `M-T9.2`'s own entry claims "each field - says so", which was half-true, and the true half is what let the sweep look done. - - **Three left an item with no documentation of its own**: `Registration::owe_repair` and - `release` had their docs stolen by the item above them, and `ThreadpoolWait::recover_repair` - carried two `# Safety` sections. `missing_docs` cannot see any of these -- two are `pub(crate)`, - and the third has a comment, just not its own. - - **One was actively misleading.** `WaitMember::stop_and_drain` carried a section headed "This - brings a process-wide hazard forward; it does not create it", which is the opposite of what - draining does. The reviewer read that text as belonging to no existing method and suggested - deleting it; that was wrong -- `WaitMember` has two cancelling methods, and the paragraph makes - a group-specific point neither of them stated. It was **relocated** to - `try_cancel_pending_no_heal_tracking`, whose obligation it describes. - - **The check, and what it deliberately does not cover.** `tests/doc_blocks.rs` errors on a doc - block carrying the same heading twice: there is no legitimate item with two `# Safety` sections, - so that signal is sound. It skips fenced regions, because a doctest hides a line with `# ` and - would otherwise be indistinguishable from a heading. It walks `src` recursively -- a shallow - walk would skip `heal/`, `timer/` and `trace/` and report success over a fraction of the tree -- - and asserts both a floor on files visited and a floor on headings recognised, so a broken walk - or a blind parser fails loudly instead of passing vacuously. Its rejecting direction is verified - by sabotage: re-fusing the block the reviewer found fails it by file and line. - - **The general case is not automated, and that is the decision rather than an omission.** Two - fused *summary sentences* carry no sound signal. The best heuristic -- a one-sentence paragraph - with no blank line before it and one after -- flags about twenty-five sites in this crate, of - which four are real; shipping it would be shipping an alarm nobody can act on. It is a useful - one-off grep, recorded here, and the sweep it drove found exactly the reviewer's eight and no - more. - -## M-T13 -- The twelfth review round: an uncovered rule, a half-converted twin, and the hook's residue - -Nine findings, all confirmed. The round is characterised by *residue*: four of the nine are -leftovers from two of this branch's own larger edits -- the hook deletion in `1eca1e16` and the -heal rework in `M-T9.2` -- that removed a thing without removing what pointed at it. The first -finding is of a different and worse kind: a rule nothing tests. - -- [x] **M-T13.1** -- **Make the tick's coalescing skip actually tested.** `tick_inner`'s - `if !entry.unhealed() { continue; }` is the coalescing rule: a pool that dispatched after its - cancellation is live and is owed no repair. **Deleting the skip outright leaves all 286 lib - tests passing** -- measured, not argued. Both tests that exist to cover it are inert: - `a_tick_leaves_a_pool_its_own_repair_has_answered` asserts `repairs_run() == 0` on the line - after `tick_inner()`, and `repairs_run` counts repairs that have *run*, which on a fresh private - pool with no thread yet is asynchronous and still 0 whether or not one was submitted. Its own - assertion message names the choice -- "on the repair having run rather than on the mark" -- so - the reasoning is recorded and wrong. `a_tick_leaves_a_pool_alone_when_nothing_is_owed` asserts - only `!unhealed()`, which is false-by-construction when nothing ever stamped a cancellation, so - no behaviour of the tick can make it fail. - - **Target:** assert on the synchronous evidence -- `last_submitted()` and `repair_in_flight()` -- - which are written by `submit_repair` before it returns. Add a sabotage entry for the skip, since - none covers it, and verify the rejecting direction by deleting the skip and watching the test go - red. - -- [x] **M-T13.2** -- **Give the periodic timer one drain body instead of two.** - `PeriodicTimerMember::stop_and_drain` passes `TRUE` to `WaitForThreadpoolTimerCallbacks` -- - cancel -- while `ThreadpoolPeriodicTimer::stop_and_drain` passes `FALSE` and documents at length - that "the drain no longer cancels". The member's own doc says "As with - `ThreadpoolPeriodicTimer::stop_and_drain`", so it cross-references the twin it disagrees with, - and its trace label still reads `(cancel)`. - - **This is a two-site rule half-converted, and the structural cause is visible in the siblings - that did not drift:** `TimerMember` and `WaitMember` both delegate to a shared - `ThreadpoolTimer::stop_and_drain_parts` / `ThreadpoolWait::stop_and_drain_parts`. The periodic - timer has no such shared body, which is why it was possible to convert one copy and not the - other. **Target:** extract `stop_and_drain_parts` for the periodic timer and have both call it, - so the next conversion cannot reach one site only. - -- [x] **M-T13.3** -- **Repair the sabotage entry that now invokes undefined behaviour.** The entry - "the heal creates a work object instead of submitting the pre-created one" passes - `Box::new(AtomicU64::new(0))` as the fresh work object's context. `repair_trampoline` casts its - context to `&PoolEntry` and writes stamps and counters through it, so the patched build writes - past the end of an 8-byte allocation. The entry's `whyNotNullContext` explains that a null - context was avoided precisely so the result would not be a crash-caught one -- and the context - type changed underneath that reasoning, so it now risks exactly the outcome it was written to - avoid, plus heap damage that other tests in the same process would see. - -- [x] **M-T13.4** -- **Correct the disarm documentation, which says the opposite of what the crate - measures.** `ThreadpoolTimer::disarm` says "a callback already queued still runs; use - `cancel_pending` to drop those as well", and `ThreadpoolPeriodicTimer::stop` says "a tick already - queued still runs". `disarming_cancels_a_queued_tick_which_a_waits_disarm_does_not` pins the - opposite for the one-shot timer: with a disarm the queued tick is discarded, asserted as - `outcomes[1] == 0` against `outcomes[0] == 1` without one. The doc therefore sends a caller to - `cancel_pending` for something that has already happened. - - **The periodic half needs its own measurement rather than a copied claim.** The pinning test - drives a one-shot `ThreadpoolTimer`; `ThreadpoolPeriodicTimer::stop_and_drain`'s doc already - extrapolates from it ("the way a one-shot timer's disarm does"), and this item must either - measure the periodic type or state plainly that the behaviour is pinned only for the one-shot. - -- [x] **M-T13.5** -- **Drop the four `windows-sys` features the deleted hook facility needed.** The - `trace` feature still pulls `Win32_System_Diagnostics_ToolHelp`, `Win32_System_LibraryLoader`, - `Win32_System_Memory` and `Win32_System_SystemInformation`, with a five-line comment explaining - the page-size arithmetic "the hook installer needs" and how `VirtualProtect` reports a whole - page's previous protection. `1eca1e16` deleted that facility; none of the four has a single use - left in the crate. A consumer who turns tracing on compiles four modules for nothing, and the - comment describes machinery that is not there. - -- [x] **M-T13.6** -- **Read the error before freeing the context in `ThreadpoolWork::new`.** The - failure path runs `drop(Box::from_raw(ctx))` and *then* `io::Error::last_os_error()`. Since this - branch, `ctx` holds a `Registration`, whose drop can reach `PoolEntry::drop` and from there - `WaitForThreadpoolWorkCallbacks` and `CloseThreadpoolWork`. `timer.rs` and `timer/periodic.rs` - both capture the error first and free second; `work.rs` is the only one of the four that does - not. - - **State the finding honestly: the clobber is not established.** Nothing in the Win32 - documentation says those two calls set the last error on success, so this is an inconsistency - among four sites that should agree rather than a demonstrated bug. It is worth fixing because it - costs one line and because the ordering that is obviously safe should not be the minority. - -- [x] **M-T13.7** -- **Stop crediting a `Drop` impl that does not exist.** Eight comments across - `heal.rs` (lines 295, 353, 774, 807, 849, 865, 1170) and `heal/tests.rs` (1058) attribute the - repair object's drain to `RepairWork::drop`. There is no `impl Drop for RepairWork`; the drain - moved into `PoolEntry::drop` when the teardown-order invariant was made unrepresentable. The - trampoline's `SAFETY` comment is the one that matters, because it justifies a raw dereference - with "the entry's first field drains this object in its `Drop`" -- an argument about field order - that the `PoolEntry::drop` impl deliberately replaced. - -- [x] **M-T13.8** -- **Say what the fail-fast child test establishes, which is not the abort.** Its - doc opens "**This aborts rather than unwinds**, so it cannot be observed with `catch_unwind`", - and attributes that to the panic escaping the healer's `extern "system"` trampoline. The child - calls `crate::heal::tick_inner()` directly on the libtest thread, so the panic unwinds normally - and the child exits 101. Every parent assertion -- not `SURVIVED`, not `SETUP_FAILED`, non-zero, - stderr text present -- holds under that unwind, so none of them distinguishes an abort from a - caught panic. The test is worth keeping: it proves the panic fires and that the message reaches - stderr, which is the claim `fail_fast_if_unrepairable` makes. Only the doc overstates it. - -- [x] **M-T13.9** -- **Sweep the hook facility's remaining name.** `trace.rs`'s module doc tells a - reader the crate records under `wait`, `work`, `io`, `timer` and `timer-periodic`; it also - records under `heal`, `syscall-enter` and `syscall-leave`, so a reader building a filter from - that list silently loses the self-heal subsystem. `sabotage.json`'s `whyTestArgs` justifies - `--all-features` with "the whole hook module is behind the `trace` feature", and three messages - in `trace/tests.rs` (614, 617, 630) still speak of hooks installing. The `--all-features` - justification needs replacing with the real reason rather than deleting, since the flag is still - required. - -## M-T14+ -- Follow-ups to the `Event` conversion - -- [ ] **M-T14+.1** -- **A thread-pool wait target from `win_sync_sys::WaitOnlyEvent`.** From +- [ ] **M-T16.1** (was `M-T14+.1`) -- **A thread-pool wait target from `win_sync_sys::WaitOnlyEvent`.** From [WS-1+.5](../win-sync-sys/COMPLETED-CHECKLIST.md#ws-1+5): a wait-only handle is still known to be an event and carries `SYNCHRONIZE`, which is all a pool wait needs, so a `From` conversion alongside `Event`'s is safe for the reason [WS-D-5](../win-sync-sys/DESIGN-NOTES.md#ws-d-5) gives. Decide whether it is a `From` impl or a second constructor, with the pool's `Event` conversion as the model, and test it as that conversion is. It lets a consumer hand a thread-pool wait the handle it was given without giving the pool the power to signal it. - -## M-inf -- Diagnostic work with no gating deliverable - -- [ ] **M-T-inf.1** (was `M26.14.4`) -- **Find the threshold the close races.** `M26.14.2` used 1ms because it is - about 80x the natural 12us gap; nothing establishes what the minimum is. A sweep -- 0, 10us, - 50us, 100us, 500us, 1ms, interleaved at 20000 each -- would say whether the window is - microseconds or milliseconds, which is itself evidence about what the close races. - - **Moved here 2026-10-01, when its gate resolved the "never ships" way.** This item was gated on - `M26.14.3` and said so itself: if the draining teardown were adopted, the sleep never ships and - the sweep is only of diagnostic interest. It was adopted, so that is where this now sits -- not - cancelled, because the question it asks is about the *kernel* window the close races, which the - drain avoids rather than explains, and that remains the one thing about this stall nobody has - been able to see directly. diff --git a/crates/windows-threadpool-sys/COMPLETED-CHECKLIST.md b/crates/windows-threadpool-sys/COMPLETED-CHECKLIST.md index feb573e5c..2667c08ad 100644 --- a/crates/windows-threadpool-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-threadpool-sys/COMPLETED-CHECKLIST.md @@ -8,23 +8,23 @@ Append-only archive of completed work items. See [CHECKLIST.md](CHECKLIST.md) fo ### M1 — Callback environment -- [x] **M1-1** — Implement SDK-equivalent `TP_CALLBACK_ENVIRON_V3` initialization and mutation — the +- [x] **M1-1** -- Implement SDK-equivalent `TP_CALLBACK_ENVIRON_V3` initialization and mutation -- the header-only helpers that `windows-sys` does not emit. See [DESIGN-NOTES.md](../../DESIGN-NOTES.md). -- [x] **M1-2** — Unit-test callback-environment init and mutation against the documented default field values +- [x] **M1-2** -- Unit-test callback-environment init and mutation against the documented default field values (version, priority, size). ### M2 — Work submission and callback ownership -- [x] **M2-1** — Implement and test one end-to-end work submission abstraction over `CreateThreadpoolWork`, +- [x] **M2-1** -- Implement and test one end-to-end work submission abstraction over `CreateThreadpoolWork`, `SubmitThreadpoolWork`, `WaitForThreadpoolWorkCallbacks`, and `CloseThreadpoolWork`. -- [x] **M2-2** — Validate the callback ownership model with the work abstraction before extending it to timers +- [x] **M2-2** -- Validate the callback ownership model with the work abstraction before extending it to timers and waits. ### M3 — TP_IO backend over the shared seam -- [x] **M3-1** — Wire the `windows-threadpool-sys` → `windows-overlapped-io-sys` dependency (path plus +- [x] **M3-1** -- Wire the `windows-threadpool-sys` -> `windows-overlapped-io-sys` dependency (path plus version), and update the publish workflow so the overlapped crate releases before this one. *(completed 2026-08-17 15:37:00 -04:00)* @@ -33,7 +33,7 @@ Append-only archive of completed work items. See [CHECKLIST.md](CHECKLIST.md) fo resolves the versioned dependency from the registry rather than the workspace path. That makes the release order independent of the order the two tags are pushed. -- [x] **M3-2** — Implement the `TP_IO` backend over the shared seam with balanced `StartThreadpoolIo` / +- [x] **M3-2** -- Implement the `TP_IO` backend over the shared seam with balanced `StartThreadpoolIo` / `CancelThreadpoolIo` accounting and callback-driven reclamation. *(completed 2026-08-17 15:56:00 -04:00)* @@ -42,7 +42,7 @@ Append-only archive of completed work items. See [CHECKLIST.md](CHECKLIST.md) fo kernel or pool still owns. Required widening the shared seam with `OperationId::from_ptr`, which was fixed in `windows-overlapped-io-sys` rather than worked around here. -- [x] **M3-3** — Test `TP_IO` across the behavioral matrix: immediate failure, immediate success, pending +- [x] **M3-3** -- Test `TP_IO` across the behavioral matrix: immediate failure, immediate success, pending completion, cancellation, and object rundown with operations outstanding. *(completed 2026-08-17 16:06:00 -04:00)* @@ -52,7 +52,7 @@ Append-only archive of completed work items. See [CHECKLIST.md](CHECKLIST.md) fo zero-length reads, mixed payload types, unclaimed completions, and repeated idempotent rundown. The matrix surfaced that an operation identity is unique only among *simultaneously outstanding* - operations — reclaiming an operation returns its storage address to the allocator, which may reissue it. + operations -- reclaiming an operation returns its storage address to the allocator, which may reissue it. That contract was undocumented and is now recorded on `OperationId` and in the overlapped crate's design notes. @@ -63,7 +63,7 @@ landed in M2 and I/O in M3, so only timer and wait remained; those became separa items. Two items were added from defects found while building: M4-1 (a `CallbackEnviron` soundness hole) and M4-8 (the one-shot/periodic timer split). -- [x] **M4-1** — Close the `CallbackEnviron` soundness hole: add an owned `ThreadpoolPool` and change +- [x] **M4-1** -- Close the `CallbackEnviron` soundness hole: add an owned `ThreadpoolPool` and change `set_pool` to accept it, and make `set_cleanup_group` `unsafe` pending a full cleanup-group design. *(completed 2026-08-17 17:00:00 -04:00)* @@ -71,31 +71,31 @@ M4-8 (the one-shot/periodic timer split). (bare `isize`) that the thread pool later dereferences, so safe code could cause undefined behavior. `set_library` next door was already `unsafe` for exactly this reason. -- [x] **M4-2** — Implement a safe timer over `CreateThreadpoolTimer`, `SetThreadpoolTimer`, +- [x] **M4-2** -- Implement a safe timer over `CreateThreadpoolTimer`, `SetThreadpoolTimer`, `IsThreadpoolTimerSet`, `WaitForThreadpoolTimerCallbacks`, and `CloseThreadpoolTimer`. *(completed 2026-08-17 17:05:00 -04:00)* -- [x] **M4-3** — Test the timer across one-shot, periodic, absolute, disarming, cancellation, and destruction. +- [x] **M4-3** -- Test the timer across one-shot, periodic, absolute, disarming, cancellation, and destruction. *(completed 2026-08-17 17:05:00 -04:00)* The tests corrected the implementation's specification: `IsThreadpoolTimerSet` reports whether a due time is set, not whether the timer will fire again, so a one-shot timer stays set after firing. The `is_set` documentation had said the opposite. -- [x] **M4-4** — Implement a safe `ThreadpoolWait` that owns its waitable handle and rearms per activation. +- [x] **M4-4** -- Implement a safe `ThreadpoolWait` that owns its waitable handle and rearms per activation. *(completed 2026-08-17 17:12:00 -04:00)* The object owns the handle so it cannot be closed under a pending wait, and the callback receives a `WaitActivation` carrying `rearm`, since the SDK consumes the arming on each activation. -- [x] **M4-5** — Test the wait across signalled and timeout activation, rearming, disarming, and destruction. +- [x] **M4-5** -- Test the wait across signalled and timeout activation, rearming, disarming, and destruction. *(completed 2026-08-17 17:12:00 -04:00)* Also de-flaked the identity tests in both crates: they required the allocator to naturally reuse an address, which is not guaranteed. Natural reuse is now reported rather than asserted, and the hazard is covered deterministically by tests that synthesize a stale generation at a live address via `OperationId::from_parts`. -- [x] **M4-8** — Split the timer into `ThreadpoolTimer` (one-shot) and `ThreadpoolPeriodicTimer`, and give each +- [x] **M4-8** -- Split the timer into `ThreadpoolTimer` (one-shot) and `ThreadpoolPeriodicTimer`, and give each callback a token. *(completed 2026-08-17 17:25:00 -04:00)* The first implementation followed the platform and modelled both kinds with one object, where a `period` @@ -105,20 +105,20 @@ M4-8 (the one-shot/periodic timer split). `PeriodicTick::stop` lets a periodic timer end itself. A zero period is rejected rather than silently degenerating to a one-shot. -- [x] **M4-6** — Design and implement safe cleanup-group membership across every callback object. +- [x] **M4-6** -- Design and implement safe cleanup-group membership across every callback object. *(completed 2026-08-17 17:33:00 -04:00)* Option (A) of the three considered: the group creates and owns its members. Flagging objects as "group-owned" is insufficient, because each also owns a heap callback context that is only safe to free once - the bulk release has finished — a moment an individual object cannot observe. Use-after-release is a compile + the bulk release has finished -- a moment an individual object cannot observe. Use-after-release is a compile error, pinned by a `compile_fail` doc test. Thread-pool I/O is excluded on purpose: a `TP_IO` object must not be closed with an operation outstanding, and a bulk release cannot satisfy that. -- [x] **M4-7** — Add API examples and generated documentation. +- [x] **M4-7** -- Add API examples and generated documentation. *(completed 2026-08-17 17:20:00 -04:00)* Crate-level guidance, runnable doc examples for every object type, a rewritten README, and docs.rs metadata - pinning a Windows target — without which documentation for this Windows-only crate would fail to build. + pinning a Windows target -- without which documentation for this Windows-only crate would fail to build. ## Moved 2026-08-17 — M5 timer stress suite @@ -131,33 +131,33 @@ Two measurements shaped the whole suite and are recorded in the design note: poo system tick, and a loop that arms without pausing outruns the pool entirely -- which made three early scenarios record zero callbacks while appearing to pass. -- [x] **ST-1** — Stress harness plus the one-shot arming and re-arming scenarios. *(completed 2026-08-17 20:14:49 -04:00)* +- [x] **ST-1** -- Stress harness plus the one-shot arming and re-arming scenarios. *(completed 2026-08-17 20:14:49 -04:00)* Env-var gate applied by a macro so it cannot be forgotten per test, a scale knob, and a serialization lane so two heavy scenarios never measure each other. Scenarios: self-re-arm chains asserting the documented non-overlap guarantee, past-instant `rearm_at` chains, arming churn across eight threads, arm/disarm races, deterministic arm-fire cycles, a large one-shot population, coalescing windows, and contained panics. -- [x] **ST-2** — One-shot teardown stress: `Drop` racing a firing and a re-arming callback. *(completed 2026-08-17 20:14:49 -04:00)* +- [x] **ST-2** -- One-shot teardown stress: `Drop` racing a firing and a re-arming callback. *(completed 2026-08-17 20:14:49 -04:00)* Rapid create/arm/drop churn, drops walking across the due time, drops landing mid-callback with a deferred re-arm pending, and concurrent teardown across eight threads. Targets the window closed by the second PR review round, where a regression appears as a hang or a crash rather than a failed assertion. -- [x] **ST-3** — Periodic timer stress: high-frequency ticking, self-stop, and deliberate tick overlap. *(completed 2026-08-17 20:14:49 -04:00)* +- [x] **ST-3** -- Periodic timer stress: high-frequency ticking, self-stop, and deliberate tick overlap. *(completed 2026-08-17 20:14:49 -04:00)* Sustained ticking, deliberately overlapping ticks (peak 3 concurrent, confirming the documented contract empirically), self-stop from inside the callback, start/stop churn, drops while ticking and mid-tick, a large ticking population, and zero-period rejection under repetition. -- [x] **ST-4** — Cleanup-group timer members and a mixed load scenario. *(completed 2026-08-17 20:14:49 -04:00)* +- [x] **ST-4** -- Cleanup-group timer members and a mixed load scenario. *(completed 2026-08-17 20:14:49 -04:00)* A group releasing its members is a distinct teardown path: large member populations released as a unit under both dispositions of `cancel_pending`, concurrent release across threads, and a group left to drop. The mixed scenario runs a self-re-arming one-shot, a periodic population, and timer and group churn together, asserting the non-overlap guarantee survives a loaded pool. -- [x] **ST-5** — Document how to run the suite. *(completed 2026-08-17 20:14:49 -04:00)* +- [x] **ST-5** -- Document how to run the suite. *(completed 2026-08-17 20:14:49 -04:00)* Crate README section covering both environment variables, the deliberate CI exclusion, the two measurements that shape the scenarios, and what is asserted versus reported. @@ -179,6 +179,7 @@ implementation, and it is still named. Recorded in [DESIGN-NOTES.md](../../DESIG The comment said `set_pool` takes an owned `ThreadpoolPool`; it takes `&ThreadpoolPool` and records the borrow as `CallbackEnviron<'pool>`. The fourth instance of this same false claim about `set_pool`, and the first found in test commentary rather than in documentation or the pull request description. + ## Moved 2026-08-21 -- M17 custom-close owner for non-`CloseHandle` wait targets - [x] **M17.1** -- Let `ThreadpoolWait` own a wait target whose close routine is **not** `CloseHandle`. Today @@ -427,6 +428,7 @@ the already-traced `io` trampoline. **The one gap left open deliberately:** `CallbackEnviron::set_cleanup_group` is a public raw seam that lets a caller install their own cleanup-group cancel callback. That function would be the caller's, not ours, so there is nothing here to bracket. + ## Moved 2026-10-01 11:54:34 -04:00 -- M-T4.9, which channel carries a developer-facing report ### M-T4.9 -- Decided: a developer-facing report is a trace event, and the crate writes nothing to stderr. *(completed 2026-10-01 11:54:34 -04:00)* @@ -584,6 +586,7 @@ shows the cancelling form leaves the default pool unable to make its first worke Note what it does *not* establish: it excludes "the rate is unchanged", not "the rate is zero", and it is not a root cause. `M26.9` may be called closed on this; the open question is why the close-behind-disarm stalls the pool at all. + ### M-T5 -- Why the pool stops making workers Opened 2026-09-30. `M-T4` shipped a fix whose correctness is structural rather than statistical -- @@ -672,7 +675,8 @@ what is not.** below are **ours, assigned for this investigation**, not the platform's. They are written in `snake_case` to keep that visible. The sole exception is `create_in_progress`, which is a real field of the public `WORKER_FACTORY_BASIC_INFORMATION` and is already what - [hook.rs](src/trace/hook.rs) records. + `src/trace/hook.rs` recorded (that file was deleted with the crate's inline hooking facility, under the + repository's rule against undocumented APIs). Four properties matter for this investigation: @@ -1426,3 +1430,1067 @@ The item as it stood at completion: > **CROSS-COMPONENT PREREQUISITE:** `crates/win-sync-sys` -> `WS-M1` -> `WS-1.1` (the crate, with `Event`). See [CHECKLIST.md](../win-sync-sys/CHECKLIST.md). > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/durable-ioring` -> `DI-M3` -> `DI-3.2.2` (dioring's plain I/O and Model A front end, whose readiness event is an `Event`). See [CHECKLIST.md](../durable-ioring/CHECKLIST.md). + +## Moved 2026-10-18 22:58:35 -04:00 -- M-T15.1, a wait that shares its handle + +### M-T15.1 -- A wait target may be a shared handle, through `assume_waitable_shared`, and `into_handle` gives it back only to its last holder. *(completed 2026-10-18 22:58:35 -04:00)* + +Decided by the engineer on 2026-10-18 ("take the decision as yes now"); the decision, and the two points the +assistant settled within it (the seam's contract, and what `into_handle` returns), are in the workspace +[DESIGN-NOTES.md](../../DESIGN-NOTES.md#a-wait-may-share-its-handle). `WaitTarget` has a `Shared(SharedHandle)` arm; +releasing the wait's clone is the target's destructor, so both teardown paths already order it after the drain. +Restructured while `WS-2.2` (then numbered `WS-2.1`) was implemented: switching `From` to the shared arm moved to +[WS-2.2](../win-sync-sys/COMPLETED-CHECKLIST.md#ws-22), because it needs a shareable `Event` and `WS-2.2` needs this arm first. +Six tests, and three sabotages (two caught, one control survived); one is caught by the pool failing fast on a +null handle, which the manifest says. + +## Moved 2026-10-21 21:46:52 -04:00 -- windows-threadpool-sys: milestones M-T10, M-T11, M-T12, M-T13 and M-T15 are complete + +### M-T10 -- The second review round: ordering, serialization, and a test that never ran + +Opened 2026-10-02 by a review of everything on this branch, and grown by each review round that +followed it; every item is in `windows-threadpool-sys`. The round's lesson is recorded with +`M-T10.1`: the reviewer named two sites for a rule that lived at eight, and the sweep the house +rules require is what found the third. + +- [x] **M-T10.1** -- **Record the drain obligation before the arming is published, at one site + rather than eight.** The re-arm paths in `wait` and `timer`, and `work`'s submit, set the + obligation flag *after* the native call that publishes the arming. A dispatch entering in + that window settles the obligation and is then overwritten, so a correctly-drained object + claims it owes a drain -- under `fail-fast` a panic, and an abort if `Drop` was already + unwinding. Fixed by moving the pair into `CloseObligation::record_live_before` and making + the bare store private, so the inverted order is a compile error. Recorded as + [The drain obligation is recorded before the arming is + published](../../DESIGN-NOTES.md#obligation-recorded-before-arming). + +- [x] **M-T10.2** -- **Make `tests/obligation_report.rs` run in CI, then make it pass there.** + + **The defect, and which half matters.** The test deliberately drops objects that owe a drain, so + under `--all-features` -- which arms `fail-fast` -- it exits 101. That is the reported symptom. + The *defect* is why nobody noticed: the test early-returns when no trace filter is set, so on + every CI run it does nothing and reports success. It is inert, and an inert test that reads as + green is worse than an absent one. + + **Target.** Fix the inertness first and confirm the test then fails, which is what shows the + early return was hiding it. Then make it pass under `fail-fast` -- either by catching the + expected panic, or by gating the owing cases off the feature -- without reintroducing a path + where the whole body is skipped silently. + + **Sabotage it**: break the behaviour the test asserts and confirm it goes red *in the + configuration CI actually runs*. A guard that only fires under a locally-set environment + variable has not been shown to guard anything. + +- [x] **M-T10.3** -- **Serialize every entry point that suspends other threads.** Done by a + `Mutex` inside `with_others_suspended` itself rather than a guard at each caller, so the + rule holds for every path into it including ones not yet written. `install_requested`'s + `AtomicBool` stops it running twice, which is idempotence and not mutual exclusion; + `install_by_label` had nothing. + + **The reported hang did not reproduce, and the report should not be relied on.** The review + said two named tests hang at `--test-threads 2` and pass 20 of 20 at `--test-threads 1`. Neither + half holds here: 0 hangs in 40 runs of that pair at `--test-threads 2`, 0 hangs in 25 runs of + the whole `trace::` module at `--test-threads 2`, and the pair *fails* rather than passes at + `--test-threads 1` -- for the unrelated reason that became `M-T10.6`. + + **The fix is justified analytically, not by that measurement.** Two threads inside + `with_others_suspended` can each enumerate the other and then suspend it, leaving neither + running to resume the other; `SuspendThread` is documented as not guaranteeing the suspension + is complete when it returns, which is the window. One `Mutex` removes the state entirely and + costs nothing on a facility that installs hooks once per process. An unreproduced hazard that a + cheap change makes unrepresentable is worth closing; what is not acceptable is recording it as + measured when it was not. + +- [x] **M-T10.4** -- **DECIDED 2026-10-02: hot-patching is exclusive, and the inline hooks spend + that exclusivity knowingly.** Documented as a hazard rather than engineered around, and + recorded as [Hot-patching is + exclusive](../../DESIGN-NOTES.md#hot-patching-is-exclusive). + + **The decision reframed the finding, and the reframing is the useful part.** The review raised + a patch window: suspension does not place a thread's instruction pointer *outside* the fourteen + bytes being overwritten. True, and the narrower half. The larger half is that inline hooking is + **process-exclusive** -- one slot per stub, no way to share or negotiate, and a second patcher + captures the first one's jump as though it were the original. Setting + `WINDOWS_THREADPOOL_TRACE_HOOKS` therefore gives up hot-patching those stubs for the life of + the process, and the crate never undoes it. + + **Why documenting beats fixing.** Relocating trapped instruction pointers would close the + window and would not make two patchers able to share a stub, because the exclusivity belongs to + the technique and not to this implementation. The expensive fix buys the smaller half. The cost + is better spent making the choice visible where it is made, which is what this item did: the + module docs own the statement, the variable's own documentation and the README point at it. + + **The window itself is narrower than reported -- but not for the reason this item first gave.** + Installation runs from the `.CRT$XCU` initialiser before `main`, so no other thread exists to be + inside the range. This item originally asserted that as already true; a later review found it + was not. Installation sat at the end of the public `observe_exceptions`, which `enabled` calls + only when the trace is armed, so a process with `WINDOWS_THREADPOOL_TRACE_HOOKS` set and + `WINDOWS_THREADPOOL_TRACE` unset could reach the installer after its threads existed. Closed by + `M-T10.8`, which moved installation into the initialiser and sealed the window. What remains is + `install_by_label`, which is `#[cfg(test)]`. + +- [x] **M-T10.6** -- **Stop the recogniser's live canary reading a stub a sibling test has + patched.** Found while trying to reproduce `M-T10.3`'s reported hang, which is the only + reason it was found at all. + + **The defect.** `the_stub_recogniser_accepts_the_shape_and_rejects_everything_else` ended by + resolving the `selftest` hook target and asserting the bytes there are a syscall stub -- the + canary that says the recognised shape still describes this machine's Windows. + `a_hooked_stub_records_both_ends_and_still_performs_its_syscall` patches that same stub and, by + design, never removes it. Whichever ran first decided the answer, so the canary was asserting + the planted jump rather than the shape Windows shipped. + + **Measured.** 25 of 25 runs of the `trace::` module failed at `--test-threads 2` before the + fix and 0 of 25 after. It is an order dependency and not a thread-count race: running just the + two tests single-threaded, with the hooking one first, failed too. The full suite passes only + because the default thread count happens to order them the other way -- luck that changes with + the test count, the machine, or a rename. + + **Fixed** by giving the canary `unhookable_stub_entry()`, an `ntdll` export deliberately absent + from `HOOKS` and asserted to be absent, so this module cannot patch it however many hooks a run + installs. + +- [x] **M-T10.8** -- **Enforce the one-thread hook-installation window at the installer.** Found + by the third review round, which falsified a claim `M-T10.4` had written into three + documents one commit earlier. + + **The defect.** Hook installation sat at the end of `observe_exceptions`, which `enabled` calls + only when the trace is armed. A process with `WINDOWS_THREADPOOL_TRACE_HOOKS` set and + `WINDOWS_THREADPOOL_TRACE` unset therefore left the installer's once-flag unconsumed, and + `observe_exceptions` is public -- so a later call from a running process would patch `ntdll` + with the pool's threads alive, the one case the recogniser cannot make safe. + + **The shape is the lesson.** The guarantee was carried by *which function happened to call the + installer*: correct while nobody moved the call, silently false afterwards, and checked by + nothing. Fixed by moving installation into the pre-`main` initialiser, unconditionally and + independent of the trace, then sealing the window; `install_requested` now refuses and records + `refused-after-seal`. Guarded by `the_hook_installation_window_is_shut_before_any_test_runs`, + verified by sabotage. + +- [x] **M-T10.9** -- **Take no process lock while the other threads are suspended.** Found by the + third review round. + + **The defect.** The suspended window took three process-wide locks: the allocator (through + `ntdll_proc`'s owned name, and through the suspend loop consuming its enumeration vector by + value so the `Vec` was freed with threads already stopped), the loader (`GetModuleHandleA`, + `GetProcAddress`), and the memory manager (`VirtualAlloc`, `VirtualProtect`). A thread stopped + holding any of them can never give it back. + + **The shape is the lesson, again.** The window's own comment said "Nothing in here may + allocate", and the enumeration vectors were reserved ahead of time for exactly that reason, with + a control in `sabotage.json` recording it -- while the install the window wrapped called the + loader three lines later. The rule was stated at the allocation somebody noticed rather than at + the window it belongs to. + + **Fixed** by splitting install into prepare / commit / finish: everything that can take a lock + happens while the process is still running, and the window holds a single fourteen-byte store. + +- [x] **M-T10.10** -- **Mark a cancelling group release's repairs after the cancellation, not + before.** Found by the third review round. + + **The defect.** `CleanupGroup::close_members(true)` marked each member's pool as owing a repair + *before* calling `CloseThreadpoolCleanupGroupMembers`, justified in a comment by the claim that + the native release frees the member contexts. It does not -- this crate frees them, in the loop + immediately after. So the justification was false and the ordering it bought was harmful: the + healer could see the mark, find the pool still dispatching, clear it as repaired, and then the + real cancellation would happen with no mark outstanding. An unrepaired wedge from a **single** + cancellation, where the overlapping-cancellation race already recorded in + [`M-T9.2`](COMPLETED-CHECKLIST.md#m-t92) needs two. + + **Guarded.** `a_cancelling_release_marks_its_pool_after_the_cancellation` installs a test-only + hook that clears the mark at the instant before the native release, standing in for the healer's + tick landing there -- which is necessary because both orderings leave a mark outstanding once + the release has returned, so no end-state assertion can tell them apart. Verified by sabotage: + restoring the old ordering fails it. + +- [x] **M-T10.11** -- **Give the worker-factory snapshot named fields.** Found by the third review + round. + + **The defect.** `worker_factory_snapshot` returned `(usize, u32, u32, u32)` and its doc comment + described `(handle, total, waiting, pending)` -- every field after the handle shifted by one + from what the code returns, which is the handle, the thread *maximum*, the total workers and the + waiting workers. A caller who believed the comment read the configured maximum as the number of + workers that exist, so a cold or stalled pool looked fully staffed: the exact inverse of the + reading the data is gathered for. There is no `pending` field at all. + + **Fixed at the build rung, not in the comment.** The return type is now + `WorkerFactorySnapshot` with named fields, so the mismatch is unrepresentable. Nothing could + have caught the tuple version: both halves type-check, and a tuple carries no statement about + which field is which for anything to check against. The consuming tests destructured correctly, + which is why the suite stayed green and the defect lived only in what a reader was told. + +- [x] **M-T10.12** -- **Make the lifecycle trace test run, and expect what teardown actually + calls.** Found by the third review round; the same inertness as `M-T10.2`, which was fixed + without sweeping for siblings. + + **Two defects, one hiding the other.** The test early-returned when the trace filter was unset, + which is every ordinary run and every CI run. Underneath that, it expected + `WaitForThreadpoolWaitCallbacks(cancel)` and `WaitForThreadpoolTimerCallbacks(cancel)` -- forms + that only `try_cancel_pending` makes, and that teardown stopped making when it changed from + cancelling to draining. Run alone with the filter armed it failed; run in a full armed suite it + passed, because sibling tests supplied those records into the one process-wide buffer and + `counted` only asks for a non-zero count. + + **Fixed** by re-executing the one test single-threaded in a child with the filter set, and by + correcting the two expectations. The child exits with a distinctive code, so a filter that + matched nothing -- a rename -- fails loudly instead of exiting 0 and looking like a pass. + Verified by sabotage in both directions: the old labels fail the body, and a wrong test name + fails the parent. + + **The sweep this time:** two tests had the early-return shape; + `the_exception_observer_notes_a_first_chance_exception` passes when armed. + +- [x] **M-T10.13** -- **Restore page protection once per page, not once per stub.** A defect + introduced by `M-T10.9`'s own fix, found by the fourth review round. + + **The defect.** `VirtualProtect` reports the previous protection of the whole **page**, not of + the byte range asked about. Splitting install into prepare / commit / finish moved protection + to one save-and-restore per stub, so two stubs sharing a page had the second save the writable + state the first had just installed -- and the last restore left `ntdll` executable **and + writable** for the rest of the process's life. `park`, `set-info` and `shutdown` share a page on + this host. The single-phase install it replaced did not have this, because it protected and + restored around each store in turn. + + **Guarded** by `stubs_sharing_a_page_are_opened_once_and_the_page_is_restored`, hermetic on a + page the test allocates rather than on `ntdll` -- the real stubs are patched once per process, + so whichever test ran first would decide the answer. Verified by sabotage. + +- [x] **M-T10.14** -- **Read `AlreadySignaled` only on a successful association.** The + `associate` hook read its out-parameter unconditionally after forwarding the call. A failed + `NtAssociateWaitCompletionPacket` need not have written through that pointer, or validated + it, so the instrument could turn an ordinary error return into an access violation raised + by the tracing facility itself. A failed association has nothing to report there anyway: + no packet was associated, and the `-leave` record already carries the status. + +- [x] **M-T10.15** -- **Make the periodic-timer exercise reach its second re-arm entry point.** + The callback switched on a `load` taken *after* its own `fetch_add`, so the second firing + compared 2 against 1 and the `rearm_at` arm was unreachable: the exercise finished a firing + early having driven only `rearm_after`, while its comment claimed both. Both entry points + emit the same event tag, so neither the test nor the trace assertion it feeds could notice. + Now switches on the value `fetch_add` returns. + +- [x] **M-T10.16** -- **Run every trace assertion in a trace-armed child, from one site.** The + sweep for `M-T10.12` asked the wrong question -- it grepped for early `return`, while + `io::tests`' two trace blocks are `if wants(..) { .. }` -- so both were still inert. + + **Fixed at one site rather than a fourth copy.** `trace::in_a_trace_armed_child` now owns the + parent/child protocol; the lifecycle test and both `io::tests` blocks go through it. The first + two hand-written copies had already disagreed about how to distinguish a child that ran from + one whose filter matched nothing. Verified by sabotage: removing the `start-cancelled` record + fails the child on its assertion and the parent on its exit-code check. + + **The real population**, found by grepping for `wants(` rather than for `return`: four sites. + Two were these; one is `the_filter_narrows_to_the_targets_named`, where the gating *is* the + subject; the fourth was the hook records, converted by `M-T10.5`. Re-run after that conversion, + the grep leaves no assertion *silently* skipped on an unset filter: what remains is this crate's + `wants` definition and macros, four sites that **assert** `wants` is true inside an armed child, + the one test whose subject is the gating, and `tests/obligation_report.rs`, which gates but then + exits with a distinct code its parent checks rather than passing quietly. + +- [x] **M-T10.17** -- **Bound the warm-up's teardown on the path where its callback never + arrived.** `prewarm_default_pool` timed out after two seconds and then called + `stop_and_drain`, which waits with no deadline for the very callback whose absence caused + the timeout -- so the bounded check was followed by an unbounded one and the `false` it + exists to return could never arrive. It now cancels the queued invocation first, on that + path only. + + **Not covered by a test**, and stated plainly rather than papered over: reaching the timeout + needs a pool that has stopped dispatching, which is the stall this repository has spent `M-T7` + failing to produce on demand. + +- [x] **M-T10.19** -- **Handle the failures the patch path assumed away, and check the branch the + trampoline does not rebuild.** Three findings from the fifth review round, all in + `trace/hook.rs`, two of them the same recorded rule broken twice. + + **Two unchecked failable calls**, against [A failable call has its failure handled, + always](../../DESIGN-NOTES.md#a-failable-call-has-its-failure-handled-always). The trampoline + page's `VirtualProtect` result was discarded and the pointer published regardless, so a failure + left the hook jumping into a page the processor will not execute. And `with_others_suspended` + skipped past a thread it could not open or suspend and patched anyway -- while that thread was + still running through the bytes being overwritten, which is the precondition the suspension + exists to establish. Both now refuse, with their own `Refusal` variants. + + **The refusal had to learn a distinction, and a test is what taught it.** Refusing on every + failure made `install_by_label` fail in an ordinary test process, because the thread snapshot is + taken while the process runs and a pool starting and finishing workers routinely leaves a listed + thread gone before `OpenThread` reaches it. `ERROR_INVALID_PARAMETER` names that case, and a + thread that does not exist cannot be executing the bytes -- so it is skipped and everything else + refuses. + + **An assumption the module did not admit to.** The recognised stub tests a shared-data byte and + branches past `syscall`; the trampoline rebuilds only the fall-through, so "the same call by + construction" held while that bit is clear and not otherwise. It is clear on ordinary x64, which + is why it went unstated -- an observation about this machine rather than a property of the + technique. `syscall_path_is_direct` now reads it, derived from the recogniser's own bytes rather + than written out a second time, and an install refuses when it is set. + +- [x] **M-T10.20** -- **Stop the fail-fast and the cancellation safety claim promising more than + they detect.** Two findings from the fifth review round that are not defects in the code but + in what it says. + + **The fail-fast** records an outstanding *arming*, not "this `Drop` blocked". A dispatch settles + it for a wait and a one-shot timer while `Drop` still waits for that callback, so dropping + during an executing callback drains without reporting -- and `CleanupGroup` carries no + obligation flag at all. Both now stated at the one site that owns the message. + + **`try_cancel_pending`** is documented as safe because the crate repairs the pool afterwards. + On a pool whose repair item could not be created there is no entry and nothing repairs it, and + `owe_repair` returned in silence. It now records `cancel-untracked`, and both statements of the + claim name the exception. + +- [x] **M-T10.21** -- **Make the thread enumeration fail closed before anything is patched.** The + sixth review round. `with_others_suspended` refused when a thread could not be opened or + suspended, but every way of failing to *find* the threads reported success: a snapshot that + could not be taken, an enumeration that could not be started, and a thread count past `ROOM` + each left `ids` empty or short, which read as "everything is suspended" and patched fourteen + bytes of live code with nothing stopped. Strictly worse than the per-thread case fixed one + block above it in `M-T10.19`. An `enumerated` flag now gates `quiesced`, and the suspend loop + is skipped entirely when it is false, so a refused install perturbs nothing. + +- [x] **M-T10.22** -- **Stop a transient healer-start failure disabling self-heal for the process.** + `ensure_running` cached its result in a `OnceLock>` built with `.ok()?`, so one + failed `ThreadpoolPool::new()` -- a transient condition -- latched `None` permanently and + silently, and no later cancellation anywhere in the process was ever repaired. Now an + `AtomicBool` fast path over a `Mutex>`: a failure records `healer-start-failed` + and leaves the slot empty, so the next cancellation retries. + +- [x] **M-T10.23** -- **Isolate the heal tests from every other tick in the process.** `tick` walks + the whole registry, so a private pool does not isolate a test: another test's tick, or the + background healer's, can clear a mark between the cancellation that sets it and the assertion + about it. A `TICK_GATE` now brackets each critical section. + + **Reproduction, stated honestly.** The review saw this fail on run 19 at 32 test threads. 60 runs + here with the gate disconnected, and 60 more with a thread calling `tick` in a loop, did not + reproduce it -- the window is microseconds wide, so missing it settles nothing. What settles it is + placing a tick in the window by hand: inserting `tick_inner()` between + `a_cancelling_group_release_marks_a_wait_members_pool`'s `close_members_cancelling()` and its + assertion fails that test every time, on the assertion the review named. + +- [x] **M-T10.24** -- **Retry a refused quiesce instead of weakening the refusal.** Found while + verifying `M-T10.23`, not by the review: the full lib suite at 32 test threads failed 13 of 30 + runs in `trace::tests::a_hooked_stub_records_both_ends_and_still_performs_its_syscall` with + `NotQuiesced`. Instrumenting rather than guessing named the cause -- `SuspendThread` returning + `ERROR_ACCESS_DENIED`, enumeration intact -- which is a thread that is *terminating*. + + This was `M-T10.19` half-converted: the benign/dangerous distinction was given to `OpenThread` and + not to `SuspendThread` one line below. The carve-out was **not** extended, because the two cases + differ -- an id that names nothing is gone, whereas a terminating thread may still be running its + exit path, which is the property the refusal establishes. The refusal stands and the whole attempt + is retried outside the suspended window. Measured 13/30 before, 0/30 after. + +- [x] **M-T10.25** -- **Split the cancelling group release out of `close_members(bool)`, and make + posting fabricated packets `unsafe`.** Two findings of the same shape: a hazard gated by the + build for one entry point and by prose for another. + + **`close_members(cancel_pending: bool)`** was safe and ungated in every configuration, while the + per-object `try_cancel_pending` it reaches is `self-heal`-gated and `unsafe` without the feature. + With the feature off, `close_members(true)` cancelled every wait member and landed on a no-op + `owe_repair`. Now `close_members()`, `close_members_cancelling()` (gated), and + `close_members_cancelling_no_heal_tracking()` (ungated, `unsafe`) -- a `bool` cannot be `cfg`-gated, + an item can. + + **`poke_completion_ports`** is now `unsafe fn`. It posts a zero key and a null `OVERLAPPED` to + every completion port in the process above a depth, including ports this crate does not own; the + "**Destructive** ... only for a process that has already failed" rule was enforced by prose alone. + + Both breaking; both recorded in [DESIGN-NOTES.md](../../DESIGN-NOTES.md). + +- [x] **M-T10.18** -- **DECIDED 2026-10-02: a cancellation registers the pool itself, and reports + it when even that fails.** `heal::register` is best-effort, so a pool whose repair item could + not be created had nothing for a later `try_cancel_pending` to mark. + + **The decision.** The cancellation retries the registration at cancel time rather than giving up. + Allocating at the moment of need is what the pre-created repair object exists to avoid, but that + argument does not reach this path: the alternative is not "allocate earlier", it is "never repair + this pool at all". If the retry also fails, `cancel-untracked` is recorded **with the pool key** + and, under `fail-fast`, the call panics. + + **A prior defect surfaced while deciding it, fixed in the same change.** `Registration` held only + `Option>` and discarded the key, so an object whose registration failed could not + find an entry another object later created for the same pool -- the pool was repairable, a healthy + entry existed, and the cancellation still reported it untracked. The same omission made + `cancel-untracked` record `0` rather than naming the pool. `Registration` now keeps its key. + + **Where the fail-fast fires is load-bearing.** `CleanupGroup`'s cancelling release marks members + between the native release and the loop that frees their contexts, so a panic raised there would + unwind past the frees and leak every context. The report is accumulated and acted on after the + frees, matching `fail_fast_if_owed`'s rule that the panic must report a violation and not cause + one. + + **The error edge is now reachable from a test.** It runs only when `CreateThreadpoolWork` fails, + which no test can arrange, so a test-only `FORCE_REPAIR_FAILURE_FOR` forces it -- keyed to one + pool, because a global flag failed an unrelated test's registration on the first run. Both + directions are asserted, and sabotaging the retry fails the recovery test by name. + + Recorded in [DESIGN-NOTES.md](../../DESIGN-NOTES.md#a-cancellation-allocates-its-own-repair-rather-than-giving-up). + +- [x] **M-T10.26** -- **Release a trampoline page when the batch that prepared it is refused.** The + seventh review round, and its only finding. `prepare` allocates a 4 KiB executable page per + hook and publishes it into `TRAMPOLINES[index]` before the batch knows whether it will commit, + because the patch it builds points at that page. Both post-preparation refusals -- `open_pages` + failing, and the quiesce failing after all its attempts -- returned with the pages still + allocated and still published. Bounded at one page per hook and once per process, but a leak. + + **The obvious fix would have introduced a use-after-free, which is why it is not the one taken.** + `prepare` publishes unconditionally, so if an index were already installed its live, patched stub + would be jumping through the slot this preparation just overwrote; freeing there would release a + page a running hook is inside. `discard_prepared` therefore releases only when it found the slot + **empty**, and restores the displaced pointer otherwise. Only the test-only `install_by_label` can + reach the non-empty case -- `install_requested` runs the batch once per process behind `DONE` -- + but the branch exists because the function must be correct for its callers rather than for the one + that happens to exist today. + + **Verified on the real path rather than by reading.** Forcing `open_pages` to return `None` and + running the hook test printed `DISCARD index=5 previous=0 slot_now=0` and reported `NotWritable`: + the cleanup runs, takes the releasing branch, and clears the slot before the free. Neither refusal + branch is reachable from a test without that kind of injection, which is pre-existing and remains + true. + + The macro's `SAFETY` note that the slot is "never cleared" was corrected in the same change; it is + now cleared on exactly one path, and the comment states why that cannot race a call already inside + the hook body. + +- [x] **M-T10.27** -- **Run the exception-observer assertion in a trace-armed child.** The eighth + review round's headline finding, and this branch's own signature defect recurring: + `the_exception_observer_notes_a_first_chance_exception` opened with + `if !wants("exception") { return; }`, so in every ordinary run and in CI it returned having + raised nothing and asserted nothing. `M-T10.12` and `M-T10.16` exist to remove exactly this, + and the remedy they introduced -- `in_a_trace_armed_child` -- was already applied at three + other sites. Measured before the fix: the test passed in 0.00s with the filter unset, and + since its only other exit is a `panic!`, passing was itself the proof it returned early. + Measured after: 0.06s, and sabotaging the recorder fails it by name. + +- [x] **M-T10.28** -- **Stop the healer parking on the one pool it is trying to repair.** `tick` + submits a repair, clears the mark, then calls `retire_idle` in the same pass. An entry whose + objects are gone is then idle, so the last `Arc` drops, `RepairWork::drop` drains the work + object -- and that drain cannot return until the pool dispatches. The pool is by construction + the one suspected of not dispatching, and the healer is built with `set_max_threads(1)`, so + one wedged pool parked the only thread the facility has and no pool in the process would ever + be repaired again. + + `retire_idle` already moved the drop out of the registry lock for this exact reason, and its + comment says so; that protected every *other* pool's registration from the wait but not the + healer's own thread. Retirement now also requires `repair_settled()` -- a submission counter + paired against the existing `runs` -- so an entry is retired only once the pool has given every + repair back. An entry kept alive for a pool that never dispatches is the cheaper failure by a + wide margin. The counter is incremented *before* the submit, because a repair can be dispatched + the instant it is handed over and a count taken afterwards could be overtaken by the run it is + meant to be paired against. + +- [x] **M-T10.29** -- **Bound the writable window to one attempt, not sixteen.** A defect introduced + by `M-T10.24`'s retry. `install_batch` opened the `ntdll` pages, called the retrying + `with_others_suspended`, and restored them only afterwards -- so a contended install held live + code pages `PAGE_EXECUTE_READWRITE`, with every other thread running, across all sixteen + attempts and their snapshots, where one attempt holds them for microseconds. The retry now + lives in `install_batch` and opens and restores per attempt; `quiesce_once` is a single + attempt again and its doc records why the retry cannot wrap it. `NotWritable` is not retried, + because a page this process cannot make writable will not become writable a millisecond + later. Re-verified that the flake `M-T10.24` fixed stays fixed: 0 failures in 30 runs at 32 + test threads. + +- [x] **M-T10.30** -- **Carry the factory handle on the two records the investigation turns on, and + five smaller corrections.** `counts-waiting` and `counts-pending` passed a literal `0` while + `read_one`'s doc promised every record carries the handle -- and those two are precisely what + the facility exists to read. `counts()` reads every factory in the process rather than + guessing which is the default pool's, so without the handle a two-factory capture could only + be attributed by row adjacency. Both now carry it, and the doc names the records that + genuinely spend the slot on a partner value. + + Also: the module doc linked `../../../windows-ioring-sys/STALL-TIMELINE.md`, a path `b4a0a886` + emptied when it re-homed that file here; `commit`'s safety contract credited `prepare` with + opening the page, which `prepare` explicitly does not do (`open_pages` does, per `M-T10.13`); + `fail-fast` appeared in neither the README nor the crate docs, leaving its build-unification + hazard stated only in a `Cargo.toml` comment; the `compile_fail` guarding the feature-off API + shape called a made-up method and so asserted only that `rustc` rejects unknown names -- it now + names the real method under `cfg_attr(not(feature = "self-heal"))`, verified bidirectionally by + forcing the block on with the feature enabled and watching it fail; and `Win32_System_IO` under + `trace` was redundant against the unconditional dependency feature and justified by a comment + calling it test-only, which `poke_completion_ports` being public contradicts. + +- [x] **M-T10.31** -- **Claim a member's repair before the release that can free its pool.** The + ninth review round, and a use-after-free introduced by `M-T10.18`'s cancel-time retry. A wait + whose pool could not be registered holds nothing that keeps that pool alive -- the repair work + object, which would be a bound object deferring `CloseThreadpool`, was never created. A + cancelling group release frees the last bound object and *then* marks, so the retry inside + `owe_repair` could call `CreateThreadpoolWork` against freed memory, reachable through the + safe API. `release_members` now takes each member's claim **before** the native release and + holds it across, which both creates that object and keeps the pool alive. The per-object path + was never exposed: `self` is a live bound object for the whole call. + +- [x] **M-T10.32** -- **Give retirement one predicate instead of two.** `M-T10.28` added the + outstanding-repair condition to `retire_idle` and not to `release`, so the hazard it was added + for -- `RepairWork::drop` draining an undispatched repair on the healer's only thread -- was + still reachable through the other site. Both now call `is_retirable`. This is the fourth + partial fix of this shape on this branch; the answer is one definition, not a third careful + reading. + +- [x] **M-T10.33** -- **Make `close_members_cancelling_no_heal_tracking` actually not track.** It + called `release_members(true)`, whose repair pass was unconditional, so the method named for + *not* tracking marked pools, allocated repair objects, started the process-lifetime healer, + and could panic under `fail-fast` -- a contract its own documentation denies, and the opposite + of what a caller diagnosing a stall asks for. Tracking is now a parameter the safe form passes + and this one does not, which also stops the fail-fast firing for a caller who took the + obligation deliberately. + +- [x] **M-T10.34** -- **Check the patch-window precondition rather than asserting it from + placement.** The module documented the hazard exactly -- a suspended thread's instruction + pointer can be inside the bytes being replaced -- and then claimed the supported path avoids + it "because it patches when the process has no other threads", justified by running from a + `.CRT$XCU` initialiser. Placement does not establish that: an initialiser ordered earlier may + have started threads, and in a DLL the same initialiser runs at attach inside a running + process. No instruction pointer was ever read. + + Each suspended thread's `Rip` is now compared against the ranges about to be written, and the + install refuses when one is inside. Verified by sabotage: forcing the comparison to match refuses + with `NotQuiesced`. The `CONTEXT` buffer is wrapped in a 16-byte-aligned type because + `GetThreadContext` documents that alignment on this architecture while `align_of::()` + from `windows-sys` measures 8 -- it worked without the wrapper, which is incidental behaviour + rather than a contract. + +- [x] **M-T10.35** -- **Stop the hook tests failing whenever hooks are enabled.** The initialiser + installs whatever `WINDOWS_THREADPOOL_TRACE_HOOKS` names before any test runs; the two + installation tests then installed again, met their own patch, and failed on the recogniser's + correct `NotAStub` refusal. The suite was green only because nothing ever set the variable. + Reproduced at `exit 101` before the fix. They now install only when `installed_by_label` + reports the stub is not already patched, which does not weaken rejection of a foreign patch. + + One assertion was wrong in the same configuration for a different reason: it required + `factory_handle()` to be zero, but `FACTORY` is process-wide and the worker-factory hooks + legitimately write it, so with every hook installed a real handle is already learned. The claim is + that *this* call contributes none, so it now compares against the value captured before the call. + Measured after: 299 pass with hooks unset, `*`, `selftest`, and `associate`. + +- [x] **M-T10.7** -- **DECIDED 2026-10-02: yes. CI documents private items.** + `--document-private-items` added to all five `cargo doc` steps, which already denied the link + lints but only ever resolved the public surface. + + **The cost it was weighed against did not materialise.** The open question was whether the flag + would surface links that are useful to a maintainer but meaningless in published docs, forcing a + choice between noise and rewriting prose to satisfy a build nobody reads. It surfaced **nine dead + links across four crates**, and every one was simply wrong -- naming an item that had been + renamed, removed, or gated out of the configuration being documented. None was a judgement call: + + - `heal.rs` x3 -- `record_repair_run` (renamed by [`M-T9.1`](COMPLETED-CHECKLIST.md#m-t91)), + `PoolEntry::arm` (never existed; `arm_repair` is a free function), + `PoolEntry::dispatched_since` (removed by [`M-T9.2`](COMPLETED-CHECKLIST.md#m-t92)). All + three were written during this branch's last two commits. + - `trace/hook.rs` x3 -- `install`, which has been `install_batch` throughout. + - `windows-waitable-queues` x3 -- `reserving_mpsc::ClaimLayout` from two sibling modules needing + a `crate::` path, and `BOUNDS` where the item is `BOUNDS_MAX`. + - Found afterwards, because cargo stops at the first failing crate: `windows-file-watcher` x3 + (`WatcherInner::reopen`, renamed to `install`) and a `windows-platform-probes` link ambiguous + between a function and a macro. + + **Verified the flag is what catches them**, rather than assuming: a dead link planted in a + private item passes `cargo doc -p windows-threadpool-sys --no-deps --all-features` and fails the + same command with `--document-private-items`. The planted link was first placed in a + `self-heal`-gated module and checked under `--no-default-features`, where both forms passed -- + the module is not compiled there, so the first attempt proved nothing. + + **One link was deliberately left unlinked.** `pending.rs` referenced + `Completion::with_injected_failure`, which is gated on `fault-injection`; a link dangles in the + `--no-default-features` build CI also documents. Named in backticks with the reason, matching the + precedent set for `close_members_cancelling`. + +- [x] **M-T10.5** -- **Make the hook tests' trace-record assertions reachable, so a sabotage can + reach them.** Done 2026-10-02. + + **What landed.** `a_hooked_stub_records_both_ends_and_still_performs_its_syscall` now runs its + body in a trace-armed child through `trace::in_a_trace_armed_child`, the one site `M-T10.16` + built for exactly this, and its two record assertions lost the `if wants("wfactory")` guard that + made them inert on every ordinary run and in CI. The child opens by asserting `wants("wfactory")` + is true, so a filter that failed to reach it fails loudly rather than passing vacuously -- the + trap being closed here, arriving one level up. + + **`fired` stays counted rather than traced**, deliberately. It is the half of that test which + needs no armed trace, and keeping it independent of the filter means the two halves cannot fail + for the same reason. + + **Both record sabotages are in `sabotage.json` and are caught by name**, not by a crash: + deleting the `-enter` record fails on "the hook must record entering the call", and deleting + `-leave` on its own message, each surfacing through the parent's exit-code check. Verified + individually before being written down, and the manifest's `notCoveredHere` note -- which had + cited a completed, unrelated decision for this work -- was rewritten to say what is now swept. + + **The note did not simply go away, because the gap it describes did not.** What `M-T10.5` could + fix was assertions that existed and could not run. The hook module's other records have no + assertions at all, which is a different defect; it is now stated precisely in the manifest and + queued as `M-T10.36` rather than left implied by a note that would read as though everything + were covered. + +- [x] **M-T10.36** -- **Assert the hook module's remaining records, which no test checks at all.** + **Dissolved 2026-10-03 by [`M-T12.2`](#m-t122): the hook module was deleted.** The records + it names no longer exist, so there is nothing left to assert. Recorded as dissolved rather + than deleted because the gap it describes was real while the module was. + +- [x] **M-T10.37** -- **Gate the five heal tests that drive an entry with synthetic stamps.** + Found 2026-10-02 by `M-T10.5`'s stress run, and a defect in `M-T9.2`'s own tests. + + **The failure.** `an_entry_owing_a_repair_outlives_its_last_object` failed on run 18 of 25 at + `--test-threads 32`, on its final assertion -- the entry it had just healed was still in the + registry. It took no tick gate. + + **The mechanism, and why `M-T9.2` created it.** These tests stamp an entry by hand with small + values (`1`, `2`, `10`, `12`). A concurrent tick -- the background healer's, or another test's -- + writes `last_submitted` with `now()`, and the repair it submits writes `last_started` the same + way. Both are astronomically larger than the synthetic values, so a single interleaved tick + inverts `repair_in_flight`, `unhealed`, or retirability, whichever the test went on to assert. + The flag this design replaced could not do it: holding no clock value, a tick could only set or + clear it, so these tests were safe when they were written and stopped being safe when the stamps + landed. + + **Fixed at all five sites, not at the one that failed.** Grepping for tests that stamp without + taking the gate found five; fixing only the observed one would have left the identical defect at + four. This is the half-converted-rule shape the house rules name, and it is the fifth time it has + appeared on this branch. + + **Confirmed by placing the interleaving rather than by waiting for it**, since a window this + narrow proves nothing by failing to recur: inserting one `tick_inner()` between the test's + `stamp_cancelled(1)` and `stamp_started(2)` fails it every time, on the same assertion the stress + run hit. 30 runs at 32 threads are clean afterwards. The mechanism is recorded on `gate()`, beside + the first reason it exists. + +### M-T11 -- The tenth review round: a lost trait, two stamp defects, and the hook's ABI + +Opened 2026-10-03 by a review of everything on this branch. Seven findings. Four are this branch's +own regressions and two of those are `M-T9.2`'s, landed the same day; two are long-standing in the +`trace`-gated hook facility. The round's lesson is that **the gate cannot see any of them**: every +one passed `cargo clippy -D warnings` on four configurations, the full test suite on five, 30 +stress runs at 32 threads, and a 27/27 sabotage sweep. Four of the seven are a claim in a doc +comment that the code next to it does not keep. + +- [x] **M-T11.1** -- **Restore `TimerMember`'s `Send` and `Sync`, and put the rule on the build.** + Done 2026-10-03. The two `unsafe impl`s, with `WaitMember`'s SAFETY reasoning, plus a + `const _` block instantiating `needs_send` / `needs_sync` over all four member types. + Verified by sabotage in the direction that matters: with the impls removed the build fails + `E0277` **at lines 942-943 of the assertion itself**, not at some incidental use, so the + guard is what catches it rather than a bystander. + + **The regression.** At the merge base `TimerMember` held only a `PTP_TIMER` and a `PhantomData`, + so it was auto-`Send + Sync`. This branch added `context: *mut c_void` and did not add the + `unsafe impl`s, so a public type stopped being movable or shareable between threads. Verified by + compiling an assertion: `TimerMember` fails both bounds with `E0277` while `WorkMember`, + `PeriodicTimerMember` and `WaitMember` pass. + + **`WaitMember` is the precedent and the tell.** It carries two pointers and has both impls with a + SAFETY note. The same reasoning applies verbatim to the timer's context, so this is one rule + applied at one of two sites -- the sixth time that shape has appeared on this branch. + + **Fix at the build rung, not by adding two lines.** A `const _: () = ...` assertion over all four + member types makes the next pointer-shaped field a compile error rather than a silent API break. + Assert the accepting direction too, so a bound that is accidentally removed from every type is + not reported as success. + +- [x] **M-T11.2** -- **Make the repair predicates say what their doc comments claim.** Two defects + in `M-T9.2`, found together because they are the same block of accessors. Done 2026-10-03: + `outstanding: AtomicU32` answers "is a repair queued", and `last_cancelled` / `last_started` + are published with `fetch_max`. Each has a test that traverses the defect + (`a_retry_leaves_two_repairs_outstanding_until_both_have_started`, + `a_cancellation_published_out_of_order_does_not_lower_the_stamp`), each verified to fail by + name when its fix is reverted, and both are in `sabotage.json`. `last_submitted` stays a + plain store and says why: single-writer, and the tests must be able to move it backwards. + + **A plain store cannot publish a monotonic clock.** `stamp_cancelled` is an unconditional store + whose doc says "a later cancellation can only move the stamp forward", and `now`'s doc goes + further: "the opposite mistake cannot happen at any resolution, because the counter never goes + backwards." The counter does not, but the *store* can land out of order: a thread that reads 10 + and is preempted past a dispatch at 20 and a second cancellation at 30 will write 10 last, and + `unhealed` then reads healthy with a cancellation unanswered. That is exactly the lost + cancellation `M-T9.2` was written to eliminate, re-entering through the publication rather than + through the compare-exchange. `fetch_max` makes the stated property true. + + **The retry broke the outstanding-repair predicate.** `repair_in_flight` is + `last_submitted > last_started`, which was sound only while at most one repair could be + outstanding. `M-T9.2`'s overdue path submits a second before the first has started, so the first + starting makes the predicate false while the second is still queued. `is_retirable` then permits + retirement, and `PoolEntry::drop` drains -- waiting for a callback on a pool that is by + construction suspected of not dispatching, on the healer's only thread. Self-heal stops + process-wide. + + **Count outstanding submissions rather than comparing timestamps**, so the predicate answers the + question it is named for. Timestamps then serve only health (`unhealed`) and retry timing + (`repair_overdue`), which is what they can actually support. + +- [x] **M-T11.3** -- **Update the self-heal feature guide, which documents the design `M-T9.2` + deleted.** Done 2026-10-03. The guide still tells a consumer that every callback this crate dispatches stamps + a per-pool marker, that a busy pool therefore suppresses repair submissions, and that the per + callback cost is an interrupt-time read and a relaxed store. None of that is true: health is now + evidenced only by the crate's own repair dispatching, and that store was removed from every + callback. This is the public-facing document and nothing swept it. + +- [x] **M-T11.4** -- **Stop a failed pre-release recovery claiming to pin a pool it does not.** + Done 2026-10-03. The marking pass now iterates the **claims** rather than the members' + contexts, and `Registration::owe_repair_claimed` reports untracked instead of re-registering. + Losing that retry costs little: the claim *is* a retry, taken at the only moment it is safe, + with the members still live. The `OwnedResource::owe_repair` hook is gone, since the claim + now carries the same fact and two mechanisms could disagree -- which orphaned + `ThreadpoolWait::owe_repair` in the feature-off build and it is gated to match its one + caller. Guarded by a test that drives the *difference*, not the symptom: it lifts the forced + allocation failure in the `before_release` hook, so the removed retry would have succeeded. + `recover_repair` returns `Some(reclaim())` unconditionally, and `reclaim` re-attempts + registration. When that attempt fails the `Registration` holds no entry and therefore no `Arc`, + so it keeps nothing alive -- yet the field's doc says the caller "holds [it] across the native + release so the pool cannot be freed under the marking pass that follows". On that path the + marking pass can reach `owe_repair_untracked`, which registers and arms a work object against a + pool pointer whose last member has just been released. Reachable only under allocation failure, + which is why it is stated rather than assumed. + +- [x] **M-T11.5** -- **Give each hook the arity of the stub it is planted over.** Done 2026-10-03, + and **deliberately only where the arity is establishable** -- read the next paragraph before + treating the remaining five as an oversight. + + **What landed.** The `hooks!` table gained an argument-list column that becomes each hook's own + signature. `selftest` declares three and `associate` eight. Two `const` assertions sit beside the + table: no entry may exceed the forwarding width, and the `associate` entry may not drop below + eight, since it reads `AlreadySignaled` through a `get` whose `None` arm would otherwise make the + record quietly stop being emitted with every test still passing. Both verified by sabotage: each + fails the build with its own message. + + **The other five keep the full width, and that is a decision rather than a gap.** Their stubs are + undocumented and at least `NtWaitForWorkViaWorkerFactory` has been described with different + argument counts on different Windows versions. Declaring too many reads stale bytes from the + caller's frame and forwards values the kernel ignores; declaring too few silently drops a real + argument, and on that stub the result is every pool worker parking with a zeroed parameter -- in + the exact path this facility exists to observe. So the rule is to narrow only where this crate + establishes the arity from something it owns: `call_selftest` calls its stub directly, and + `already_signalled` reads the eighth argument of the other. + + **`HOOKS` no longer stores the replacement function.** With per-stub signatures there is no single + function type for that column, so the address is reached through a generated `hook_address`; a + `const` cannot cast a function to an integer, which is why it is a function. + + > **DECISION TO RAISE: narrowing the remaining five needs an arity source good on every supported + > Windows version.** That is a different kind of work from this item -- acquiring and validating + > an external contract, not editing a table -- and is not queued. Say if it is wanted. + +- [x] **M-T11.7** -- **Make an arity change break something, rather than being discovered by a + crash.** Asked 2026-10-03, done the same day. + + **The build rung is not available here, and that is the finding rather than a limitation to work + around.** Arity is a property of the *running* operating system. A build script could read the + build machine's Windows version, but the binary may run on a different one, so a build-time check + would be a proxy that cannot establish the fact -- the "cheaper rung that looks like enforcement" + the house rules name. Runtime is the honest rung. + + **Two guards already existed without being labelled.** `call_selftest` invokes its stub through a + *three*-argument pointer and the test asserts all three out-parameters are written and ordered, + so a changed arity fails CI; that is now stated at the call, with a note not to relax those + assertions into "the call returned", which is the half that would still pass. And the five wide + hooks are immune to arity *changes* by construction, since twelve is an upper bound -- if + `NtWaitForWorkViaWorkerFactory` were mis-forwarded the pool would stop dispatching, which is the + stall this crate's whole suite detects. + + **The real gap was `associate`**, the only hook that interprets a high-numbered argument rather + than forwarding it. A shifted arity makes its eighth slot some other parameter, and dereferencing + that raises an access violation *inside the instrument*. `already_signalled` now returns a + three-way `Signalled` and checks twice before believing the value: the slot must be a possible + user-mode address, and what it points at must be a `BOOLEAN`. A failure records + `associate-flag-implausible` carrying the raw argument, because the finding is what the slot held + instead. + + **Stated limit:** a plausible-looking address may still be unmapped, and no check affordable on + every wait registration proves otherwise. What the range rejects is the likely shape of a shifted + argument -- a small integer, which is what this call's other parameters hold. + + **Only one half is swept, deliberately.** The value check fails by assertion and is in + `sabotage.json`. Removing the *range* check makes the test dereference address 1 and the process + dies with `STATUS_ACCESS_VIOLATION` -- caught by a crash rather than by a test noticing, which + this repository treats as uncovered, so it is verified by hand and recorded here rather than + written down as a guard it is not. Every replacement + is a twelve-argument `extern "system"` fn, but the stubs take fewer. Forwarding twelve + arguments *to* a smaller callee is fine; *receiving* twelve from a caller that passed three is + not -- the hook reads incoming stack slots the caller never had to supply. On x64 those slots + land inside the caller's own frame, so it reads stale data rather than faulting, which is why + this has never been seen to misbehave and why it is still wrong. The macro already varies + per-hook data; arity is one more column. + +- [x] **M-T11.6** -- **Make the quiesce account for threads created while it is enumerating.** + Done 2026-10-03. After the suspend loop, the thread set is swept again and any thread the + quiesce did not suspend refuses the attempt; `install_batch`'s retry then picks it up in a + fresh snapshot. + + **`NtGetNextThread`, not a second toolhelp snapshot, and that is forced rather than chosen.** + Re-enumerating with `CreateToolhelp32Snapshot` allocates, and an allocation with threads + suspended deadlocks against one suspended while holding the allocator lock -- the hazard this + phase is built around, which an earlier version of this function hit for real. The export is + resolved *before* anything is suspended, because `ntdll_proc` allocates and `GetProcAddress` + takes the loader lock. Unresolvable means refuse, matching the module's posture. + + **The guard needed a test, and finding that out is the point.** Making the sweep blind + (`known = true`) left all 292 lib tests passing, so the `sabotage.json` entry would have claimed + `caught` falsely -- caught only by running it rather than assuming. The race cannot be landed on + demand, so `OMIT_ONE_FROM_SNAPSHOT` produces its *effect*: it drops one id the snapshot did list, + which from the sweep's side is indistinguishable from a thread created after it. + `a_thread_the_snapshot_missed_refuses_the_install` asserts both directions, and the blind sweep + now fails it by name. The + installer snapshots thread IDs, then suspends that snapshot. A listed thread can create + another before it is suspended, and the new thread is neither in the snapshot nor suspended, so + it can execute a stub while its fourteen bytes are being written. The instruction-pointer check + covers only threads that were successfully suspended. Reach a fixed point -- re-enumerate after + suspending and repeat until a pass adds nothing -- or refuse, which is the established posture + for this module when it cannot establish its precondition. + +### M-T12 -- The eleventh review round + +Opened 2026-10-03 by a review of everything on this branch. Four findings; this milestone holds +the one acted on so far. + +- [x] **M-T12.1** -- **Submit the repair inline when the healer cannot be started.** Done + 2026-10-03. + + **The gap.** `owe_repair` stamped the entry, called `ensure_running`, and reported the + cancellation as tracked **whatever that returned**. When the healer could not be started there + was then a pool marked unhealed with nothing scheduled to visit it, while `try_cancel_pending`'s + claim that this crate repairs the pool afterwards silently did not hold -- and the untracked + fail-fast did not fire either, because by its own account nothing had gone wrong. `M-T10.22` + makes a *later* cancellation retry the start; what it does not cover is a process where no later + cancellation arrives. + + **The fix is to do what the tick would have done.** `ensure_running` now reports whether a healer + exists, and when it does not the entry's own pre-created repair is submitted on the spot. That is + the action measured to release the stall, the work object already exists so it allocates nothing, + and the only thing given up is the coalescing the timer provides -- a cost this path pays once + rather than a correctness question. The cancellation is still reported as tracked, which is + honest: its repair has been handed over rather than scheduled, which is the stronger answer. + + **One site, not one of three.** The first attempt fixed `owe_repair` alone and left + `owe_repair_claimed` and `owe_repair_untracked` with the old unconditional `true` -- the + half-converted shape this branch keeps producing. The common tail is now + `mark_and_arrange_repair`, which all three call, and the submit itself is `submit_repair`, shared + with `tick_inner` so the order of stamp, count and call cannot drift between them. + + **Guarded in both directions** by `a_cancellation_submits_its_repair_when_the_healer_will_not_start`, + which holds the tick gate so a submission it observes can only be the inline one: with a healer + available nothing is handed over, and without one the repair is submitted and counted + outstanding. In `sabotage.json`, and caught by name when the fallback is reverted. + + **Extracting `submit_repair` broke two existing sabotage anchors**, which the pre-sweep check + caught before a run was wasted on them. That check is now the habit this branch was missing. + +- [x] **M-T12.2** -- **Delete the inline hooking facility: this crate ships no + undocumented APIs.** Decided and done 2026-10-03 by the engineer. + + **The constraint.** No shipped code may call an undocumented entry point or depend on behaviour + Microsoft does not publish -- names *and* shape: instruction encodings, structure layouts, fixed + addresses, argument counts. Recorded as + [This crate ships no undocumented APIs](../../DESIGN-NOTES.md#no-undocumented-apis), which also + records what rebuilding the observations on documented ground would take. + + **The surface was wholly confined to one module**, which is why the removal is surgical: all 61 + undocumented references were in `trace/hook.rs` and 11 more in its tests. The threadpool + wrappers, the obligation model, the self-heal and the trace *record* layer contained none. + + **What went:** `trace/hook.rs`; the hook tests; four public functions (`worker_factory_counts`, + `completion_port_depths`, `worker_factory_snapshot`, `poke_completion_ports`); the + `WorkerFactorySnapshot` type; fourteen `sabotage.json` entries; and two integration tests. + + **A real reduction, stated rather than tidied away.** One of those tests, + `prewarming_makes_the_default_pool_create_a_worker`, was the only external evidence that + `prewarm_default_pool` causes a worker to be created. That claim now rests on the function's own + documented contract -- a callback having run is the proof, because it ran on a worker -- which is + self-evidencing. **I had told the engineer the hook layer's only consumers were its own tests; + that was wrong**, and the compiler found the two integration tests I had missed. + + **The manifest lost both of its `expect: "survives"` controls**, since both lived in the deleted + module. A sweep with no control can only say the tests are sensitive, never that they are + sensitive to the right things, so one was added: strengthening a `Relaxed` load to `SeqCst` + cannot change observable behaviour, so a suite reporting it CAUGHT is failing for some other + reason and the sweep's other results cannot be trusted until that is explained. + + **Two of the open review findings are dissolved with the code** -- the quiesce sweep's fail-open + and the recogniser's 16-byte validation. The third, the trace buffer's `Vec::remove(0)` eviction, + is in the record layer and survives; see `M-T12.3`. + +- [x] **M-T12.3** -- **Make the trace buffer evict in constant time.** Found by the eleventh review + round; the one finding of that round that the hook removal does not dissolve. Done + 2026-10-03. + + **What landed.** `Buffer` keeps a `head` and overwrites in place, so eviction is one indexed + write and a modulus. The vector is no longer in logical order after the first wrap, so every + reader goes through `iter`, which yields `records[head..]` then `records[..head]`; before the + first wrap `head` is zero and that is simply the vector. `dump` and the tests' `held` helper + both moved onto it. + + **The modulus is of the vector's own length, not of `capacity`.** The two can differ: a buffer + that reached a larger capacity keeps that length if a later push passes a smaller one, exactly + as the remove-and-push it replaces did. Taking `capacity` would index outside the vector. + + **The risk this fix introduces is a rotation, not a loss**, which is why the guard is shaped the + way it is. An off-by-one head, or an iterator that splits the wrong way, returns the right + records in the right number and the wrong order -- a capture of a sequence that never happened, + and nothing about it looks wrong. `the_window_stays_in_order_across_repeated_wraps` pins the + exact window after every push through several full turns; both failure shapes were verified to + fail it by name, and the reversed split is in `sabotage.json`. + + **What is deliberately not swept:** restoring `Vec::remove(0)`. It would survive, and correctly + -- it is slower, not wrong, and no test can assert the absence of a memmove without timing. The + case against it is analytic and is recorded above rather than measured. + + **The defect.** `Buffer::push` calls `self.records.remove(0)` once the buffer is full, which + shifts every remaining record. `Record` is about 72 bytes and `CAPACITY` is 262,144, so each + push past the limit memmoves roughly 18 MB, under the trace mutex. + + **Why it matters more here than the figure suggests.** This is an instrument for observing + timing-sensitive behaviour. A buffer that fills mid-capture starts perturbing -- and plausibly + manufacturing -- the very stall it is being used to study. The buffer is deliberately sized not + to fill in an ordinary capture, which is what has kept this invisible. + + **Shape.** A ring: a head index and wraparound, so eviction and insertion are both constant + time. `dump` reads from the head rather than from index zero; `dropped` and the + overflow announcement are unaffected. Watch that the existing buffer tests pin *order* as well + as contents, since a ring is where that can silently invert. + +- [x] **M-T12.4** -- **Unfuse eight doc blocks, and check the half that is checkable.** Found by + the twelfth review round, which found them by reading; done 2026-10-03. + + **The defect, and why ten rounds missed it.** An edit that replaces an item's documentation by + anchoring on its signature and prepending new prose leaves the previous block above the new one. + The result compiles, renders, and passes every gate: rustdoc validates link *syntax* rather than + prose, clippy does not read doc semantics, and `missing_docs` is satisfied by the presence of *a* + comment rather than by its being the right one. All eight are mine, all from this branch. + + **Five were a false claim repeated.** The `registration` field on every context type said it sat + there "so the trampoline can reach it to stamp a dispatch (`M-T6.2`)" -- a mechanism `M-T9.2` + deleted. In four of them the true text sat directly underneath, so each field stated two + contradictory things; `wait.rs` had only the false one. `M-T9.2`'s own entry claims "each field + says so", which was half-true, and the true half is what let the sweep look done. + + **Three left an item with no documentation of its own**: `Registration::owe_repair` and + `release` had their docs stolen by the item above them, and `ThreadpoolWait::recover_repair` + carried two `# Safety` sections. `missing_docs` cannot see any of these -- two are `pub(crate)`, + and the third has a comment, just not its own. + + **One was actively misleading.** `WaitMember::stop_and_drain` carried a section headed "This + brings a process-wide hazard forward; it does not create it", which is the opposite of what + draining does. The reviewer read that text as belonging to no existing method and suggested + deleting it; that was wrong -- `WaitMember` has two cancelling methods, and the paragraph makes + a group-specific point neither of them stated. It was **relocated** to + `try_cancel_pending_no_heal_tracking`, whose obligation it describes. + + **The check, and what it deliberately does not cover.** `tests/doc_blocks.rs` errors on a doc + block carrying the same heading twice: there is no legitimate item with two `# Safety` sections, + so that signal is sound. It skips fenced regions, because a doctest hides a line with `# ` and + would otherwise be indistinguishable from a heading. It walks `src` recursively -- a shallow + walk would skip `heal/`, `timer/` and `trace/` and report success over a fraction of the tree -- + and asserts both a floor on files visited and a floor on headings recognised, so a broken walk + or a blind parser fails loudly instead of passing vacuously. Its rejecting direction is verified + by sabotage: re-fusing the block the reviewer found fails it by file and line. + + **The general case is not automated, and that is the decision rather than an omission.** Two + fused *summary sentences* carry no sound signal. The best heuristic -- a one-sentence paragraph + with no blank line before it and one after -- flags about twenty-five sites in this crate, of + which four are real; shipping it would be shipping an alarm nobody can act on. It is a useful + one-off grep, recorded here, and the sweep it drove found exactly the reviewer's eight and no + more. + +### M-T13 -- The twelfth review round: an uncovered rule, a half-converted twin, and the hook's residue + +Nine findings, all confirmed. The round is characterised by *residue*: four of the nine are +leftovers from two of this branch's own larger edits -- the hook deletion in `1eca1e16` and the +heal rework in `M-T9.2` -- that removed a thing without removing what pointed at it. The first +finding is of a different and worse kind: a rule nothing tests. + +- [x] **M-T13.1** -- **Make the tick's coalescing skip actually tested.** `tick_inner`'s + `if !entry.unhealed() { continue; }` is the coalescing rule: a pool that dispatched after its + cancellation is live and is owed no repair. **Deleting the skip outright leaves all 286 lib + tests passing** -- measured, not argued. Both tests that exist to cover it are inert: + `a_tick_leaves_a_pool_its_own_repair_has_answered` asserts `repairs_run() == 0` on the line + after `tick_inner()`, and `repairs_run` counts repairs that have *run*, which on a fresh private + pool with no thread yet is asynchronous and still 0 whether or not one was submitted. Its own + assertion message names the choice -- "on the repair having run rather than on the mark" -- so + the reasoning is recorded and wrong. `a_tick_leaves_a_pool_alone_when_nothing_is_owed` asserts + only `!unhealed()`, which is false-by-construction when nothing ever stamped a cancellation, so + no behaviour of the tick can make it fail. + + **Target:** assert on the synchronous evidence -- `last_submitted()` and `repair_in_flight()` -- + which are written by `submit_repair` before it returns. Add a sabotage entry for the skip, since + none covers it, and verify the rejecting direction by deleting the skip and watching the test go + red. + +- [x] **M-T13.2** -- **Give the periodic timer one drain body instead of two.** + `PeriodicTimerMember::stop_and_drain` passes `TRUE` to `WaitForThreadpoolTimerCallbacks` -- + cancel -- while `ThreadpoolPeriodicTimer::stop_and_drain` passes `FALSE` and documents at length + that "the drain no longer cancels". The member's own doc says "As with + `ThreadpoolPeriodicTimer::stop_and_drain`", so it cross-references the twin it disagrees with, + and its trace label still reads `(cancel)`. + + **This is a two-site rule half-converted, and the structural cause is visible in the siblings + that did not drift:** `TimerMember` and `WaitMember` both delegate to a shared + `ThreadpoolTimer::stop_and_drain_parts` / `ThreadpoolWait::stop_and_drain_parts`. The periodic + timer has no such shared body, which is why it was possible to convert one copy and not the + other. **Target:** extract `stop_and_drain_parts` for the periodic timer and have both call it, + so the next conversion cannot reach one site only. + +- [x] **M-T13.3** -- **Repair the sabotage entry that now invokes undefined behaviour.** The entry + "the heal creates a work object instead of submitting the pre-created one" passes + `Box::new(AtomicU64::new(0))` as the fresh work object's context. `repair_trampoline` casts its + context to `&PoolEntry` and writes stamps and counters through it, so the patched build writes + past the end of an 8-byte allocation. The entry's `whyNotNullContext` explains that a null + context was avoided precisely so the result would not be a crash-caught one -- and the context + type changed underneath that reasoning, so it now risks exactly the outcome it was written to + avoid, plus heap damage that other tests in the same process would see. + +- [x] **M-T13.4** -- **Correct the disarm documentation, which says the opposite of what the crate + measures.** `ThreadpoolTimer::disarm` says "a callback already queued still runs; use + `cancel_pending` to drop those as well", and `ThreadpoolPeriodicTimer::stop` says "a tick already + queued still runs". `disarming_cancels_a_queued_tick_which_a_waits_disarm_does_not` pins the + opposite for the one-shot timer: with a disarm the queued tick is discarded, asserted as + `outcomes[1] == 0` against `outcomes[0] == 1` without one. The doc therefore sends a caller to + `cancel_pending` for something that has already happened. + + **The periodic half needs its own measurement rather than a copied claim.** The pinning test + drives a one-shot `ThreadpoolTimer`; `ThreadpoolPeriodicTimer::stop_and_drain`'s doc already + extrapolates from it ("the way a one-shot timer's disarm does"), and this item must either + measure the periodic type or state plainly that the behaviour is pinned only for the one-shot. + +- [x] **M-T13.5** -- **Drop the four `windows-sys` features the deleted hook facility needed.** The + `trace` feature still pulls `Win32_System_Diagnostics_ToolHelp`, `Win32_System_LibraryLoader`, + `Win32_System_Memory` and `Win32_System_SystemInformation`, with a five-line comment explaining + the page-size arithmetic "the hook installer needs" and how `VirtualProtect` reports a whole + page's previous protection. `1eca1e16` deleted that facility; none of the four has a single use + left in the crate. A consumer who turns tracing on compiles four modules for nothing, and the + comment describes machinery that is not there. + +- [x] **M-T13.6** -- **Read the error before freeing the context in `ThreadpoolWork::new`.** The + failure path runs `drop(Box::from_raw(ctx))` and *then* `io::Error::last_os_error()`. Since this + branch, `ctx` holds a `Registration`, whose drop can reach `PoolEntry::drop` and from there + `WaitForThreadpoolWorkCallbacks` and `CloseThreadpoolWork`. `timer.rs` and `timer/periodic.rs` + both capture the error first and free second; `work.rs` is the only one of the four that does + not. + + **State the finding honestly: the clobber is not established.** Nothing in the Win32 + documentation says those two calls set the last error on success, so this is an inconsistency + among four sites that should agree rather than a demonstrated bug. It is worth fixing because it + costs one line and because the ordering that is obviously safe should not be the minority. + +- [x] **M-T13.7** -- **Stop crediting a `Drop` impl that does not exist.** Eight comments across + `heal.rs` (lines 295, 353, 774, 807, 849, 865, 1170) and `heal/tests.rs` (1058) attribute the + repair object's drain to `RepairWork::drop`. There is no `impl Drop for RepairWork`; the drain + moved into `PoolEntry::drop` when the teardown-order invariant was made unrepresentable. The + trampoline's `SAFETY` comment is the one that matters, because it justifies a raw dereference + with "the entry's first field drains this object in its `Drop`" -- an argument about field order + that the `PoolEntry::drop` impl deliberately replaced. + +- [x] **M-T13.8** -- **Say what the fail-fast child test establishes, which is not the abort.** Its + doc opens "**This aborts rather than unwinds**, so it cannot be observed with `catch_unwind`", + and attributes that to the panic escaping the healer's `extern "system"` trampoline. The child + calls `crate::heal::tick_inner()` directly on the libtest thread, so the panic unwinds normally + and the child exits 101. Every parent assertion -- not `SURVIVED`, not `SETUP_FAILED`, non-zero, + stderr text present -- holds under that unwind, so none of them distinguishes an abort from a + caught panic. The test is worth keeping: it proves the panic fires and that the message reaches + stderr, which is the claim `fail_fast_if_unrepairable` makes. Only the doc overstates it. + +- [x] **M-T13.9** -- **Sweep the hook facility's remaining name.** `trace.rs`'s module doc tells a + reader the crate records under `wait`, `work`, `io`, `timer` and `timer-periodic`; it also + records under `heal`, `syscall-enter` and `syscall-leave`, so a reader building a filter from + that list silently loses the self-heal subsystem. `sabotage.json`'s `whyTestArgs` justifies + `--all-features` with "the whole hook module is behind the `trace` feature", and three messages + in `trace/tests.rs` (614, 617, 630) still speak of hooks installing. The `--all-features` + justification needs replacing with the real reason rather than deleting, since the flag is still + required. + +### M-T15 -- A wait that shares its handle + + > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/win-sync-sys` -> `WS-M2` -> `WS-2.2`. See + > [COMPLETED-CHECKLIST.md](../win-sync-sys/COMPLETED-CHECKLIST.md#ws-22). + +Items, each archived above under its own heading: [M-T15.1](#m-t151). diff --git a/crates/windows-threadpool-sys/Cargo.toml b/crates/windows-threadpool-sys/Cargo.toml index a6bdf01fd..e28d246c1 100644 --- a/crates/windows-threadpool-sys/Cargo.toml +++ b/crates/windows-threadpool-sys/Cargo.toml @@ -36,6 +36,10 @@ windows-overlapped-io-sys = { version = "0.3.0", path = "../windows-overlapped-i # wait target. The crate makes no event of its own (WS-D-5 in win-sync-sys' DESIGN-NOTES.md). # Published independently, so both a path and a version. win-sync-sys = { version = "0.0.2", path = "../win-sync-sys" } +# A wait target may be shared: the wait holds one clone and the caller keeps others (the workspace +# DESIGN-NOTES.md, "A wait may hold its handle shared"). Published independently, so both a path and +# a version. +win-shared-os-owned-handle = { version = "0.1.0", path = "../win-shared-os-owned-handle" } # Interrupt time, which the self-heal stamps a cancellation and its repair with: the workspace reads # its one time base through this crate rather than each crate calling `QueryInterruptTime` itself # (WT-D-1 in win-time-sys' DESIGN-NOTES.md). Optional, pulled in by `self-heal` alone. Published @@ -98,6 +102,13 @@ features = [ "Win32_System_Threading", ] +# The TP_IO tests make their overlapped named pipes with `win-pipes-sys`, in place of an `unsafe` +# `CreateNamedPipeW` call each. Path-only, with no `version`: Cargo checks a pinned version against the +# path at every build, so a release of that crate would break this one, and a versionless +# dev-dependency is left out of the published manifest (see `windows-ioring-sys`'s manifest). +[dev-dependencies] +win-pipes-sys = { path = "../win-pipes-sys" } + # The TP_IO tests issue real overlapped I/O against a named pipe, which needs # binding surface the published crate itself does not use. [dev-dependencies.windows-overlapped-io-sys] @@ -116,5 +127,4 @@ features = [ "Win32_System_Threading", "Win32_Security", "Win32_Storage_FileSystem", - "Win32_System_Pipes", ] diff --git a/crates/windows-threadpool-sys/PARKED.md b/crates/windows-threadpool-sys/PARKED.md new file mode 100644 index 000000000..f2f6542fe --- /dev/null +++ b/crates/windows-threadpool-sys/PARKED.md @@ -0,0 +1,18 @@ +# Parked: windows-threadpool-sys + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M-inf -- Diagnostic work with no gating deliverable + +- [ ] **M-T-inf.1** (was `M26.14.4`) -- **Find the threshold the close races.** `M26.14.2` used 1ms because it is + about 80x the natural 12us gap; nothing establishes what the minimum is. A sweep -- 0, 10us, + 50us, 100us, 500us, 1ms, interleaved at 20000 each -- would say whether the window is + microseconds or milliseconds, which is itself evidence about what the close races. + + **Moved here 2026-10-01, when its gate resolved the "never ships" way.** This item was gated on + `M26.14.3` and said so itself: if the draining teardown were adopted, the sleep never ships and + the sweep is only of diagnostic interest. It was adopted, so that is where this now sits -- not + cancelled, because the question it asks is about the *kernel* window the close races, which the + drain avoids rather than explains, and that remains the one thing about this stall nobody has + been able to see directly. diff --git a/crates/windows-threadpool-sys/PLANS.md b/crates/windows-threadpool-sys/PLANS.md index f02b67bfc..e14536805 100644 --- a/crates/windows-threadpool-sys/PLANS.md +++ b/crates/windows-threadpool-sys/PLANS.md @@ -7,3 +7,4 @@ Completed checklists are recorded in | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| | [CHECKLIST.md](CHECKLIST.md) | in progress | Teardown, self-heal, and the pool stall. Which milestones are open, and what each covers, is what [CHECKLIST.md](CHECKLIST.md) says; the stall's evidence is in [STALL-TIMELINE.md](STALL-TIMELINE.md). | [DESIGN-NOTES.md](../../DESIGN-NOTES.md#teardown-drains), [STALL-TIMELINE.md](STALL-TIMELINE.md) | +| [PARKED.md](PARKED.md) | parked | `M-T-inf.1`: find the threshold the pool's close races, diagnostic work with no gating deliverable since the draining teardown was adopted. | [STALL-TIMELINE.md](STALL-TIMELINE.md) | diff --git a/crates/windows-threadpool-sys/sabotage.json b/crates/windows-threadpool-sys/sabotage.json index 51c6f0954..0af2e0816 100644 --- a/crates/windows-threadpool-sys/sabotage.json +++ b/crates/windows-threadpool-sys/sabotage.json @@ -366,10 +366,10 @@ "expect": "caught", "why": "A wait target that is a different event looks like a working wait in isolation; it fails only where the caller signals through the clone it kept, which is the documented way to signal a pool-owned event. Caught in this crate by the_conversion_keeps_the_event_it_was_given, without a timing window.", "find": [ - " target: WaitTarget::Owned(event.into()),\n" + " target: WaitTarget::Shared(event.into()),\n" ], "replace": [ - " target: WaitTarget::Owned(Event::new(ResetMode::Manual, false).expect(\"sabotage\").into()),\n" + " target: WaitTarget::Shared(Event::new(ResetMode::Manual, false).expect(\"sabotage\").into()),\n" ] }, { @@ -415,6 +415,30 @@ "why": "The manifest checking itself. The forced keys are a multiset that is only searched, never read in order, so which removal is used changes nothing a test can see.", "find": [" forced.swap_remove(at);\n"], "replace": [" forced.remove(at);\n"] + }, + { + "name": "M-T15.1: a shared target never gives its handle back", + "file": "src/wait.rs", + "expect": "caught", + "why": "into_handle gives a shared target's handle to its last holder; refusing it always would leave a caller holding a wrapper it cannot unwrap.", + "find": [" WaitTarget::Shared(handle) => handle.try_into_owned().map_err(|handle| Self {\n"], + "replace": [" WaitTarget::Shared(handle) => Err::(handle).map_err(|handle| Self {\n"] + }, + { + "name": "M-T15.1: a shared target arms the wait on no handle", + "file": "src/wait.rs", + "expect": "caught", + "why": "The raw handle is what the pool waits on; a shared target that reported another value would arm a wait that never fires on the event its holders set. Caught by the process ending with STATUS_STACK_BUFFER_OVERRUN: arming a wait on a null handle is a fail-fast in the pool, deterministic rather than a heap accident, so no assertion can run before it.", + "find": [" WaitTarget::Shared(handle) => handle.as_raw_handle(),\n"], + "replace": [" WaitTarget::Shared(_) => ptr::null_mut(),\n"] + }, + { + "name": "CONTROL: M-T15.1: a shared target's debug label is reworded", + "file": "src/wait.rs", + "expect": "survives", + "why": "The manifest checking itself. The label is for a reader, and no test reads it.", + "find": [" WaitTarget::Shared(_) => \"CloseHandle, by the last holder\",\n"], + "replace": [" WaitTarget::Shared(_) => \"shared\",\n"] } ] } diff --git a/crates/windows-threadpool-sys/src/wait.rs b/crates/windows-threadpool-sys/src/wait.rs index 6486b2572..d20604578 100644 --- a/crates/windows-threadpool-sys/src/wait.rs +++ b/crates/windows-threadpool-sys/src/wait.rs @@ -6,10 +6,11 @@ //! handle is signalled or the wait times out. Two SDK contracts shape this API: //! //! - **The handle must stay valid while a wait is pending.** [`ThreadpoolWait`] -//! therefore *owns* its handle rather than borrowing one, so it cannot be -//! closed underneath a pending wait. Use [`ThreadpoolWait::handle`] to -//! inspect it; to signal an event the wait owns, build the wait from a -//! `win-sync-sys` [`Event`] and keep a clone of it. +//! therefore holds its handle rather than borrowing one -- owning it, or +//! sharing it as a `SharedHandle` -- so it cannot be closed underneath a +//! pending wait. Use [`ThreadpoolWait::handle`] to inspect it; to signal an +//! event the wait holds, build the wait from a `win-sync-sys` [`Event`] and +//! keep a clone of it. //! - **A wait fires at most once per arming.** The SDK requires the wait to be //! rearmed explicitly for each activation, so the callback receives a //! [`WaitActivation`] carrying [`WaitActivation::rearm`]. A callback that does @@ -26,6 +27,7 @@ use std::time::Duration; use crate::rearm::RearmSuppression; +use win_shared_os_owned_handle::SharedHandle; use win_sync_sys::{Event, ResetMode}; use windows_sys::Win32::Foundation::{FALSE, FILETIME, HANDLE, TRUE, WAIT_TIMEOUT}; use windows_sys::Win32::System::Threading::{ @@ -107,6 +109,10 @@ impl Drop for CustomClose { pub(crate) enum WaitTarget { /// The default: closed by [`OwnedHandle`]'s own drop, with `CloseHandle`. Owned(OwnedHandle), + /// One holder of a shared handle, closed with `CloseHandle` when the last + /// holder drops. The wait's clone is one of them, so the handle stays open + /// while the wait holds it. + Shared(SharedHandle), /// Closed with the caller-supplied routine. Custom(CustomClose), } @@ -122,6 +128,7 @@ impl WaitTarget { pub(crate) fn raw(&self) -> HANDLE { match self { WaitTarget::Owned(handle) => handle.as_raw_handle(), + WaitTarget::Shared(handle) => handle.as_raw_handle(), WaitTarget::Custom(custom) => custom.raw, } } @@ -130,6 +137,7 @@ impl WaitTarget { pub(crate) fn borrow(&self) -> BorrowedHandle<'_> { match self { WaitTarget::Owned(handle) => handle.as_handle(), + WaitTarget::Shared(handle) => handle.as_handle(), // SAFETY: the handle stays open for as long as `self` owns it, and // the returned borrow cannot outlive that. WaitTarget::Custom(custom) => unsafe { BorrowedHandle::borrow_raw(custom.raw) }, @@ -146,6 +154,7 @@ impl std::fmt::Debug for WaitTarget { "close", &match self { WaitTarget::Owned(_) => "CloseHandle", + WaitTarget::Shared(_) => "CloseHandle, by the last holder", WaitTarget::Custom(_) => "custom", }, ) @@ -163,8 +172,9 @@ impl std::fmt::Debug for WaitTarget { /// Construct one safely from a `win-sync-sys` [`Event`] (`From`) or with /// [`WaitableHandle::event`], or vouch for a handle /// obtained elsewhere with the narrow [`WaitableHandle::assume_waitable`] seam -- -/// or [`WaitableHandle::assume_waitable_with`] when the handle needs a close -/// routine other than `CloseHandle`. +/// [`WaitableHandle::assume_waitable_shared`] when the handle is shared with +/// other holders, or [`WaitableHandle::assume_waitable_with`] when the handle +/// needs a close routine other than `CloseHandle`. /// This mirrors `UnassociatedEndpoint` in `windows-overlapped-io-sys`, which /// pairs a safe `open` with an `assume_overlapped` escape hatch for the same /// reason. @@ -182,9 +192,11 @@ impl WaitableHandle { /// makes it the usual choice for handing off work one activation at a time. /// /// The event is a `win-sync-sys` [`Event`], so this crate creates none of - /// its own. To be able to set the event once the wait owns it, create the - /// [`Event`] directly, keep a [`try_clone`](Event::try_clone), and convert - /// the other with `From`. + /// its own. To be able to set the event once the wait holds it, create the + /// [`Event`] directly, keep a clone, and convert the other with + /// `From`: the two share one handle. A wait made by this function + /// keeps the only [`Event`], so [`into_handle`](Self::into_handle) returns + /// its handle. /// /// # Errors /// @@ -254,6 +266,27 @@ impl WaitableHandle { } } + /// Wrap a shared handle whose wait support the caller vouches for. + /// + /// The wait holds `handle`, one clone of a [`SharedHandle`], and the + /// caller may keep others: to signal the object, or to hand it to another + /// waiter. The handle closes only when the last holder drops, so the clone + /// the wait holds keeps it open for as long as the wait does, which is the + /// guarantee [`assume_waitable`](Self::assume_waitable) gets from exclusive + /// ownership. + /// + /// # Safety + /// + /// The caller guarantees that the handle is a waitable object the thread + /// pool supports, and in particular is **not a mutex**, which the SDK does + /// not support and which yields undefined behaviour rather than an error. + #[must_use] + pub unsafe fn assume_waitable_shared(handle: SharedHandle) -> Self { + Self { + target: WaitTarget::Shared(handle), + } + } + /// Borrow the underlying handle, for signalling or inspecting it. #[must_use] pub fn handle(&self) -> BorrowedHandle<'_> { @@ -270,9 +303,18 @@ impl WaitableHandle { /// precisely the wrong destructor for such a target, so there is no correct /// value to hand back. The handle is neither closed nor leaked -- ownership /// simply stays where it was. + /// + /// A shared target (see + /// [`assume_waitable_shared`](Self::assume_waitable_shared)) gives its + /// handle back only as its last holder, as + /// [`SharedHandle::try_into_owned`] does; while another holder remains, the + /// wrapper is returned unchanged, still holding its clone. pub fn into_handle(self) -> Result { match self.target { WaitTarget::Owned(handle) => Ok(handle), + WaitTarget::Shared(handle) => handle.try_into_owned().map_err(|handle| Self { + target: WaitTarget::Shared(handle), + }), target @ WaitTarget::Custom(_) => Err(Self { target }), } } @@ -288,10 +330,20 @@ impl WaitableHandle { /// An [`Event`] is guaranteed to be an event rather than a mutex -- adopting a /// handle as one is `unsafe` in `win-sync-sys` for exactly this reason -- and /// to carry the access a wait needs. +/// +/// The target is shared: the wait holds the event's handle with every clone of +/// the [`Event`] the caller keeps, and the handle closes when the last of them +/// drops. +/// +/// A wait built this way is a shared target, so +/// [`WaitableHandle::into_handle`] gives the handle back only when no clone of +/// the [`Event`] remains, and returns the wrapper unchanged while one does. A +/// caller that keeps a clone to signal the event therefore does not get the +/// handle back from `into_handle`; it reaches the event through its clone. impl From for WaitableHandle { fn from(event: Event) -> Self { Self { - target: WaitTarget::Owned(event.into()), + target: WaitTarget::Shared(event.into()), } } } @@ -638,9 +690,9 @@ unsafe extern "system" fn wait_trampoline( /// /// # Examples /// -/// Watch an event once. The wait takes ownership of the handle it is given, so -/// keep a clone of the [`Event`] -- a second handle to the same event -- to -/// signal it through: +/// Watch an event once. The wait holds the handle it is given, so keep a clone +/// of the [`Event`] -- a second holder of the same handle -- to signal it +/// through: /// /// ``` /// use std::sync::mpsc; @@ -648,7 +700,7 @@ unsafe extern "system" fn wait_trampoline( /// use windows_threadpool_sys::wait::{ThreadpoolWait, WaitResult}; /// /// let event = Event::new(ResetMode::Manual, false)?; -/// let signal = event.try_clone()?; +/// let signal = event.clone(); /// /// let (tx, rx) = mpsc::channel(); /// let sender = std::sync::Mutex::new(tx); @@ -673,7 +725,7 @@ unsafe extern "system" fn wait_trampoline( /// use windows_threadpool_sys::wait::ThreadpoolWait; /// /// let event = Event::new(ResetMode::Auto, false)?; -/// let signal = event.try_clone()?; +/// let signal = event.clone(); /// /// let seen = Arc::new(AtomicUsize::new(0)); /// let counter = Arc::clone(&seen); diff --git a/crates/windows-threadpool-sys/src/wait/tests.rs b/crates/windows-threadpool-sys/src/wait/tests.rs index 949f9ee94..f86196384 100644 --- a/crates/windows-threadpool-sys/src/wait/tests.rs +++ b/crates/windows-threadpool-sys/src/wait/tests.rs @@ -4,11 +4,12 @@ //! Every test drives a real event handle, since the wait object's contract is //! about handle ownership and per-activation rearming rather than about timing. -use std::os::windows::io::AsRawHandle; +use std::os::windows::io::{AsHandle, AsRawHandle}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; +use win_shared_os_owned_handle::SharedHandle; use win_sync_sys::{Event, ResetMode}; use windows_sys::Win32::Foundation::{CompareObjectHandles, FALSE, WAIT_OBJECT_0, WAIT_TIMEOUT}; use windows_sys::Win32::System::Threading::{ResetEvent, SetEvent, WaitForSingleObject}; @@ -150,7 +151,7 @@ fn same_object(a: std::os::windows::io::BorrowedHandle<'_>, b: &Event) -> bool { #[test] fn the_conversion_keeps_the_event_it_was_given() { let event = Event::new(ResetMode::Manual, false).expect("create an event"); - let kept = event.try_clone().expect("clone"); + let kept = event.clone(); let other = Event::new(ResetMode::Manual, false).expect("create another"); let waitable = WaitableHandle::from(event); assert!( @@ -163,10 +164,30 @@ fn the_conversion_keeps_the_event_it_was_given() { ); } +#[test] +fn a_converted_event_shares_the_handle_a_kept_clone_holds() { + let event = Event::new(ResetMode::Manual, false).expect("create an event"); + let kept = event.clone(); + let waitable = WaitableHandle::from(event); + assert_eq!( + waitable.handle().as_raw_handle(), + kept.as_raw_handle(), + "one handle, held by the wait and by the kept clone" + ); + let waitable = waitable + .into_handle() + .expect_err("the kept clone still holds the handle"); + drop(kept); + assert!( + waitable.into_handle().is_ok(), + "the wait is the last holder" + ); +} + #[test] fn a_converted_auto_reset_event_stays_auto_reset() { let event = Event::new(ResetMode::Auto, false).expect("create an event"); - let kept = event.try_clone().expect("clone"); + let kept = event.clone(); let waitable = WaitableHandle::from(event); kept.set().expect("set through the kept clone"); assert!(take(waitable.handle()), "the target saw the set"); @@ -176,7 +197,7 @@ fn a_converted_auto_reset_event_stays_auto_reset() { #[test] fn a_converted_manual_reset_event_stays_manual_reset() { let event = Event::new(ResetMode::Manual, false).expect("create an event"); - let kept = event.try_clone().expect("clone"); + let kept = event.clone(); let waitable = WaitableHandle::from(event); kept.set().expect("set through the kept clone"); assert!(take(waitable.handle()), "the target saw the set"); @@ -210,7 +231,7 @@ fn the_event_constructor_makes_each_reset_mode_and_initial_state() { #[test] fn a_wait_built_from_an_event_fires_when_a_kept_clone_is_set() { let event = Event::new(ResetMode::Auto, false).expect("create an event"); - let kept = event.try_clone().expect("clone"); + let kept = event.clone(); let seen = Activations::new(); let recorder = Arc::clone(&seen); let wait = ThreadpoolWait::new( @@ -1398,3 +1419,116 @@ fn drop_runs_a_queued_callback_rather_than_discarding_it() { // again -- so the occupier owes a drain even though its callback returned. occupier.stop_and_drain(); } + +// --- a shared wait target (M-T15.1) --- + +/// An auto-reset event as a shared handle, and a second holder of it. +fn shared_event() -> (SharedHandle, SharedHandle) { + let event = Event::new(ResetMode::Auto, false).expect("create an event"); + let shared = SharedHandle::from(event); + let other = shared.clone(); + (shared, other) +} + +/// A wait target from a shared event. +fn shared_target(handle: SharedHandle) -> WaitableHandle { + // SAFETY: the handle is an event, which the pool supports. + unsafe { WaitableHandle::assume_waitable_shared(handle) } +} + +#[test] +fn a_shared_target_is_the_handle_its_holders_share_and_not_a_duplicate() { + let (shared, other) = shared_event(); + let waitable = shared_target(shared); + assert_eq!( + waitable.handle().as_raw_handle(), + other.as_raw_handle(), + "one handle, held twice" + ); +} + +#[test] +fn a_wait_on_a_shared_target_fires_when_another_holder_sets_it() { + let (shared, other) = shared_event(); + let seen = Activations::new(); + let recorder = Arc::clone(&seen); + let wait = ThreadpoolWait::new( + shared_target(shared), + move |activation| recorder.record(activation.result()), + None, + ) + .expect("create wait"); + wait.arm(None); + signal(other.as_handle()); + let results = seen.wait_for(1); + wait.stop_and_drain(); + assert_eq!(results, vec![WaitResult::Signalled]); +} + +#[test] +fn a_shared_target_stays_open_after_every_other_holder_drops() { + let (shared, other) = shared_event(); + let seen = Activations::new(); + let recorder = Arc::clone(&seen); + let wait = ThreadpoolWait::new( + shared_target(shared), + move |activation| recorder.record(activation.result()), + None, + ) + .expect("create wait"); + drop(other); + wait.arm(None); + signal(wait.handle()); + let results = seen.wait_for(1); + wait.stop_and_drain(); + assert_eq!(results, vec![WaitResult::Signalled]); +} + +#[test] +fn a_dropped_wait_releases_its_clone_of_a_shared_target() { + let (shared, other) = shared_event(); + let wait = ThreadpoolWait::new(shared_target(shared), |_| {}, None).expect("create wait"); + wait.arm(None); + signal(other.as_handle()); + wait.stop_and_drain(); + drop(wait); + assert!( + other.try_into_owned().is_ok(), + "the other holder is the last one once the wait has gone" + ); +} + +#[test] +fn a_released_cleanup_group_member_releases_its_clone_of_a_shared_target() { + let (shared, other) = shared_event(); + let mut group = crate::cleanup_group::CleanupGroup::new().expect("create a group"); + let member = group + .create_wait(shared_target(shared), |_| {}, None) + .expect("create the member"); + member.arm(None); + signal(other.as_handle()); + group.close_members(); + drop(group); + assert!( + other.try_into_owned().is_ok(), + "the other holder is the last one once the group has released the member" + ); +} + +#[test] +fn into_handle_gives_a_shared_target_back_only_to_its_last_holder() { + let (shared, other) = shared_event(); + let waitable = shared_target(shared) + .into_handle() + .expect_err("another holder remains, so the wrapper is returned"); + assert_eq!( + waitable.handle().as_raw_handle(), + other.as_raw_handle(), + "the returned wrapper still holds its clone" + ); + drop(other); + assert!( + waitable.into_handle().is_ok(), + "the last holder gets the handle" + ); +} diff --git a/crates/windows-threadpool-sys/tests/tp_io_behavioral_matrix.rs b/crates/windows-threadpool-sys/tests/tp_io_behavioral_matrix.rs index fd32a935f..141dfc822 100644 --- a/crates/windows-threadpool-sys/tests/tp_io_behavioral_matrix.rs +++ b/crates/windows-threadpool-sys/tests/tp_io_behavioral_matrix.rs @@ -16,21 +16,21 @@ use std::collections::{HashMap, HashSet}; use std::io; -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::AsRawHandle; use std::path::{Path, PathBuf}; use std::ptr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; +use win_pipes_sys::{NamedPipeOptions, PipeMode, PipeOpenMode, create_named_pipe}; use windows_overlapped_io_sys::{ Issued, Operation, OperationState, Submitted, UnassociatedEndpoint, }; use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_OPERATION_ABORTED}; use windows_sys::Win32::Storage::FileSystem::{ - FILE_FLAG_OVERLAPPED, ReadFile, SetFileCompletionNotificationModes, WriteFile, + ReadFile, SetFileCompletionNotificationModes, WriteFile, }; -use windows_sys::Win32::System::Pipes::CreateNamedPipeW; use windows_threadpool_sys::callback_env::CallbackEnviron; use windows_threadpool_sys::io::{IoCompletion, ThreadpoolIo}; @@ -40,16 +40,6 @@ use windows_threadpool_sys::io::{IoCompletion, ThreadpoolIo}; /// Changing this value is a breaking change. const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS: u8 = 0x1; -/// Named-pipe creation flags; windows-sys exports these only as loose constants -/// in feature-gated modules, so the two this file needs are named here. -/// Changing either value is a breaking change. -mod pipe_mode { - /// `PIPE_ACCESS_DUPLEX`. - pub const ACCESS_DUPLEX: u32 = 0x0000_0003; - /// `PIPE_TYPE_BYTE`. - pub const TYPE_BYTE: u32 = 0x0000_0000; -} - /// How long a test will wait for the pool to deliver expected callbacks before /// declaring the backend broken. Generous enough to absorb a loaded CI machine, /// short enough that a genuine hang fails the run rather than stalling it. @@ -192,30 +182,18 @@ fn pending_pipe(tag: &str) -> (UnassociatedEndpoint, std::fs::File) { tag, std::process::id() ); - let wide: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - // SAFETY: creates a fresh overlapped named pipe from a valid wide name. - let handle = unsafe { - CreateNamedPipeW( - wide.as_ptr(), - pipe_mode::ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - pipe_mode::TYPE_BYTE, - 1, - 4096, - 4096, - 0, - ptr::null(), - ) + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_DUPLEX | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE, + max_instances: 1, + out_buffer_size: 4096, + in_buffer_size: 4096, + default_timeout_ms: 0, }; - assert!( - !handle.is_null() && handle as isize != -1, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); + let handle = create_named_pipe(&name, &options, None).expect("create the overlapped pipe"); - // SAFETY: fresh, exclusively owned, and opened with FILE_FLAG_OVERLAPPED. - let endpoint = - unsafe { UnassociatedEndpoint::assume_overlapped(OwnedHandle::from_raw_handle(handle)) }; + // SAFETY: fresh, exclusively owned, and created with `PipeOpenMode::OVERLAPPED`. + let endpoint = unsafe { UnassociatedEndpoint::assume_overlapped(handle) }; // Connecting a client makes a server read pend rather than fail with // ERROR_PIPE_LISTENING; no data is ever written, so it never completes. diff --git a/crates/windows-threadpool-sys/tests/tp_io_operation_identity.rs b/crates/windows-threadpool-sys/tests/tp_io_operation_identity.rs index d30cc7649..4ac404451 100644 --- a/crates/windows-threadpool-sys/tests/tp_io_operation_identity.rs +++ b/crates/windows-threadpool-sys/tests/tp_io_operation_identity.rs @@ -14,26 +14,21 @@ use std::collections::HashSet; use std::io; -use std::os::windows::io::{AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle}; +use std::os::windows::io::{AsRawHandle, BorrowedHandle}; use std::path::{Path, PathBuf}; use std::ptr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; +use win_pipes_sys::{NamedPipeOptions, PipeMode, PipeOpenMode, create_named_pipe}; use windows_overlapped_io_sys::{Issued, Operation, OperationId, Submitted, UnassociatedEndpoint}; use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_OPERATION_ABORTED}; -use windows_sys::Win32::Storage::FileSystem::{FILE_FLAG_OVERLAPPED, ReadFile}; +use windows_sys::Win32::Storage::FileSystem::ReadFile; use windows_sys::Win32::System::IO::OVERLAPPED; -use windows_sys::Win32::System::Pipes::CreateNamedPipeW; use windows_threadpool_sys::io::{IoCompletion, ThreadpoolIo}; -/// `PIPE_ACCESS_DUPLEX`. Changing this value is a breaking change. -const PIPE_ACCESS_DUPLEX: u32 = 0x0000_0003; -/// `PIPE_TYPE_BYTE`. Changing this value is a breaking change. -const PIPE_TYPE_BYTE: u32 = 0x0000_0000; - /// How long a test waits for the pool to deliver expected callbacks. const CALLBACK_TIMEOUT: Duration = Duration::from_secs(30); @@ -129,30 +124,18 @@ fn pending_pipe(tag: &str) -> (UnassociatedEndpoint, std::fs::File) { "{sep}{sep}.{sep}pipe{sep}windows-threadpool-sys-tp-identity-{tag}-{}", std::process::id() ); - let wide: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - // SAFETY: creates a fresh overlapped named pipe from a valid wide name. - let handle = unsafe { - CreateNamedPipeW( - wide.as_ptr(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE, - 1, - 4096, - 4096, - 0, - ptr::null(), - ) + let options = NamedPipeOptions { + open_mode: PipeOpenMode::ACCESS_DUPLEX | PipeOpenMode::OVERLAPPED, + pipe_mode: PipeMode::TYPE_BYTE, + max_instances: 1, + out_buffer_size: 4096, + in_buffer_size: 4096, + default_timeout_ms: 0, }; - assert!( - !handle.is_null() && handle as isize != -1, - "CreateNamedPipeW failed: {}", - io::Error::last_os_error() - ); + let handle = create_named_pipe(&name, &options, None).expect("create the overlapped pipe"); - // SAFETY: fresh, exclusively owned, and opened with FILE_FLAG_OVERLAPPED. - let endpoint = - unsafe { UnassociatedEndpoint::assume_overlapped(OwnedHandle::from_raw_handle(handle)) }; + // SAFETY: fresh, exclusively owned, and created with `PipeOpenMode::OVERLAPPED`. + let endpoint = unsafe { UnassociatedEndpoint::assume_overlapped(handle) }; let client = std::fs::OpenOptions::new() .read(true) .write(true) diff --git a/crates/windows-topology-sys/COMPLETED-CHECKLIST.md b/crates/windows-topology-sys/COMPLETED-CHECKLIST.md index 58bcc1fa6..b1ffad017 100644 --- a/crates/windows-topology-sys/COMPLETED-CHECKLIST.md +++ b/crates/windows-topology-sys/COMPLETED-CHECKLIST.md @@ -101,6 +101,7 @@ Design record: [DESIGN-NOTES.md](DESIGN-NOTES.md) `D-13` through `D-23`, and [DESIGN-SESSION-2026-09-02-cache-locality-model.md](../../design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md). The context the milestones were written against, preserved because the items refer to it: + ## What this is for This crate publishes a **refined view of what the platform publishes** diff --git a/crates/windows-waitable-queues/Cargo.toml b/crates/windows-waitable-queues/Cargo.toml index 9ecd63023..e7a8adabd 100644 --- a/crates/windows-waitable-queues/Cargo.toml +++ b/crates/windows-waitable-queues/Cargo.toml @@ -76,6 +76,11 @@ version = "1.15.0" default-features = false optional = true +# Duplicating a handle: this crate's `DuplicateHandle` call is `win-handles-sys`'s, the repository's one. +[dependencies.win-handles-sys] +version = "0.1.0" +path = "../win-handles-sys" + [dependencies.windows-sys] version = "0.61.2" default-features = false diff --git a/crates/windows-waitable-queues/README.md b/crates/windows-waitable-queues/README.md index d24eb7cc1..f0facfc5e 100644 --- a/crates/windows-waitable-queues/README.md +++ b/crates/windows-waitable-queues/README.md @@ -442,6 +442,7 @@ cannot be omitted again. | Host | `x86_64 16p/8c smt+ L2[2,2,2,2,2,2,2,2] ec[0:16] numa[16]` | | Profile | release | | Sampling | 50,000 pushes per producer, median of 5 repetitions, one untimed warmup pass | +| Order | each configuration's passes back to back, the only order the probe had then; `--order sequential` retakes it, the probe's default having been interleaved since its `M4.4` | | Runs | 3 whole-probe invocations; cells are the median of the three, ranges span all 15 repetitions | | Instrument | `probe-queue-contention`, built from `fecd352` (the commit that added the range columns) | | Taken | 2026-09-15 UTC-07:00 | @@ -477,8 +478,11 @@ figures predate a correction to the probe's timing window, which timed from the coordinator's clock rather than the producers' own and overstated throughput by a margin that grew with producer count; and neither of those machines is available here to retake them. The ARM64 data point is therefore gone rather than stale, -which is the lesser of the two problems. Restoring one is what M2.15 in the -probe crate's [CHECKLIST.md](../windows-platform-probes/CHECKLIST.md) is for. +which is the lesser of the two problems. The probe crate's CI job now runs +`probe-queue-contention` on an ARM64 runner at 1, 2 and 4 producers in every build +-- see its [DESIGN-NOTES.md](../windows-platform-probes/DESIGN-NOTES.md#d-coherence-probes-in-ci) +-- so an ARM64 reading is in each build's log; the table above still has no ARM64 +column, because a hosted runner's log is not an attributed capture. That comparison did carry one finding worth keeping, because it was structural rather than numeric: it was designed on the assumption that `slotwise_mpsc` would diff --git a/crates/windows-waitable-queues/src/doorbell.rs b/crates/windows-waitable-queues/src/doorbell.rs index d11db9db2..30cd14a1e 100644 --- a/crates/windows-waitable-queues/src/doorbell.rs +++ b/crates/windows-waitable-queues/src/doorbell.rs @@ -165,10 +165,9 @@ use std::ptr; use std::sync::OnceLock; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering, fence}; -use windows_sys::Win32::Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle, FALSE, TRUE}; -use windows_sys::Win32::System::Threading::{ - CreateEventW, GetCurrentProcess, ResetEvent, SetEvent, -}; +use win_handles_sys::{DuplicateAccess, Inheritance, duplicate_handle}; +use windows_sys::Win32::Foundation::{FALSE, TRUE}; +use windows_sys::Win32::System::Threading::{CreateEventW, ResetEvent, SetEvent}; /// A lazily created manual-reset event that reports whether a queue has /// anything to take. @@ -240,7 +239,11 @@ impl Doorbell { /// /// Returns the error from `CreateEventW` or `DuplicateHandle`. pub(crate) fn owned(&self) -> io::Result { - duplicate(self.handle()?) + duplicate_handle( + self.handle()?, + DuplicateAccess::Same, + Inheritance::NotInheritable, + ) } /// Report that the queue has something to take. @@ -387,28 +390,5 @@ fn create_event() -> io::Result { Ok(unsafe { OwnedHandle::from_raw_handle(raw) }) } -/// Duplicate a handle into this process, so the caller owns its own copy. -fn duplicate(handle: BorrowedHandle<'_>) -> io::Result { - let mut duplicated = ptr::null_mut(); - // SAFETY: duplicates a live handle within this process with the same - // access; `duplicated` is a valid out-pointer for the call's duration. - let ok = unsafe { - DuplicateHandle( - GetCurrentProcess(), - handle.as_raw_handle(), - GetCurrentProcess(), - &raw mut duplicated, - 0, - FALSE, - DUPLICATE_SAME_ACCESS, - ) - }; - if ok == 0 { - return Err(io::Error::last_os_error()); - } - // SAFETY: the call succeeded, so `duplicated` is a fresh owned handle. - Ok(unsafe { OwnedHandle::from_raw_handle(duplicated) }) -} - #[cfg(test)] mod tests; diff --git a/crates/wtf-string/CHECKLIST.md b/crates/wtf-string/CHECKLIST.md index c716d5b91..a1bb34cf1 100644 --- a/crates/wtf-string/CHECKLIST.md +++ b/crates/wtf-string/CHECKLIST.md @@ -10,15 +10,5 @@ Work items are dependency-ordered. Each milestone ends with tests. The implicit end-of-milestone gate (default build/test/clippy/doc clean, encoding check, sync with origin) is standard procedure and is not listed as an item. -Completed milestones are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). - -## M-inf -- Horizon (ungated, post-v1) - -Parked, not pending. The remaining item is placed outside the v1 scope by an explicit, recorded design -decision (D-7 makes the C-string companion optional). That recorded decision -- not the absence of a current -consumer -- is why it is deferred, which is a legitimate deferral rationale (a resolved, recorded scope -decision), not a scope-boundary excuse. It graduates to a numbered milestone when a post-v1 line of work -takes up that decision. See [DESIGN-NOTES.md](DESIGN-NOTES.md). - -- [ ] **M-inf.1** -- A checked no-interior-NUL C-string companion type (an enforced-guarantee analog of the - terminated pointer) (D-7). +Completed milestones are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). Nothing is pending; the horizon item is in +[PARKED.md](PARKED.md). diff --git a/crates/wtf-string/PARKED.md b/crates/wtf-string/PARKED.md new file mode 100644 index 000000000..a6e99627b --- /dev/null +++ b/crates/wtf-string/PARKED.md @@ -0,0 +1,15 @@ +# Parked: wtf-string + +Work not in scope now. Nothing here is pending; an item leaves only by graduating into a milestone of +[CHECKLIST.md](CHECKLIST.md), or by a ruling that withdraws it. + +## M-inf -- Horizon (ungated, post-v1) + +Parked, not pending. The remaining item is placed outside the v1 scope by an explicit, recorded design +decision (D-7 makes the C-string companion optional). That recorded decision -- not the absence of a current +consumer -- is why it is deferred, which is a legitimate deferral rationale (a resolved, recorded scope +decision), not a scope-boundary excuse. It graduates to a numbered milestone when a post-v1 line of work +takes up that decision. See [DESIGN-NOTES.md](DESIGN-NOTES.md). + +- [ ] **M-inf.1** -- A checked no-interior-NUL C-string companion type (an enforced-guarantee analog of the + terminated pointer) (D-7). diff --git a/crates/wtf-string/PLANS.md b/crates/wtf-string/PLANS.md index 28062b7d9..19ef32685 100644 --- a/crates/wtf-string/PLANS.md +++ b/crates/wtf-string/PLANS.md @@ -5,9 +5,10 @@ Active planned work for the crate. Completed checklists are archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md). All ten planned milestones (M1 scaffold+design -> M10 docs/publication) are complete, so there is no -active plan. [CHECKLIST.md](CHECKLIST.md) remains for its parked `M-inf` horizon bucket: work placed -outside v1 by a recorded design decision, holding nothing pending. A row returns to the table below -when a post-v1 line of work graduates a horizon item into a numbered milestone. +active plan. The `M-inf` horizon, work placed outside v1 by a recorded design decision, is in +[PARKED.md](PARKED.md). A [CHECKLIST.md](CHECKLIST.md) row returns to the table below when a post-v1 line +of work graduates a horizon item into a numbered milestone. | Path to CHECKLIST.md | Status | Brief description | Design Notes | |---|---|---|---| +| [PARKED.md](PARKED.md) | parked | The `M-inf` horizon: a checked no-interior-NUL C-string companion type, optional by D-7. | [DESIGN-NOTES.md](DESIGN-NOTES.md) (D-7) | diff --git a/design-sessions/DESIGN-SESSION-2026-10-21-router-domains-and-paired-events.md b/design-sessions/DESIGN-SESSION-2026-10-21-router-domains-and-paired-events.md new file mode 100644 index 000000000..611bc71e5 --- /dev/null +++ b/design-sessions/DESIGN-SESSION-2026-10-21-router-domains-and-paired-events.md @@ -0,0 +1,111 @@ +# Design session -- the router's domains, paired events, and how a database would use domains (2026-10-21) + +**Summary.** Began as the engineer's question whether durable-ioring's routing provider (`DI-12.2`) needed +replanning, and widened. Decisions that resulted: [DI-D-80](../crates/durable-ioring/DESIGN-NOTES.md#di-d-80) (durations +come from paired begin and end events with correlation ids, and the router measures nothing) and +[DI-D-81](../crates/durable-ioring/DESIGN-NOTES.md#di-d-81) (a provider registered with the router serves exactly its own +`domains()`). Work queued: `DI-12.2.1` to `DI-12.2.4` and `DI-M19` and `DI-M20` in +[durable-ioring's CHECKLIST.md](../crates/durable-ioring/CHECKLIST.md), and `M39.3` in the root +[CHECKLIST.md](../CHECKLIST.md). The model of a database's storage below is the engineer's, refined in discussion; it is +a working model of how a consumer would use the crate, not a decision, and most of it describes the layer outside the +crate that maps storage onto flush domains. + +## What the router reports, and where timing comes from + +The assistant split `DI-12.2` into four steps and named two open questions, the first being what the router should +report. It offered: an inconsistency for a domain no provider owns; a per-provider timing event; or leaving it coarse. Asked +whether these were exclusive, the assistant said the first two were additive and the third the only exclusive choice, and +that "per-call timing" had two readings it had conflated: the time inside a provider's `make_durable` call, measured with +nothing stored; and dispatch to a provider's last domain answering, which needs the completions wrapped and adds little +over the core's per-domain timing. + +The engineer ruled against both: + +> I don't think that the core router should implement any of this timing infrastructure, it should come in via the +> diagnostics and tracing. ETW has the ability to pair begin/end events and then the analysis tools will compute times +> from those events. We can depend on other diagnostic sinks to do similar if we can simply establish (a) correlation IDs +> and (b) reliable begin/end pairings. + +The assistant found the shipped delay events short of that in three ways: no correlation id; a flush issued once per seal +and answered once per part, so an answer had no begin of its own; and `EventWrite` taking the calling thread's activity id, +which a begin and an end on different threads do not share. On the ids the engineer said: "our correlation ids are scoped +to a process and so may be u64s generated from an atomic u64 using relaxed increments", and asked for the work to be +queued, which became `DI-M19`. + +Then, to the four questions the assistant listed: + +- Which pairs: "There should be event pairs for seals (which the diagnostic sink may trivially decline to do anything + with) and for the more weighty actions like when flushes occur. For the portable ring, I assume that flushes are modeled + as asynchronous operations by the provider (since they are with Linux iouring) so the begin/end pairing is important + there." And later: "sinks may decline any pair, to be clear". +- Whether an end keeps `elapsed`: "I say no, and this is something we can choose to enhance in the sinks rather than + pushing it into the router." +- The three candidate pairs, a seal, a flush of a file or a domain, and a lock wait: "those seem like the required ones". +- The 128-bit activity id: "true, perhaps we use the process id as the upper 64 bits and the u64 as the lower?" Held as a + working position; the assistant noted that a process id reused within one trace restarts the counter. + +Two follow-ups the assistant raised: a seal's span, answered "asked-for to durable-or-failed"; and where a lock wait's end +is written, since without `elapsed` the event's own stamp is the measure, and `DI-D-77` reported after the guard was +released. The engineer: "The lock wait end is written with the lock held. Any synchronization within the diagnostics +platform must not interfere with the synchronization in the durable-ioring." The assistant read that as a leaf-lock rule +on sinks -- a sink's locks may be taken under dioring's, never the other way, and no thread holds a sink's lock while +calling into an instance -- recorded as its statement of the rule and open to correction. + +## What decides the domains a registered provider serves + +The second open question. The assistant set out: (A) the provider's own `domains()`, one source of truth, but a provider is +registered for all it declares; or (B) domains named at registration, allowing a subset, at the cost of a second list that +can disagree with the provider's and route it domains it never said it serves. The engineer asked how things would work in +the real world. + +## The real-world model + +The engineer, thinking it through and asking to be checked: + +> On Windows, the likely use will to be to have a limited set of folders if not files where a "durability deliberate" +> client will be operating. [...] I think that they will present the storage/storage-roots to a layer slightly above us +> (e.g. the actual database) and that layer will call into the durable-ioring and want to establish a ring with correct +> durability domains for the files and directories. + +> The directory aspect is that I believe that some layer will need to map the directory to the volume / flush domain that +> files that would be created in it by name would be in. + +> in practice, I think that what would happen is that there would be one or more directories on different physical disks +> for data files where the database may balance for i/o load or disk space, and a log file on another distinct physical +> drive optimized for log write patterns. The data files may be single files where the database manages pages within it or +> the database may decide to use multiple files for whatever reason. + +What held, against the API: the layer above knows the roots and so mints the domain bytes, which is what +[API.md](../crates/durable-ioring/API.md) section 13 already expects of "a locality helper's job outside this crate", root +`M39`. + +What the assistant refined: + +- **A directory maps to a volume, not a physical disk.** Two volumes can share one disk, and so its cache and its failures; + one volume can span several. A file's declared domains are its volume and every disk beneath it, since extra domains are + safe and a missing one is the one way a declaration makes the contract false. `M39.2` already plans that traversal. +- **Predict by directory, confirm by handle.** A file created by name in a directory lands on the directory's volume, + mount points included, so mapping a directory once is sound -- but its path is resolved again at each create, and a + junction swapped between them sends the file elsewhere. Opening relative to a directory handle would close that, and is + parked as `DI-15.3` for want of a documented call. The substitute: read the new file's volume from its own handle + (`FileIdInfo` carries the volume serial number) and compare it with the prediction before the file is added. The engineer: + "The volume id refinement is a good catch for that race condition." Queued as `M39.3`. +- **The log on its own drive is where domains pay, and through fate, not routing.** The log is its own lineage, and + section 12's gates hold a data page for the log through its LSN. With nothing declared every file is one domain, so a + failed flush on a data disk would make the log's writes suspect; declared apart, it does not. That needs no provider. +- **The built-in default is most of the story for a typical database.** A volume-wide flush needs administrative privilege, + per the `FlushFileBuffers` page, and a log is one file, so per-file is per-volume for it. Providers, and the router, matter + for remote shares, exotic devices and a privileged service. +- **The mapping layer is the natural builder of the router**, since it mints the bytes the providers must carry. That made + (A) the natural shape: whatever builds a provider already has its domains. The engineer agreed: DI-D-81. + +## Directory entries + +Raised by the assistant and kept apart by agreement: a database that creates a data file at run time needs the creation +durable before it relies on it. Linux requires an `fsync` of the directory. What Windows documents -- whether flushing the +new file makes its entry durable, whether a directory handle may be flushed -- the assistant does not know, and the API does +not say. Queued as research and a discussion, `DI-M20`, deliberately coarse beyond that. + +*Afterward, 2026-10-21:* `DI-M20` was withdrawn once the research was done +([DI-D-95](../crates/durable-ioring/DESIGN-NOTES.md#di-d-95)). The durable ioring never creates, renames or deletes a file, +and the engineer's directory was framing for root `M39.3`, which holds the mapping of a directory to its flush domains. diff --git a/release-please-config.json b/release-please-config.json index e95c26a5e..23d7a35ee 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -73,6 +73,22 @@ "package-name": "win-volume-sys", "component": "win-volume-sys" }, + "crates/win-text-sys": { + "package-name": "win-text-sys", + "component": "win-text-sys" + }, + "crates/win-debugging-sys": { + "package-name": "win-debugging-sys", + "component": "win-debugging-sys" + }, + "crates/win-etw-sys": { + "package-name": "win-etw-sys", + "component": "win-etw-sys" + }, + "crates/durable-ioring-events": { + "package-name": "durable-ioring-events", + "component": "durable-ioring-events" + }, "crates/windows-file-enumeration-sys": { "package-name": "windows-file-enumeration-sys", "component": "windows-file-enumeration-sys" diff --git a/tools/check-archives.ps1 b/tools/check-archives.ps1 new file mode 100644 index 000000000..f6a82ea4d --- /dev/null +++ b/tools/check-archives.ps1 @@ -0,0 +1,182 @@ +# Copyright (c) Mike Grier. +<# +.SYNOPSIS + Lint every COMPLETED-CHECKLIST.md: heading style, open boxes, and immutable + headings and anchors. + +.DESCRIPTION + The archives are append-only history, read by people and linked to by + anchor, and the defects that reached review in them were each decidable by + a script (root CHECKLIST.md, M34.18). Four rules, settled by the engineer on + 2026-10-19: + + 1. A heading is preceded by a blank line. CommonMark renders it either way, + so this is style -- applied to every heading level, since the reason is + the same at each. + 2. A heading is never glued onto the end of another line, which is the shape + that does NOT render: an archived heading carries an `` anchor, or + is a `## Moved` group heading, so those two forms are what is looked for + mid-line. + 3. No `- [ ]` remains. An archived item is done; a verbatim copy of an item + "as it stood" is ticked rather than exempted. + 4. Headings and anchors are immutable. Against the merge base with -Base, + every heading line and every `` anchor an archive had must still be + there. Other lines may change: this repository's own rules require + repointing a link when its target moves, and archives carry factual + corrections. A deleted archive breaks the rule too. + + Lines inside fenced code blocks are not headings or boxes, and are skipped. + +.PARAMETER Root + The repository to check. Defaults to the one this script is in. + +.PARAMETER Base + A git ref to compare against for rule 4; the check uses its merge base with + HEAD. Defaults to `origin/main`. Pass `-NoHistory` to skip rule 4 instead, + for a tree with no such ref. + +.PARAMETER NoHistory + Skip rule 4. + +.NOTES + Exits 0 when every archive passes, 1 when any rule is broken, and 2 for a + usage or configuration error (no repository, an unresolvable -Base). +#> +[CmdletBinding()] +param( + [string] $Root, + [string] $Base = 'origin/main', + [switch] $NoHistory +) + +$ErrorActionPreference = 'Stop' + +if (-not $Root) { + $Root = (& git -C $PSScriptRoot rev-parse --show-toplevel 2>$null) + if ($LASTEXITCODE -ne 0 -or -not $Root) { + Write-Host "check-archives: not inside a git repository; pass -Root." -ForegroundColor Red + exit 2 + } +} +$Root = (Resolve-Path -LiteralPath $Root).Path + +$archivePattern = '(^|/)COMPLETED-CHECKLIST\.md$' +$headingPattern = '^#{1,6} ' +$anchorPattern = '' +# Rule 2's two shapes, each preceded by something other than the start of a line. +$gluedPattern = '\S\s*(#{2,6} +function Get-Landmarks { + param([string] $Text) + $headings = New-Object System.Collections.Generic.List[string] + $anchors = New-Object System.Collections.Generic.List[string] + foreach ($line in (Get-Lines $Text)) { + if ($line.Code) { continue } + if ($line.Text -match $headingPattern) { $headings.Add($line.Text.TrimEnd()) } + foreach ($m in [regex]::Matches($line.Text, $anchorPattern)) { $anchors.Add($m.Groups[1].Value) } + } + [pscustomobject]@{ Headings = $headings; Anchors = $anchors } +} + +# Untracked files too, so an archive created and not yet added is checked before +# its first commit rather than after it. +$archives = @(& git -C $Root ls-files --cached --others --exclude-standard | Where-Object { $_ -match $archivePattern } | Sort-Object -Unique) +if ($LASTEXITCODE -ne 0) { + Write-Host "check-archives: git ls-files failed in $Root." -ForegroundColor Red + exit 2 +} + +$problems = New-Object System.Collections.Generic.List[string] + +# Rules 1 to 3: the files as they stand. +foreach ($archive in $archives) { + # A tracked archive deleted from the working tree has nothing to lint here; + # rule 4 is what reports it. + $path = Join-Path $Root $archive + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { continue } + $text = [System.IO.File]::ReadAllText($path) + $previous = $null + foreach ($line in (Get-Lines $text)) { + if (-not $line.Code) { + if ($line.Text -match $headingPattern -and $line.Number -gt 1 -and $previous.Trim() -ne '') { + $problems.Add("${archive}:$($line.Number): HEADING WITHOUT A BLANK LINE ABOVE: $($line.Text)") + } + if ($line.Text -notmatch $headingPattern -and $line.Text -match $gluedPattern) { + $problems.Add("${archive}:$($line.Number): HEADING GLUED ONTO A LINE: $($line.Text)") + } + if ($line.Text -match $openBoxPattern) { + $problems.Add("${archive}:$($line.Number): OPEN BOX IN AN ARCHIVE: $($line.Text)") + } + } + $previous = $line.Text + } +} + +# Rule 4: against the merge base. +if (-not $NoHistory) { + $baseCommit = (& git -C $Root merge-base $Base HEAD 2>$null) + if ($LASTEXITCODE -ne 0 -or -not $baseCommit) { + Write-Host "check-archives: cannot find the merge base of '$Base' and HEAD in $Root." -ForegroundColor Red + Write-Host "Fetch the base, name another with -Base, or pass -NoHistory to skip the immutability rule." -ForegroundColor Red + exit 2 + } + $baseArchives = @(& git -C $Root ls-tree -r --name-only $baseCommit | Where-Object { $_ -match $archivePattern }) + foreach ($archive in $baseArchives) { + $current = Join-Path $Root $archive + if (-not (Test-Path -LiteralPath $current -PathType Leaf)) { + $problems.Add("${archive}: ARCHIVE DELETED since $($baseCommit.Substring(0, 12))") + continue + } + $before = Get-Landmarks ((& git -C $Root show "${baseCommit}:$archive") -join "`n") + $after = Get-Landmarks ([System.IO.File]::ReadAllText($current)) + + $remaining = New-Object System.Collections.Generic.List[string] + $after.Headings | ForEach-Object { $remaining.Add($_) } + foreach ($heading in $before.Headings) { + if (-not $remaining.Remove($heading)) { + $problems.Add("${archive}: HEADING CHANGED OR DELETED since $($baseCommit.Substring(0, 12)): $heading") + } + } + foreach ($anchor in $before.Anchors) { + if (-not $after.Anchors.Contains($anchor)) { + $problems.Add("${archive}: ANCHOR REMOVED since $($baseCommit.Substring(0, 12)): $anchor") + } + } + } +} + +if ($problems.Count -eq 0) { + $history = if ($NoHistory) { 'history not checked' } else { "headings and anchors unchanged since $($baseCommit.Substring(0, 12))" } + Write-Host "Archives clean: $($archives.Count) file(s), $history." + exit 0 +} + +foreach ($problem in $problems) { Write-Host $problem -ForegroundColor Red } +Write-Host "" +Write-Host "$($problems.Count) problem(s) in the archives. See CHECKLIST.md M34.18 for the rules." -ForegroundColor Red +exit 1 diff --git a/tools/common.ps1 b/tools/common.ps1 index 4809b377a..142e0e2b1 100644 --- a/tools/common.ps1 +++ b/tools/common.ps1 @@ -426,14 +426,16 @@ function Test-SabotagePatch { # reaches every manifest: the workspace's manifest, lockfile and toolchain, the # sabotage tools and the helper they share, the sweep workflow, and # win-job-launcher, which supervises every phase of every sweep and decides what -# its exit means. It has no path dependency on any workspace crate; a test checks -# that against cargo, so one added later is not missed. `Path` is relative to the +# its exit means, with win-handles-sys, the one workspace crate it builds on for +# its handle duplication. A test checks the list against cargo, so a path +# dependency the launcher gains later is not missed. `Path` is relative to the # repository root; either slash. function Test-SabotageSharedInput { param([string] $Path) $normal = $Path.Replace('\', '/') foreach ($pattern in @('Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', 'rust-toolchain', '.cargo/*', - 'tools/common.ps1', 'tools/*sabotage*.ps1', '.github/workflows/sabotage-sweep.yml', 'crates/win-job-launcher/*')) { + 'tools/common.ps1', 'tools/*sabotage*.ps1', '.github/workflows/sabotage-sweep.yml', 'crates/win-job-launcher/*', + 'crates/win-handles-sys/*')) { if ($normal -like $pattern) { return $true } } return $false diff --git a/tools/sabotage2.json b/tools/sabotage2.json index 1a08f408b..2c9b5b60a 100644 --- a/tools/sabotage2.json +++ b/tools/sabotage2.json @@ -28,6 +28,14 @@ "find": [" if ($TimeoutSeconds -eq 0) { $TimeoutSeconds = [int]$spec.timeoutSeconds }"], "replace": [" if ($TimeoutSeconds -ne 0) { return }", " $TimeoutSeconds = [int]$spec.timeoutSeconds"] }, + { + "name": "the inert check ignores case", + "file": "tools/common.ps1", + "expect": "caught", + "why": "This shipped: the check compared with PowerShell's case-insensitive -eq, so a patch changing a variable to the constant of the same name was refused as inert and never swept, while the patch itself is applied with an ordinal replace. Found by an M4.2 entry in windows-platform-probes that did exactly that. The check now lives in Test-SabotagePatch, shared with the no-build patch check.", + "find": [" if ([string]::Equals($patched, $Original, [System.StringComparison]::Ordinal)) {"], + "replace": [" if ($patched -eq $Original) {"] + }, { "name": "the reserved baseline stem is no longer refused", "file": "tools/run-sabotage.ps1", diff --git a/tools/snapshot-learn-toc.ps1 b/tools/snapshot-learn-toc.ps1 new file mode 100644 index 000000000..d3bbdb935 --- /dev/null +++ b/tools/snapshot-learn-toc.ps1 @@ -0,0 +1,157 @@ +# Copyright (c) Michael Grier +# +# tools/snapshot-learn-toc.ps1 -- records where Microsoft Learn files each Win32 symbol, as of the +# day it runs, so that "which section does this belong to" is a lookup in a file and not a +# search of a website. +# +# The repository places a safe wrapper of a platform API by the section Learn documents it under +# (WIN32-LEARN-MAP.md names the rule). Learn's section names and its headers drift, so the rule +# only classifies: neither string appears in a crate name or a public path, and the dated +# snapshot is the one place that carries them. A rename is a new snapshot, never a release. +# +# What it reads: https://learn.microsoft.com/en-us/windows/win32/toc.json for the areas under +# Develop > Desktop App Technologies, then each area's own toc.json, and every node in them that +# links to a reference page of a function, structure, enumeration, callback, interface or class. +# What it writes: a text file of one block per node that lists symbols, headed by the path of +# section titles that reach it, one symbol per line. +# +# Usage: +# pwsh tools/snapshot-learn-toc.ps1 # whole of Desktop App Technologies +# pwsh tools/snapshot-learn-toc.ps1 -Area 'sysinfo','sync' # only these area folders +# +# Exits 0 on success and 1 when a request fails or a toc has an unexpected shape. + +[CmdletBinding()] +param( + # Where the snapshot is written. Default: WIN32-LEARN-MAP-DATA.txt at the repository root. + [string]$Out, + # Restrict to these area folders (the first segment of an area's link, e.g. sysinfo). + [string[]]$Area, + # Pause between requests, in milliseconds, so the crawl is polite. + [int]$DelayMs = 150 +) + +$ErrorActionPreference = 'Stop' +$Root = Split-Path -Parent $PSScriptRoot +if (-not $Out) { $Out = Join-Path $Root 'WIN32-LEARN-MAP-DATA.txt' } +$Base = 'https://learn.microsoft.com/en-us/windows/win32' + +# 7-bit ASCII only, as the repository's planning documents are: common punctuation is spelled +# out and anything else is written as \uXXXX, so the file survives every code page. +function ConvertTo-Ascii([string]$text) { + $text = $text -replace [char]0x2019, "'" -replace [char]0x2018, "'" -replace [char]0x201C, '"' -replace [char]0x201D, '"' + $text = $text -replace [char]0x2013, '-' -replace [char]0x2014, '--' -replace [char]0x00A0, ' ' + $builder = New-Object System.Text.StringBuilder + foreach ($ch in $text.ToCharArray()) { + if ([int]$ch -lt 128) { [void]$builder.Append($ch) } else { [void]$builder.AppendFormat('\u{0:X4}', [int]$ch) } + } + $builder.ToString() +} + +function Get-Toc([string]$url) { + Start-Sleep -Milliseconds $DelayMs + $response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 90 + $response.Content | ConvertFrom-Json +} + +# Reference pages are /api/
/-
-: nf function, ns structure, ne enumeration, +# nc callback, nn interface, nl class, nm method group, nd delegate-like entries, ni a control code +# (IOCTL_ and FSCTL_). +$ApiLink = [regex]'/api/(?
[^/]+)/(?n[a-z])-(?[^/#?]+)$' +$KindNames = @{ nf = 'function'; ns = 'struct'; ne = 'enum'; nc = 'callback'; nn = 'interface'; nl = 'class'; nm = 'method'; nd = 'delegate'; ni = 'control-code' } + +$script:Lines = New-Object System.Collections.Generic.List[string] +$script:Counts = @{} + +function Add-Group($path, $symbols) { + $script:Lines.Add('') + $script:Lines.Add('# ' + (ConvertTo-Ascii ($path -join ' > '))) + foreach ($symbol in $symbols) { $script:Lines.Add($symbol) } +} + +function Walk($node, [string[]]$path) { + $path = $path + $node.toc_title + $symbols = New-Object System.Collections.Generic.List[string] + foreach ($child in @($node.children)) { + if (-not $child) { continue } + $match = if ($child.href) { $ApiLink.Match($child.href) } else { $null } + if ($match -and $match.Success) { + $kind = $match.Groups['kind'].Value + $header = $match.Groups['header'].Value.ToLowerInvariant() + '.h' + $title = ConvertTo-Ascii $child.toc_title + $slug = $match.Groups['slug'].Value + # The slug is "
-" in lowercase. Say so when the name Learn lists differs + # from it, as for the A and W forms (GetProfileInt is getprofileinta). + $name = $slug -replace ('^' + [regex]::Escape($match.Groups['header'].Value.ToLowerInvariant()) + '-'), '' + $flat = ($child.toc_title -replace '[^A-Za-z0-9]', '').ToLowerInvariant() + $note = if (($name -replace '[^a-z0-9]', '') -ne $flat) { "`t=" + $name } else { '' } + # Not named `$kindName`: PowerShell variable names are case-insensitive, so that would + # be the lookup table itself. + $label = if ($KindNames.ContainsKey($kind)) { $KindNames[$kind] } else { $kind } + $symbols.Add("$label`t$header`t$title$note") + $script:Counts[$label] = 1 + [int]$script:Counts[$label] + } else { + Walk $child $path + } + } + if ($symbols.Count -gt 0) { + Add-Group $path ($symbols | Sort-Object) + } +} + +$top = Get-Toc "$Base/toc.json" +$develop = $top.items[0].children | Where-Object toc_title -eq 'Develop' +$technologies = $develop.children | Where-Object toc_title -eq 'Desktop App Technologies' +if (-not $technologies) { Write-Error 'Desktop App Technologies is not where it was in the top toc.'; exit 1 } + +# An area is a node whose link is relative and names a folder with its own toc.json. +function Get-Areas($node, [string[]]$path) { + foreach ($child in @($node.children)) { + if (-not $child) { continue } + $here = $path + $child.toc_title + # A link with no slash is a page at the root of the reference and has no toc of its own. + if ($child.href -and $child.href -notmatch '^(/|https?:)' -and $child.href -match '/') { + [pscustomobject]@{ Path = $here; Folder = ($child.href -split '/')[0]; Href = $child.href } + } elseif ($child.children) { + Get-Areas $child $here + } + } +} + +$areas = @(Get-Areas $technologies @('Desktop App Technologies')) | Where-Object { $_.Folder -and $_.Folder -ne 'desktop-app-technologies' } +if ($Area) { $areas = @($areas | Where-Object { $Area -contains $_.Folder }) } +$seen = @{} +$skipped = @() +$failures = 0 +foreach ($entry in $areas) { + if ($seen.ContainsKey($entry.Folder)) { continue } + $seen[$entry.Folder] = $true + try { + $toc = Get-Toc "$Base/$($entry.Folder)/toc.json" + foreach ($item in $toc.items) { Walk $item $entry.Path[0..($entry.Path.Count - 2)] } + Write-Host ("{0,-28} {1}" -f $entry.Folder, ($entry.Path -join ' > ')) + } catch { + if ($_.Exception.Message -match '404') { + # A portal whose folder has no toc of its own: its symbols are not in this record. + $skipped += $entry.Folder + } else { + $failures++ + Write-Warning "$($entry.Folder): $($_.Exception.Message)" + } + } +} + +$stamp = Get-Date -Format 'yyyy-MM-dd' +$summary = ($script:Counts.GetEnumerator() | Sort-Object Name | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ' ' +$header = @( + 'WIN32-LEARN-MAP-DATA: where Microsoft Learn files each Win32 symbol.', + "Snapshot: $stamp. Source: $Base/toc.json and each area's toc.json. Regenerate with tools/snapshot-learn-toc.ps1.", + 'Layout: "# " lines are the path of section titles; under each, "kindheadername[=slug]" per symbol.', + '"=slug" appears only where the name in Learn differs from the page slug (the A and W forms).', + "Areas: $($seen.Count - $skipped.Count). Symbols: $summary.", + "Not covered, no toc of their own: $(($skipped | Sort-Object) -join ', ')." +) +$text = (($header + $script:Lines) -join "`n") + "`n" +[System.IO.File]::WriteAllText($Out, $text, (New-Object System.Text.UTF8Encoding($false))) +Write-Host "Wrote $Out ($([math]::Round($text.Length / 1KB)) KiB, $($seen.Count - $skipped.Count) areas, $($skipped.Count) without a toc, $failures failures)." +if ($failures -gt 0) { exit 1 } diff --git a/tools/test-check-archives.ps1 b/tools/test-check-archives.ps1 new file mode 100644 index 000000000..15cd9f535 --- /dev/null +++ b/tools/test-check-archives.ps1 @@ -0,0 +1,240 @@ +# Copyright (c) Mike Grier. +<# +.SYNOPSIS + Tests for [check-archives.ps1](check-archives.ps1). + +.DESCRIPTION + Every rule is tested in both directions: the checker must REJECT what it + exists to catch and ACCEPT what is legitimate, because a rule that rejects + everything passes a rejection test and a rule that was switched off passes + an acceptance test. + + Each case builds a throwaway git repository under TEMP, commits a base, then + changes the working tree and runs the checker against `-Base HEAD`, so the + immutability rule compares the change with the commit before it. Nothing + here reads this repository's own archives. + + WHY NOT PESTER: see the same note in + [test-run-sabotage.ps1](test-run-sabotage.ps1). The sibling tools are + standalone and dependency-free, and CI invokes them directly. + +.PARAMETER Name + Optional wildcard filter over case names, to re-run just one. +#> +[CmdletBinding()] +param( + [string] $Name = '*' +) + +$ErrorActionPreference = 'Stop' + +$checker = Join-Path $PSScriptRoot 'check-archives.ps1' +if (-not (Test-Path -LiteralPath $checker)) { + throw "check-archives.ps1 not found beside this script at $checker" +} + +function Assert-True { + param([bool] $Condition, [string] $Because = '') + if (-not $Condition) { throw "expected true -- $Because" } +} + +function Assert-Match { + param([string] $Pattern, [string] $Text, [string] $Because = '') + if ($Text -notmatch $Pattern) { + throw "expected a match for [$Pattern] -- $Because`n--- output ---`n$Text" + } +} + +function Write-Fixture { + param([string] $Path, [string] $Content) + $parent = Split-Path -Parent $Path + if (-not (Test-Path -LiteralPath $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null } + [System.IO.File]::WriteAllText($Path, $Content, (New-Object System.Text.UTF8Encoding $false)) +} + +<# +.SYNOPSIS + Commit `Base` (a map of relative path to content) in a new repository, apply + `Change` (a map of path to content, or to $null to delete), and run the + checker. Returns the exit code and output. +#> +function Invoke-Case { + param([hashtable] $Base, [hashtable] $Change = @{}, [string[]] $Arguments = @('-Base', 'HEAD')) + + $root = Join-Path ([System.IO.Path]::GetTempPath()) ("check-archives-test-" + [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $root | Out-Null + try { + & git -C $root init --quiet 2>&1 | Out-Null + foreach ($path in $Base.Keys) { Write-Fixture (Join-Path $root $path) $Base[$path] } + & git -C $root add -A 2>&1 | Out-Null + & git -C $root -c user.name=test -c user.email=test@example.invalid commit --quiet -m base 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { throw "fixture commit failed in $root" } + foreach ($path in $Change.Keys) { + $full = Join-Path $root $path + if ($null -eq $Change[$path]) { Remove-Item -LiteralPath $full } else { Write-Fixture $full $Change[$path] } + } + $output = & pwsh -NoProfile -File $checker -Root $root @Arguments *>&1 | Out-String + [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $output } + } + finally { + Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue + } +} + +$archive = 'crate/COMPLETED-CHECKLIST.md' +$good = @" +# Completed checklist + +## Moved 2026-10-19 12:00:00 -04:00 -- M1: a group + +### M1.1 -- An item. *(completed 2026-10-19)* + +Body text, with an issue #12 and C# mentioned in passing. + +- [x] **M1.1** -- the item as it stood. +"@ + "`n" + +$cases = @( + @{ + Name = 'a-clean-archive-is-accepted' + Run = { + $r = Invoke-Case -Base @{ $archive = $good } + Assert-True ($r.ExitCode -eq 0) "a clean archive must pass: $($r.Output)" + Assert-Match 'Archives clean: 1 file' $r.Output 'and say what it checked' + } + }, + @{ + Name = 'a-heading-without-a-blank-line-above-is-rejected-at-every-level' + Run = { + foreach ($heading in '## Moved later -- M2', '### M1.2 -- x', '#### A detail') { + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $good + "text`n$heading`n" } + Assert-True ($r.ExitCode -eq 1) "[$heading] directly under text must fail" + Assert-Match 'HEADING WITHOUT A BLANK LINE ABOVE' $r.Output 'and say which rule fired' + } + } + }, + @{ + Name = 'a-heading-on-the-first-line-needs-no-blank-above' + Run = { + $r = Invoke-Case -Base @{ $archive = "# Title`n`ntext`n" } + Assert-True ($r.ExitCode -eq 0) "line 1 has nothing above it: $($r.Output)" + } + }, + @{ + Name = 'a-heading-glued-onto-a-line-is-rejected-in-both-shapes' + Run = { + foreach ($glued in 'text. ### M1.2 -- x', 'text. ## Moved 2026-10-20 -- M2') { + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $good + "`n$glued`n" } + Assert-True ($r.ExitCode -eq 1) "[$glued] must fail" + Assert-Match 'HEADING GLUED ONTO A LINE' $r.Output 'and say which rule fired' + } + } + }, + @{ + Name = 'an-open-box-is-rejected-and-a-ticked-one-accepted' + Run = { + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $good + "`n- [ ] **M1.2** -- still open`n" } + Assert-True ($r.ExitCode -eq 1) 'an open box must fail' + Assert-Match 'OPEN BOX IN AN ARCHIVE' $r.Output 'and say which rule fired' + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $good + "`n - [x] nested and ticked`n" } + Assert-True ($r.ExitCode -eq 0) "a ticked box must pass: $($r.Output)" + } + }, + @{ + Name = 'lines-inside-a-code-fence-are-not-headings-or-boxes' + Run = { + $fenced = $good + "`n``````markdown`ntext`n## not a heading`n- [ ] not a box`n``````n" + $r = Invoke-Case -Base @{ $archive = $fenced } + Assert-True ($r.ExitCode -eq 0) "fenced lines must be skipped: $($r.Output)" + } + }, + @{ + Name = 'only-completed-checklists-are-linted' + Run = { + $r = Invoke-Case -Base @{ $archive = $good; 'crate/CHECKLIST.md' = "text`n## M2`n- [ ] open`n" } + Assert-True ($r.ExitCode -eq 0) "a live checklist is not an archive: $($r.Output)" + } + }, + @{ + Name = 'an-untracked-new-archive-is-linted' + Run = { + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ 'other/COMPLETED-CHECKLIST.md' = "text`n- [ ] open`n" } + Assert-True ($r.ExitCode -eq 1) 'a new archive not yet added must still be checked' + Assert-Match 'other/COMPLETED-CHECKLIST.md' $r.Output 'and be named' + } + }, + @{ + Name = 'a-changed-heading-is-rejected' + Run = { + $changed = $good -replace 'M1: a group', 'M1: a group, renamed' + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $changed } + Assert-True ($r.ExitCode -eq 1) 'rewriting an archived heading must fail' + Assert-Match 'HEADING CHANGED OR DELETED' $r.Output 'and say which rule fired' + } + }, + @{ + Name = 'a-removed-anchor-is-rejected' + Run = { + $changed = $good -replace '', '' + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $changed } + Assert-True ($r.ExitCode -eq 1) 'removing an anchor must fail' + Assert-Match 'ANCHOR REMOVED.*m11' $r.Output 'and name the anchor' + } + }, + @{ + Name = 'a-deleted-archive-is-rejected' + Run = { + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $null } + Assert-True ($r.ExitCode -eq 1) 'deleting an archive must fail' + Assert-Match 'ARCHIVE DELETED' $r.Output 'and say which rule fired' + } + }, + @{ + Name = 'body-corrections-and-appended-entries-are-accepted' + Run = { + # The acceptance half of rule 4: this repository's rules require + # repointing links and allow factual corrections, so only headings and + # anchors are fixed. + $changed = ($good -replace 'issue #12', 'issue #13') + "`n## Moved 2026-10-20 -- M2: another`n`ntext`n" + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $changed } + Assert-True ($r.ExitCode -eq 0) "a body edit and an appended group must pass: $($r.Output)" + } + }, + @{ + Name = 'an-unresolvable-base-is-a-usage-error-and-no-history-skips-the-rule' + Run = { + $r = Invoke-Case -Base @{ $archive = $good } -Arguments @('-Base', 'no-such-ref') + Assert-True ($r.ExitCode -eq 2) "an unknown base must exit 2: $($r.Output)" + $changed = $good -replace 'M1: a group', 'M1: renamed' + $r = Invoke-Case -Base @{ $archive = $good } -Change @{ $archive = $changed } -Arguments @('-NoHistory') + Assert-True ($r.ExitCode -eq 0) "-NoHistory must skip rule 4: $($r.Output)" + Assert-Match 'history not checked' $r.Output 'and say it did' + } + } +) + +$failures = 0 +$ran = 0 +foreach ($case in $cases | Where-Object { $_.Name -like $Name }) { + $ran++ + try { + & $case.Run + Write-Host " PASS $($case.Name)" + } + catch { + $failures++ + Write-Host " FAIL $($case.Name)" -ForegroundColor Red + Write-Host " $($_.Exception.Message)" -ForegroundColor Red + } +} + +if ($ran -eq 0) { + Write-Host "No case matches '$Name'." -ForegroundColor Red + exit 1 +} +if ($failures -gt 0) { + Write-Host "$failures of $ran cases failed." -ForegroundColor Red + exit 1 +} +Write-Host "All $ran cases passed." +exit 0 diff --git a/tools/test-common.ps1 b/tools/test-common.ps1 index 731cb4dd0..a5c7859f5 100644 --- a/tools/test-common.ps1 +++ b/tools/test-common.ps1 @@ -490,11 +490,11 @@ Test-Case 'manifests are found under crates, in order, and nowhere else' { Test-Case 'the inputs every sweep shares are recognised, and a crate''s own files are not' { foreach ($path in 'Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml', 'tools/common.ps1', 'tools/run-sabotage.ps1', 'tools\plan-sabotage-matrix.ps1', 'tools/check-sabotage-patches.ps1', - '.github/workflows/sabotage-sweep.yml') { + '.github/workflows/sabotage-sweep.yml', 'crates/win-handles-sys/src/lib.rs', 'crates\win-handles-sys\Cargo.toml') { if (-not (Test-SabotageSharedInput $path)) { throw "not taken as shared: $path" } } foreach ($path in 'crates/a/Cargo.toml', 'crates/a/sabotage.json', 'tools/check-encoding.ps1', 'README.md', - '.github/workflows/ci.yml', 'docs/Cargo.toml') { + '.github/workflows/ci.yml', 'docs/Cargo.toml', 'crates/win-handles-sysx/src/lib.rs', 'crates/win-sync-sys/src/lib.rs') { if (Test-SabotageSharedInput $path) { throw "taken as shared: $path" } } } diff --git a/tools/test-run-sabotage.ps1 b/tools/test-run-sabotage.ps1 index 900220aaa..0be972c99 100644 --- a/tools/test-run-sabotage.ps1 +++ b/tools/test-run-sabotage.ps1 @@ -1189,6 +1189,22 @@ Test-Case 'reports a patch that changes nothing as inert' { finally { Remove-Fixture $root } } +# The inert check once compared with PowerShell's `-eq`, which ignores case, so a +# patch that changed nothing but case -- a variable to the constant of the same +# name, say -- was refused as inert while the patch itself was applied with an +# ordinal replace. Source code is case-sensitive; so is this check. +Test-Case 'sweeps a patch that changes only the case of the text' { + $root = New-Fixture -Manifest (New-Spec -EntryExtra @{ replace = @('// MARKER LINE') }) + try { + $stub = New-Stub -Behaviour 'fail' -Root $root + $result = Invoke-Harness -Root $root ` + -Arguments @('-Manifest', 'sabotage.json', '-CargoCommand', $stub) + Assert-Equal 0 $result.ExitCode $result.Output + Assert-Match 'caught \(suite failed' $result.Output + } + finally { Remove-Fixture $root } +} + Test-Case 'accepts a control that is declared to survive' { $root = New-Fixture -Manifest (New-Spec -EntryExtra @{ expect = 'survives' }) try {