use crate::read::tree::{
TreeVisitor,
checksum::{ChecksumVisitor, TreeChecksumError},
walk_tree,
};
use ic_host_artifacts::artifact::Sha256Digest;
use rustix::{fd::OwnedFd, fs::FileType};
use std::{
fs::File,
io::{self, Seek, SeekFrom},
os::fd::AsFd,
path::Path,
};
#[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 {
checksum: ChecksumVisitor::new(max_file_bytes),
before_sync: &mut before_sync,
};
walk_tree(&directory_fd, Path::new(""), display_root, &mut visitor)?;
visitor.checksum.finish()
}
struct SyncVisitor<'a, F> {
checksum: ChecksumVisitor,
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))?;
self.checksum.record_file(file, relative)
}
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 {
self.checksum.unsupported(display, kind)
}
fn non_utf8(&self, display: &Path) -> Self::Error {
self.checksum.non_utf8(display)
}
}
#[cfg(test)]
mod tests;