Skip to content

nvx-rs: read a GPT disk's table through the guest's block device - #771

Merged
Enrique Saurez (esaurez) merged 2 commits into
devfrom
esaurez/a3-gpt-disk-guid-20261022
Oct 22, 2026
Merged

Enrique Saurez (esaurez) merged 2 commits into
devfrom
esaurez/a3-gpt-disk-guid-20261022

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Adds nvx::guest::gpt::read_disk, which reads the partition table of a GPT disk through the block device that the guest's kernel attached, and checks it, with the expected disk GUID, through nvx::gpt (#673). The kernel keeps no disk GUID, so a guest that must check the one that a boot parameter names reads it from the table, whose header CRC covers it. The partitions that the guest then uses, such as /dev/vda2, are the ones that the kernel created when it found the disk.

This is the first, smaller iteration of A3.3's device code. #715 also compared the kernel's partitions with the table, reread the table when they differed, and tested all of it in a microVM. That guards against a table that changes under a running kernel, which a disk that the host stages read-only and attaches when it creates the VM never does, so #770 tracks it, with #715 as its reference.

What changed

  • nvx::guest::gpt::read_disk(&File, &Expected) -> Result<Disk, DeviceError> asks the device for its logical block size and its size, with BLKSSZGET and BLKGETSIZE64, as blockdev --getss and --getsize64 do, and reads and checks the table there with Disk::read. It only reads, at explicit offsets, so a read-only descriptor will do, and it never moves the file's position, so threads may share the file. DeviceError says whether the block size, the size, or the table failed; a file that is not a block device fails at its block size, with ENOTTY.
  • nvx::guest::sys wraps BLKSSZGET and BLKGETSIZE64 beside BLKFLSBUF. The wrappers borrow the caller's descriptor, which they neither read nor write.
  • nvx::gpt's Disk::p1, Disk::read_p1, and GptError::P1TooLarge, from nvx-rs: add a strict GPT reader for layered root disks #673, become Disk::first_partition, Disk::read_first_partition, and GptError::FirstPartitionTooLarge, so that no name or document uses the shorthand "p1". Nothing else calls them yet.
  • The crate documentation, its README, and doc/project-structure.md list the module, and the project structure now lists nvx::gpt too.

Part of #621. Refs #622, #770.

Validation

This head, 934b0aa, is on dev at ed6a603. dev has since merged #743 and #672, which change none of this pull request's files, and in nvx-rs only its host side, from which the guest binaries are not built.

On Windows 11 (MSVC, Rust 1.95.0) and on an Azure Linux 3.0 virtual machine as a non-root user (Rust 1.95.0), in nvx-rs with NVX_BUNDLE=skip:

  • cargo fmt --all --check
  • cargo clippy --locked --all-targets --all-features -- -D warnings
  • cargo clippy --locked --lib --no-default-features -- -D warnings
  • cargo test --locked --all-features, then cargo test --locked
  • cargo doc --locked --no-deps --all-features with RUSTDOCFLAGS=-D warnings
  • cargo +1.89 check --locked --all-targets --all-features, the minimum supported Rust version
  • tests/runtime-adapter in its protocol-only and host-only configurations, and on Linux its musl guest-only one: rustfmt, Clippy, build, tests, and the 1.89 check
  • aci_edge_sandboxes, which builds on nvx, as its CI checks it, with ACI_EDGE_SANDBOXES_AGENT_BUNDLE=skip: rustfmt, Clippy, tests, nvx.py test-agent-bundling, rustdoc, and its minimum supported Rust version, and on Linux its macOS check

The guest module builds only on Linux, where these ran too:

  • the artifact bundling test over a synthetic bundle, as check-nvx-rs runs it, and cargo check --locked --all-targets --all-features --target aarch64-apple-darwin
  • python3 scripts/nvx.py check-guest-binaries, which also runs the new tests for x86_64-unknown-linux-musl, then build-guest-binaries --output DIR and test-guest-binaries --binaries DIR. The nine guest binaries have the same instructions as those built from dev at ed6a603, since nothing calls the new code yet. Each differs from them in one byte: the line number of a panic location in src/guest/sys/mod.rs, whose re-exports grew by two lines.
  • python3 -m compileall -q scripts, .github/skills/nvx-host-connect/scripts/test_hosts.py, the nvx.py help commands, and Specula's tests

