Skip to content

nvx-rs: compute the outermost export paths with one function - #606

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/outermost-paths
Oct 19, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/outermost-paths

Conversation

@ppenna

Copy link
Copy Markdown
Contributor

Summary

plan in nvx-rs/src/host/filesystem.rs reduces three lists of canonical host paths to the paths that no other path in the list contains: the export roots, the denied paths, and the paths to which OpenVMM limits a read-only child's writes. It did that three ways:

  • an inline sort, dedup, and filter in export_roots;
  • an inline sort, dedup, nested list, and retain in denied_paths;
  • outermost, which filtered Mapped entries by their canonical paths, followed by a map and a sort in plan.

This change gives the module one outermost(Vec<PathBuf>) -> Vec<PathBuf>, which sorts the paths, drops duplicates, and keeps those that no other contains. All three lists use it. The rule that decides which paths OpenVMM exports, hides, and lets the guest write now lives in one place, so the three lists cannot drift apart.

This is the third nvx-rs-quality/ run. #603 and #605, both merged, had approving Copilot reviews and no feedback to follow. They changed protocol/exec.rs and guest/managed_agent/exec.rs. This run surveyed the rest of the crate, by reading and with a throwaway duplicate-window script, and skipped every file that an open pull request to dev changed when it started: #585, #596, #597, #598, and #605. It picked host/filesystem.rs, which no recent run touched and existing tests cover.

The survey's strongest production candidate was the six identical fail_fatal calls in guest/time/daemon.rs. As #605 noted, no unit test can reach that code without a seam over the event log and console, so the conservative choice was to leave it as a follow-up.

Changes

All changes are in nvx-rs/src/host/filesystem.rs: 25 insertions and 39 deletions. No public item, message, serde form, fixture, or other file changes.

  • outermost now takes and returns paths: it sorts them, drops duplicates, and returns those that no other contains. Three sites now share it:
    • export_roots, for the canonical mapped paths, which become the children of the aggregate export;
    • denied_paths, for the denied paths that exist;
    • plan, for the read-write mappings of a read-only child, which become its write list.
  • plan collects a child's read-write mappings as their canonical paths instead of &Mapped entries, since the writable flag and the write list need only those.
  • A new unit test, outermost_paths_are_sorted_once_without_the_paths_inside_others, pins the shared behavior that the plan tests do not reach directly: nested and repeated paths, and containment by whole components (/a/b keeps /a/bc).

Behavior

  • export_roots and denied_paths ran the same sort, dedup, and containment test (other != path && path.starts_with(other)) that outermost now runs, so their results are unchanged. denied_paths removed the nested paths with retain instead of a filter, but after dedup both keep exactly the paths that no other contains, in sorted order.

  • plan keeps one Mapped entry per canonical path, so deduplicating the read-write paths removes nothing. Sorting before the containment test instead of after it yields the same sorted write list.

  • Every message, ErrorKind, check order, limit, OpenVMM argument, and recorded ExportPlan is unchanged. The only difference is internal: plan now clones the read-write paths of every child that has any, where it cloned only the outermost ones of a read-only child.

  • I wrote a throwaway differential test, which is not committed. It compiled the three former computations beside the new outermost and compared their results as raw path strings, so spellings that compare equal had to match too. It ran on two sets of lists:

    • every list of up to three paths, drawn from 155 paths on Windows and 116 on Linux;
    • one million random lists of up to twelve paths on each OS.

    The paths included Windows drive, verbatim, and root forms, and Linux /, //, and .. forms. All 7,322,525 cases were identical.

Follow-ups

