Compose cold-start resources under Runtime Supervisor ownership

This commit is contained in:
ms2k 2026-07-16 17:29:23 +09:00
commit 33ac0ba4ee

View file

@ -388,7 +388,223 @@ pub struct StateAnchorAcquisition<'path> {
pub ownership: ProcessOwnershipContext,
}
pub struct StateAnchorLaunchAuthority {
lower: OwnedFd,
persistent_anchor: OwnedFd,
runtime_anchor: OwnedFd,
expected_lower: (u64, u64),
}
impl StateAnchorLaunchAuthority {
/// Captures exact directory authorities for inheritance by the Runtime Supervisor.
///
/// # Errors
/// Refuses a changed layout or prepared lower.
pub fn capture(
layout: &StateAnchorLayout<'_>,
artifact: &PreparedArtifact,
) -> Result<Self, StateAnchorError> {
layout.revalidate()?;
let expected_lower = (artifact.device.get(), artifact.inode.get());
let lower = open_exact_directory_path(&path_from_host(&artifact.path), expected_lower)?;
Ok(Self {
lower: inherit_for_exec(lower)?,
persistent_anchor: inherit_for_exec(&layout.persistent_anchor)?,
runtime_anchor: inherit_for_exec(&layout.runtime_anchor)?,
expected_lower,
})
}
#[must_use]
pub fn lower_fd(&self) -> i32 {
self.lower.as_raw_fd()
}
#[must_use]
pub fn persistent_anchor_fd(&self) -> i32 {
self.persistent_anchor.as_raw_fd()
}
#[must_use]
pub fn runtime_anchor_fd(&self) -> i32 {
self.runtime_anchor.as_raw_fd()
}
#[must_use]
pub const fn expected_lower(&self) -> (u64, u64) {
self.expected_lower
}
}
pub struct RuntimeStateAnchorAuthority {
lower: OwnedFd,
persistent_anchor: OwnedFd,
runtime_anchor: OwnedFd,
merged_descriptor: OwnedFd,
}
impl RuntimeStateAnchorAuthority {
/// Reopens inherited authorities without falling back to ambient configured paths.
///
/// # Errors
/// Refuses missing, replaced, or incorrectly typed descriptors and fixed children.
pub fn from_inherited(
lower_fd: i32,
persistent_anchor_fd: i32,
runtime_anchor_fd: i32,
expected_lower: (u64, u64),
) -> Result<Self, StateAnchorError> {
let lower = reopen_inherited_directory(lower_fd)?;
require_directory_descriptor_identity(&lower, expected_lower)?;
let persistent_anchor = reopen_inherited_directory(persistent_anchor_fd)?;
let runtime_anchor = reopen_inherited_directory(runtime_anchor_fd)?;
for name in ["upper", "work"] {
drop(open_directory(&persistent_anchor, Path::new(name))?);
}
let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?;
Ok(Self {
lower,
persistent_anchor,
runtime_anchor,
merged_descriptor,
})
}
/// Mounts the exact inherited prepared lower and anchors under RS child ownership.
///
/// # Errors
/// Refuses acquisition, acknowledgment, readiness, or authority failures.
pub fn acquire<Stage, Acknowledge, Ready>(
self,
expected_parent: lazaret_core::identity::PidfsProcessIdentityV1,
ownership: ProcessOwnershipContext,
stage: Stage,
acknowledge: Acknowledge,
ready: Ready,
) -> Result<RuntimeStateAnchorMount, StateAnchorError>
where
Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>,
Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result<ProcessOwnershipCommitAck>,
Ready: FnOnce(VerifiedStateAnchorReadiness) -> io::Result<()>,
{
if ownership.slot != HostProcessOwnershipSlot::StateAnchorDaemon {
return Err(StateAnchorError::InvalidLayout(
"process slot is not State Anchor daemon",
));
}
let baseline = mount_identity(&self.merged_descriptor)?;
let lower_launch = inherit_for_exec(&self.lower)?;
let persistent_launch = inherit_for_exec(&self.persistent_anchor)?;
let runtime_launch = inherit_for_exec(&self.runtime_anchor)?;
let target = TargetCommand::new("fuse-overlayfs")
.argument("-f")
.argument("-o")
.argument(overlay_options(
&descriptor_path(&lower_launch),
&descriptor_child_path(&persistent_launch, "upper"),
&descriptor_child_path(&persistent_launch, "work"),
))
.argument(descriptor_child_path(&runtime_launch, "merged"));
let process =
child_broker().acquire(expected_parent, ownership, &target, stage, acknowledge)?;
let staged = StagedProcessOwnership::v1(
ownership.domain_id,
ownership.generation_id,
ownership.coordinator_id,
ownership.slot,
process.identity(),
);
let deadline = Instant::now() + MOUNT_TIMEOUT;
loop {
if process.has_exited()? {
child_broker().reap(process)?;
return Err(StateAnchorError::DaemonExited);
}
let observed = mount_identity(&open_mounted_directory(
&self.runtime_anchor,
Path::new("merged"),
)?)?;
if observed.mount_id != baseline.mount_id {
let readiness = VerifiedStateAnchorReadiness {
staged,
mount: observed,
};
if let Err(error) = ready(readiness) {
process.send_signal(rustix::process::Signal::KILL)?;
while !process.has_exited()? {
thread::yield_now();
}
let _ = rootless_unmount_anchor(&self.runtime_anchor);
child_broker().reap(process)?;
return Err(error.into());
}
return Ok(RuntimeStateAnchorMount {
authority: self,
process,
mount: observed,
});
}
if Instant::now() >= deadline {
process.send_signal(rustix::process::Signal::KILL)?;
while !process.has_exited()? {
thread::yield_now();
}
let merged = open_mounted_directory(&self.runtime_anchor, Path::new("merged"))?;
let observed = mount_identity(&merged)?;
if observed.mount_id != baseline.mount_id {
let _ = rootless_unmount_anchor(&self.runtime_anchor);
}
child_broker().reap(process)?;
return Err(StateAnchorError::MountTimeout);
}
thread::sleep(MOUNT_POLL_INTERVAL);
}
}
}
pub struct RuntimeStateAnchorMount {
authority: RuntimeStateAnchorAuthority,
process: RetainedProcess,
mount: MountIdentity,
}
impl RuntimeStateAnchorMount {
#[must_use]
pub const fn mount_identity(&self) -> MountIdentity {
self.mount
}
/// Unmounts and reaps only the retained exact RS-owned State Anchor.
///
/// # Errors
/// Refuses mount replacement, unmount failure, or exact child cleanup failure.
pub fn unmount(self) -> Result<(), StateAnchorError> {
let observed = mount_identity(&open_mounted_directory(
&self.authority.runtime_anchor,
Path::new("merged"),
)?)?;
if observed != self.mount {
return Err(StateAnchorError::OwnershipMismatch);
}
rootless_unmount_anchor(&self.authority.runtime_anchor)?;
let after = mount_identity(&open_directory(
&self.authority.runtime_anchor,
Path::new("merged"),
)?)?;
if after == self.mount {
return Err(StateAnchorError::OwnershipMismatch);
}
child_broker().reap(self.process)?;
Ok(())
}
}
impl VerifiedStateAnchorReadiness {
#[must_use]
pub const fn from_parts(staged: StagedProcessOwnership, mount: MountIdentity) -> Self {
Self { staged, mount }
}
#[must_use]
pub const fn staged(self) -> StagedProcessOwnership {
self.staged
@ -757,6 +973,24 @@ fn inherit_for_exec(descriptor: impl AsFd) -> Result<OwnedFd, StateAnchorError>
dup(descriptor).map_err(io::Error::from).map_err(Into::into)
}
fn reopen_inherited_directory(descriptor: i32) -> Result<OwnedFd, StateAnchorError> {
if descriptor < 0 {
return Err(StateAnchorError::InvalidLayout(
"inherited directory descriptor is invalid",
));
}
let file = File::open(format!("/proc/self/fd/{descriptor}"))?;
let descriptor: OwnedFd = file.into();
let metadata = rustix::fs::fstat(&descriptor).map_err(io::Error::from)?;
if FileType::from_raw_mode(metadata.st_mode).is_dir() {
Ok(descriptor)
} else {
Err(StateAnchorError::InvalidLayout(
"inherited authority is not a directory",
))
}
}
fn overlay_options(lower: &Path, upper: &Path, work: &Path) -> OsString {
let mut bytes = b"lowerdir=".to_vec();
append_fuse_option_path(&mut bytes, lower);
@ -781,7 +1015,12 @@ 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> {
let inherited_anchor = inherit_for_exec(&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> {
let inherited_anchor = inherit_for_exec(runtime_anchor)?;
let path = descriptor_child_path(&inherited_anchor, "merged");
let status = std::process::Command::new("fusermount3")
.arg("-u")