Skip to main content

ic_backup/ops/persistence/restore_safety/
mod.rs

1//! Immutable original restore/source safety retention under both layout guards.
2
3use super::{
4    BackupLayoutGuard, JournalLock, JournalLockError, OperationPlanPersistenceError,
5    PersistenceError, create_json_durable, read_json, read_operation_plan,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    operation_plan::OperationPlanRecord,
10    restore_safety::{
11        MAX_RESTORE_SAFETY_REQUIREMENT_BYTES, RestoreSafetyRequirementError,
12        RestoreSafetyRequirementRecord,
13    },
14};
15use thiserror::Error;
16
17/// Durably create fixed `restore-safety-requirement.json` without replacement.
18///
19/// Both exact original plans must already be retained under their layout guards.
20/// Artifact completeness, source-reference retention and current application/fence
21/// custody remain separately admitted by their owners. This persists declarations.
22/// # Errors
23/// Rejects changed/missing plans, inappropriate source, excessive bytes or unsafe/existing paths.
24pub fn create_restore_safety_requirement(
25    layout: &BackupLayoutGuard,
26    source_layout: &BackupLayoutGuard,
27    plan: &OperationPlanRecord,
28    source: &OperationPlanRecord,
29    record: &RestoreSafetyRequirementRecord,
30) -> Result<(), RestoreSafetyPersistenceError> {
31    record.validate_plans(plan, source)?;
32    read_operation_plan(layout, &plan.digest())?;
33    read_operation_plan(source_layout, &source.digest())?;
34    let path = layout.root().join("restore-safety-requirement.json");
35    let _lock = JournalLock::acquire(&path)?;
36    check_size(record)?;
37    create_json_durable(&path, record)?;
38    Ok(())
39}
40/// Read bounded exact original requirement under both unchanged retained plans.
41///
42/// Lost creation replies reconcile through this read; absence never recreates a
43/// requirement, grants fresh source/fence authority or replenishes original attempts.
44/// # Errors
45/// Rejects missing/unsafe/oversized records, changed plans or exact requirement digest mismatch.
46pub fn read_restore_safety_requirement(
47    layout: &BackupLayoutGuard,
48    source_layout: &BackupLayoutGuard,
49    plan: &OperationPlanRecord,
50    source: &OperationPlanRecord,
51    expected: &ArtifactChecksumRecord,
52) -> Result<RestoreSafetyRequirementRecord, RestoreSafetyPersistenceError> {
53    read_operation_plan(layout, &plan.digest())?;
54    read_operation_plan(source_layout, &source.digest())?;
55    let path = layout.root().join("restore-safety-requirement.json");
56    let _lock = JournalLock::acquire(&path)?;
57    let record: RestoreSafetyRequirementRecord =
58        read_json(&path, MAX_RESTORE_SAFETY_REQUIREMENT_BYTES)?;
59    check_size(&record)?;
60    record.validate_plans(plan, source)?;
61    if &record.digest() != expected {
62        return Err(RestoreSafetyPersistenceError::DigestMismatch);
63    }
64    Ok(record)
65}
66fn check_size(record: &RestoreSafetyRequirementRecord) -> Result<(), PersistenceError> {
67    if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_RESTORE_SAFETY_REQUIREMENT_BYTES {
68        return Err(PersistenceError::RecordTooLarge {
69            limit: MAX_RESTORE_SAFETY_REQUIREMENT_BYTES,
70        });
71    }
72    Ok(())
73}
74/// Typed original restore/source safety persistence denial.
75#[derive(Debug, Error)]
76pub enum RestoreSafetyPersistenceError {
77    /// Exact original retained requirement differs.
78    #[error("restore safety requirement digest mismatch")]
79    DigestMismatch,
80    /// Original source or restore declarations differ.
81    #[error(transparent)]
82    Requirement(#[from] RestoreSafetyRequirementError),
83    /// An exact original plan is not retained under its unchanged layout.
84    #[error(transparent)]
85    Plan(#[from] OperationPlanPersistenceError),
86    /// Journal exclusion failed.
87    #[error(transparent)]
88    Lock(#[from] JournalLockError),
89    /// Bounded JSON/durable IO failed.
90    #[error(transparent)]
91    Persistence(#[from] PersistenceError),
92}
93
94#[cfg(all(test, unix))]
95mod tests;