These turned up in the survey; this change leaves them alone.

  • guest/time/daemon.rs repeats runtime.fail_fatal(PowerOffStatus::Repair, ViolationCode::RepairSuppression, EventSource::Repair, Phase::Restore, ...) six times, and restore_work inlines the body of release_suppression. commands::repair_failed wraps the same status, source, and phase. A Runtime::repair_failure beside Runtime::conformance_failure could serve all of them, once a test seam over Runtime::emit_event lets a unit test reach them.
  • In host/instance.rs, Instance::runtime and Instance::launch read a JSON record and refuse another STATE_FORMAT in two shapes. A private generic reader could serve both while keeping their messages.
  • In guest/sys/socket.rs, connect_datagram and UdpSocket::ipv4 create the same SOCK_CLOEXEC IPv4 datagram socket.
  • In guest/device_io.rs, run_file and run_network destructure a Measurement, format it with result_line, and write it with the same CommandError::Output mapping; one helper could serve both.
  • The managed-agent test fixtures and LoopbackSystem forwarding that nvx-rs: track open workload output pipes by their Option alone #605 lists remain.

I found no suspected bug in the code that this change touches.

Validation

  • BASE: f4b4b8d3f884897952d336f61bc830a11599bf76, which was origin/dev when the run started: the merge of nvx-rs: decode the EXEC header once for both payload decoders #603. Before this pull request was opened, origin/dev had advanced only by the merge of nvx-rs: track open workload output pipes by their Option alone #605 and a performance-baseline update, neither of which changes nvx-rs/src/host/filesystem.rs, and no open pull request to dev changed it.

  • TREE: 450381979ff474189789371b73f45166ecaafced, the tree of this commit. Every host applied the patch to BASE and checked that git write-tree equals TREE.

  • Release on every host: v0.1.0-dev.e8ff3343bcc8. BASE has no release yet, so download took the one from two first-parent commits earlier. Its OpenVMM, 5ab8a84621524f1777b2cb5e65a70dab42c8b453, is the pinned revision.

  • Guest artifacts were rebuilt from TREE on prometheus30, with these steps:

    • build-guest-binaries --output;
    • test-guest-binaries --binaries, where 34 tests passed;
    • the Alpine build-initramfs with NVX_GUEST_BINARIES.

    initramfs.cpio.gz (SHA-256 b71e6fb868575c71d733f819d877b63ca426fbb7cf64894d88f81727172f6125), its packages manifest, and its provenance went to the other two hosts, where the SHA-256 digests matched.

  • Local checks on Windows with Rust 1.95.0:

    • rustfmt;
    • Clippy with -D warnings, for all targets and features and for the library without default features;
    • cargo test with all features (262 library tests, 6 fake-OpenVMM tests, and 1 doctest) and with the default features (259 and 1);
    • rustdoc with -D warnings;
    • the steps that no host could run, because the hosts lack the toolchain or target: the Windows MSRV (1.89) checks and the macOS (aarch64-apple-darwin) checks, for both nvx-rs and aci_edge_sandboxes;
    • the Windows half of the differential test above.
Host profile Backend Host type check-nvx-rs and check-aci-edge-sandboxes without a hypervisor Guest artifacts test-aci-edge-sandboxes test-microvm, default suite
prometheus32 kvm baremetal Pass: every Linux step, including MSRV 1.89, check-guest-binaries (277 + 34 musl tests), 493 library tests with all features, artifact bundling, agent bundling, and the Linux half of the differential test; the macOS checks ran locally Copied in, digests match Pass (18/18) Pass (41 scenario runs)
prometheus30 mshv baremetal None; prometheus32 and prometheus28 ran the gates Built and tested here Pass (18/18) Pass (41 scenario runs)
prometheus28 whp baremetal Pass: every Windows step, including artifact bundling and agent bundling; the MSRV checks ran locally Copied in, digests match Pass (18/18) Pass (41 scenario runs)

On prometheus32, the suite's time-ABI evidence line counted 1 of 58 boot checks over KVM's CPU-time budget. The suite reports that count but never gates on it, and this change does not touch the guest's time code.

