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};
11use std::{
12 io::{self, Read, Write},
13 path::{Path, PathBuf},
14};
15use thiserror::Error;
16
17pub fn checksum_file(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
22 secure::checksum_path(path, secure::ExpectedArtifactType::File)
23}
24
25pub fn checksum_path(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
30 secure::checksum_path(path, secure::ExpectedArtifactType::Any)
31}
32
33pub fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
38 secure::checksum_path(path, secure::ExpectedArtifactType::Directory)
39}
40
41pub fn checksum_reader(reader: &mut impl Read) -> Result<ArtifactChecksumRecord, ArtifactError> {
46 let mut hasher = Sha256::new();
47 let mut buffer = vec![0; 64 * 1024];
48 loop {
49 let read = reader.read(&mut buffer)?;
50 if read == 0 {
51 break;
52 }
53 hasher.update(&buffer[..read]);
54 }
55 Ok(ArtifactChecksumRecord::from_digest(
56 hasher.finalize().into(),
57 ))
58}
59
60pub(crate) fn copy_from_reader(
61 reader: &mut impl Read,
62 writer: &mut impl Write,
63) -> Result<ArtifactChecksumRecord, ArtifactError> {
64 let mut hasher = Sha256::new();
65 let mut buffer = vec![0; 64 * 1024];
66 loop {
67 let read = reader.read(&mut buffer)?;
68 if read == 0 {
69 break;
70 }
71 writer.write_all(&buffer[..read])?;
72 hasher.update(&buffer[..read]);
73 }
74 Ok(ArtifactChecksumRecord::from_digest(
75 hasher.finalize().into(),
76 ))
77}
78
79pub(crate) fn checksum_relative_files(
80 mut files: Vec<(PathBuf, ArtifactChecksumRecord)>,
81) -> ArtifactChecksumRecord {
82 files.sort_by(|left, right| left.0.cmp(&right.0));
83 let mut hasher = Sha256::new();
84 for (relative, checksum) in files {
85 hasher.update(relative.to_string_lossy().as_bytes());
86 hasher.update([0]);
87 hasher.update(checksum.hash().as_bytes());
88 hasher.update(*b"\n");
89 }
90 ArtifactChecksumRecord::from_digest(hasher.finalize().into())
91}
92
93#[cfg(unix)]
94fn require_utf8_tree_name(
95 name: &std::ffi::OsStr,
96 display_root: &Path,
97) -> Result<(), ArtifactError> {
98 if name.to_str().is_none() {
99 return Err(ArtifactError::NonUtf8Path {
100 path: display_root.join(name),
101 });
102 }
103 Ok(())
104}
105
106pub fn checksum_relative_path(
111 root: &Path,
112 relative: &Path,
113) -> Result<ArtifactChecksumRecord, ArtifactError> {
114 secure::checksum_relative_path(root, relative)
115}
116
117pub fn stage_relative_path(
125 root: &Path,
126 relative: &Path,
127 destination: &Path,
128) -> Result<ArtifactChecksumRecord, ArtifactError> {
129 secure::stage_relative_path(root, relative, destination)
130}
131
132#[derive(Debug, Error)]
134pub enum ArtifactError {
135 #[error("artifact path is not UTF-8: {path:?}")]
137 NonUtf8Path {
138 path: PathBuf,
140 },
141 #[error(transparent)]
143 Io(#[from] io::Error),
144 #[error("unsupported artifact entry at {path}: {kind}")]
146 UnsupportedEntry {
147 path: String,
149 kind: String,
151 },
152 #[error("secure artifact traversal is unsupported on platform {0}")]
154 UnsupportedPlatform(&'static str),
155}