mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-20 01:35:19 +03:00
Get this thing going..
<!-- greptile_comment -->
<!-- greptile_summary -->
<h2><a
href="https://app.greptile.com/api/retrigger?id=63134761"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=1"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=1"><img
alt="Retrigger"
src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=1"
align="right"></picture></a>Confidence Score: 5/5</h2>
The PR appears safe to merge; all previous findings are resolved and the
latest readiness change introduces no established actionable regression.
<h3>Summary</h3>
- Establishes required guest filesystems, runtime directories, device
permissions, and service processes.
- Reports initialization and service deaths over the lifecycle channel.
- Supports launch, restart, and shutdown commands for a resident guest.
- Separates service and workload identities and configures per-launch
runtime environments.
- Removes the currently inactive nescope screenshot option and makes
capture-chain verification fail explicitly when compositor readback is
unavailable.
- Reworks the guest image around `nesinit` as PID 1 without a
distribution service manager.
<h3>Diagram</h3>
```mermaid
sequenceDiagram
participant Host
participant Init as nesinit
participant FS as Guest filesystems
participant Services as Service stack
participant Workload
Init->>Host: Ready(protocol version)
Host->>Init: Boot(mount descriptors)
Init->>FS: Establish and mount shares
Init->>Services: Spawn services in order
Services-->>Init: Required sockets ready
Init->>Host: Initialized(service names)
Host->>Init: Launch(id, exec, on_exit)
Init->>Workload: Spawn with isolated UID/runtime
Init->>Host: Started(id)
Workload-->>Init: Exit status
Init->>Host: WorkloadExited(id, status)
Host->>Init: Launch / Restart / Shutdown
```
<sub>Reviews (4) · Last reviewed commit: ["fix(nesinit): readiness is a
socket
that..."](731d34df9d)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.8 KiB
Rust
83 lines
2.8 KiB
Rust
// What the service stack does with its children when it goes away, against
|
|
// real processes.
|
|
//
|
|
// Its own test binary for the same reason as `reaping`: these wait on children,
|
|
// and a reaper in another test in the same binary would collect them.
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
use nesinit::reap::Waiters;
|
|
use nesinit::services::{Service, Services, Stack};
|
|
|
|
/// A service that stays up until something stops it, and one that binds a
|
|
/// socket -- which is all the table needs to be for either question here.
|
|
static SLEEPERS: &[Service] = &[
|
|
Service {
|
|
name: "sleeper",
|
|
argv: &["/bin/sleep", "60"],
|
|
env: &[],
|
|
user: None,
|
|
cost: "nothing: this is a test",
|
|
required: true,
|
|
umask: None,
|
|
ready: None,
|
|
},
|
|
Service {
|
|
name: "second-sleeper",
|
|
argv: &["/bin/sleep", "60"],
|
|
env: &[],
|
|
user: None,
|
|
cost: "nothing: this is a test",
|
|
required: true,
|
|
umask: None,
|
|
ready: None,
|
|
},
|
|
];
|
|
|
|
/// Whether a pid is still a live process, asked without reaping it.
|
|
fn alive(pid: i32) -> bool {
|
|
// Signal 0 checks for the process without sending anything.
|
|
unsafe { libc::kill(pid, 0) == 0 }
|
|
}
|
|
|
|
/// Dropping the stack stops what it started.
|
|
///
|
|
/// As PID 1 the ordered shutdown would reach these anyway. Run by hand -- which
|
|
/// is how a guest that will not boot is debugged -- nothing else does, and the
|
|
/// bus, the audio server and the hub were left running with sockets nobody was
|
|
/// serving.
|
|
#[tokio::test]
|
|
async fn a_stack_that_goes_away_takes_its_services_with_it() {
|
|
let waiters = Waiters::new();
|
|
let mut stack = Stack::from_table(waiters, SLEEPERS);
|
|
let up = stack.bring_up().expect("two sleeps did not start");
|
|
assert_eq!(up.len(), 2);
|
|
|
|
let pids = stack.pids();
|
|
assert_eq!(pids.len(), 2, "the stack did not keep what it started");
|
|
assert!(pids.iter().all(|&pid| alive(pid)));
|
|
|
|
drop(stack);
|
|
|
|
// Signalled, not waited for: the stack cannot reap on its way out, so what
|
|
// is asserted is that each one leaves, not how fast.
|
|
let deadline = Instant::now() + Duration::from_secs(5);
|
|
for pid in pids {
|
|
loop {
|
|
// Nothing here reaps, so a signalled child becomes a zombie rather
|
|
// than disappearing -- and a zombie still answers signal 0. It is
|
|
// waited for explicitly instead.
|
|
let mut status = 0;
|
|
let seen = unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) };
|
|
if seen == pid || seen == -1 {
|
|
break;
|
|
}
|
|
assert!(
|
|
Instant::now() < deadline,
|
|
"{pid} was still running five seconds after its stack was dropped"
|
|
);
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
}
|
|
}
|
|
}
|