vhdxtool: add cross-platform VHDX utility - #4370
Conversation
Add a standalone utility for creating, inspecting, mapping, validating, replaying, and converting VHDX images. This provides a portable workflow for working with VHDX files directly through the repository's native format implementation. Extend the VHDX creation API with typed dynamic, fixed, and differencing disk configuration so invalid parent combinations cannot be represented. Add parent-locator serialization and typed interpretation, including canonical linkage identifiers and optional parent paths, to support creating and validating differencing chains.
|
This PR modifies files containing For more on why we check whole files, instead of just diffs, check out the Rustonomicon |
There was a problem hiding this comment.
🟡 Changes recommended
Differencing-disk creation currently treats relative-path computation as mandatory (breaking common cross-root/drive cases) and there’s a misleading new test name that should be corrected to reflect actual assertions.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new cross-platform vhdxtool CLI to create/inspect/map/convert/check/replay VHDX images, and extends the vhdx crate creation/open surface to represent disk allocation + differencing parent metadata with typed APIs (including parent-locator serialization/parsing) so invalid configurations are unrepresentable.
Changes:
- Introduce
vhdxtool(new crate) with commands for creating, inspecting, mapping, converting, validating parent chains, and replaying logs. - Refactor
vhdx::CreateParamsto use typedDiskType+VhdxParent, and add parent-locator building/parsing and open-error classification. - Update
vhdxtests to the new creation API and validate locator behavior.
File summaries
| File | Description |
|---|---|
| vm/devices/storage/vhdxtool/src/util.rs | Adds CLI helpers for size parsing/formatting and relative parent-path computation. |
| vm/devices/storage/vhdxtool/src/main.rs | Implements the vhdxtool CLI and operational logic for create/info/map/convert/check/replay. |
| vm/devices/storage/vhdxtool/src/file.rs | Provides a cross-platform AsyncFile implementation for positional I/O backing vhdxtool. |
| vm/devices/storage/vhdxtool/README.md | Documents vhdxtool usage and expected behaviors (exit codes, parent locator notes, sparsity). |
| vm/devices/storage/vhdxtool/Cargo.toml | Defines the new vhdxtool crate and dependencies. |
| vm/devices/storage/vhdx/tests/native_cross_validation.rs | Updates cross-validation harness to create differencing disks via typed DiskType. |
| vm/devices/storage/vhdx/src/tests/trim_tests.rs | Updates fixed-disk creation in trim tests to use DiskType::Fixed. |
| vm/devices/storage/vhdx/src/tests/mod.rs | Updates integration tests to use DiskType::Differencing(...). |
| vm/devices/storage/vhdx/src/tests/io_tests.rs | Updates I/O tests for differencing disks to use typed DiskType. |
| vm/devices/storage/vhdx/src/sector_bitmap.rs | Updates sector-bitmap tests to use DiskType::Differencing(...). |
| vm/devices/storage/vhdx/src/open.rs | Adds/updates tests around differencing opens and fully allocated behavior. |
| vm/devices/storage/vhdx/src/locator.rs | Adds typed vhdx_parent() interpretation + makes locator construction size-safe. |
| vm/devices/storage/vhdx/src/lib.rs | Re-exports new public types (DiskType, VhdxParent, OpenErrorKind, etc.). |
| vm/devices/storage/vhdx/src/error.rs | Adds OpenErrorKind classification and new invalid-format reasons related to parent locators. |
| vm/devices/storage/vhdx/src/create.rs | Implements typed disk creation + parent-locator metadata emission with size validation. |
| Cargo.toml | Registers vhdxtool as a workspace member. |
| Cargo.lock | Adds the new vhdxtool package entry and its resolved dependency set. |
Review details
- Files reviewed: 16/17 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| let absolute_parent = fs_err::canonicalize(&parent_path) | ||
| .with_context(|| format!("failed to resolve parent {}", parent_path.display()))?; | ||
| let relative_path = util::relative_path(child_directory, &absolute_parent)? | ||
| .to_string_lossy() | ||
| .replace(std::path::MAIN_SEPARATOR, "\\"); | ||
| let vhdx_parent = | ||
| VhdxParent::new(parent.data_write_guid())?.with_relative_path(relative_path)?; |
| #[async_test] | ||
| async fn open_differencing_disk_ignores_leave_blocks_allocated() { | ||
| let file = InMemoryFile::new(0); |
There was a problem hiding this comment.
🟡 Changes recommended
OpenError::kind() currently matches on self.0 by value (moving out of &self) and will not compile.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
vm/devices/storage/vhdx/src/locator.rs:235
- The doc comment doesn’t explain why this now returns
Option<Vec<u8>>. Since callers mapNonetoParentLocatorTooLarge, it would be helpful to document the failure conditions (oversize locator / overflow / too many entries).
vm/devices/storage/vhdxtool/src/main.rs:764 - Opening the newly created output file can fail, but this
?loses the path context in the error chain. Addingwith_contexthere will make failures easier to diagnose (e.g., permission issues, file deleted between create and open).
This issue also appears on line 810 of the same file.
vm/devices/storage/vhdxtool/src/main.rs:810
- Same as above: this open is part of the conversion flow, but failing here won’t include the output path unless you add context.
let output_file = BlockingFile::open(output_path, false)?;
- Files reviewed: 16/17 changed files
- Comments generated: 2
- Review effort level: Lite
| pub fn kind(&self) -> OpenErrorKind { | ||
| match self.0 { | ||
| OpenErrorInner::Io(_) => OpenErrorKind::Io, | ||
| OpenErrorInner::Corrupt(CorruptionType::LogReplayRequired) => { | ||
| OpenErrorKind::LogReplayRequired | ||
| } | ||
| OpenErrorInner::Corrupt(_) => OpenErrorKind::Corruption, | ||
| OpenErrorInner::InvalidParameter(_) => OpenErrorKind::InvalidParameter, | ||
| OpenErrorInner::PipelineFailed(_) | OpenErrorInner::MetadataCache(_) => { | ||
| OpenErrorKind::Other | ||
| } | ||
| } | ||
| } |
| let relative_path = util::relative_path(child_directory, &absolute_parent).map(|path| { | ||
| path.to_string_lossy() | ||
| .replace(std::path::MAIN_SEPARATOR, "\\") | ||
| }); |
| //! can be repaired with `replay`. | ||
|
|
||
| #![forbid(unsafe_code)] | ||
| #![deny(missing_docs)] |
There was a problem hiding this comment.
Just warn on missing_docs so that it doesn't block local builds.
| block_alignment: Option<u64>, | ||
| /// Data write GUID. A random GUID is generated when omitted. | ||
| #[arg(long)] | ||
| id: Option<String>, |
There was a problem hiding this comment.
I don't see why we would want to expose this.
| /// VHDX payload block size. Accepts binary size suffixes. | ||
| #[arg(long, value_parser = util::parse_size)] | ||
| block_size: Option<u64>, | ||
| /// Logical sector size in bytes: 512 or 4096. |
There was a problem hiding this comment.
Does the spec/vhdx implementation really limit us to these two values?
| /// Parent VHDX path. Required for, and only valid with, differencing images. | ||
| #[arg(long)] | ||
| parent: Option<PathBuf>, | ||
| /// VHDX payload block size. Accepts binary size suffixes. |
There was a problem hiding this comment.
Worth documenting here the minimum and default.
Add a standalone utility for creating, inspecting, mapping, validating, replaying, and converting VHDX images. This provides a portable workflow for working with VHDX files directly through the repository's native format implementation.
Extend the VHDX creation API with typed dynamic, fixed, and differencing disk configuration so invalid parent combinations cannot be represented. Add parent-locator serialization and typed interpretation, including canonical linkage identifiers and optional parent paths, to support creating and validating differencing chains.