use crate::{CopyError, CopyOptions};
use std::fs;
use std::io::{self, copy};
use std::os::unix::fs::{self as unix_fs, FileTypeExt, PermissionsExt};
use std::path::Path;
use walkdir_minimal::WalkDir;
impl CopyOptions {
fn build_walker(&self, path: &Path) -> Result<WalkDir, CopyError> {
Ok(WalkDir::new(path)?
.max_depth(self.depth)
.follow_links(self.follow_symlinks)
.detect_loops(true)
.ignore_permission_denied(self.ignore_permission_denied))
}
}
pub fn copy_all<P: AsRef<Path>>(src: P, dst: P, opts: &CopyOptions) -> Result<(), CopyError> {
let src = src.as_ref();
let dst = dst.as_ref();
if !src.exists() {
return Err(CopyError::Io(io::Error::from(io::ErrorKind::NotFound)));
}
if src.is_file() {
let dest_path = if dst.is_dir() {
dst.join(src.file_name().unwrap_or_default())
} else {
dst.to_path_buf()
};
return copy_one(src, &dest_path, opts);
}
if src.is_dir() {
if dst.exists() && !dst.is_dir() {
return Err(CopyError::Io(io::Error::from(io::ErrorKind::NotADirectory)));
}
let base_dst = if !dst.exists() {
fs::create_dir_all(dst)?;
dst.to_path_buf()
} else if opts.content_only {
dst.to_path_buf()
} else {
let p = dst.join(src.file_name().unwrap_or_default());
fs::create_dir_all(&p)?;
p
};
return walk_and_copy(src, &base_dst, opts);
}
Err(CopyError::Io(io::Error::from(io::ErrorKind::Unsupported)))
}
fn walk_and_copy(src: &Path, dst: &Path, opts: &CopyOptions) -> Result<(), CopyError> {
let src_real = if opts.follow_symlinks && opts.restrict_symlinks {
src.canonicalize().ok()
} else {
None
};
let walker = opts.build_walker(src)?;
for entry_res in walker {
let entry = entry_res.map_err(CopyError::Walk)?;
let src_path = entry.path();
let dst_path = dst.join(src_path.strip_prefix(src).unwrap_or(src_path));
let ft = entry.symlink_metadata().map_err(CopyError::Io)?.file_type();
if ft.is_block_device() || ft.is_char_device() || ft.is_fifo() || ft.is_socket() {
continue;
}
if let Some(ref base_real) = src_real {
if let Ok(entry_real) = src_path.canonicalize() {
if !entry_real.starts_with(base_real) {
eprintln!("Skipping entry outside source tree: {}", src_path.display());
continue;
}
}
}
if ft.is_dir() {
if !dst_path.exists() {
fs::create_dir_all(&dst_path)?;
}
} else if ft.is_file() {
copy_one(src_path, &dst_path, opts)?;
} else if ft.is_symlink() {
recreate_symlink(src_path, &dst_path, opts)?;
}
}
Ok(())
}
pub fn copy_one<P: AsRef<Path>>(src: P, dst: P, opts: &CopyOptions) -> Result<(), CopyError> {
let src = src.as_ref();
let dst = dst.as_ref();
if dst.exists() {
if !opts.overwrite {
return Ok(());
}
fs::remove_file(dst)?;
} else if let Some(p) = dst.parent() {
fs::create_dir_all(p)?;
}
let mut input = fs::File::open(src)?;
let mut output = fs::File::create(dst)?;
copy(&mut input, &mut output)?;
let mode = fs::metadata(src)?.permissions().mode() & 0o777;
let mut perms = output.metadata()?.permissions();
perms.set_mode(mode);
fs::set_permissions(dst, perms)?;
Ok(())
}
fn recreate_symlink(src: &Path, dst: &Path, opts: &CopyOptions) -> Result<(), CopyError> {
let target = fs::read_link(src)?;
if dst.exists() {
if opts.overwrite {
fs::remove_file(dst)?;
} else {
return Ok(());
}
}
if let Some(p) = dst.parent() {
fs::create_dir_all(p)?;
}
unix_fs::symlink(&target, dst)?;
Ok(())
}