use std::fs;
use std::io::ErrorKind;
use std::path::Path;
use std::sync::LazyLock;
use anyhow::Context;
use rayon::prelude::*;
static COPY_POOL: LazyLock<rayon::ThreadPool> = LazyLock::new(|| {
rayon::ThreadPoolBuilder::new()
.num_threads(4)
.stack_size(8 * 1024 * 1024)
.build()
.expect("failed to build copy thread pool")
});
pub fn copy_pool_install<R: Send>(f: impl FnOnce() -> R + Send) -> R {
COPY_POOL.install(f)
}
pub fn lower_process_priority() {
#[cfg(unix)]
{
use std::process::{Command, Stdio};
let _ = Command::new("renice")
.args(["-n", "19", "-p", &std::process::id().to_string()])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
}
pub fn copy_dir_recursive(src: &Path, dest: &Path, force: bool) -> anyhow::Result<()> {
COPY_POOL.install(|| copy_dir_recursive_inner(src, dest, force))
}
fn copy_dir_recursive_inner(src: &Path, dest: &Path, force: bool) -> anyhow::Result<()> {
fs::create_dir_all(dest).with_context(|| format!("creating directory {}", dest.display()))?;
let entries: Vec<_> = fs::read_dir(src)?.collect::<Result<Vec<_>, _>>()?;
entries.into_par_iter().try_for_each(|entry| {
let file_type = entry.file_type()?;
let src_path = entry.path();
let dest_path = dest.join(entry.file_name());
if file_type.is_symlink() {
if force {
remove_if_exists(&dest_path)?;
}
if dest_path.symlink_metadata().is_err() {
let target = fs::read_link(&src_path)
.with_context(|| format!("reading symlink {}", src_path.display()))?;
create_symlink(&target, &src_path, &dest_path)?;
}
} else if file_type.is_dir() {
copy_dir_recursive_inner(&src_path, &dest_path, force)?;
} else if !file_type.is_file() {
log::debug!("skipping non-regular file: {}", src_path.display());
} else {
if force {
remove_if_exists(&dest_path)?;
}
if dest_path.symlink_metadata().is_err() {
match reflink_copy::reflink_or_copy(&src_path, &dest_path) {
Ok(_) => {}
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
Err(e) => {
return Err(anyhow::Error::from(e)
.context(format!("copying {}", src_path.display())));
}
}
}
}
Ok(())
})?;
#[cfg(unix)]
{
let src_perms = fs::metadata(src)
.with_context(|| format!("reading permissions for {}", src.display()))?
.permissions();
fs::set_permissions(dest, src_perms)
.with_context(|| format!("setting permissions on {}", dest.display()))?;
}
Ok(())
}
pub fn remove_if_exists(path: &Path) -> anyhow::Result<()> {
if let Err(e) = fs::remove_file(path) {
anyhow::ensure!(e.kind() == ErrorKind::NotFound, e);
}
Ok(())
}
pub fn create_symlink(target: &Path, src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
#[cfg(unix)]
{
let _ = src_path; std::os::unix::fs::symlink(target, dest_path)
.with_context(|| format!("creating symlink {}", dest_path.display()))?;
}
#[cfg(windows)]
{
let is_dir = src_path.metadata().map(|m| m.is_dir()).unwrap_or(false);
if is_dir {
std::os::windows::fs::symlink_dir(target, dest_path)
.with_context(|| format!("creating symlink {}", dest_path.display()))?;
} else {
std::os::windows::fs::symlink_file(target, dest_path)
.with_context(|| format!("creating symlink {}", dest_path.display()))?;
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = (target, src_path, dest_path);
anyhow::bail!("symlink creation not supported on this platform");
}
Ok(())
}