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, JournalLockError, OperationPlanPersistenceError, PersistenceError,
5    immutable_record, 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    immutable_record::create(
35        layout,
36        "restore-safety-requirement.json",
37        MAX_RESTORE_SAFETY_REQUIREMENT_BYTES,
38        record,
39    )
40}
41/// Read bounded exact original requirement under both unchanged retained plans.
42///
43/// Lost creation replies reconcile through this read; absence never recreates a
44/// requirement, grants fresh source/fence authority or replenishes original attempts.
45/// # Errors
46/// Rejects missing/unsafe/oversized records, changed plans or exact requirement digest mismatch.
47pub fn read_restore_safety_requirement(
48    layout: &BackupLayoutGuard,
49    source_layout: &BackupLayoutGuard,
50    plan: &OperationPlanRecord,
51    source: &OperationPlanRecord,
52    expected: &ArtifactChecksumRecord,
53) -> Result<RestoreSafetyRequirementRecord, RestoreSafetyPersistenceError> {
54    read_operation_plan(layout, &plan.digest())?;
55    read_operation_plan(source_layout, &source.digest())?;
56    immutable_record::read_with(
57        layout,
58        "restore-safety-requirement.json",
59        MAX_RESTORE_SAFETY_REQUIREMENT_BYTES,
60        |record: &RestoreSafetyRequirementRecord| {
61            record.validate_plans(plan, source)?;
62            if &record.digest() != expected {
63                return Err(RestoreSafetyPersistenceError::DigestMismatch);
64            }
65            Ok(())
66        },
67    )
68}
69/// Typed original restore/source safety persistence denial.
70#[derive(Debug, Error)]
71pub enum RestoreSafetyPersistenceError {
72    /// Exact original retained requirement differs.
73    #[error("restore safety requirement digest mismatch")]
74    DigestMismatch,
75    /// Original source or restore declarations differ.
76    #[error(transparent)]
77    Requirement(#[from] RestoreSafetyRequirementError),
78    /// An exact original plan is not retained under its unchanged layout.
79    #[error(transparent)]
80    Plan(#[from] OperationPlanPersistenceError),
81    /// Journal exclusion failed.
82    #[error(transparent)]
83    Lock(#[from] JournalLockError),
84    /// Bounded JSON/durable IO failed.
85    #[error(transparent)]
86    Persistence(#[from] PersistenceError),
87}
88
89#[cfg(all(test, unix))]
90mod tests;