use crate::subvolume_anchor::Anchor;
use crate::subvolume_error::{Interrupt, MigrateError};
use crate::subvolume_platform::Platform;
use crate::subvolume_remove::remove_tree;
use crate::subvolume_sync_attrs::{AttributeFailure, AttributeLosses};
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
pub(crate) const PARTIAL_NAME: &str = ".cli-git-migrate-partial";
pub(crate) const COPY_ATTEMPTS: u32 = 3;
#[derive(Clone, Copy, Debug)]
pub struct SyncOptions<'a> {
pub delete: bool,
pub update_only: bool,
pub excluded: &'a [PathBuf],
pub created_since: Option<i64>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SyncStats {
pub entries: u64,
pub directories: u64,
pub files_copied: u64,
pub cloned: u64,
pub hard_linked: u64,
pub symlinks: u64,
pub fifos: u64,
pub unchanged: u64,
pub deleted: u64,
pub vanished: u64,
pub unreadable: Vec<PathBuf>,
pub special_skipped: Vec<PathBuf>,
pub losses: AttributeLosses,
pub conflicts: Vec<PathBuf>,
pub recopied: u64,
pub left_out: u64,
pub copied_sample: Vec<PathBuf>,
}
pub(crate) const SAMPLE_SIZE: usize = 20;
pub(crate) enum Work {
Visit(PathBuf),
Finish(PathBuf, Metadata),
}
pub(crate) struct Directory {
pub(crate) anchor: Anchor,
pub(crate) temporary: OsString,
}
impl Directory {
pub(crate) fn child(&self, name: &OsStr) -> PathBuf {
return self.anchor.child(name);
}
pub(crate) fn partial(&self) -> PathBuf {
return self.anchor.child(self.temporary.as_os_str());
}
}
pub(crate) struct Pass<'a> {
pub(crate) platform: &'a dyn Platform,
pub(crate) source: &'a Path,
pub(crate) destination: &'a Path,
pub(crate) options: SyncOptions<'a>,
pub(crate) interrupt: &'a Interrupt,
pub(crate) stats: SyncStats,
pub(crate) links: HashMap<(u64, u64), Vec<PathBuf>>,
}
pub(crate) fn file_is_equal(copy: &Metadata, original: &Metadata) -> bool {
return copy.is_file()
&& copy.len() == original.len()
&& copy.mtime() == original.mtime()
&& copy.mtime_nsec() == original.mtime_nsec()
&& copy.mode() & 0o7777 == original.mode() & 0o7777;
}
pub(crate) fn is_newer(left: &Metadata, right: &Metadata) -> bool {
return (left.mtime(), left.mtime_nsec()) > (right.mtime(), right.mtime_nsec());
}
pub(crate) fn is_settled(before: &Metadata, after: &Metadata) -> bool {
return before.len() == after.len()
&& (before.mtime(), before.mtime_nsec()) == (after.mtime(), after.mtime_nsec())
&& (before.ctime(), before.ctime_nsec()) == (after.ctime(), after.ctime_nsec());
}
pub(crate) fn remove_entry(path: &Path, shown: &Path) -> Result<(), MigrateError> {
return MigrateError::from_io_result(remove_tree(path), "remove", shown);
}
pub(crate) fn remove_if_present(path: &Path, shown: &Path) -> Result<(), MigrateError> {
return match std::fs::symlink_metadata(path) {
Ok(_) => remove_entry(path, shown),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(MigrateError::io("examine", shown, &error)),
};
}
pub(crate) fn attribute_result<T>(result: Result<T, AttributeFailure>, shown: &Path) -> Result<T, MigrateError> {
return match result {
Ok(value) => Ok(value),
Err(failure) => Err(attribute_error(failure, shown)),
};
}
pub(crate) fn attribute_error(failure: AttributeFailure, shown: &Path) -> MigrateError {
return MigrateError::io(failure.action, shown, &failure.error);
}
pub(crate) fn is_replaced(error: &std::io::Error) -> bool {
return matches!(error.raw_os_error(), Some(libc::ELOOP | libc::ENOTDIR | libc::ENOENT));
}
pub(crate) fn vanished_is_counted<T>(outcome: std::io::Result<T>, vanished: &mut u64) -> std::io::Result<Option<T>> {
return match outcome {
Ok(found) => Ok(Some(found)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
*vanished += 1;
Ok(None)
}
Err(error) => Err(error),
};
}
impl Pass<'_> {
pub(crate) fn shown(&self, relative: &Path) -> PathBuf {
return self.destination.join(relative);
}
pub(crate) fn replace_allowed(&mut self, relative: &Path, found: &Metadata, original: &Metadata) -> bool {
if !self.options.update_only {
return true;
}
if found.is_dir() || !is_newer(original, found) {
self.stats.conflicts.push(relative.to_path_buf());
return false;
}
return true;
}
pub(crate) fn publish(&self, directory: &Directory, relative: &Path, existing: Option<&Metadata>) -> Result<(), MigrateError> {
let name: &OsStr = relative.file_name().unwrap_or_default();
let destination: PathBuf = directory.child(name);
if let Some(found) = existing && found.is_dir() {
remove_entry(destination.as_path(), self.shown(relative).as_path())?;
}
return MigrateError::from_io_result(std::fs::rename(directory.partial(), &destination), "put the finished copy in place of", self.shown(relative).as_path());
}
}
pub(crate) fn make_writable(anchor: &Anchor, shown: &Path) -> Result<(), MigrateError> {
let metadata: Metadata = MigrateError::from_io_result(anchor.file().metadata(), "examine", shown)?;
if metadata.mode() & 0o700 != 0o700 {
MigrateError::from_io_result(anchor.file().set_permissions(std::os::unix::fs::PermissionsExt::from_mode(metadata.mode() | 0o700))
, "make writable", shown)?;
}
return Ok(());
}