use std::io::Write as _;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use anyhow::Context as _;
pub(crate) fn hardlink_tree(source: &Path, target_root: &Path) -> anyhow::Result<()> {
fn visit(source: &Path, target_root: &Path, directory: &Path) -> anyhow::Result<()> {
for entry in std::fs::read_dir(directory)? {
let entry = entry?;
let path = entry.path();
let relative = path
.strip_prefix(source)
.expect("entry path is below entry root");
if entry.file_type()?.is_dir() {
visit(source, target_root, &path)?;
} else if relative != Path::new(crate::store::MARKER_FILE) {
let target = target_root.join(relative);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::hard_link(&path, &target)
.or_else(|_| std::fs::copy(&path, &target).map(|_| ()))?;
}
}
Ok(())
}
visit(source, target_root, source)
}
static EXPORT_WRITE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub(crate) fn write_unlinked(path: &Path, bytes: &[u8]) -> anyhow::Result<()> {
write_unlinked_with(path, |file| Ok(file.write_all(bytes)?)).map(|_| ())
}
/// an in-place write would change the prior cache entries too, because delta exports hardlink them.
pub(crate) fn write_unlinked_with(
path: &Path,
fill: impl FnOnce(&mut std::fs::File) -> anyhow::Result<()>,
) -> anyhow::Result<u64> {
write_unlinked_making(path, fill).map(|(len, ())| len)
}
pub(crate) fn write_unlinked_making<T>(
path: &Path,
fill: impl FnOnce(&mut std::fs::File) -> anyhow::Result<T>,
) -> anyhow::Result<(u64, T)> {
let unique = EXPORT_WRITE_COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let tmp = path.with_file_name(format!(".tmp-export-write.{pid}.{unique}"));
let mut file = std::fs::File::options()
.read(true)
.write(true)
.create_new(true)
.open(&tmp)
.with_context(|| format!("create staging {}", tmp.display()))?;
let filled = fill(&mut file).and_then(|made| Ok((file.metadata()?.len(), made)));
let made = match filled {
Ok(made) => made,
Err(error) => {
let _ = std::fs::remove_file(&tmp);
return Err(error).with_context(|| format!("write staging {}", tmp.display()));
}
};
crate::datastore::write::start_writeback(&file);
std::fs::rename(&tmp, path).with_context(|| format!("publish {}", path.display()))?;
Ok(made)
}