Require fresh State Anchor absence before lease release

This commit is contained in:
ms2k 2026-07-17 04:40:02 +09:00
commit 3b76b59bdf

View file

@ -530,33 +530,39 @@ pub struct RuntimeStateAnchorAuthority {
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 {
/// 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,
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),
inherited: RuntimeStateAnchorInherited<'_>,
) -> Result<Self, StateAnchorError> {
let retained_lower = reopen_inherited_directory(lower_fd)?;
require_directory_descriptor_identity(&retained_lower, expected_lower)?;
if !lower_label.is_absolute() {
let retained_lower = reopen_inherited_directory(inherited.lower_fd)?;
require_directory_descriptor_identity(&retained_lower, inherited.expected_lower)?;
if !inherited.lower_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout(
"prepared lower label is not absolute",
));
}
let lower = open_exact_directory_path(lower_label, expected_lower)?;
let persistent_root = reopen_inherited_directory(persistent_root_fd)?;
let retained_persistent_anchor = reopen_inherited_directory(persistent_anchor_fd)?;
if !persistent_anchor_label.is_absolute() {
let lower = open_exact_directory_path(inherited.lower_label, inherited.expected_lower)?;
let persistent_root = reopen_inherited_directory(inherited.persistent_root_fd)?;
let retained_persistent_anchor =
reopen_inherited_directory(inherited.persistent_anchor_fd)?;
if !inherited.persistent_anchor_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout(
"persistent State Anchor label is not absolute",
));
@ -570,12 +576,12 @@ impl RuntimeStateAnchorAuthority {
(retained_persistent.st_dev, retained_persistent.st_ino),
)?;
let persistent_anchor = open_exact_directory_path(
persistent_anchor_label,
inherited.persistent_anchor_label,
(retained_persistent.st_dev, retained_persistent.st_ino),
)?;
let runtime_root = reopen_inherited_directory(runtime_root_fd)?;
let retained_runtime_anchor = reopen_inherited_directory(runtime_anchor_fd)?;
if !runtime_anchor_label.is_absolute() {
let runtime_root = reopen_inherited_directory(inherited.runtime_root_fd)?;
let retained_runtime_anchor = reopen_inherited_directory(inherited.runtime_anchor_fd)?;
if !inherited.runtime_anchor_label.is_absolute() {
return Err(StateAnchorError::InvalidLayout(
"runtime State Anchor label is not absolute",
));
@ -586,13 +592,15 @@ impl RuntimeStateAnchorAuthority {
&confined_runtime_anchor,
(retained.st_dev, retained.st_ino),
)?;
let runtime_anchor =
open_exact_directory_path(runtime_anchor_label, (retained.st_dev, retained.st_ino))?;
let runtime_anchor = open_exact_directory_path(
inherited.runtime_anchor_label,
(retained.st_dev, retained.st_ino),
)?;
for name in ["upper", "work"] {
drop(open_directory(&persistent_anchor, Path::new(name))?);
}
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 {
lower,
persistent_anchor,
@ -699,6 +707,32 @@ pub struct RuntimeStateAnchorMount {
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 {
#[must_use]
pub const fn mount_identity(&self) -> MountIdentity {
@ -756,7 +790,7 @@ impl RuntimeStateAnchorMount {
///
/// # Errors
/// 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 {
authority,
process,
@ -777,7 +811,10 @@ impl RuntimeStateAnchorMount {
return Err(StateAnchorError::OwnershipMismatch);
}
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> {
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");
@ -1208,17 +1243,14 @@ fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorE
}
fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
let status = std::process::Command::new("fusermount3")
.arg("-u")
.arg(path)
.status()?;
if status.success() {
Ok(())
} else {
Err(StateAnchorError::Io(io::Error::other(format!(
"fusermount3 exited with {status}"
))))
}
child_broker()
.run_transient(
&TargetCommand::new("fusermount3")
.argument("-u")
.argument(path),
)
.map(drop)
.map_err(StateAnchorError::from)
}
fn require_mount_record(