Require fresh State Anchor absence before lease release

This commit is contained in:
ms2k 2026-07-17 04:40:02 +09:00
commit 3b76b59bdf
9 changed files with 543 additions and 194 deletions

View file

@ -26,7 +26,10 @@ use crate::lifecycle::{
SessionLifecycleAdapter, SessionLifecycleAdapter,
}; };
use crate::network::NetworkCoordinator; use crate::network::NetworkCoordinator;
use crate::runtime::{AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator}; use crate::runtime::{
AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator,
StateAnchorAbsenceObservation,
};
use crate::state_anchor::{StateAnchorCoordinator, StateAnchorReleaseAuthority}; use crate::state_anchor::{StateAnchorCoordinator, StateAnchorReleaseAuthority};
#[derive(Debug)] #[derive(Debug)]
@ -80,6 +83,7 @@ pub struct ProductionLifecycleAdapter<'a> {
runtime: Option<CommittedSandboxRuntime>, runtime: Option<CommittedSandboxRuntime>,
state_anchor_mount: Option<MountIdentity>, state_anchor_mount: Option<MountIdentity>,
state_anchor_release: Option<StateAnchorReleaseAuthority>, state_anchor_release: Option<StateAnchorReleaseAuthority>,
state_anchor_absence: Option<StateAnchorAbsenceObservation>,
} }
pub type ProductionHostCoordinatorLifecycle<'store, 'resources> = HostCoordinatorLifecycle< pub type ProductionHostCoordinatorLifecycle<'store, 'resources> = HostCoordinatorLifecycle<
@ -175,6 +179,7 @@ impl<'a> ProductionLifecycleAdapter<'a> {
runtime: None, runtime: None,
state_anchor_mount: None, state_anchor_mount: None,
state_anchor_release: None, state_anchor_release: None,
state_anchor_absence: None,
}, },
ProductionSessionRegistry { registry }, ProductionSessionRegistry { registry },
) )
@ -409,18 +414,26 @@ impl<'a> ProductionLifecycleAdapter<'a> {
) )
.map_err(failure)?, .map_err(failure)?,
); );
self.runtime let runtime = self
.runtime
.as_mut() .as_mut()
.ok_or_else(|| ProductionLifecycleError("committed runtime is absent".into()))? .ok_or_else(|| ProductionLifecycleError("committed runtime is absent".into()))?;
.unmount_state_anchor( let release = self
self.state_anchor_release .state_anchor_release
.as_ref() .as_ref()
.ok_or_else(|| { .ok_or_else(|| ProductionLifecycleError("State Anchor release is absent".into()))?;
ProductionLifecycleError("State Anchor release is absent".into()) runtime
})? .unmount_state_anchor(release.mount())
.mount(),
)
.map_err(failure)?; .map_err(failure)?;
self.state_anchor_absence = Some(
runtime
.observe_state_anchor_absence(
release.user_namespace(),
release.mount_namespace(),
release.mount(),
)
.map_err(failure)?,
);
self.state_anchor_mount = None; self.state_anchor_mount = None;
Ok(()) Ok(())
} }
@ -428,11 +441,44 @@ impl<'a> ProductionLifecycleAdapter<'a> {
fn release_prepared_lower(&mut self) -> Result<(), ProductionLifecycleError> { fn release_prepared_lower(&mut self) -> Result<(), ProductionLifecycleError> {
let release = self let release = self
.state_anchor_release .state_anchor_release
.take() .as_ref()
.ok_or_else(|| ProductionLifecycleError("State Anchor release is absent".into()))?; .ok_or_else(|| ProductionLifecycleError("State Anchor release is absent".into()))?;
self.state_anchor() let first_observation = self.state_anchor_absence.ok_or_else(|| {
.complete_release_after_unmount(&release, self.plan.state_anchor_layout) ProductionLifecycleError("State Anchor absence evidence is absent".into())
.map_err(failure) })?;
let runtime = self
.runtime
.as_mut()
.ok_or_else(|| ProductionLifecycleError("committed runtime is absent".into()))?;
let coordinator = StateAnchorCoordinator::new(&self.plan.store, self.plan.catalog);
coordinator
.complete_release_after_unmount(
release,
self.plan.state_anchor_layout,
first_observation,
|| {
runtime
.observe_state_anchor_absence(
release.user_namespace(),
release.mount_namespace(),
release.mount(),
)
.map_err(|error| {
crate::state_anchor::StateAnchorCoordinatorError::InvalidState(
match error {
crate::runtime::SandboxCoordinatorError::InvalidState(
detail,
) => detail,
_ => "Runtime Supervisor absence observation failed",
},
)
})
},
)
.map_err(failure)?;
self.state_anchor_release = None;
self.state_anchor_absence = None;
Ok(())
} }
fn graceful_exit(&mut self) -> Result<(), ProductionLifecycleError> { fn graceful_exit(&mut self) -> Result<(), ProductionLifecycleError> {

View file

@ -30,6 +30,14 @@ use crate::network::{NetworkCoordinator, NetworkCoordinatorError};
const SUPERVISOR_EXIT_TIMEOUT: Duration = Duration::from_secs(5); const SUPERVISOR_EXIT_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StateAnchorAbsenceObservation {
pub user_namespace: NamespaceIdentity,
pub mount_namespace: NamespaceIdentity,
pub mount: MountIdentity,
pub observed: MountIdentity,
}
#[derive(Debug)] #[derive(Debug)]
pub enum SandboxCoordinatorError { pub enum SandboxCoordinatorError {
Store(DomainStoreError), Store(DomainStoreError),
@ -967,6 +975,45 @@ impl CommittedSandboxRuntime {
} }
} }
/// Requests a fresh namespace-local observation of exact State Anchor absence.
///
/// # Errors
/// Refuses an RS refusal, echoed mount, mismatched authority, or channel failure.
pub fn observe_state_anchor_absence(
&mut self,
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
) -> Result<StateAnchorAbsenceObservation, SandboxCoordinatorError> {
self.control
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)?;
match self.control.receive()? {
SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: observed_user_namespace,
mount_namespace: observed_mount_namespace,
mount: observed_mount,
observed,
} if observed_user_namespace == user_namespace
&& observed_mount_namespace == mount_namespace
&& observed_mount == mount
&& observed != mount =>
{
Ok(StateAnchorAbsenceObservation {
user_namespace: observed_user_namespace,
mount_namespace: observed_mount_namespace,
mount: observed_mount,
observed,
})
}
SupervisorEvent::Refused(detail) => {
Err(SandboxCoordinatorError::Io(io::Error::other(detail)))
}
_ => Err(SandboxCoordinatorError::InvalidState(
"Runtime Supervisor did not provide exact State Anchor absence evidence",
)),
}
}
/// Gracefully exits and reaps the resource-empty exact Runtime Supervisor. /// Gracefully exits and reaps the resource-empty exact Runtime Supervisor.
/// ///
/// # Errors /// # Errors

