Restore persistent system path composition

This commit is contained in:
ms2k 2026-07-22 03:07:42 +09:00
commit 7aff8e542e

View file

@ -160,6 +160,7 @@ impl StateAnchorLaunchAuthority {
pub struct RuntimeStateAnchorAuthority {
lower: OwnedFd,
persistent_anchor: OwnedFd,
upper: OwnedFd,
runtime_anchor: OwnedFd,
merged_label: PathBuf,
merged_descriptor: OwnedFd,
@ -214,6 +215,7 @@ impl RuntimeStateAnchorAuthority {
inherited.persistent_anchor_label,
(retained_persistent.st_dev, retained_persistent.st_ino),
)?;
let upper = open_directory(&persistent_anchor, Path::new("upper"))?;
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() {
@ -231,14 +233,13 @@ impl RuntimeStateAnchorAuthority {
inherited.runtime_anchor_label,
(retained.st_dev, retained.st_ino),
)?;
for name in ["upper", "work"] {
drop(open_directory(&persistent_anchor, Path::new(name))?);
}
drop(open_directory(&persistent_anchor, Path::new("work"))?);
let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?;
let merged_label = inherited.runtime_anchor_label.join("merged");
Ok(Self {
lower,
persistent_anchor,
upper,
runtime_anchor,
merged_label,
merged_descriptor,
@ -421,6 +422,54 @@ impl RuntimeStateAnchorMount {
Ok(())
}
/// Validates exact transient child targets and proves they are absent from the upper layer.
///
/// # Errors
/// Refuses changed mounts, invalid paths, non-files, links, nested mounts, or upper entries.
pub fn require_transient_override_targets(
&self,
targets: &[PathBuf],
) -> Result<(), StateAnchorError> {
if mount_identity(&self.merged)? != self.mount {
return Err(StateAnchorError::OwnershipMismatch);
}
for target in targets {
if target.as_os_str().is_empty()
|| target
.components()
.any(|component| !matches!(component, std::path::Component::Normal(_)))
{
return Err(StateAnchorError::InvalidLayout(
"transient override target is not normalized and relative",
));
}
let descriptor = openat2(
&self.merged,
target,
OFlags::PATH | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::empty(),
ResolveFlags::BENEATH
| ResolveFlags::NO_MAGICLINKS
| ResolveFlags::NO_SYMLINKS
| ResolveFlags::NO_XDEV,
)
.map_err(io::Error::from)?;
if !FileType::from_raw_mode(
rustix::fs::fstat(&descriptor)
.map_err(io::Error::from)?
.st_mode,
)
.is_file()
{
return Err(StateAnchorError::InvalidLayout(
"transient override target is not a regular file",
));
}
require_upper_target_absent(&self.authority.upper, target)?;
}
Ok(())
}
/// Unmounts and reaps only the retained exact RS-owned State Anchor.
///
/// # Errors
@ -453,6 +502,38 @@ impl RuntimeStateAnchorMount {
}
}
fn require_upper_target_absent(upper: &OwnedFd, target: &Path) -> Result<(), StateAnchorError> {
require_upper_path_absent(upper, target)?;
let leaf = target.file_name().ok_or(StateAnchorError::InvalidLayout(
"transient override target has no final component",
))?;
let mut whiteout = std::ffi::OsString::from(".wh.");
whiteout.push(leaf);
require_upper_path_absent(
upper,
&target.parent().unwrap_or(Path::new("")).join(whiteout),
)
}
fn require_upper_path_absent(upper: &OwnedFd, target: &Path) -> Result<(), StateAnchorError> {
match openat2(
upper,
target,
OFlags::PATH | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::empty(),
ResolveFlags::BENEATH
| ResolveFlags::NO_MAGICLINKS
| ResolveFlags::NO_SYMLINKS
| ResolveFlags::NO_XDEV,
) {
Err(rustix::io::Errno::NOENT) => Ok(()),
Ok(_) => Err(StateAnchorError::InvalidLayout(
"transient override target is present in the upper layer",
)),
Err(error) => Err(io::Error::from(error).into()),
}
}
impl VerifiedStateAnchorReadiness {
#[must_use]
pub const fn from_parts(staged: StagedProcessOwnership, mount: MountIdentity) -> Self {