Skip to main content

ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs

1//! Fresh original source-byte preparation; payloads confer no upload permission.
2
3mod plan;
4pub use plan::IcSnapshotDataUploadPreparationError;
5
6use super::{
7    DownloadJournalGuard, File, IcSnapshotArtifactError, Mode, OFlags, REGIONS,
8    check_directory_identity, member, open_directory_no_follow, unix_fs,
9    verification::open_regular_child,
10};
11use crate::model::{
12    ic_snapshot_data::{MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES, validate_kind},
13    ic_snapshot_metadata::IcSnapshotMetadataReply,
14    ic_snapshot_upload::{IcSnapshotUploadError, IcSnapshotUploadRequest},
15    operation_plan::OperationPlanRecord,
16};
17use ic_management_canister_types::SnapshotDataKind;
18use std::io::{self, Read, Seek, SeekFrom};
19use thiserror::Error;
20
21impl DownloadJournalGuard<'_> {
22    /// Freshly verify an original published IC source and encode exact upload metadata.
23    ///
24    /// Requires the full retained source plan, unchanged journal and complete Durable
25    /// selection. Target remains the source canister and replacement stays absent.
26    /// The payload must precede its separately retained original upload plan/reservation.
27    /// Stable future bytes, authentic source, actual context/permissions and same-release
28    /// restore obligations stay integration-owned. Nothing is written or dispatched.
29    /// # Errors
30    /// Rejects original/byte/custody drift or unrepresentable original metadata.
31    pub fn prepare_ic_snapshot_upload_metadata<'source>(
32        &self,
33        plan: &'source OperationPlanRecord,
34        snapshot: &str,
35        metadata: &'source IcSnapshotMetadataReply<'source>,
36    ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
37        let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
38        Ok(IcSnapshotUploadRequest::metadata(
39            plan, metadata, &checksum,
40        )?)
41    }
42
43    /// Read one bounded exact source extent/chunk and encode a distinct destination ID.
44    ///
45    /// Fresh full IC-tree verification before/after descriptor-relative reading binds
46    /// actual bytes to the original metadata/checksum and guarded source plan/journal.
47    /// This is an explicit local byte operation, not resume or a transfer-completion view.
48    /// Only one <=1 MiB chunk is buffered; no aggregate region or allowance is allocated.
49    /// The caller qualifies destination allocation and retains the new exact data intent
50    /// before its own reservation. No source/restore reference, journal or fence changes.
51    /// # Errors
52    /// Rejects wrong source declaration, ranges/hash/IDs, changed/unsafe files or custody,
53    /// short reads, encoding failures and mismatching retained original evidence.
54    pub fn prepare_ic_snapshot_upload_data<'source>(
55        &self,
56        plan: &OperationPlanRecord,
57        snapshot: &str,
58        metadata_upload: &IcSnapshotUploadRequest<'source>,
59        snapshot_id: &[u8],
60        source_kind: SnapshotDataKind,
61    ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
62        let metadata = metadata_upload.source();
63        metadata_upload.validate_data_destination(snapshot_id)?;
64        validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
65        self.with_upload_source(plan, snapshot, metadata_upload, |directory| {
66            let chunk = read_chunk(directory, metadata, &source_kind)?;
67            Ok(IcSnapshotUploadRequest::data(
68                metadata_upload,
69                snapshot_id,
70                source_kind,
71                &chunk,
72            )?)
73        })
74    }
75
76    fn with_upload_source<T>(
77        &self,
78        plan: &OperationPlanRecord,
79        snapshot: &str,
80        metadata_upload: &IcSnapshotUploadRequest<'_>,
81        read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
82    ) -> Result<T, IcSnapshotUploadArtifactError> {
83        let metadata = metadata_upload.source();
84        self.require_upload_source(plan, snapshot, metadata_upload)?;
85        let entry = self
86            .record
87            .artifact(metadata.request().target(), snapshot)
88            .map_err(super::DownloadJournalError::from)
89            .map_err(IcSnapshotArtifactError::from)?;
90        let parent_path = self.layout.root().join("artifacts");
91        let parent =
92            open_directory_no_follow(&parent_path).map_err(IcSnapshotArtifactError::from)?;
93        let path = self.layout.root().join(entry.artifact_path());
94        let directory = File::from(
95            unix_fs::openat(
96                &parent,
97                path.file_name()
98                    .ok_or(IcSnapshotArtifactError::CustodyChanged)?,
99                OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
100                Mode::empty(),
101            )
102            .map_err(io::Error::from)
103            .map_err(IcSnapshotArtifactError::from)?,
104        );
105        let result = read(&directory)?;
106        check_directory_identity(&parent_path, &parent)?;
107        check_directory_identity(&path, &directory)?;
108        self.require_upload_source(plan, snapshot, metadata_upload)?;
109        Ok(result)
110    }
111
112    fn require_upload_source(
113        &self,
114        plan: &OperationPlanRecord,
115        snapshot: &str,
116        upload: &IcSnapshotUploadRequest<'_>,
117    ) -> Result<(), IcSnapshotUploadArtifactError> {
118        if plan.digest() != upload.source_plan().digest() {
119            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
120        }
121        let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
122        if actual != *upload.source_checksum() {
123            return Err(IcSnapshotUploadArtifactError::SourceMismatch);
124        }
125        Ok(())
126    }
127}
128
129fn read_chunk(
130    directory: &File,
131    metadata: &IcSnapshotMetadataReply<'_>,
132    kind: &SnapshotDataKind,
133) -> Result<Vec<u8>, IcSnapshotArtifactError> {
134    let values = metadata.metadata();
135    let (name, length, offset, size) = match member::select(kind)? {
136        member::Member::Region {
137            index,
138            offset,
139            size,
140        } => {
141            let sizes = [
142                values.wasm_module_size,
143                values.wasm_memory_size,
144                values.stable_memory_size,
145            ];
146            (
147                REGIONS[index].to_owned(),
148                Some(sizes[index]),
149                offset,
150                Some(size),
151            )
152        }
153        member::Member::Chunk { name } => (name, None, 0, None),
154    };
155    let maximum = length.unwrap_or(MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64);
156    let (mut file, original) = open_regular_child(directory, &name, length, maximum)?;
157    file.seek(SeekFrom::Start(offset))?;
158    let size = size.unwrap_or(original.len());
159    let limit = usize::try_from(size)
160        .ok()
161        .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
162        .ok_or(IcSnapshotArtifactError::FileShape)?;
163    ic_host_artifacts::artifact::read_exact_reader(file.take(size), limit).map_err(|error| {
164        use ic_host_artifacts::artifact::ArtifactError;
165        match error {
166            ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
167                IcSnapshotArtifactError::FileShape
168            }
169            error => IcSnapshotArtifactError::Io(error.into()),
170        }
171    })
172}
173
174/// Typed local preparation failure; all original bytes, spending and references survive.
175#[derive(Debug, Error)]
176pub enum IcSnapshotUploadArtifactError {
177    /// Original declared source checksum differs from the freshly verified retained tree.
178    #[error("snapshot upload source checksum differs")]
179    SourceMismatch,
180    /// Existing guarded IC artifact admission or descriptor IO failed.
181    #[error(transparent)]
182    Artifact(#[from] IcSnapshotArtifactError),
183    /// Canonical pure bounded upload construction failed.
184    #[error(transparent)]
185    Upload(#[from] IcSnapshotUploadError),
186}