use std::fs::{self, File}; use std::io; use std::os::fd::OwnedFd; use std::os::unix::fs::PermissionsExt; use std::process::ExitCode; use lazaret_core::identity::{ BootId, BundleDigest, ConfigurationFingerprint, ContainerId, CoordinatorId, DomainId, GenerationId, HostPath, HostPidLocator, MountId, MountIdentity, NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, ProcessStartTimeTicks, SandboxId, }; use lazaret_core::records::{ DurableDecision, Generation, Participant, ParticipantOwnership, ParticipantRole, ParticipantState, PersistedHostProcessOwnership, SandboxOwnership, StateAnchorOwnership, }; use lazaret_host_coordinator::recovery::{ delete_runsc_container, prove_runtime_owners_absent, runtime_recovery_ownership, }; use lazaret_linux::process::{TargetCommand, child_broker, dispatch_private_process_role}; use rustix::process::Signal; fn main() -> ExitCode { let arguments = std::env::args_os().skip(1).collect::>(); if let Some(result) = dispatch_private_process_role(&arguments) { return exit(result); } exit(run()) } fn exit(result: io::Result<()>) -> ExitCode { match result { Ok(()) => ExitCode::SUCCESS, Err(error) => { eprintln!("recovery process test failed: {error}"); ExitCode::FAILURE } } } fn run() -> io::Result<()> { verify_exact_runsc_delete()?; verify_exact_action(None)?; verify_exact_action(Some(Signal::STOP)) } fn verify_exact_runsc_delete() -> io::Result<()> { let tree = tempfile::tempdir()?; let root_path = tree.path().join("runsc"); fs::create_dir(&root_path)?; fs::create_dir(root_path.join("container"))?; let program = tree.path().join("runsc-fixture"); fs::write( &program, b"#!/bin/sh\nroot=${1#-root=}\n[ \"$2\" = delete ] && [ \"$3\" = --force ] && rmdir \"$root/$4\"\n", )?; fs::set_permissions(&program, fs::Permissions::from_mode(0o700))?; let root: OwnedFd = File::open(&root_path)?.into(); delete_runsc_container(&root, "container", program.as_os_str()).map_err(io::Error::other)?; if root_path.join("container").exists() { return Err(io::Error::other("exact runsc container remained")); } Ok(()) } fn verify_exact_action(signal: Option) -> io::Result<()> { let state = child_broker().run_transient_captured(&TargetCommand::new("/bin/sleep").argument("60"))?; if let Some(signal) = signal { state.process().send_signal(signal)?; } let state = state.into_process(); let supervisor = child_broker() .run_transient_captured(&TargetCommand::new("/bin/sleep").argument("60"))? .into_process(); let generation = generation(); let participants = [ Participant::snapshot( generation.generation_id, ParticipantRole::Sandbox, ParticipantState::Intent, Some(ParticipantOwnership::Sandbox(SandboxOwnership { container_id: Some(ContainerId::new([6; 32])), runtime_root: Some(HostPath::new(b"/runtime/runsc".to_vec())), runtime_root_identity: Some(MountIdentity { mount_id: MountId::new(7), device: lazaret_core::identity::DeviceId::new(8), inode: lazaret_core::identity::InodeId::new(9), }), bundle_digest: Some(BundleDigest::new([10; 32])), host_coordinator: PersistedHostProcessOwnership::committed(absent_process()), runtime_supervisor: PersistedHostProcessOwnership::staged(supervisor.identity()), attached_runsc: PersistedHostProcessOwnership::unassigned(), })), ) .map_err(|_| io::Error::other("Sandbox participant is invalid"))?, Participant::bare_intent(generation.generation_id, ParticipantRole::PreparedLower), Participant::snapshot( generation.generation_id, ParticipantRole::StateAnchor, ParticipantState::Intent, Some(ParticipantOwnership::StateAnchor(StateAnchorOwnership { lower_artifact_id: None, upper_path: None, work_path: None, merged_path: None, daemon: PersistedHostProcessOwnership::staged(state.identity()), user_namespace: None, mount_namespace: None, mount: None, })), ) .map_err(|_| io::Error::other("State Anchor participant is invalid"))?, ]; let ownership = runtime_recovery_ownership(&participants, &generation).map_err(io::Error::other)?; prove_runtime_owners_absent(&ownership)?; child_broker().reap(state)?; child_broker().reap(supervisor) } fn absent_process() -> PidfsProcessIdentityV1 { PidfsProcessIdentityV1::new( PidfsProcessAuthorityV1 { boot_id: BootId::new([0xff; 16]), pidfs_st_dev: PidfsDeviceId::new(1), pidfs_st_ino: PidfsInodeId::new(2), }, HostPidLocator::new(1), ProcessStartTimeTicks::new(3), ) } fn generation() -> Generation { Generation { domain_id: DomainId::new([1; 32]), generation_id: GenerationId::new(1), configuration_fingerprint: ConfigurationFingerprint::new([2; 32]), coordinator_id: CoordinatorId::new([3; 32]), network_sidecar_id: NetworkSidecarId::new([4; 32]), sandbox_id: SandboxId::new([5; 32]), durable_decision: DurableDecision::Preparing, } }