use crate::read::tree::{TreeVisitor, checksum::TreeChecksumError, walk_tree};
use ic_host_artifacts::artifact::{Sha256Digest, hash_reader, tree::checksum_relative_files};
use rustix::{fd::OwnedFd, fs::FileType};
use std::{
fs::File,
io::{self, Seek, SeekFrom},
os::fd::AsFd,
path::{Path, PathBuf},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TreeSyncStep {
RegularFile,
NestedDirectory,
RootDirectory,
}
pub fn sync_checksum_directory_at(
directory: &File,
display_root: &Path,
max_file_bytes: u64,
mut before_sync: impl FnMut(TreeSyncStep, &Path) -> io::Result<()>,
) -> Result<Sha256Digest, TreeChecksumError> {
let directory_fd = directory.as_fd().try_clone_to_owned()?;
let mut visitor = SyncVisitor {
checksums: Vec::new(),
max_file_bytes,
before_sync: &mut before_sync,
};
walk_tree(&directory_fd, Path::new(""), display_root, &mut visitor)?;
checksum_relative_files(visitor.checksums).map_err(TreeChecksumError::Declaration)
}
struct SyncVisitor<'a, F> {
checksums: Vec<(PathBuf, Sha256Digest)>,
max_file_bytes: u64,
before_sync: &'a mut F,
}
impl<F> TreeVisitor for SyncVisitor<'_, F>
where
F: FnMut(TreeSyncStep, &Path) -> io::Result<()>,
{
type Error = TreeChecksumError;
fn file(&mut self, fd: &OwnedFd, relative: &Path, display: &Path) -> Result<(), Self::Error> {
let mut file = File::from(fd.try_clone()?);
(self.before_sync)(TreeSyncStep::RegularFile, display)?;
file.sync_all()?;
file.seek(SeekFrom::Start(0))?;
let identity = hash_reader(file, self.max_file_bytes).map_err(TreeChecksumError::Input)?;
self.checksums
.push((relative.to_path_buf(), identity.sha256));
Ok(())
}
fn enter_directory(&mut self, _relative: &Path, _display: &Path) -> Result<(), Self::Error> {
Ok(())
}
fn leave_directory(
&mut self,
fd: &OwnedFd,
relative: &Path,
display: &Path,
) -> Result<(), Self::Error> {
let step = if relative.as_os_str().is_empty() {
TreeSyncStep::RootDirectory
} else {
TreeSyncStep::NestedDirectory
};
(self.before_sync)(step, display)?;
rustix::fs::fsync(fd).map_err(io::Error::from)?;
Ok(())
}
fn unsupported(&self, display: &Path, kind: FileType) -> Self::Error {
TreeChecksumError::UnsupportedEntry {
path: display.to_path_buf(),
kind,
}
}
fn non_utf8(&self, display: &Path) -> Self::Error {
TreeChecksumError::NonUtf8Path {
path: display.to_path_buf(),
}
}
}
#[cfg(test)]
mod tests;