use camino::Utf8Path;
use color_eyre::eyre::Context;
use std::fs;
use std::io;
use std::path::Path;
use tracing::debug;
use crate::error::BootstrapResult;
const LOG_TARGET: &str = "pg_embed::cache";
pub fn copy_from_cache(source: &Utf8Path, target: &Utf8Path) -> BootstrapResult<()> {
log_copy_start(source, target);
fs::create_dir_all(target)
.with_context(|| format!("failed to create target directory for cache copy: {target}"))?;
copy_dir_recursive(source.as_std_path(), target.as_std_path())
.with_context(|| format!("failed to copy cached binaries from {source} to {target}"))?;
log_copy_complete(source, target);
Ok(())
}
fn log_copy_start(source: &Utf8Path, target: &Utf8Path) {
debug!(
target: LOG_TARGET,
source = %source,
target = %target,
"copying binaries from cache"
);
}
fn log_copy_complete(source: &Utf8Path, target: &Utf8Path) {
debug!(
target: LOG_TARGET,
source = %source,
target = %target,
"cache copy completed"
);
}
pub(crate) fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
if !dst.exists() {
fs::create_dir_all(dst)?;
}
for dir_entry in fs::read_dir(src)? {
let entry = dir_entry?;
let file_type = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else if file_type.is_symlink() {
copy_symlink(&src_path, &dst_path)?;
} else {
copy_file_with_permissions(&src_path, &dst_path)?;
}
}
copy_permissions(src, dst);
Ok(())
}
fn copy_file_with_permissions(src: &Path, dst: &Path) -> io::Result<()> {
fs::copy(src, dst)?;
copy_permissions(src, dst);
Ok(())
}
fn copy_permissions(src: &Path, dst: &Path) {
let Ok(metadata) = fs::metadata(src) else {
return;
};
if let Err(err) = fs::set_permissions(dst, metadata.permissions()) {
log_permission_copy_failure(src, dst, &err);
}
}
fn log_permission_copy_failure(src: &Path, dst: &Path, err: &io::Error) {
debug!(
target: LOG_TARGET,
src = %src.display(),
dst = %dst.display(),
error = %err,
"failed to copy permissions (best effort)"
);
}
#[cfg(unix)]
fn copy_symlink(src: &Path, dst: &Path) -> io::Result<()> {
let target = fs::read_link(src)?;
std::os::unix::fs::symlink(&target, dst)?;
Ok(())
}
#[cfg(not(unix))]
fn copy_symlink(src: &Path, dst: &Path) -> io::Result<()> {
if src.is_file() {
fs::copy(src, dst)?;
} else if src.is_dir() {
copy_dir_recursive(src, dst)?;
} else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"symlink target does not exist or cannot be read: {}",
src.display()
),
));
}
Ok(())
}