Skip to content

nvx-rs: compare lock files by their 128-bit file IDs on Windows, as ReFS needs #923

Closed

Description

Related: a follow-up to #885 (#839's PR 3), whose removal check this concerns. Refs #839. Needs: #885.

Goal. On Windows, the checks that tell whether two handles or paths reach the same lock file tell any two files of a volume apart, ReFS volumes included, as a store's directory identities already do.

Today (#885's head 4a8b5341):

  • A removal's unbinding compares 64-bit file indexes. LockedStore::unbind opens the sandbox's directory once, and refuses unless the directory's sandbox.lock entry is the file whose lock the removal holds (lifecycle.rs#L382-L392). On Windows, both sides are the volume serial number and the 64-bit file index that GetFileInformationByHandle returns: opened_file_identity and handle_identity (windows.rs#L1463-L1475), and Directory::entry_identity (windows.rs#L1299-L1318).
  • The lock checks compare the same identity. After it takes a lock, the sandbox store keeps it only if the path still names the locked file (take_lock, store/mod.rs#L575-L585), as nvx-rs: add the template store #881's template store does (template/store.rs#L799-L806). remove_unprovisioned_lock_file removes the lock file only if it is the held one (lifecycle.rs#L248-L253).
  • ReFS doesn't keep those indexes unique. BY_HANDLE_FILE_INFORMATION: "The 64-bit identifier in this structure is not guaranteed to be unique on ReFS." ReFS identifies a file by 128 bits, which GetFileInformationByHandleEx returns in FILE_ID_INFO. NTFS keeps its 64-bit indexes unique within a volume.
  • A store's directory identities already use 128 bits. Since 46ddf46, identity_of identifies a file or directory by the volume serial number and the 128-bit file ID of FILE_ID_INFO, "which tell the files of ReFS volumes apart too" (lifecycle.rs#L715-L724 and #L741-L749). It takes them from FileSeal::of, which falls back to the 64-bit index on a volume without file ID information (seal/windows.rs#L47-L68).

Why it matters. Two files of one ReFS volume that share a 64-bit index pass these checks as one file.

  • The unbinding's check keeps a link in the sandbox's path, changed after the removal took its lock, from redirecting the unbinding to another sandbox, whose binding a clone may still need (#885's review). A collision between the two sandboxes' lock files reopens that race.
  • A collision between a replaced lock file and its replacement lets one transition keep a lock on the replaced file while another one locks the new file.

Scope (one PR, after #885):

  1. On Windows, compare the lock files by the volume serial number and the 128-bit file ID of FILE_ID_INFO, with FileSeal::of's fallback, in the unbinding's check and in the lock checks above. These checks compare two files at one moment, so a crate-internal identity, such as the one that identity_of reads, changes no record and no public signature.
  2. Tests that two files of one directory get different identities, and that each identity matches the file's FileSeal.

Not in scope: FileIdentity, whose index is 64 bits (filesystem.rs#L189-L197), which the launch journal records for its claimed log, and which log_claim_held checks (windows.rs#L1503-L1519); and the public platform::file_identity (windows.rs#L1240-L1249). Widening them changes a record format and the crate's API, which a later issue can decide.

Done when: on Windows, the unbinding's check and the lock checks compare 128-bit file IDs wherever the volume provides them, and the tests pass.

Activity

  1. ppenna commented on Oct 29, 2026

    @ppenna
    Contributor

    Fixed on dev by e9d7531, which #949 merged:

    • The sandbox store's and the template store's take_lock, the release that removes a holder's lock file, and the removal's unbinding now compare platform::opened_file_id, platform::file_id, and platform::Directory::entry_id. Each returns the identity that the file's FileSeal holds: on Windows, the volume serial number and the 128-bit file ID of FILE_ID_INFO, with FileSeal::of's fallback; on Linux, the device and inode.
    • host::platform::tests::the_stores_tell_files_apart_by_the_identities_that_their_seals_hold checks, through a path, an open file, and a directory entry, that two files of one directory get different identities, each matching the file's FileSeal.
    • As scoped, FileIdentity and the public platform::file_identity keep their 64-bit indexes.

    Validation on ReFS. On nvx-whp-test (Windows Server 2025), with a temporary ReFS 3.14 volume and TMP and TEMP on it, cargo test --all-features --lib --tests of dev at 5e95d35 passes: 1,006 library tests and every integration test, including the test above and a_removal_through_a_path_that_names_another_sandbox_meanwhile_removes_nothing.

    How ReFS 3.14 forms the 64-bit index, from a probe on that volume. The 128-bit ID holds the directory's ID in its high half and the file's ID within that directory in its low half. ReFS packs them into the 64-bit index as the number of hexadecimal digits of the directory ID, then the file's ID, then the directory ID: 0000000000001a92_0000000000000002 becomes 4000000000021a92. No two of 10,000 test files collided, one in each of 5,000 directories and 5,000 in one more. A file whose ID cannot be packed gets 0xFFFFFFFFFFFFFFFF, as MS-FSCC §2.1.9 requires, and all such files collide.

    #984, which made the same change, is closed as superseded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions