ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs1use super::super::metrics::LocalOperation;
4use super::{
5 DownloadJournalGuard, File, IcSnapshotArtifactError, Mode, OFlags, REGIONS,
6 check_directory_identity, hex_bytes, open_directory, unix_fs, verification::open_regular_child,
7};
8use crate::model::{
9 ic_snapshot_data::{MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES, validate_kind},
10 ic_snapshot_metadata::IcSnapshotMetadataReply,
11 ic_snapshot_upload::{IcSnapshotUploadError, IcSnapshotUploadRequest},
12 operation_plan::OperationPlanRecord,
13};
14use ic_management_canister_types::SnapshotDataKind;
15use std::io::{self, Read, Seek, SeekFrom};
16use std::time::Instant;
17use thiserror::Error;
18
19impl DownloadJournalGuard<'_> {
20 pub fn prepare_ic_snapshot_upload_metadata<'source>(
30 &self,
31 plan: &'source OperationPlanRecord,
32 snapshot: &str,
33 metadata: &'source IcSnapshotMetadataReply<'source>,
34 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
35 let started = Instant::now();
36 let result = (|| {
37 let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
38 Ok(IcSnapshotUploadRequest::metadata(
39 plan, metadata, &checksum,
40 )?)
41 })();
42 self.record_ic_snapshot_metrics(
43 LocalOperation::UploadMetadata,
44 started,
45 result.is_ok(),
46 None,
47 );
48 result
49 }
50
51 pub fn prepare_ic_snapshot_upload_data<'source>(
63 &self,
64 plan: &OperationPlanRecord,
65 snapshot: &str,
66 metadata_upload: &IcSnapshotUploadRequest<'source>,
67 snapshot_id: &[u8],
68 source_kind: SnapshotDataKind,
69 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
70 let started = Instant::now();
71 let result = (|| {
72 let metadata = metadata_upload.source();
73 metadata_upload.validate_data_destination(snapshot_id)?;
74 validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
75 self.require_upload_source(plan, snapshot, metadata_upload)?;
76 let entry = self
77 .record
78 .artifact(metadata.request().target(), snapshot)
79 .map_err(super::DownloadJournalError::from)
80 .map_err(IcSnapshotArtifactError::from)?;
81 let parent_path = self.layout.root().join("artifacts");
82 let parent = open_directory(&parent_path).map_err(IcSnapshotArtifactError::from)?;
83 let path = self.layout.root().join(entry.artifact_path());
84 let directory = File::from(
85 unix_fs::openat(
86 &parent,
87 path.file_name()
88 .ok_or(IcSnapshotArtifactError::CustodyChanged)?,
89 OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
90 Mode::empty(),
91 )
92 .map_err(io::Error::from)
93 .map_err(IcSnapshotArtifactError::from)?,
94 );
95 let chunk = read_chunk(&directory, metadata, &source_kind)?;
96 let payload =
97 IcSnapshotUploadRequest::data(metadata_upload, snapshot_id, source_kind, &chunk)?;
98 check_directory_identity(&parent_path, &parent)?;
99 check_directory_identity(&path, &directory)?;
100 self.require_upload_source(plan, snapshot, metadata_upload)?;
101 Ok((payload, chunk.len()))
102 })();
103 self.record_ic_snapshot_metrics(
104 LocalOperation::UploadData,
105 started,
106 result.is_ok(),
107 result.as_ref().ok().map(|(_, bytes)| *bytes),
108 );
109 result.map(|(payload, _)| payload)
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 kind {
136 SnapshotDataKind::WasmModule { offset, size } => (
137 REGIONS[0].to_owned(),
138 Some(values.wasm_module_size),
139 *offset,
140 Some(*size),
141 ),
142 SnapshotDataKind::WasmMemory { offset, size } => (
143 REGIONS[1].to_owned(),
144 Some(values.wasm_memory_size),
145 *offset,
146 Some(*size),
147 ),
148 SnapshotDataKind::StableMemory { offset, size } => (
149 REGIONS[2].to_owned(),
150 Some(values.stable_memory_size),
151 *offset,
152 Some(*size),
153 ),
154 SnapshotDataKind::WasmChunk { hash } => {
155 (format!("chunk-{}.bin", hex_bytes(hash)), None, 0, None)
156 }
157 };
158 let maximum = length.unwrap_or(MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64);
159 let (mut file, original) = open_regular_child(directory, &name, length, maximum)?;
160 file.seek(SeekFrom::Start(offset))?;
161 let size = size.unwrap_or(original.len());
162 let limit = usize::try_from(size)
163 .ok()
164 .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
165 .ok_or(IcSnapshotArtifactError::FileShape)?;
166 let bytes =
167 ic_host_artifacts::artifact::read_reader(file.take(size), limit).map_err(|error| {
168 use ic_host_artifacts::artifact::ArtifactError;
169 match error {
170 ArtifactError::LimitExceeded { .. } => IcSnapshotArtifactError::FileShape,
171 error => IcSnapshotArtifactError::Io(error.into()),
172 }
173 })?;
174 if u64::try_from(bytes.len()).ok() != Some(size) {
175 return Err(IcSnapshotArtifactError::FileShape);
176 }
177 Ok(bytes)
178}
179
180#[derive(Debug, Error)]
182pub enum IcSnapshotUploadArtifactError {
183 #[error("snapshot upload source checksum differs")]
185 SourceMismatch,
186 #[error(transparent)]
188 Artifact(#[from] IcSnapshotArtifactError),
189 #[error(transparent)]
191 Upload(#[from] IcSnapshotUploadError),
192}