ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs1mod 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 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 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#[derive(Debug, Error)]
176pub enum IcSnapshotUploadArtifactError {
177 #[error("snapshot upload source checksum differs")]
179 SourceMismatch,
180 #[error(transparent)]
182 Artifact(#[from] IcSnapshotArtifactError),
183 #[error(transparent)]
185 Upload(#[from] IcSnapshotUploadError),
186}