View file

@ -15,6 +15,7 @@ use lazaret_state_anchor::{StateAnchorError, StateAnchorLayout, VerifiedStateAnc
use lazaret_storage_catalog::{Catalog, CatalogError}; use lazaret_storage_catalog::{Catalog, CatalogError};
use crate::domain_store::{DomainStoreError, DomainStoreMutation, HostCoordinatorDomainStore}; use crate::domain_store::{DomainStoreError, DomainStoreMutation, HostCoordinatorDomainStore};
use crate::runtime::StateAnchorAbsenceObservation;
#[derive(Debug)] #[derive(Debug)]
pub enum StateAnchorCoordinatorError { pub enum StateAnchorCoordinatorError {
@ -367,11 +368,17 @@ impl<'a> StateAnchorCoordinator<'a> {
/// ///
/// # Errors /// # Errors
/// Refuses a stale authority, any remaining mount, or release-evidence mismatch. /// Refuses a stale authority, any remaining mount, or release-evidence mismatch.
pub fn complete_release_after_unmount( pub fn complete_release_after_unmount<Observe>(
&self, &self,
authority: &StateAnchorReleaseAuthority, authority: &StateAnchorReleaseAuthority,
layout: &StateAnchorLayout<'_>, layout: &StateAnchorLayout<'_>,
) -> Result<(), StateAnchorCoordinatorError> { first_observation: StateAnchorAbsenceObservation,
mut observe_again: Observe,
) -> Result<(), StateAnchorCoordinatorError>
where
Observe: FnMut() -> Result<StateAnchorAbsenceObservation, StateAnchorCoordinatorError>,
{
require_absence_observation(authority, first_observation)?;
let expected_layout = layout.ownership_intent(authority.lease.prepared_artifact_id); let expected_layout = layout.ownership_intent(authority.lease.prepared_artifact_id);
let expected_merged = expected_layout.merged_path.as_ref().ok_or( let expected_merged = expected_layout.merged_path.as_ref().ok_or(
StateAnchorCoordinatorError::InvalidState("State Anchor merged path is absent"), StateAnchorCoordinatorError::InvalidState("State Anchor merged path is absent"),
@ -407,7 +414,7 @@ impl<'a> StateAnchorCoordinator<'a> {
)); ));
} }
} }
self.release_catalog_lease_if_present(authority)?; self.release_catalog_lease_if_present(authority, &mut observe_again)?;
self.domain_store self.domain_store
.apply(&[DomainStoreMutation::CleanParticipantAfterAbsence( .apply(&[DomainStoreMutation::CleanParticipantAfterAbsence(
clean_participant(authority.generation_id, ParticipantRole::PreparedLower)?, clean_participant(authority.generation_id, ParticipantRole::PreparedLower)?,
@ -528,10 +535,14 @@ impl<'a> StateAnchorCoordinator<'a> {
} }
} }
fn release_catalog_lease_if_present( fn release_catalog_lease_if_present<Observe>(
&self, &self,
authority: &StateAnchorReleaseAuthority, authority: &StateAnchorReleaseAuthority,
) -> Result<(), StateAnchorCoordinatorError> { observe_again: &mut Observe,
) -> Result<(), StateAnchorCoordinatorError>
where
Observe: FnMut() -> Result<StateAnchorAbsenceObservation, StateAnchorCoordinatorError>,
{
let snapshot = self.catalog.snapshot()?; let snapshot = self.catalog.snapshot()?;
let generation_lease = snapshot.artifact_leases.iter().find(|lease| { let generation_lease = snapshot.artifact_leases.iter().find(|lease| {
lease.domain_id == authority.lease.domain_id lease.domain_id == authority.lease.domain_id
@ -570,12 +581,33 @@ impl<'a> StateAnchorCoordinator<'a> {
"Domain release evidence is not committed", "Domain release evidence is not committed",
))); )));
} }
let observation =
observe_again().map_err(|error| CatalogError::Io(io::Error::other(error)))?;
require_absence_observation(authority, observation)
.map_err(|error| CatalogError::Io(io::Error::other(error)))?;
Ok(()) Ok(())
})?; })?;
Ok(()) Ok(())
} }
} }
fn require_absence_observation(
authority: &StateAnchorReleaseAuthority,
observation: StateAnchorAbsenceObservation,
) -> Result<(), StateAnchorCoordinatorError> {
if observation.user_namespace == authority.user_namespace
&& observation.mount_namespace == authority.mount_namespace
&& observation.mount == authority.mount
&& observation.observed != authority.mount
{
Ok(())
} else {
Err(StateAnchorCoordinatorError::InvalidState(
"State Anchor absence evidence mismatches release authority",
))
}
}
fn release_authority( fn release_authority(
domain: &lazaret_core::records::Domain, domain: &lazaret_core::records::Domain,
anchor: &Participant, anchor: &Participant,

View file

@ -182,6 +182,44 @@ fn durable_phases_resume_release_without_collapsing_lease_order() {
ParticipantState::ReleaseIntent, ParticipantState::ReleaseIntent,
); );
assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]); assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]);
let valid = absence_observation(&authority);
let invalid_observations = [
StateAnchorAbsenceObservation {
observed: authority.mount(),
..valid
},
StateAnchorAbsenceObservation {
user_namespace: NamespaceIdentity {
device: DeviceId::new(193),
inode: InodeId::new(194),
},
..valid
},
StateAnchorAbsenceObservation {
mount_namespace: NamespaceIdentity {
device: DeviceId::new(195),
inode: InodeId::new(196),
},
..valid
},
StateAnchorAbsenceObservation {
mount: valid.observed,
..valid
},
];
for invalid in invalid_observations {
assert!(
coordinator
.complete_release_after_unmount(&authority, &layout, invalid, || Ok(valid))
.is_err()
);
assert_states(
&domain_store,
ParticipantState::LeaseHeld,
ParticipantState::ReleaseIntent,
);
assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]);
}
let snapshot = domain_store.snapshot().unwrap(); let snapshot = domain_store.snapshot().unwrap();
let prepared = role( let prepared = role(
&snapshot.participants, &snapshot.participants,
@ -197,9 +235,42 @@ fn durable_phases_resume_release_without_collapsing_lease_order() {
DomainStoreMutation::PutParticipant(prepared_release_intent(prepared).unwrap()), DomainStoreMutation::PutParticipant(prepared_release_intent(prepared).unwrap()),
]) ])
.unwrap(); .unwrap();
for invalid in invalid_observations {
assert!(
coordinator
.complete_release_after_unmount(&authority, &layout, valid, || Ok(invalid))
.is_err()
);
assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]);
}
assert!(
coordinator
.complete_release_after_unmount(&authority, &layout, valid, || {
Err(StateAnchorCoordinatorError::InvalidState(
"State Anchor absence evidence is unreadable",
))
})
.is_err()
);
assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]);
assert!(
coordinator
.complete_release_after_unmount(&authority, &layout, valid, || {
Err(StateAnchorCoordinatorError::InvalidState(
"second State Anchor observation was skipped",
))
})
.is_err()
);
assert_eq!(catalog.snapshot().unwrap().artifact_leases, vec![lease]);
let mut observations = 0;
coordinator coordinator
.complete_release_after_unmount(&authority, &layout) .complete_release_after_unmount(&authority, &layout, valid, || {
observations += 1;
Ok(valid)
})
.unwrap(); .unwrap();
assert_eq!(observations, 1);
assert_states( assert_states(
&domain_store, &domain_store,
ParticipantState::Clean, ParticipantState::Clean,
@ -208,6 +279,19 @@ fn durable_phases_resume_release_without_collapsing_lease_order() {
assert!(catalog.snapshot().unwrap().artifact_leases.is_empty()); assert!(catalog.snapshot().unwrap().artifact_leases.is_empty());
} }
fn absence_observation(authority: &StateAnchorReleaseAuthority) -> StateAnchorAbsenceObservation {
StateAnchorAbsenceObservation {
user_namespace: authority.user_namespace(),
mount_namespace: authority.mount_namespace(),
mount: authority.mount(),
observed: MountIdentity {
mount_id: MountId::new(authority.mount().mount_id.get() + 1),
device: authority.mount().device,
inode: authority.mount().inode,
},
}
}
#[test] #[test]
fn acquisition_resumes_after_prepared_intent_reopen() { fn acquisition_resumes_after_prepared_intent_reopen() {
let fixture = AcquisitionFixture::new(17); let fixture = AcquisitionFixture::new(17);

View file

@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
use lazaret_core::codec::{decode, encode}; use lazaret_core::codec::{decode, encode};
use lazaret_core::identity::{ use lazaret_core::identity::{
ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, DomainId, GenerationId, ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, DomainId, GenerationId,
NetworkSidecarId, MountIdentity, NamespaceIdentity, NetworkSidecarId,
}; };
use lazaret_core::network::CanonicalDerivedNetworkPolicy; use lazaret_core::network::CanonicalDerivedNetworkPolicy;
use lazaret_core::records::{ use lazaret_core::records::{
@ -23,7 +23,8 @@ use lazaret_linux::process::{
child_broker, current_process_identity, child_broker, current_process_identity,
}; };
use lazaret_state_anchor::{ use lazaret_state_anchor::{
RuntimeStateAnchorAuthority, RuntimeStateAnchorMount, StateAnchorLaunchAuthority, RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
RuntimeStateAnchorMount, StateAnchorLaunchAuthority,
}; };
use rustix::event::{PollFd, PollFlags, Timespec, poll}; use rustix::event::{PollFd, PollFlags, Timespec, poll};
use rustix::fs::{MemfdFlags, memfd_create}; use rustix::fs::{MemfdFlags, memfd_create};
@ -86,6 +87,12 @@ pub enum SupervisorEvent {
StateAnchorUnmounted { StateAnchorUnmounted {
mount: lazaret_core::identity::MountIdentity, mount: lazaret_core::identity::MountIdentity,
}, },
StateAnchorAbsenceObserved {
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
observed: MountIdentity,
},
Exited, Exited,
AttachedRunscExited, AttachedRunscExited,
Stopped, Stopped,
@ -106,6 +113,11 @@ enum CoordinatorMessage {
StopSandbox, StopSandbox,
StopSidecar, StopSidecar,
UnmountStateAnchor, UnmountStateAnchor,
ObserveStateAnchorAbsence {
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
},
Exit, Exit,
Stop, Stop,
} }
@ -225,6 +237,24 @@ impl CoordinatorChannel {
send_message(&self.descriptor, &CoordinatorMessage::UnmountStateAnchor) send_message(&self.descriptor, &CoordinatorMessage::UnmountStateAnchor)
} }
/// # Errors
/// Returns an error when exact namespace-local absence observation cannot be requested.
pub fn observe_state_anchor_absence(
&self,
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
) -> io::Result<()> {
send_message(
&self.descriptor,
&CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace,
mount_namespace,
mount,
},
)
}
/// # Errors /// # Errors
/// Returns an error when graceful Runtime Supervisor exit cannot be requested. /// Returns an error when graceful Runtime Supervisor exit cannot be requested.
pub fn exit(&self) -> io::Result<()> { pub fn exit(&self) -> io::Result<()> {
@ -657,8 +687,19 @@ fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
let control = std::io::stdout(); let control = std::io::stdout();
let parent = current_process_identity()?; let parent = current_process_identity()?;
let mut state_anchor = prepare_state_anchor(arguments, parent, namespaces, &control)?; let mut state_anchor = prepare_state_anchor(arguments, parent, namespaces, &control)?;
let result = run_supervisor_with_state(arguments, parent, &control, &mut state_anchor); let mut state_anchor_absence = None;
let cleanup = state_anchor.take().map(RuntimeStateAnchorMount::unmount); let result = run_supervisor_with_state(
arguments,
parent,
&control,
namespaces,
&mut state_anchor,
&mut state_anchor_absence,
);
let cleanup = state_anchor
.take()
.map(RuntimeStateAnchorMount::unmount)
.map(|result| result.map(drop));
match (result, cleanup) { match (result, cleanup) {
(Err(error), Some(Err(cleanup))) => Err(io::Error::new( (Err(error), Some(Err(cleanup))) => Err(io::Error::new(
error.kind(), error.kind(),
@ -688,17 +729,17 @@ fn prepare_state_anchor(
"Runtime Supervisor received no State Anchor mount command", "Runtime Supervisor received no State Anchor mount command",
)); ));
} }
let authority = RuntimeStateAnchorAuthority::from_inherited( let authority = RuntimeStateAnchorAuthority::from_inherited(RuntimeStateAnchorInherited {
state.lower_fd, lower_fd: state.lower_fd,
&state.lower_label, lower_label: &state.lower_label,
state.persistent_root_fd, persistent_root_fd: state.persistent_root_fd,
state.persistent_anchor_fd, persistent_anchor_fd: state.persistent_anchor_fd,
&state.persistent_anchor_label, persistent_anchor_label: &state.persistent_anchor_label,
state.runtime_root_fd, runtime_root_fd: state.runtime_root_fd,
state.runtime_anchor_fd, runtime_anchor_fd: state.runtime_anchor_fd,
&state.runtime_anchor_label, runtime_anchor_label: &state.runtime_anchor_label,
state.expected_lower, expected_lower: state.expected_lower,
) })
.map_err(io::Error::other)?; .map_err(io::Error::other)?;
let ownership = ProcessOwnershipContext { let ownership = ProcessOwnershipContext {
slot: HostProcessOwnershipSlot::StateAnchorDaemon, slot: HostProcessOwnershipSlot::StateAnchorDaemon,
@ -742,7 +783,9 @@ fn run_supervisor_with_state(
arguments: &RuntimeArguments, arguments: &RuntimeArguments,
parent: lazaret_core::identity::PidfsProcessIdentityV1, parent: lazaret_core::identity::PidfsProcessIdentityV1,
control: &std::io::Stdout, control: &std::io::Stdout,
namespaces: (NamespaceIdentity, NamespaceIdentity),
state_anchor: &mut Option<RuntimeStateAnchorMount>, state_anchor: &mut Option<RuntimeStateAnchorMount>,
state_anchor_absence: &mut Option<RuntimeStateAnchorAbsenceAuthority>,
) -> io::Result<()> { ) -> io::Result<()> {
if arguments.state_anchor.is_some() { if arguments.state_anchor.is_some() {
let message: CoordinatorMessage = receive_message(control)?; let message: CoordinatorMessage = receive_message(control)?;
@ -827,7 +870,15 @@ fn run_supervisor_with_state(
io::Error::other("sandbox commit acknowledgment was not received"), io::Error::other("sandbox commit acknowledgment was not received"),
); );
} }
supervise(control, process, network, state_anchor, &delete) supervise(
control,
process,
network,
namespaces,
state_anchor,
state_anchor_absence,
&delete,
)
} }
fn prepare_runtime_network( fn prepare_runtime_network(
@ -983,10 +1034,17 @@ fn supervise(
control: &std::io::Stdout, control: &std::io::Stdout,
process: RetainedProcess, process: RetainedProcess,
mut network: Option<PreparedNetwork>, mut network: Option<PreparedNetwork>,
namespaces: (NamespaceIdentity, NamespaceIdentity),
state_anchor: &mut Option<RuntimeStateAnchorMount>, state_anchor: &mut Option<RuntimeStateAnchorMount>,
state_anchor_absence: &mut Option<RuntimeStateAnchorAbsenceAuthority>,
delete: &TargetCommand, delete: &TargetCommand,
) -> io::Result<()> { ) -> io::Result<()> {
let mut process = Some(process); let mut process = Some(process);
let mut state_anchor = StateAnchorSupervision {
namespaces,
mount: state_anchor,
absence: state_anchor_absence,
};
loop { loop {
let exited = match process let exited = match process
.as_ref() .as_ref()
@ -1059,7 +1117,7 @@ fn supervise(
&message, &message,
&mut process, &mut process,
&mut network, &mut network,
state_anchor, &mut state_anchor,
delete, delete,
) { ) {
Ok(true) => return Ok(()), Ok(true) => return Ok(()),
@ -1070,12 +1128,18 @@ fn supervise(
} }
} }
struct StateAnchorSupervision<'a> {
namespaces: (NamespaceIdentity, NamespaceIdentity),
mount: &'a mut Option<RuntimeStateAnchorMount>,
absence: &'a mut Option<RuntimeStateAnchorAbsenceAuthority>,
}
fn handle_supervisor_message( fn handle_supervisor_message(
control: &std::io::Stdout, control: &std::io::Stdout,
message: &CoordinatorMessage, message: &CoordinatorMessage,
process: &mut Option<RetainedProcess>, process: &mut Option<RetainedProcess>,
network: &mut Option<PreparedNetwork>, network: &mut Option<PreparedNetwork>,
state_anchor: &mut Option<RuntimeStateAnchorMount>, state_anchor: &mut StateAnchorSupervision<'_>,
delete: &TargetCommand, delete: &TargetCommand,
) -> io::Result<bool> { ) -> io::Result<bool> {
match message { match message {
@ -1102,25 +1166,17 @@ fn handle_supervisor_message(
stop_network(network.take())?; stop_network(network.take())?;
send_message(control, &SupervisorEvent::SidecarStopped)?; send_message(control, &SupervisorEvent::SidecarStopped)?;
} }
CoordinatorMessage::UnmountStateAnchor => { CoordinatorMessage::UnmountStateAnchor
if process.is_some() || network.is_some() { | CoordinatorMessage::ObserveStateAnchorAbsence { .. } => {
return Err(io::Error::other( handle_state_anchor_message(
"State Anchor unmount preceded runtime resource stop", control,
)); message,
} process.is_some() || network.is_some(),
let mount = state_anchor state_anchor,
.as_ref() )?;
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
.mount_identity();
state_anchor
.take()
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
.unmount()
.map_err(io::Error::other)?;
send_message(control, &SupervisorEvent::StateAnchorUnmounted { mount })?;
} }
CoordinatorMessage::Exit => { CoordinatorMessage::Exit => {
if process.is_some() || network.is_some() || state_anchor.is_some() { if process.is_some() || network.is_some() || state_anchor.mount.is_some() {
return Err(io::Error::other( return Err(io::Error::other(
"Runtime Supervisor exit preceded resource cleanup", "Runtime Supervisor exit preceded resource cleanup",
)); ));
@ -1150,6 +1206,70 @@ fn handle_supervisor_message(
Ok(false) Ok(false)
} }
fn handle_state_anchor_message(
control: &std::io::Stdout,
message: &CoordinatorMessage,
runtime_resources_present: bool,
state: &mut StateAnchorSupervision<'_>,
) -> io::Result<()> {
match message {
CoordinatorMessage::UnmountStateAnchor => {
if runtime_resources_present {
return Err(io::Error::other(
"State Anchor unmount preceded runtime resource stop",
));
}
let mount = state
.mount
.as_ref()
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
.mount_identity();
let absence = state
.mount
.take()
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
.unmount()
.map_err(io::Error::other)?;
*state.absence = Some(absence);
send_message(control, &SupervisorEvent::StateAnchorUnmounted { mount })
}
CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace,
mount_namespace,
mount,
} => {
if (*user_namespace, *mount_namespace) != state.namespaces {
return send_message(
control,
&SupervisorEvent::Refused(
"State Anchor absence request namespace authority mismatched".into(),
),
);
}
match state.absence.as_ref().map_or_else(
|| {
Err(lazaret_state_anchor::StateAnchorError::InvalidLayout(
"State Anchor absence authority is absent",
))
},
|authority| authority.observe_absence(*mount),
) {
Ok(observed) => send_message(
control,
&SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: state.namespaces.0,
mount_namespace: state.namespaces.1,
mount: *mount,
observed,
},
),
Err(error) => send_message(control, &SupervisorEvent::Refused(error.to_string())),
}
}
_ => Err(io::Error::other("message is not a State Anchor command")),
}
}
fn fail_active_runtime( fn fail_active_runtime(
process: Option<RetainedProcess>, process: Option<RetainedProcess>,
network: Option<PreparedNetwork>, network: Option<PreparedNetwork>,
@ -1347,6 +1467,30 @@ mod tests {
receive_message::<CoordinatorMessage>(&supervisor).unwrap(), receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::UnmountStateAnchor CoordinatorMessage::UnmountStateAnchor
); );
let user_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(11),
inode: lazaret_core::identity::InodeId::new(12),
};
let mount_namespace = NamespaceIdentity {
device: lazaret_core::identity::DeviceId::new(13),
inode: lazaret_core::identity::InodeId::new(14),
};
let mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(15),
device: lazaret_core::identity::DeviceId::new(16),
inode: lazaret_core::identity::InodeId::new(17),
};
channel
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
.unwrap();
assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace,
mount_namespace,
mount,
}
);
channel.exit().unwrap(); channel.exit().unwrap();
assert_eq!( assert_eq!(
receive_message::<CoordinatorMessage>(&supervisor).unwrap(), receive_message::<CoordinatorMessage>(&supervisor).unwrap(),

View file

@ -11,7 +11,7 @@ use lazaret_core::identity::{ContainerId, DeviceId, InodeId, NamespaceIdentity};
use lazaret_runtime_supervisor::sandbox::{ use lazaret_runtime_supervisor::sandbox::{
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal, CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal,
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess, MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess,
SANDBOX_INIT_DESTINATION, SandboxBuildErrorKind, SandboxBuildRequest, SANDBOX_INIT_DESTINATION, SandboxBuildErrorKind, SandboxBuildRequest, SandboxBundle,
TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION, build_sandbox_bundle, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION, build_sandbox_bundle,
production_sandbox_layout, stage_host_terminal, production_sandbox_layout, stage_host_terminal,
}; };
@ -312,7 +312,20 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
}]) }])
); );
assert_eq!(first.command().program(), OsStr::new("runsc")); assert_runtime_command(&first, &fixture);
assert_eq!(
specification["root"]["path"],
fixture
.tree
.path()
.join("prepared-root")
.display()
.to_string()
);
}
fn assert_runtime_command(bundle: &SandboxBundle, fixture: &Fixture) {
assert_eq!(bundle.command().program(), OsStr::new("runsc"));
let runtime_domain = String::from_utf8( let runtime_domain = String::from_utf8(
fixture fixture
.configuration .configuration
@ -323,7 +336,7 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
first bundle
.command() .command()
.arguments() .arguments()
.iter() .iter()
@ -355,16 +368,7 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
"11".repeat(32), "11".repeat(32),
] ]
); );
assert_eq!( assert!(!bundle.command().arguments().iter().any(|argument| {
specification["root"]["path"],
fixture
.tree
.path()
.join("prepared-root")
.display()
.to_string()
);
assert!(!first.command().arguments().iter().any(|argument| {
matches!( matches!(
argument.to_str(), argument.to_str(),
Some("-detach" | "-console-socket" | "-c") Some("-detach" | "-console-socket" | "-c")

View file

@ -530,33 +530,39 @@ pub struct RuntimeStateAnchorAuthority {
merged_descriptor: OwnedFd, merged_descriptor: OwnedFd,
} }
#[derive(Clone, Copy)]
pub struct RuntimeStateAnchorInherited<'a> {
pub lower_fd: i32,
pub lower_label: &'a Path,
pub persistent_root_fd: i32,
pub persistent_anchor_fd: i32,
pub persistent_anchor_label: &'a Path,
pub runtime_root_fd: i32,
pub runtime_anchor_fd: i32,
pub runtime_anchor_label: &'a Path,
pub expected_lower: (u64, u64),
}
impl RuntimeStateAnchorAuthority { impl RuntimeStateAnchorAuthority {
/// Reopens inherited authorities without falling back to ambient configured paths. /// Reopens inherited authorities without falling back to ambient configured paths.
/// ///
/// # Errors /// # Errors
/// Refuses missing, replaced, or incorrectly typed descriptors and fixed children. /// Refuses missing, replaced, or incorrectly typed descriptors and fixed children.
pub fn from_inherited( pub fn from_inherited(
lower_fd: i32, inherited: RuntimeStateAnchorInherited<'_>,
lower_label: &Path,
persistent_root_fd: i32,
persistent_anchor_fd: i32,
persistent_anchor_label: &Path,
runtime_root_fd: i32,
runtime_anchor_fd: i32,
runtime_anchor_label: &Path,
expected_lower: (u64, u64),
) -> Result<Self, StateAnchorError> { ) -> Result<Self, StateAnchorError> {
let retained_lower = reopen_inherited_directory(lower_fd)?; let retained_lower = reopen_inherited_directory(inherited.lower_fd)?;
require_directory_descriptor_identity(&retained_lower, expected_lower)?; require_directory_descriptor_identity(&retained_lower, inherited.expected_lower)?;
if !lower_label.is_absolute() { if !inherited.lower_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout( return Err(StateAnchorError::InvalidLayout(
"prepared lower label is not absolute", "prepared lower label is not absolute",
)); ));
} }
let lower = open_exact_directory_path(lower_label, expected_lower)?; let lower = open_exact_directory_path(inherited.lower_label, inherited.expected_lower)?;
let persistent_root = reopen_inherited_directory(persistent_root_fd)?; let persistent_root = reopen_inherited_directory(inherited.persistent_root_fd)?;
let retained_persistent_anchor = reopen_inherited_directory(persistent_anchor_fd)?; let retained_persistent_anchor =
if !persistent_anchor_label.is_absolute() { reopen_inherited_directory(inherited.persistent_anchor_fd)?;
if !inherited.persistent_anchor_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout( return Err(StateAnchorError::InvalidLayout(
"persistent State Anchor label is not absolute", "persistent State Anchor label is not absolute",
)); ));
@ -570,12 +576,12 @@ impl RuntimeStateAnchorAuthority {
(retained_persistent.st_dev, retained_persistent.st_ino), (retained_persistent.st_dev, retained_persistent.st_ino),
)?; )?;
let persistent_anchor = open_exact_directory_path( let persistent_anchor = open_exact_directory_path(
persistent_anchor_label, inherited.persistent_anchor_label,
(retained_persistent.st_dev, retained_persistent.st_ino), (retained_persistent.st_dev, retained_persistent.st_ino),
)?; )?;
let runtime_root = reopen_inherited_directory(runtime_root_fd)?; let runtime_root = reopen_inherited_directory(inherited.runtime_root_fd)?;
let retained_runtime_anchor = reopen_inherited_directory(runtime_anchor_fd)?; let retained_runtime_anchor = reopen_inherited_directory(inherited.runtime_anchor_fd)?;
if !runtime_anchor_label.is_absolute() { if !inherited.runtime_anchor_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout( return Err(StateAnchorError::InvalidLayout(
"runtime State Anchor label is not absolute", "runtime State Anchor label is not absolute",
)); ));
@ -586,13 +592,15 @@ impl RuntimeStateAnchorAuthority {
&confined_runtime_anchor, &confined_runtime_anchor,
(retained.st_dev, retained.st_ino), (retained.st_dev, retained.st_ino),
)?; )?;
let runtime_anchor = let runtime_anchor = open_exact_directory_path(
open_exact_directory_path(runtime_anchor_label, (retained.st_dev, retained.st_ino))?; inherited.runtime_anchor_label,
(retained.st_dev, retained.st_ino),
)?;
for name in ["upper", "work"] { for name in ["upper", "work"] {
drop(open_directory(&persistent_anchor, Path::new(name))?); drop(open_directory(&persistent_anchor, Path::new(name))?);
} }
let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?; let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?;
let merged_label = runtime_anchor_label.join("merged"); let merged_label = inherited.runtime_anchor_label.join("merged");
Ok(Self { Ok(Self {
lower, lower,
persistent_anchor, persistent_anchor,
@ -699,6 +707,32 @@ pub struct RuntimeStateAnchorMount {
merged: OwnedFd, merged: OwnedFd,
} }
pub struct RuntimeStateAnchorAbsenceAuthority {
runtime_anchor: OwnedFd,
mount: MountIdentity,
}
impl RuntimeStateAnchorAbsenceAuthority {
/// Reopens the exact runtime anchor to produce a fresh post-unmount observation.
///
/// # Errors
/// Refuses an unreadable anchor or the continued presence of the retired mount.
pub fn observe_absence(
&self,
expected_mount: MountIdentity,
) -> Result<MountIdentity, StateAnchorError> {
if expected_mount != self.mount {
return Err(StateAnchorError::OwnershipMismatch);
}
let observed = mount_identity(&open_directory(&self.runtime_anchor, Path::new("merged"))?)?;
if observed == self.mount {
Err(StateAnchorError::OwnershipMismatch)
} else {
Ok(observed)
}
}
}
impl RuntimeStateAnchorMount { impl RuntimeStateAnchorMount {
#[must_use] #[must_use]
pub const fn mount_identity(&self) -> MountIdentity { pub const fn mount_identity(&self) -> MountIdentity {
@ -756,7 +790,7 @@ impl RuntimeStateAnchorMount {
/// ///
/// # Errors /// # Errors
/// Refuses mount replacement, unmount failure, or exact child cleanup failure. /// Refuses mount replacement, unmount failure, or exact child cleanup failure.
pub fn unmount(self) -> Result<(), StateAnchorError> { pub fn unmount(self) -> Result<RuntimeStateAnchorAbsenceAuthority, StateAnchorError> {
let Self { let Self {
authority, authority,
process, process,
@ -777,7 +811,10 @@ impl RuntimeStateAnchorMount {
return Err(StateAnchorError::OwnershipMismatch); return Err(StateAnchorError::OwnershipMismatch);
} }
child_broker().reap(process)?; child_broker().reap(process)?;
Ok(()) Ok(RuntimeStateAnchorAbsenceAuthority {
runtime_anchor: authority.runtime_anchor,
mount,
})
} }
} }
@ -1195,12 +1232,10 @@ fn append_fuse_option_path(target: &mut Vec<u8>, path: &Path) {
} }
} }
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
fn rootless_unmount(layout: &StateAnchorLayout<'_>) -> Result<(), StateAnchorError> { fn rootless_unmount(layout: &StateAnchorLayout<'_>) -> Result<(), StateAnchorError> {
rootless_unmount_anchor(&layout.runtime_anchor) rootless_unmount_anchor(&layout.runtime_anchor)
} }
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorError> { fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorError> {
let inherited_anchor = inherit_for_exec(runtime_anchor)?; let inherited_anchor = inherit_for_exec(runtime_anchor)?;
let path = descriptor_child_path(&inherited_anchor, "merged"); let path = descriptor_child_path(&inherited_anchor, "merged");
@ -1208,17 +1243,14 @@ fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorE
} }
fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> { fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
let status = std::process::Command::new("fusermount3") child_broker()
.arg("-u") .run_transient(
.arg(path) &TargetCommand::new("fusermount3")
.status()?; .argument("-u")
if status.success() { .argument(path),
Ok(()) )
} else { .map(drop)
Err(StateAnchorError::Io(io::Error::other(format!( .map_err(StateAnchorError::from)
"fusermount3 exited with {status}"
))))
}
} }
fn require_mount_record( fn require_mount_record(

View file

@ -15,8 +15,8 @@ use lazaret_core::identity::{
}; };
use lazaret_core::records::{ArtifactState, PreparedArtifact}; use lazaret_core::records::{ArtifactState, PreparedArtifact};
use lazaret_state_anchor::{ use lazaret_state_anchor::{
ProtectedRuntimeFiles, RuntimeStateAnchorAuthority, StateAnchorLaunchAuthority, ProtectedRuntimeFiles, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
StateAnchorLayout, StateAnchorLaunchAuthority, StateAnchorLayout,
}; };
static SEQUENCE: AtomicU64 = AtomicU64::new(0); static SEQUENCE: AtomicU64 = AtomicU64::new(0);
@ -78,30 +78,12 @@ fn inherited_runtime_authority_accepts_only_the_exact_prepared_lower() {
}; };
let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap(); let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap();
let expected = launch.expected_lower(); let expected = launch.expected_lower();
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(&launch, expected)).unwrap();
launch.lower_fd(),
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
expected,
)
.unwrap();
assert!( assert!(
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(
launch.lower_fd(), &launch,
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
(expected.0, expected.1 + 1), (expected.0, expected.1 + 1),
) ))
.is_err() .is_err()
); );
} }
@ -150,18 +132,8 @@ fn namespace_local_rebound_refuses_a_replaced_state_anchor() {
fs::create_dir(&anchor).unwrap(); fs::create_dir(&anchor).unwrap();
assert!( assert!(
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(&launch, launch.expected_lower(),))
launch.lower_fd(), .is_err()
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
launch.expected_lower(),
)
.is_err()
); );
} }
@ -178,18 +150,8 @@ fn namespace_local_rebound_refuses_a_replaced_prepared_lower() {
fs::create_dir(&lower).unwrap(); fs::create_dir(&lower).unwrap();
assert!( assert!(
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(&launch, launch.expected_lower(),))
launch.lower_fd(), .is_err()
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
launch.expected_lower(),
)
.is_err()
); );
} }
@ -207,18 +169,8 @@ fn namespace_local_rebound_refuses_a_replaced_persistent_anchor() {
fs::create_dir(&anchor).unwrap(); fs::create_dir(&anchor).unwrap();
assert!( assert!(
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(&launch, launch.expected_lower(),))
launch.lower_fd(), .is_err()
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
launch.expected_lower(),
)
.is_err()
); );
} }
@ -237,21 +189,28 @@ fn namespace_local_rebound_refuses_a_state_anchor_symlink() {
std::os::unix::fs::symlink(&retained, &anchor).unwrap(); std::os::unix::fs::symlink(&retained, &anchor).unwrap();
assert!( assert!(
RuntimeStateAnchorAuthority::from_inherited( RuntimeStateAnchorAuthority::from_inherited(inherited(&launch, launch.expected_lower(),))
launch.lower_fd(), .is_err()
launch.lower_label(),
launch.persistent_root_fd(),
launch.persistent_anchor_fd(),
launch.persistent_anchor_label(),
launch.runtime_root_fd(),
launch.runtime_anchor_fd(),
launch.runtime_anchor_label(),
launch.expected_lower(),
)
.is_err()
); );
} }
fn inherited(
launch: &StateAnchorLaunchAuthority,
expected_lower: (u64, u64),
) -> RuntimeStateAnchorInherited<'_> {
RuntimeStateAnchorInherited {
lower_fd: launch.lower_fd(),
lower_label: launch.lower_label(),
persistent_root_fd: launch.persistent_root_fd(),
persistent_anchor_fd: launch.persistent_anchor_fd(),
persistent_anchor_label: launch.persistent_anchor_label(),
runtime_root_fd: launch.runtime_root_fd(),
runtime_anchor_fd: launch.runtime_anchor_fd(),
runtime_anchor_label: launch.runtime_anchor_label(),
expected_lower,
}
}
fn prepared_artifact(lower: &Path) -> PreparedArtifact { fn prepared_artifact(lower: &Path) -> PreparedArtifact {
let metadata = lower.metadata().unwrap(); let metadata = lower.metadata().unwrap();
PreparedArtifact { PreparedArtifact {

View file

@ -19,9 +19,9 @@ use lazaret_linux::process::{
ProcessOwnershipContext, current_process_identity, dispatch_private_process_role, ProcessOwnershipContext, current_process_identity, dispatch_private_process_role,
}; };
use lazaret_state_anchor::{ use lazaret_state_anchor::{
FuseOverlayfsMount, ProtectedRuntimeFiles, RuntimeStateAnchorAuthority, StateAnchorAcquisition, FuseOverlayfsMount, ProtectedRuntimeFiles, RuntimeStateAnchorAuthority,
StateAnchorLaunchAuthority, StateAnchorLayout, StateAnchorSandboxView, RuntimeStateAnchorInherited, StateAnchorAcquisition, StateAnchorLaunchAuthority,
unmount_after_coordinator_loss, StateAnchorLayout, StateAnchorSandboxView, unmount_after_coordinator_loss,
}; };
use rustix::process::Signal; use rustix::process::Signal;
@ -80,18 +80,19 @@ fn run() -> io::Result<()> {
let launch = let launch =
StateAnchorLaunchAuthority::capture(&layout, &artifact).map_err(io::Error::other)?; StateAnchorLaunchAuthority::capture(&layout, &artifact).map_err(io::Error::other)?;
let runtime_authority = RuntimeStateAnchorAuthority::from_inherited( let runtime_authority =
launch.lower_fd(), RuntimeStateAnchorAuthority::from_inherited(RuntimeStateAnchorInherited {
launch.lower_label(), lower_fd: launch.lower_fd(),
launch.persistent_root_fd(), lower_label: launch.lower_label(),
launch.persistent_anchor_fd(), persistent_root_fd: launch.persistent_root_fd(),
launch.persistent_anchor_label(), persistent_anchor_fd: launch.persistent_anchor_fd(),
launch.runtime_root_fd(), persistent_anchor_label: launch.persistent_anchor_label(),
launch.runtime_anchor_fd(), runtime_root_fd: launch.runtime_root_fd(),
launch.runtime_anchor_label(), runtime_anchor_fd: launch.runtime_anchor_fd(),
launch.expected_lower(), runtime_anchor_label: launch.runtime_anchor_label(),
) expected_lower: launch.expected_lower(),
.map_err(io::Error::other)?; })
.map_err(io::Error::other)?;
let expected = std::cell::Cell::new(None); let expected = std::cell::Cell::new(None);
let runtime_mount = runtime_authority let runtime_mount = runtime_authority
.acquire( .acquire(