Left to CI:

  • the OpenVMM unit and VMM suites, test-openvmm-unit and test-openvmm;
  • the microVM scenarios that run only when named: smp-lapic, managed-exec-config, and sandbox-lifecycle;
  • the Ubuntu and Azure Linux guests, and guest determinism;
  • the benchmarks;
  • the virtual-machine runners;
  • check-quality and validate-nvx, whose inputs this change does not touch.

filesystem::plan reduces three lists of canonical host paths to the
paths that no other path in the list contains, and did it three ways.
export_roots sorted, deduplicated, and filtered the mapped paths
inline. denied_paths sorted and deduplicated the denied paths,
collected the nested ones, and removed them with retain. The paths to
which OpenVMM limits a read-only child's writes went through
outermost, which filtered Mapped entries by their canonical paths, and
plan then mapped and sorted the survivors.

outermost now takes the paths themselves: it sorts them, drops
duplicates, and returns those that no other contains. export_roots,
denied_paths, and the write list of plan call it, and the two inline
copies are gone. plan collects a child's read-write mappings as their
canonical paths, which is all that the writable flag and the write
list need.

Each list keeps its contents and order. export_roots and denied_paths
ran the same sort, deduplication, and containment test that outermost
runs now. plan keeps one mapped entry per canonical path, so
deduplicating the read-write paths removes nothing, and sorting them
before the containment test instead of after it gives the same sorted
list. The plan tests cover the three lists, and a new unit test pins
the order, deduplication, and containment by whole components that
they share. A throwaway differential test compared the three former
computations with outermost on 7.3 million lists of paths, every list
of up to three paths and random lists of up to twelve, in Windows and
Linux path forms: all results were identical.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 15:26

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 refactor preserves path-selection semantics, and no blocking issues were identified.

No open issues 🎉

What changed in this PR

Consolidates NVX host filesystem path reduction into one private helper while preserving export, deny, and write-list behavior.

Changes:

  • Reuses outermost to sort, deduplicate, and remove nested paths across all three lists.
  • Collects read-write mappings directly as canonical paths.
  • Adds a unit test covering ordering, duplicates, and whole-component containment.
File Description
nvx-rs/​src/​host/​filesystem.rs Centralizes path reduction and tests the shared behavior.

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) merged commit da39944 into dev Oct 19, 2026
33 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the nvx-rs-quality/outermost-paths branch October 19, 2026 17:00
Jay (jay23606) pushed a commit to jay23606/nvx that referenced this pull request Oct 19, 2026
A failure of restore-time repair is a fatal time-ABI event: it has the
repair source and the restore phase, and it stops the VM with the
repair power-off status unless the tools run report-only.
commands::repair_failed passed those three to Runtime::fail_fatal, and
daemon.rs spelled them out again at six sites, each with
G_REPAIR_SUPPRESSION. restore_work also inlined the body of
release_suppression. No unit test reached any of them, because
Runtime::emit_event opened /dev/kmsg and /dev/console itself, so
microsoft#605, microsoft#606, and microsoft#607 left this refactor until a test seam existed.

Runtime::repair_failure, beside Runtime::conformance_failure, now
passes the repair status, source, and phase. repair_failed and the six
daemon sites call it, and restore_work calls release_suppression. Two
new PrivilegedOps methods, write_kernel_log and write_console, are the
seam. RealOps runs the code with which emit_event wrote an event's
kernel log record and console line, and the FakeOps of the unit tests
records their bytes, as it already records port writes and power-off
requests.

Behavior is unchanged. Every repair failure passes the status, code,
source, phase, and detail that it passed before. An event writes the
same bytes, with the same open flags, to the kernel log, port B, and
the console, in that order, and reads the asynchronous-output flag at
the same points. A new unit test checks a repair failure's event line
at all three destinations and its power-off status, with and without
report-only mode, in the glibc and musl test runs. The microVM restore
scenarios run restore_work, and so release_suppression, in guests on
KVM, MSHV, and WHP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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