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;
10#[cfg(any(unix, test))]
11use std::io::Read;
12use std::{
13    io,
14    path::{Path, PathBuf},
15};
16use thiserror::Error;
17
18pub(crate) fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
19    #[cfg(unix)]
20    {
21        use ic_host_fs::read::tree::checksum::{TreeChecksumError, checksum_directory};
22        let digest = checksum_directory(path, u64::MAX).map_err(|error| match error {
23            TreeChecksumError::Io(error) => ArtifactError::Io(error),
24            TreeChecksumError::Input(error) => ArtifactError::Io(error.into()),
25            TreeChecksumError::Declaration(error) => ArtifactError::from(error),
26            TreeChecksumError::NonUtf8Path { path } => ArtifactError::NonUtf8Path { path },
27            TreeChecksumError::InvalidPath { path } => ArtifactError::UnsupportedEntry {
28                path: path.display().to_string(),
29                kind: "Unknown".into(),
30            },
31            TreeChecksumError::UnsupportedEntry { path, kind } => ArtifactError::UnsupportedEntry {
32                path: path.display().to_string(),
33                kind: format!("{kind:?}"),
34            },
35        })?;
36        Ok(ArtifactChecksumRecord::from_digest(*digest.as_bytes()))
37    }
38    #[cfg(not(unix))]
39    {
40        let _ = path;
41        Err(ArtifactError::UnsupportedPlatform(std::env::consts::OS))
42    }
43}
44
45#[cfg(any(unix, test))]
46/// Stream an already-open reader using a bounded transfer buffer.
47///
48/// Interrupted reads retry internally. The caller owns blocking and timeouts;
49/// no network or paid-operation retry is performed.
50///
51/// # Errors
52/// Returns other reader IO failures unchanged; impossible byte counts reject as
53/// [`io::ErrorKind::InvalidData`] rather than indexing outside the transfer buffer.
54pub(crate) fn checksum_reader(
55    reader: &mut impl Read,
56) -> Result<ArtifactChecksumRecord, ArtifactError> {
57    let identity =
58        ic_host_artifacts::artifact::hash_reader(reader, u64::MAX).map_err(io::Error::from)?;
59    Ok(ArtifactChecksumRecord::from_digest(
60        *identity.sha256.as_bytes(),
61    ))
62}
63
64pub(crate) fn checksum_relative_files(
65    files: Vec<(PathBuf, ArtifactChecksumRecord)>,
66) -> Result<ArtifactChecksumRecord, DirectoryChecksumError> {
67    let digest = ic_host_artifacts::artifact::tree::checksum_relative_files(
68        files
69            .into_iter()
70            .map(|(path, record)| (path, record.sha256()))
71            .collect(),
72    )?;
73    Ok(ArtifactChecksumRecord::from_digest(*digest.as_bytes()))
74}
75
76impl From<DirectoryChecksumError> for ArtifactError {
77    fn from(error: DirectoryChecksumError) -> Self {
78        match error {
79            DirectoryChecksumError::NonUtf8Path { path } => Self::NonUtf8Path { path },
80            error => Self::Io(io::Error::new(io::ErrorKind::InvalidData, error)),
81        }
82    }
83}
84
85/// Typed artifact traversal or IO failure.
86#[derive(Debug, Error)]
87pub enum ArtifactError {
88    /// A path cannot be represented exactly in the maintained UTF-8 tree digest.
89    #[error("artifact path is not UTF-8: {path:?}")]
90    NonUtf8Path {
91        /// Exact rejected filesystem path.
92        path: PathBuf,
93    },
94    /// A filesystem operation or stream failed.
95    #[error(transparent)]
96    Io(#[from] io::Error),
97    /// A tree entry is neither a regular file nor a directory.
98    #[error("unsupported artifact entry at {path}: {kind}")]
99    UnsupportedEntry {
100        /// Entry path for diagnostics.
101        path: String,
102        /// Observed filesystem entry kind.
103        kind: String,
104    },
105    /// Secure descriptor traversal is unavailable on this host.
106    #[error("secure artifact traversal is unsupported on platform {0}")]
107    UnsupportedPlatform(&'static str),
108}