From 8cc30fc17e1724537a29a5b8c679c89a5184ad96 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Mon, 19 Oct 2026 13:09:49 -0300 Subject: [PATCH] nvx-rs: replace the loopback System wrapper with a spawn hook The managed agent's loopback test needs a System that records mounts and diagnostics as TestSystem does but really spawns each workload. It got one from LoopbackSystem, a wrapper that forwarded eleven of the twelve System methods to an inner TestSystem and sent spawn to spawn_unprivileged. That made it a third implementation of System, beside RealSystem and TestSystem, and every new System method needed a hand-written forward there too. TestSystem now has a spawner field: a function that spawns each workload when set. Its spawn still counts the call, then runs the spawner, or fails with ENOSYS without one, as it always did. The loopback test sets the spawner to spawn_unprivileged and uses TestSystem directly, so LoopbackSystem, its 60 lines of forwarding, and the imports that only it used are gone. Behavior is unchanged. The change is confined to test code: TestSystem and the loopback module build only under cfg(test), and RealSystem, the agent, and the guest binaries are untouched. Every other test leaves the spawner unset and keeps the ENOSYS spawn. The loopback test spawns through the same spawn_unprivileged and reaches the same TestSystem methods for every other operation; it now also counts its spawns in spawn_calls, which it does not check. The loopback test covers the spawner, and the dispatch tests, which count spawn_calls and expect the launch failure that ENOSYS causes, cover its absence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nvx-rs/src/guest/managed_agent/loopback.rs | 80 +++------------------- nvx-rs/src/guest/managed_agent/seam.rs | 10 ++- 2 files changed, 16 insertions(+), 74 deletions(-) diff --git a/nvx-rs/src/guest/managed_agent/loopback.rs b/nvx-rs/src/guest/managed_agent/loopback.rs index b1f0f01b..4b8648db 100644 --- a/nvx-rs/src/guest/managed_agent/loopback.rs +++ b/nvx-rs/src/guest/managed_agent/loopback.rs @@ -1,11 +1,11 @@ use std::collections::VecDeque; -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::io::{self, Read, Write}; use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::guest::sys::{self, Errno, FileDescriptor, Mode, MountFlags, ProcessId, c_string}; +use crate::guest::sys::{self, Errno, FileDescriptor, ProcessId, c_string}; use crate::host::platform::Transport; use crate::protocol::{ self, OUTER_ACK, OUTER_CREDIT, OUTER_DATA, OUTER_GUEST_ATTACH, OUTER_HEADER_LEN, @@ -13,7 +13,7 @@ use crate::protocol::{ }; use super::launch::SpawnRequest; -use super::seam::{Paths, System, TestSystem}; +use super::seam::{Paths, TestSystem}; struct BrokerCore { guest: UnixStream, @@ -262,71 +262,6 @@ impl BrokerHandle { } } -struct LoopbackSystem { - inner: TestSystem, -} - -impl System for LoopbackSystem { - fn paths(&self) -> &Paths { - self.inner.paths() - } - - fn mount( - &mut self, - source: Option<&CStr>, - target: &CStr, - filesystem: Option<&CStr>, - flags: MountFlags, - ) -> Result<(), Errno> { - self.inner.mount(source, target, filesystem, flags) - } - - fn chown(&mut self, path: &CStr, uid: u32, gid: u32) -> Result<(), Errno> { - self.inner.chown(path, uid, gid) - } - - fn chmod(&mut self, path: &CStr, mode: Mode) -> Result<(), Errno> { - self.inner.chmod(path, mode) - } - - fn configure_tty(&mut self, descriptor: &FileDescriptor) -> Result<(), Errno> { - self.inner.configure_tty(descriptor) - } - - fn drain_tty(&mut self, descriptor: &FileDescriptor) -> Result<(), Errno> { - self.inner.drain_tty(descriptor) - } - - fn write_diagnostic(&mut self, message: &[u8]) -> Result<(), Errno> { - self.inner.write_diagnostic(message) - } - - fn write_barrier(&mut self, descriptor: &FileDescriptor, message: &[u8]) -> Result<(), Errno> { - self.inner.write_barrier(descriptor, message) - } - - fn take_inherited_descriptor(&mut self, text: &CStr) -> Result { - self.inner.take_inherited_descriptor(text) - } - - fn validate_environment(&mut self, name: &CStr, value: &CStr) -> Result<(), Errno> { - self.inner.validate_environment(name, value) - } - - fn exec( - &mut self, - path: &CStr, - arguments: &[CString], - environment: Option<&[CString]>, - ) -> Result<(), Errno> { - self.inner.exec(path, arguments, environment) - } - - fn spawn(&mut self, request: SpawnRequest<'_>) -> Result { - spawn_unprivileged(request) - } -} - fn spawn_unprivileged(request: SpawnRequest<'_>) -> Result { let arguments: Vec = request .execution @@ -428,8 +363,9 @@ mod tests { hostfs_dir: hostfs, ..Paths::default() }; - let system = LoopbackSystem { - inner: TestSystem::new(paths), + let system = TestSystem { + spawner: Some(spawn_unprivileged), + ..TestSystem::new(paths) }; let (broker_guest, agent) = UnixStream::pair().unwrap(); let agent_descriptor = FileDescriptor::from_owned(agent.into()); @@ -556,7 +492,7 @@ mod tests { drop(session); let (_outcome, system) = agent_thread.join().unwrap(); - assert_eq!(system.inner.mounts.len(), 2); - assert!(system.inner.diagnostics.is_empty()); + assert_eq!(system.mounts.len(), 2); + assert!(system.diagnostics.is_empty()); } } diff --git a/nvx-rs/src/guest/managed_agent/seam.rs b/nvx-rs/src/guest/managed_agent/seam.rs index 30880c0e..7138afd6 100644 --- a/nvx-rs/src/guest/managed_agent/seam.rs +++ b/nvx-rs/src/guest/managed_agent/seam.rs @@ -194,6 +194,8 @@ pub(crate) struct TestSystem { pub(crate) fail_environment: Option>, pub(crate) execs: Vec, pub(crate) spawn_calls: usize, + /// Spawns each workload when set; `spawn` otherwise fails with `ENOSYS`. + pub(crate) spawner: Option) -> Result>, pub(crate) tty_configured: usize, pub(crate) tty_drained: usize, pub(crate) close_barrier_reader: Option, @@ -213,6 +215,7 @@ impl TestSystem { fail_environment: None, execs: Vec::new(), spawn_calls: 0, + spawner: None, tty_configured: 0, tty_drained: 0, close_barrier_reader: None, @@ -327,8 +330,11 @@ impl System for TestSystem { Ok(()) } - fn spawn(&mut self, _request: SpawnRequest<'_>) -> Result { + fn spawn(&mut self, request: SpawnRequest<'_>) -> Result { self.spawn_calls += 1; - Err(Errno::ENOSYS) + match self.spawner { + Some(spawner) => spawner(request), + None => Err(Errno::ENOSYS), + } } }