On Windows: python scripts\nvx.py verify and python scripts\check_markdown_links.py. No Python file changed.

read_disk on real block devices, on the Linux virtual machine as root, with a test program outside the repository: sfdisk (util-linux 2.40.2) wrote 8 MiB GPT disks, with text in the first partition and two layers, through loop devices with 512- and 4096-byte logical blocks, and read-only loop devices with the same block sizes then held them. Through each one, read_disk returned the logical block size and size that blockdev reports, the disk GUID, and each partition's LBAs and GUID as sfdisk --dump lists them; read_first_partition returned the text; and the file's position was where it was before, also after eight threads had each called read_disk 200 times on the one file. A wrong expected disk GUID failed with GptError::DiskGuidMismatch, a disk read at the other block size failed at LBA 1, which held no GPT header there, and a regular file failed at its block size with ENOTTY.

CI runs the hypervisor matrix. This change adds no microVM scenario, and no guest binary calls the new code.

"p1" was shorthand that each reader had to decode. Name the partition
explicitly in nvx::gpt's API, errors, and documentation:
`Disk::p1` and `Disk::read_p1` become `Disk::first_partition` and
`Disk::read_first_partition`, and `GptError::P1TooLarge` becomes
`GptError::FirstPartitionTooLarge`. Tests and comments name the other
partitions by their numbers.

Nothing else in the repository calls these yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b4b569c-a481-43aa-bfd8-c14a9762c371

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The seek-based size query can disrupt concurrent readers and leave the shared file offset at EOF.

1 open issue
What changed in this PR

Adds guest-side GPT table reading through a Linux block device, reusing the existing strict GPT validator as an incremental step toward layered-root support. Kernel partition comparison remains deferred to #770.

Changes:

  • Adds guest::gpt::read_disk, device geometry discovery, and typed errors.
  • Renames first-partition APIs and updates their tests.
  • Documents the new module and its scope.
File Description
nvx-rs/​src/​lib.rs Lists guest GPT support in crate documentation.
nvx-rs/​src/​guest/​sys/​mod.rs Re-exports the logical block-size wrapper.
nvx-rs/​src/​guest/​sys/​ioctl.rs Adds BLKSSZGET support and tests.
nvx-rs/​src/​guest/​mod.rs Exposes and documents the GPT module.
nvx-rs/​src/​guest/​gpt.rs Implements device-backed table reading, errors, and tests.
nvx-rs/​src/​gpt/​tests.rs Updates tests for renamed first-partition APIs.
nvx-rs/​src/​gpt/​mod.rs Renames first-partition methods and documentation.
nvx-rs/​src/​gpt/​error.rs Renames the first-partition size error.
nvx-rs/​README.md Documents the new module and renamed APIs.
doc/​project-structure.md Lists shared and guest GPT functionality.

Review effort: Balanced

Comment thread nvx-rs/src/guest/gpt.rs Outdated
A guest that boots from a GPT disk needs the disk's table, whose disk
GUID a boot parameter names, from the block device that the kernel
attached. nvx::gpt reads a table through any source at a geometry that
its caller states, so the guest only has to supply the device's.

Add nvx::guest::gpt::read_disk, which asks a block device that the
caller opened for its logical block size and its size, with BLKSSZGET
and BLKGETSIZE64, as blockdev does, and reads and checks the table
there with Disk::read and the caller's Expected values. It only reads
the device, at explicit offsets, so it never moves the file's position
and callers may share the file. It neither compares the partitions that
the kernel created with the table nor asks the kernel to reread it; a
disk that the host stages read-only does not change under the guest.

The two ioctl wrappers live in guest::sys beside BLKFLSBUF. They borrow
the caller's descriptor, which they neither read nor write.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b4b569c-a481-43aa-bfd8-c14a9762c371
Copilot AI balanced review requested due to automatic review settings October 22, 2026 21:24
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a3-gpt-disk-guid-20261022 branch from bee76fb to 934b0aa Compare October 22, 2026 21:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused implementation preserves file offsets, delegates validation to the existing parser, and leaves no unresolved blocking findings.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants