Skip to main content

ic_backup/ops/artifacts/
mod.rs

1//! Stream artifact bytes through descriptor-based no-follow traversal.
2
3#[cfg(test)]
4mod regressions;
5#[cfg(test)]
6mod tests;
7
8use crate::model::artifacts::ArtifactChecksumRecord;
9use ic_host_artifacts::artifact::tree::DirectoryChecksumError;
10use std::{
11    io,
12    path::{Path, PathBuf},
13};
14use thiserror::Error;
15
16pub(crate) fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
17    #[cfg(unix)]
18    {
19        use ic_host_fs::read::tree::checksum::{TreeChecksumError, checksum_directory};
20        let digest = checksum_directory(path, u64::MAX).map_err(|error| match error {
21            TreeChecksumError::Io(error) => ArtifactError::Io(error),
22            TreeChecksumError::Input(error) => ArtifactError::Io(error.into()),
23            TreeChecksumError::Declaration(error) => ArtifactError::from(error),
24            TreeChecksumError::NonUtf8Path { path } => ArtifactError::NonUtf8Path { path },
25            TreeChecksumError::InvalidPath { path } => ArtifactError::UnsupportedEntry {
26                path: path.display().to_string(),
27                kind: "Unknown".into(),
28            },
29            TreeChecksumError::UnsupportedEntry { path, kind } => ArtifactError::UnsupportedEntry {
30                path: path.display().to_string(),
31                kind: format!("{kind:?}"),
32            },
33        })?;
34        Ok(ArtifactChecksumRecord::from_digest(*digest.as_bytes()))
35    }
36    #[cfg(not(unix))]
37    {
38        let _ = path;
39        Err(ArtifactError::UnsupportedPlatform(std::env::consts::OS))
40    }
41}
42
43pub(crate) fn checksum_relative_files(
44    files: Vec<(PathBuf, ArtifactChecksumRecord)>,
45) -> Result<ArtifactChecksumRecord, DirectoryChecksumError> {
46    let digest = ic_host_artifacts::artifact::tree::checksum_relative_files(
47        files
48            .into_iter()
49            .map(|(path, record)| (path, record.sha256()))
50            .collect(),
51    )?;
52    Ok(ArtifactChecksumRecord::from_digest(*digest.as_bytes()))
53}
54
55impl From<DirectoryChecksumError> for ArtifactError {
56    fn from(error: DirectoryChecksumError) -> Self {
57        match error {
58            DirectoryChecksumError::NonUtf8Path { path } => Self::NonUtf8Path { path },
59            error => Self::Io(io::Error::new(io::ErrorKind::InvalidData, error)),
60        }
61    }
62}
63
64/// Typed artifact traversal or IO failure.
65#[derive(Debug, Error)]
66pub enum ArtifactError {
67    /// A path cannot be represented exactly in the maintained UTF-8 tree digest.
68    #[error("artifact path is not UTF-8: {path:?}")]
69    NonUtf8Path {
70        /// Exact rejected filesystem path.
71        path: PathBuf,
72    },
73    /// A filesystem operation or stream failed.
74    #[error(transparent)]
75    Io(#[from] io::Error),
76    /// A tree entry is neither a regular file nor a directory.
77    #[error("unsupported artifact entry at {path}: {kind}")]
78    UnsupportedEntry {
79        /// Entry path for diagnostics.
80        path: String,
81        /// Observed filesystem entry kind.
82        kind: String,
83    },
84    /// Secure descriptor traversal is unavailable on this host.
85    #[error("secure artifact traversal is unsupported on platform {0}")]
86    UnsupportedPlatform(&'static str),
87}