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