Repository navigation
nvx-rs: run the guest time and reseed steps through typed calls - #663
Merged
Enrique Saurez (esaurez) merged 2 commits intoOct 22, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Embedded daemon discovery and restore failure propagation need correction before callers can reliably use the typed APIs.
4 open issues
What changed in this PR
Exposes typed guest time and reseed APIs, advancing #619’s reusable guest runtime work on top of #662’s port-permission changes.
Changes:
- Adds typed boot, capture, restore, generation-ID, and reseed operations.
- Routes guest commands through shared implementations.
- Adds API tests and documentation.
| File | Description |
|---|---|
| nvx-rs/src/lib.rs | Documents typed guest APIs. |
| nvx-rs/src/guest/time/system.rs | Separates lock errors from diagnostic formatting. |
| nvx-rs/src/guest/time/mod.rs | Exports and documents time APIs. |
| nvx-rs/src/guest/time/decoders.rs | Exposes restore flags internally. |
| nvx-rs/src/guest/time/daemon.rs | Houses deferred boot checks and daemon startup. |
| nvx-rs/src/guest/time/cpu_mask.rs | Adds hashing support for CPU sets. |
| nvx-rs/src/guest/time/commands.rs | Routes commands through typed calls. |
| nvx-rs/src/guest/time/api.rs | Adds time operations, types, errors, and tests. |
| nvx-rs/src/guest/sys/mod.rs | Shares error diagnostic formatting. |
| nvx-rs/src/guest/reseed.rs | Adds typed seed and reseed APIs and tests. |
| nvx-rs/src/guest/mod.rs | Exports generation IDs and documents APIs. |
| nvx-rs/src/guest/generation.rs | Adds the shared generation-ID type. |
| nvx-rs/README.md | Describes public guest APIs. |
| doc/project-structure.md | Updates the guest module description. |
Review effort: Balanced
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-20261020
branch
from
October 20, 2026 21:49
680b261 to
4496196
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 20, 2026 21:49
cc410fd to
331db01
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 20, 2026 22:13
331db01 to
6c38613
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
2 times, most recently
from
October 20, 2026 22:33
2380d85 to
56f7c2f
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 20, 2026 23:08
56f7c2f to
4b42bfd
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 20, 2026 23:23
4b42bfd to
438b3c9
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 21, 2026 19:13
438b3c9 to
63c0602
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 21, 2026 20:31
63c0602 to
e3ff160
Compare
Enrique Saurez (esaurez)
marked this pull request as ready for review
October 21, 2026 20:37
Enrique Saurez (esaurez)
requested a review
from Pedro Henrique Penna (ppenna)
October 21, 2026 22:02
A guest init or agent can now run the time ABI's steps and the reseed after a restore in its own process, with typed inputs, results, and errors, instead of running nvx-time and nvx-reseed and parsing their output. - nvx::guest::time::TimeAbi runs the cold-boot time step, starts the time daemon from its outcome as a separate step, in a child process that the caller supervises, reads the VM generation ID, requests a capture that repairs a restore, and finishes a restore. Each call holds the port permission only for its own transactions, on the calling thread, and fails with TimeError::PortRelease when the thread may still hold some of it. No call waits for a child that it did not start, and only start_daemon leaves the caller a child to reap: a grace-period release that outlives its deadline, when restore_finish runs repair step 7 itself, passes to init without the caller's descriptors, whatever the caller's disposition of SIGCHLD. In report-only mode, a call fails with TimeError::Violation when a violation keeps it from doing its work, and otherwise goes on as nvx-time does. - The time daemon takes the command name nvx-time, by which the calls recognize it, whichever program started it. - nvx::guest::reseed::Seed reads a restore's fresh entropy, and nvx::guest::reseed::reseed adds it to the kernel's entropy pool and reseeds the CRNG. - nvx::guest::GenerationId is the VM generation ID that both use. nvx-time and nvx-reseed run on these calls, and keep their arguments, output, exit statuses, and golden fixtures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b57d5617-d565-4d34-a871-0077d2c11184
A time call that the kernel refused to revoke leaves the calling thread with some of its permission for OpenVMM's I/O ports, which a process that the thread starts inherits and keeps across exec. The time tools already refuse to fork from such a thread, and the typed time calls tell their caller not to. Now the crate's own spawn path refuses too: PreparedChild::spawn fails with EPERM before it forks, so Command::spawn returns PermissionDenied, and a guest init or agent that runs the time calls in its own process cannot pass the permission to a program that it starts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b57d5617-d565-4d34-a871-0077d2c11184
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-guest-g5-time-api-20261020
branch
from
October 21, 2026 22:09
e3ff160 to
db51c83
Compare
Enrique Saurez (esaurez)
deleted the
esaurez/a1-guest-g5-time-api-20261020
branch
October 22, 2026 00:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

