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::json::check_json_size;
4use super::{
5    BackupLayoutGuard, JournalLock, JournalLockError, OperationPlanPersistenceError,
6    PersistenceError, create_json_durable, read_json, read_operation_plan,
7};
8use crate::model::{
9    artifacts::ArtifactChecksumRecord,
10    operation_plan::OperationPlanRecord,
11    restore_safety::{
12        MAX_RESTORE_SAFETY_REQUIREMENT_BYTES, RestoreSafetyRequirementError,
13        RestoreSafetyRequirementRecord,
14    },
15};
16use thiserror::Error;
17
18/// Durably create fixed `restore-safety-requirement.json` without replacement.
19///
20/// Both exact original plans must already be retained under their layout guards.
21/// Artifact completeness, source-reference retention and current application/fence
22/// custody remain separately admitted by their owners. This persists declarations.
23/// # Errors
24/// Rejects changed/missing plans, inappropriate source, excessive bytes or unsafe/existing paths.
25pub fn create_restore_safety_requirement(
26    layout: &BackupLayoutGuard,
27    source_layout: &BackupLayoutGuard,
28    plan: &OperationPlanRecord,
29    source: &OperationPlanRecord,
30    record: &RestoreSafetyRequirementRecord,
31) -> Result<(), RestoreSafetyPersistenceError> {
32    record.validate_plans(plan, source)?;
33    read_operation_plan(layout, &plan.digest())?;
34    read_operation_plan(source_layout, &source.digest())?;
35    let path = layout.root().join("restore-safety-requirement.json");
36    let _lock = JournalLock::acquire(&path)?;
37    check_json_size(record, MAX_RESTORE_SAFETY_REQUIREMENT_BYTES)?;
38    create_json_durable(&path, record)?;
39    Ok(())
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    let path = layout.root().join("restore-safety-requirement.json");
57    let _lock = JournalLock::acquire(&path)?;
58    let record: RestoreSafetyRequirementRecord =
59        read_json(&path, MAX_RESTORE_SAFETY_REQUIREMENT_BYTES)?;
60    check_json_size(&record, MAX_RESTORE_SAFETY_REQUIREMENT_BYTES)?;
61    record.validate_plans(plan, source)?;
62    if &record.digest() != expected {
63        return Err(RestoreSafetyPersistenceError::DigestMismatch);
64    }
65    Ok(record)
66}
67/// Typed original restore/source safety persistence denial.
68#[derive(Debug, Error)]
69pub enum RestoreSafetyPersistenceError {
70    /// Exact original retained requirement differs.
71    #[error("restore safety requirement digest mismatch")]
72    DigestMismatch,
73    /// Original source or restore declarations differ.
74    #[error(transparent)]
75    Requirement(#[from] RestoreSafetyRequirementError),
76    /// An exact original plan is not retained under its unchanged layout.
77    #[error(transparent)]
78    Plan(#[from] OperationPlanPersistenceError),
79    /// Journal exclusion failed.
80    #[error(transparent)]
81    Lock(#[from] JournalLockError),
82    /// Bounded JSON/durable IO failed.
83    #[error(transparent)]
84    Persistence(#[from] PersistenceError),
85}
86
87#[cfg(all(test, unix))]
88mod tests;