use crate::fs_primitive_error::PrimitiveError;
use crate::subvolume_anchor::Anchor;
use crate::subvolume_error::MigrateError;
use crate::subvolume_sync_attrs::{carried_attributes_equal, make_fifo, open_fifo, preserve_on_descriptor, preserve_on_link};
use std::fs::{File, Metadata};
use std::io::Seek;
use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt};
use std::path::{Path, PathBuf};
use crate::subvolume_sync_pass_state_and_checks::*;
impl Pass<'_> {
pub(crate) fn copy_symlink(&mut self, directory: &Directory, relative: &Path, metadata: &Metadata, existing: Option<Metadata>) -> Result<(), MigrateError> {
let source: PathBuf = self.source.join(relative);
let destination: PathBuf = directory.child(relative.file_name().unwrap_or_default());
let target: PathBuf = MigrateError::from_io_result(std::fs::read_link(&source), "read the link", source.as_path())?;
if let Some(found) = &existing {
if found.file_type().is_symlink() && std::fs::read_link(&destination).ok().as_ref() == Some(&target) {
if (found.mtime(), found.mtime_nsec()) != (metadata.mtime(), metadata.mtime_nsec()) && !self.options.update_only {
attribute_result(preserve_on_link(source.as_path(), destination.as_path(), metadata, &mut self.stats.losses)
, self.shown(relative).as_path())?;
}
self.stats.unchanged += 1;
return Ok(());
}
if !self.replace_allowed(relative, found, metadata) {
return Ok(());
}
}
let partial: PathBuf = directory.partial();
MigrateError::from_io_result(std::os::unix::fs::symlink(&target, &partial), "create the link", self.shown(relative).as_path())?;
attribute_result(preserve_on_link(source.as_path(), partial.as_path(), metadata, &mut self.stats.losses)
, self.shown(relative).as_path())?;
self.publish(directory, relative, existing.as_ref())?;
self.stats.symlinks += 1;
return Ok(());
}
pub(crate) fn copy_fifo(&mut self, directory: &Directory, relative: &Path, metadata: &Metadata, existing: Option<Metadata>) -> Result<(), MigrateError> {
let source: PathBuf = self.source.join(relative);
let destination: PathBuf = directory.child(relative.file_name().unwrap_or_default());
if let Some(found) = &existing {
if found.file_type().is_fifo() {
let same: bool = found.mode() & 0o7777 == metadata.mode() & 0o7777 && (found.mtime(), found.mtime_nsec()) == (metadata.mtime(), metadata.mtime_nsec());
if !same && !self.options.update_only {
let handle: File = MigrateError::from_io_result(open_fifo(destination.as_path()), "open the named pipe", self.shown(relative).as_path())?;
attribute_result(preserve_on_descriptor(source.as_path(), &handle, metadata, true, &mut self.stats.losses)
, self.shown(relative).as_path())?;
}
self.stats.unchanged += 1;
return Ok(());
}
if !self.replace_allowed(relative, found, metadata) {
return Ok(());
}
}
let partial: PathBuf = directory.partial();
MigrateError::from_io_result(make_fifo(partial.as_path(), metadata.mode()), "create the named pipe", self.shown(relative).as_path())?;
let handle: File = MigrateError::from_io_result(open_fifo(partial.as_path()), "open the named pipe", self.shown(relative).as_path())?;
attribute_result(preserve_on_descriptor(source.as_path(), &handle, metadata, true, &mut self.stats.losses)
, self.shown(relative).as_path())?;
self.publish(directory, relative, existing.as_ref())?;
self.stats.fifos += 1;
return Ok(());
}
pub(crate) fn is_linked_as_recorded(&self, key: (u64, u64), found: &Metadata) -> bool {
let Some(copies) = self.links.get(&key) else { return true };
for copy in copies {
if let Ok(copy_meta) = std::fs::symlink_metadata(self.destination.join(copy))
&& copy_meta.ino() == found.ino()
{
return true;
}
}
return false;
}
pub(crate) fn record_link(&mut self, key: (u64, u64), relative: &Path) {
let copies: &mut Vec<PathBuf> = self.links.entry(key).or_default();
let mut recorded: bool = false;
for copy in copies.iter() {
if copy == relative {
recorded = true;
break;
}
}
if !recorded {
copies.push(relative.to_path_buf());
}
}
pub(crate) fn link_to_earlier_copy(&mut self, directory: &Directory, relative: &Path, key: (u64, u64), existing: Option<&Metadata>) -> Result<bool, MigrateError> {
for copy in self.links.get(&key).cloned().unwrap_or_default() {
let Ok(earlier) = Anchor::open_below(self.destination, copy.parent().unwrap_or(Path::new(""))) else { continue };
let first: PathBuf = earlier.child(copy.file_name().unwrap_or_default());
remove_if_present(directory.partial().as_path(), self.shown(relative).as_path())?;
if self.platform.hard_link(first.as_path(), directory.partial().as_path()).is_err() {
continue;
}
self.publish(directory, relative, existing)?;
self.stats.hard_linked += 1;
return Ok(true);
}
return Ok(false);
}
pub(crate) fn copy_file(&mut self, directory: &Directory, relative: &Path, listing: &Metadata, existing: Option<Metadata>) -> Result<(), MigrateError> {
let source: PathBuf = self.source.join(relative);
let destination: PathBuf = directory.child(relative.file_name().unwrap_or_default());
let shown: PathBuf = self.shown(relative);
let listed_key: (u64, u64) = (listing.dev(), listing.ino());
if let Some(found) = &existing {
if !found.is_file() {
if !self.replace_allowed(relative, found, listing) {
return Ok(());
}
} else if self.options.update_only && is_newer(found, listing) {
self.stats.unchanged += 1;
return Ok(());
} else if file_is_equal(found, listing) && (listing.nlink() <= 1 || self.is_linked_as_recorded(listed_key, found)) {
if !self.options.update_only && !carried_attributes_equal(source.as_path(), destination.as_path()) {
self.refresh_attributes(&source, &destination, listing, shown.as_path())?;
}
self.stats.unchanged += 1;
if listing.nlink() > 1 {
self.record_link(listed_key, relative);
}
return Ok(());
}
}
if listing.nlink() > 1 && self.link_to_earlier_copy(directory, relative, listed_key, existing.as_ref())? {
return Ok(());
}
let mut input: File = match File::open(&source) {
Ok(opened) => opened,
Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {
self.stats.unreadable.push(relative.to_path_buf());
return Ok(());
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
self.stats.vanished += 1;
return Ok(());
}
Err(error) => return Err(MigrateError::io("open", source.as_path(), &error)),
};
let mut attempt: u32 = 0;
loop {
attempt += 1;
let before: Metadata = MigrateError::from_io_result(input.metadata(), "examine", source.as_path())?;
if !before.is_file() {
self.stats.vanished += 1;
return Ok(());
}
remove_if_present(directory.partial().as_path(), shown.as_path())?;
let mut output: File = MigrateError::from_io_result(std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(directory.partial())
, "create", shown.as_path())?;
self.interrupt.check("copy-partial")?;
MigrateError::from_io_result(input.rewind(), "rewind", source.as_path())?;
let cloned: Result<(), PrimitiveError> = self.platform.clone_file(&input, &output, directory.partial().as_path());
if cloned.is_err() {
MigrateError::from_io_result(std::io::copy(&mut input, &mut output), "copy to", shown.as_path())?;
}
let after: Metadata = MigrateError::from_io_result(input.metadata(), "examine", source.as_path())?;
if !is_settled(&before, &after) {
drop(output);
remove_if_present(directory.partial().as_path(), shown.as_path())?;
if attempt >= COPY_ATTEMPTS {
return Err(MigrateError::Io {
action: String::from("copy"),
path: source,
reason: format!("it changed while it was being copied, {COPY_ATTEMPTS} times in a row; stop the programs writing to it and run the command again"),
});
}
self.stats.recopied += 1;
continue;
}
attribute_result(preserve_on_descriptor(source.as_path(), &output, &before, !self.options.update_only, &mut self.stats.losses)
, shown.as_path())?;
drop(output);
self.publish(directory, relative, existing.as_ref())?;
if cloned.is_ok() {
self.stats.cloned += 1;
}
self.stats.files_copied += 1;
if self.stats.copied_sample.len() < SAMPLE_SIZE {
self.stats.copied_sample.push(relative.to_path_buf());
}
if before.nlink() > 1 {
self.record_link((before.dev(), before.ino()), relative);
}
return self.interrupt.check("copy-entry");
}
}
pub(crate) fn refresh_attributes(&mut self, source: &Path, destination: &Path, metadata: &Metadata, shown: &Path) -> Result<(), MigrateError> {
let handle: File = MigrateError::from_io_result(std::fs::OpenOptions::new().read(true).custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC).open(destination)
, "open", shown)?;
return attribute_result(preserve_on_descriptor(source, &handle, metadata, true, &mut self.stats.losses)
, shown);
}
}