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> {
52 use ic_host_artifacts::artifact::ArtifactError as InputError;
53
54 let identity =
55 ic_host_artifacts::artifact::hash_reader(reader, u64::MAX).map_err(
56 |error| match error {
57 InputError::Io(error) => ArtifactError::Io(error),
58 error => ArtifactError::Io(io::Error::other(error)),
59 },
60 )?;
61 Ok(ArtifactChecksumRecord::from_digest(
62 *identity.sha256.as_bytes(),
63 ))
64}
65
66#[cfg(unix)]
67pub(crate) fn copy_from_reader(
68 reader: &mut impl Read,
69 writer: &mut impl Write,
70) -> Result<ArtifactChecksumRecord, ArtifactError> {
71 use ic_host_artifacts::artifact::{ArtifactError as InputError, CopyError};
72
73 let identity =
76 ic_host_artifacts::artifact::copy_reader(reader, writer, u64::MAX).map_err(|error| {
77 match error {
78 CopyError::Input(InputError::Io(error)) | CopyError::Output(error) => {
79 ArtifactError::Io(error)
80 }
81 CopyError::Input(error) => ArtifactError::Io(io::Error::other(error)),
82 }
83 })?;
84 Ok(ArtifactChecksumRecord::from_digest(
85 *identity.sha256.as_bytes(),
86 ))
87}
88
89pub(crate) fn checksum_relative_files(
90 mut files: Vec<(PathBuf, ArtifactChecksumRecord)>,
91) -> ArtifactChecksumRecord {
92 files.sort_by(|left, right| left.0.cmp(&right.0));
93 let mut hasher = Sha256::new();
94 for (relative, checksum) in files {
95 hasher.update(relative.to_string_lossy().as_bytes());
96 hasher.update([0]);
97 hasher.update(checksum.hash().as_bytes());
98 hasher.update(*b"\n");
99 }
100 ArtifactChecksumRecord::from_digest(hasher.finalize().into())
101}
102
103#[cfg(unix)]
104fn require_utf8_tree_name(
105 name: &std::ffi::OsStr,
106 display_root: &Path,
107) -> Result<(), ArtifactError> {
108 if name.to_str().is_none() {
109 return Err(ArtifactError::NonUtf8Path {
110 path: display_root.join(name),
111 });
112 }
113 Ok(())
114}
115
116pub fn checksum_relative_path(
121 root: &Path,
122 relative: &Path,
123) -> Result<ArtifactChecksumRecord, ArtifactError> {
124 secure::checksum_relative_path(root, relative)
125}
126
127pub fn stage_relative_path(
135 root: &Path,
136 relative: &Path,
137 destination: &Path,
138) -> Result<ArtifactChecksumRecord, ArtifactError> {
139 secure::stage_relative_path(root, relative, destination)
140}
141
142#[derive(Debug, Error)]
144pub enum ArtifactError {
145 #[error("artifact path is not UTF-8: {path:?}")]
147 NonUtf8Path {
148 path: PathBuf,
150 },
151 #[error(transparent)]
153 Io(#[from] io::Error),
154 #[error("unsupported artifact entry at {path}: {kind}")]
156 UnsupportedEntry {
157 path: String,
159 kind: String,
161 },
162 #[error("secure artifact traversal is unsupported on platform {0}")]
164 UnsupportedPlatform(&'static str),
165}