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