A guest init or agent can now run the time ABI's steps and the reseed after a restore in its own process, with typed inputs, results, and errors, instead of running
nvx-timeandnvx-reseedand parsing their output. Both programs now run on these calls.New public API (feature
guest)nvx::guest::time::TimeAbi(x86_64), in the mode that the kernel command line selects (new) or report-only (report_only):cold_boot() -> ColdBoot: the synchronous cold-boot time step. It takes the initial sample, steps the wall clock, and publishes the time state, and starts no process.start_daemon(ColdBoot) -> u32: daemon creation, as a separate step. It forks the time daemon, which runs the deferred boot checks and then the discipline and violation watcher, and returns its process ID, which the caller supervises and reaps. The daemon takes the command namenvx-time, by which the calls recognize it whichever program started it, and reaps only its own children. A process with more than one thread getsTimeError::Spawn(EBUSY) instead of a fork, so a caller with an async runtime starts the daemon before the runtime.generation_id() -> GenerationId.capture(CaptureRequest) -> CaptureOutcome: preparation step 5, with repair steps 1 to 3 in a restored clone.CaptureRequest::new(ScratchPolicy, GenerationId, PathBuf)sets the request, with.finish()for a restore that has nothing to activate.CaptureOutcome::Restored(Restore)gives the acknowledgement flag, the processor target, the memory target and itsMemoryRanges, the new generation ID, and whether the call finished the restore, which it doesn't when the calling thread can't take the permission for the acknowledgement.restore_finish(RestoreFinish): repair step 6, then step 7 is handed to the daemon.RestoreFinish::new().acknowledge().new_cpus(CpuSet)sets the request. Without a daemon, a report-only call runs step 7 itself. Its RCU grace-period release then runs in a grandchild, behind a child that waits for it until the stall timeout and that the call alone waits for, so a release that outlives the timeout passes to init, whatever the caller's disposition ofSIGCHLD.TimeErroris#[non_exhaustive], withPortAccess,PortRelease,PortLock,StateUnavailable,EntropyFile,GenerationIdUnavailable,Spawn, andViolation(Violation). ItsDisplayis a one-line diagnostic, such asioperm: Operation not permitted.Violationwhen the violation keeps it from doing its work, and otherwise goes on asnvx-timedoes, with the event as the only report: a cold boot without an initial sample, step 7's failed checks, and a handoff that fails after the acknowledgement, which still counts as finished so that the restore isn't acknowledged twice.nvx::guest::reseed:Seed::read(path)reads a restore's 64 bytes of fresh entropy without retrying afterEINTR,Seed::generation_id, andreseed(&Seed)adds the seed with full credit and reseeds the CRNG.ReseedErroris#[non_exhaustive].Seed'sDebugshows only the generation ID.nvx::guest::GenerationId: the 16-byte VM generation ID, whoseDisplayis lowercase hexadecimal.Each time call holds the port permission only for its own transactions, on the calling thread, and checks at its end that the thread holds none of it, or fails with
TimeError::PortRelease. No call waits for a child that it didn't start.nvx::guest::spawn'sCommand::spawnnow fails withPermissionDeniedon a thread that may still hold some of the permission, before it forks, because the child would inherit it, as the time tools' own forks already refuse to. A caller that runs the time calls in its own process thus can't pass the permission to a program that it starts through this crate.Programs
nvx-time bootiscold_bootfollowed bystart_daemon, andcapture,restore-finish, andgeneration-idrun the matching calls.nvx-reseedrunsSeedandreseed. Their arguments, output, exit statuses, and goldens don't change:tests/guest_tools.rsandtest-guest-binariespass unchanged.check,pre-capture,cancel-capture,status,sample,exhaustive, and the test hooks stay commands of the program.Not here, as #619 scopes G5: the rest of a restore's repair in process, such as processor and memory activation and the identity refresh, and the per-clone machine ID, which platform-clone work builds on these calls; and NVX's PID 1 (A3).
Part of #619. Refs #535.
Validation
Local, on a Windows/WHP host, with the Linux steps as root in a Linux VM on it. On this head, db51c83, on
devat 8f2565b:nvx-rs, withNVX_BUNDLE=skip:cargo fmt --all --check;cargo clippy --locked --all-targets --all-features -- -D warningsandcargo clippy --locked --lib --no-default-features -- -D warnings;cargo test --locked --all-featuresandcargo test --locked;bundledartifacts test, andcargo doc --locked --no-deps --all-featureswith-D warnings;cargo +1.89 check --locked --all-targets --all-features, andcargo check --locked --all-targets --all-features --target aarch64-apple-darwin.python3 scripts/nvx.py check-guest-binaries, thenbuild-guest-binariesandtest-guest-binaries.tests/runtime-adapter:cargo fmt,clippy,build,testand the 1.89 check in the default,host, andguest(musl) configurations.Everything passes but
tests/cli.rs'snvx_rs_reproduces_the_recorded_scenarios, which fails the same way ondevas root, which caller-owned shares refuse.This PR changes no script. Before its rebase onto
dev, on 63c0602, the typed-call commit on #662's head, without the spawn check:python scripts/nvx.py verify, and the Python checks of validate-nvx on Linux (1062 tests) and Windows,ruff check,ruff format --check,pyrightfor Linux and Windows, and the Markdown link check, which passed but for failures thatdevshared in this environment:test_adversarial.py's SIGKILL status case as root, andtest_setup_azure.py's shell tests on Windows, wherebashis a Linux VM's.python scripts/nvx.py test-microvm --backend <backend>and its default scenarios, includingguest-boot,snapshot-core,time-abi-conformance,smp-snapshot,snapshot-tiers,restore-processors,restore-memory, andrestore-downtime:snapshot-corerun failed its check that a restore advances the wall clock and the uptime alike, while NTP stepped the Linux VM's clock forward by about 1.5 s every 34 s, as on nvx-rs: hold the guest port permission for one transaction at a time #662's head; the next run passed.snapshot-coreprintedNVX-SNAPSHOT-PORT-ACCESS-deniedafter each restore, in every run. Every boot rancold_bootandstart_daemon, and every snapshot rancapture,restore_finish, and the reseed through the programs.--cpu-profile autoselects (intel.alderlake.v1), and OpenVMM fails withE_PROFILE_UNSUPPORTED, as nested MSHV did withamd.genoa.v1. Those runs therefore passed OpenVMM--cpu-profile host, through a local wrapper of its binary.Stripped static guest binaries (
build-guest-binaries) and the Alpine initramfs, in bytes:dev8f2565bnvx-timenvx-reseednvx-managed-agentinitramfs.cpio.gzinitramfs.cpio, uncompressedLeft to CI: the KVM, MSHV, and WHP microVM and platform tests of this head, on its runners, with the built-in CPU profiles and the OpenVMM of
dev's gitlink, which itscommand-line-limitscenario needs; the Python checks; the tests that need a non-root Linux user; and the release and determinism jobs.