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