use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
use super::{Drift, HookPins, Lock, key, repin, split, verify_state};
use crate::home::Home;
#[derive(Debug)]
pub struct Checkpoint(Option<Lock>);
impl Checkpoint {
pub fn take(home: &Home) -> Result<Self> {
let path = home.lock_path();
if !path.exists() {
return Ok(Self(None));
}
Ok(Self(Some(Lock::load(&path)?)))
}
pub fn verify(&self, home: &Home, written: &[PathBuf]) -> Result<()> {
let Some(before) = &self.0 else {
return Ok(());
};
let lock_path = home.lock_path();
if !Lock::load(&lock_path).is_ok_and(|now| now == *before) {
bail!(
"refusing to re-pin: {} changed while OpenMoat waited for your answer, so it \
no longer pins what was checked; run `openmoat doctor` to see it",
lock_path.display()
);
}
let written: Vec<String> = written.iter().map(|p| key(p)).collect();
let (drift, kept) = verify_state(home, before);
let drift: Vec<Drift> = drift
.into_iter()
.filter(|d| !written.contains(&key(d.path())))
.collect();
if let Some(deny) = split(&kept, before, drift).deny {
bail!(
"refusing to re-pin: a pinned file changed while OpenMoat waited for your \
answer, and re-pinning now would accept it unseen ({}):\n {}",
super::INTEGRITY_RULE,
deny.reasons.join("\n ")
);
}
Ok(())
}
}
impl Drift {
pub(crate) fn path(&self) -> &Path {
match self {
Self::Modified(p)
| Self::KeysChanged(p, _)
| Self::Missing(p)
| Self::Unreadable(p, _) => p,
Self::Proposed { target, .. } => target,
}
}
}
pub fn repin_after(
home: &Home,
binary: &Path,
hooks: HookPins,
checkpoint: &Checkpoint,
written: &[PathBuf],
) -> Result<Lock> {
checkpoint.verify(home, written)?;
repin(home, binary, hooks)
}
#[cfg(test)]
mod tests;