ic_backup/ops/artifacts/
mod.rs1#[cfg(test)]
4mod regressions;
5mod secure;
6#[cfg(test)]
7mod tests;
8
9use crate::model::artifacts::ArtifactChecksumRecord;
10use sha2::{Digest, Sha256};
11#[cfg(unix)]
12use std::io::Write;
13use std::{
14 io::{self, Read},
15 path::{Path, PathBuf},
16};
17use thiserror::Error;
18
19pub fn checksum_file(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
24 secure::checksum_path(path, secure::ExpectedArtifactType::File)
25}
26
27pub fn checksum_path(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
32 secure::checksum_path(path, secure::ExpectedArtifactType::Any)
33}
34
35pub fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
40 secure::checksum_path(path, secure::ExpectedArtifactType::Directory)
41}
42
43pub fn checksum_reader(reader: &mut impl Read) -> Result<ArtifactChecksumRecord, ArtifactError> {
48 let mut hasher = Sha256::new();
49 let mut buffer = vec![0; 64 * 1024];
50 loop {
51 let read = reader.read(&mut buffer)?;
52 if read == 0 {
53 break;
54 }
55 hasher.update(&buffer[..read]);
56 }
57 Ok(ArtifactChecksumRecord::from_digest(
58 hasher.finalize().into(),
59 ))
60}
61
62#[cfg(unix)]
63pub(crate) fn copy_from_reader(
64 reader: &mut impl Read,
65 writer: &mut impl Write,
66) -> Result<ArtifactChecksumRecord, ArtifactError> {
67 use ic_host_tools::artifact::{ArtifactError as InputError, CopyError};
68
69 let identity = ic_host_tools::artifact::copy_reader(reader, writer, u64::MAX).map_err(
72 |error| match error {
73 CopyError::Input(InputError::Io(error)) | CopyError::Output(error) => {
74 ArtifactError::Io(error)
75 }
76 CopyError::Input(error) => ArtifactError::Io(io::Error::other(error)),
77 },
78 )?;
79 Ok(ArtifactChecksumRecord::from_digest(
80 *identity.sha256.as_bytes(),
81 ))
82}
83
84pub(crate) fn checksum_relative_files(
85 mut files: Vec<(PathBuf, ArtifactChecksumRecord)>,
86) -> ArtifactChecksumRecord {
87 files.sort_by(|left, right| left.0.cmp(&right.0));
88 let mut hasher = Sha256::new();
89 for (relative, checksum) in files {
90 hasher.update(relative.to_string_lossy().as_bytes());
91 hasher.update([0]);
92 hasher.update(checksum.hash().as_bytes());
93 hasher.update(*b"\n");
94 }
95 ArtifactChecksumRecord::from_digest(hasher.finalize().into())
96}
97
98#[cfg(unix)]
99fn require_utf8_tree_name(
100 name: &std::ffi::OsStr,
101 display_root: &Path,
102) -> Result<(), ArtifactError> {
103 if name.to_str().is_none() {
104 return Err(ArtifactError::NonUtf8Path {
105 path: display_root.join(name),
106 });
107 }
108 Ok(())
109}
110
111pub fn checksum_relative_path(
116 root: &Path,
117 relative: &Path,
118) -> Result<ArtifactChecksumRecord, ArtifactError> {
119 secure::checksum_relative_path(root, relative)
120}
121
122pub fn stage_relative_path(
130 root: &Path,
131 relative: &Path,
132 destination: &Path,
133) -> Result<ArtifactChecksumRecord, ArtifactError> {
134 secure::stage_relative_path(root, relative, destination)
135}
136
137#[derive(Debug, Error)]
139pub enum ArtifactError {
140 #[error("artifact path is not UTF-8: {path:?}")]
142 NonUtf8Path {
143 path: PathBuf,
145 },
146 #[error(transparent)]
148 Io(#[from] io::Error),
149 #[error("unsupported artifact entry at {path}: {kind}")]
151 UnsupportedEntry {
152 path: String,
154 kind: String,
156 },
157 #[error("secure artifact traversal is unsupported on platform {0}")]
159 UnsupportedPlatform(&'static str),
160}