Add crash-consistent State Anchor maintenance

This commit is contained in:
ms2k 2026-07-17 07:54:46 +09:00
commit 24d7d5b6e0

View file

@ -16,11 +16,74 @@ use lazaret_core::identity::{
use lazaret_core::records::{ArtifactState, PreparedArtifact};
use lazaret_state_anchor::{
ProtectedRuntimeFiles, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
StateAnchorLaunchAuthority, StateAnchorLayout,
StateAnchorLaunchAuthority, StateAnchorLayout, StateAnchorResetScope,
};
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
#[test]
fn selected_reset_removes_exact_state_and_whiteout_without_touching_siblings() {
let tree = TestTree::new();
let configuration = configuration(&tree);
let layout = StateAnchorLayout::create(&configuration).unwrap();
let upper = domain_path(&configuration).join("state-anchor/upper");
fs::create_dir_all(upper.join("usr/bin")).unwrap();
fs::write(upper.join("usr/bin/tool"), b"state").unwrap();
fs::write(upper.join(".wh.usr"), b"").unwrap();
fs::create_dir(upper.join("var")).unwrap();
fs::write(upper.join("var/unrelated"), b"unchanged").unwrap();
let inspection = layout.inspect_state().unwrap();
assert!(
inspection
.entries
.iter()
.any(|entry| entry.path == b"usr/bin/tool")
);
layout
.complete_state_reset(StateAnchorResetScope::Selected(Path::new("/usr")))
.unwrap();
assert!(!upper.join("usr").exists());
assert!(!upper.join(".wh.usr").exists());
assert_eq!(fs::read(upper.join("var/unrelated")).unwrap(), b"unchanged");
layout.require_no_reset_quarantine().unwrap();
}
#[test]
fn all_reset_unlinks_symlink_state_without_following_it() {
let tree = TestTree::new();
let configuration = configuration(&tree);
let layout = StateAnchorLayout::create(&configuration).unwrap();
let upper = domain_path(&configuration).join("state-anchor/upper");
let foreign = tree.path.join("foreign");
fs::create_dir(&foreign).unwrap();
fs::write(foreign.join("value"), b"foreign").unwrap();
std::os::unix::fs::symlink(&foreign, upper.join("link")).unwrap();
layout
.complete_state_reset(StateAnchorResetScope::All)
.unwrap();
assert!(upper.read_dir().unwrap().next().is_none());
assert_eq!(fs::read(foreign.join("value")).unwrap(), b"foreign");
}
#[test]
fn arbitrary_target_and_orphan_quarantine_are_refused() {
let tree = TestTree::new();
let configuration = configuration(&tree);
let layout = StateAnchorLayout::create(&configuration).unwrap();
assert!(
layout
.complete_state_reset(StateAnchorResetScope::Selected(Path::new("/etc")))
.is_err()
);
fs::create_dir(domain_path(&configuration).join("state-anchor/reset-quarantine")).unwrap();
assert!(layout.require_no_reset_quarantine().is_err());
assert!(layout.inspect_state().is_err());
}
#[test]
fn replaced_runtime_root_is_refused_before_runtime_file_write() {
let tree = TestTree::new();
@ -226,6 +289,18 @@ fn prepared_artifact(lower: &Path) -> PreparedArtifact {
}
}
fn domain_path(
configuration: &lazaret_core::configuration::ResolvedDomainConfiguration,
) -> PathBuf {
PathBuf::from(OsString::from_vec(
configuration
.persistent_domain_root()
.canonical_label()
.as_bytes()
.to_vec(),
))
}
struct TestTree {
path: PathBuf,
}