use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::provider_v3::{Error, Result, WireReason};
use serde::Deserialize;
use crate::setup_core::{digest, target::Target};
use crate::harness_runtime::facts::Harness;
const PREDECESSOR_SCHEMA: u32 = 1;
const KEPT_IN: &str = "adopted";
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Predecessor {
pub schema_version: u32,
pub product_name: String,
pub build_version: String,
pub setup_id: String,
pub canonical_target: String,
pub managed_files: BTreeMap<String, String>,
}
impl Predecessor {
pub(crate) fn written_elsewhere(&self, target: &Target) -> Option<&str> {
let here = target.root().to_string_lossy();
(self.canonical_target != here).then_some(self.canonical_target.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Claim {
Intact,
Changed,
Missing,
}
impl Claim {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Intact => "intact",
Self::Changed => "changed",
Self::Missing => "missing",
}
}
}
pub(crate) fn read(harness: &Harness, target: &Target) -> Result<Option<(PathBuf, Predecessor)>> {
if harness.predecessor_state_file.is_empty() {
return Ok(None);
}
let path = target.root().join(harness.predecessor_state_file);
if !path.is_file() {
return Ok(None);
}
let bytes = std::fs::read(&path).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{} cannot be read: {error}", path.display()),
)
})?;
let found: Predecessor = serde_json::from_slice(&bytes).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} is not a stamp this build understands: {error}",
path.display()
),
)
})?;
if found.schema_version != PREDECESSOR_SCHEMA {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} is schema {}, and this build reads {PREDECESSOR_SCHEMA}; adopting a record it \
cannot read would be claiming files it did not check",
path.display(),
found.schema_version
),
));
}
Ok(Some((path, found)))
}
impl Predecessor {
pub(crate) fn account_for(&self, target: &Target) -> Result<Vec<(String, Claim)>> {
let mut found = Vec::with_capacity(self.managed_files.len());
for (relative, expected) in &self.managed_files {
let path = target.root().join(relative);
let claim = if path.is_file() {
let measured = digest::of_file(&path)?;
if measured == format!("{}{expected}", digest::PREFIX) {
Claim::Intact
} else {
Claim::Changed
}
} else {
Claim::Missing
};
found.push((relative.clone(), claim));
}
Ok(found)
}
pub(crate) fn outside(&self, harness: &Harness) -> Vec<&str> {
self.managed_files
.keys()
.map(String::as_str)
.filter(|relative| !harness.owns(relative))
.collect()
}
}
pub(crate) fn keep_aside(control: &Path, stamp: &Path, name: &str) -> Result<PathBuf> {
let kept = control.join(KEPT_IN);
std::fs::create_dir_all(&kept).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{} cannot be created: {error}", kept.display()),
)
})?;
let to = kept.join(name);
std::fs::rename(stamp, &to).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} could not be moved to {}: {error}",
stamp.display(),
to.display()
),
)
})?;
Ok(to)
}