summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 03:27:18 +0000
committerHermes Agent <hermes@localhost>2026-08-12 03:27:18 +0000
commit434fc29ab867d630185b632a43e82513ac918fe5 (patch)
treed6fd932fa082b6eebe5701e46fd9fee42b675f0b /src
parent47f461b35fe77c9454488b64fddc2576ff2561be (diff)
Preserve hardlinks in native tar archives
Diffstat (limited to 'src')
-rw-r--r--src/archive.rs36
1 files changed, 31 insertions, 5 deletions
diff --git a/src/archive.rs b/src/archive.rs
index 0076364..70392fa 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -1,4 +1,6 @@
+use std::collections::BTreeMap;
use std::fs::{self, File};
+use std::os::unix::fs::MetadataExt;
use std::path::Path;
use anyhow::{bail, Context, Result};
@@ -26,7 +28,7 @@ impl NativeTarWriter {
let output = output.as_ref();
let file = File::create(output).with_context(|| format!("create archive {}", output.display()))?;
let mut archive = Builder::new(file);
- append_tree(&mut archive, rootfs, Path::new(""))?;
+ append_tree(&mut archive, rootfs, Path::new(""), &mut BTreeMap::new())?;
archive
.finish()
.with_context(|| format!("finish archive {}", output.display()))?;
@@ -34,7 +36,12 @@ impl NativeTarWriter {
}
}
-fn append_tree(archive: &mut Builder<File>, rootfs: &Path, relative: &Path) -> Result<()> {
+fn append_tree(
+ archive: &mut Builder<File>,
+ rootfs: &Path,
+ relative: &Path,
+ hardlink_targets: &mut BTreeMap<(u64, u64), std::path::PathBuf>,
+) -> Result<()> {
let directory = rootfs.join(relative);
let mut entries = fs::read_dir(&directory)
.with_context(|| format!("read rootfs directory {}", directory.display()))?
@@ -54,9 +61,9 @@ fn append_tree(archive: &mut Builder<File>, rootfs: &Path, relative: &Path) -> R
if file_type.is_dir() {
append_directory(archive, &archive_path, &metadata)?;
- append_tree(archive, rootfs, &archive_path)?;
+ append_tree(archive, rootfs, &archive_path, hardlink_targets)?;
} else if file_type.is_file() {
- append_file(archive, &path, &archive_path, &metadata)?;
+ append_file(archive, &path, &archive_path, &metadata, hardlink_targets)?;
} else if file_type.is_symlink() {
append_symlink(archive, &path, &archive_path, &metadata)?;
} else {
@@ -91,12 +98,31 @@ fn append_file(
source: &Path,
path: &Path,
metadata: &fs::Metadata,
+ hardlink_targets: &mut BTreeMap<(u64, u64), std::path::PathBuf>,
) -> Result<()> {
+ let key = (metadata.dev(), metadata.ino());
+ if let Some(target) = hardlink_targets.get(&key) {
+ return append_hardlink(archive, path, target, metadata);
+ }
let mut header = deterministic_header(metadata, EntryType::Regular, metadata.len());
let input = File::open(source).with_context(|| format!("open rootfs file {}", source.display()))?;
archive
.append_data(&mut header, path, input)
- .with_context(|| format!("append file {}", path.display()))
+ .with_context(|| format!("append file {}", path.display()))?;
+ hardlink_targets.insert(key, path.to_path_buf());
+ Ok(())
+}
+
+fn append_hardlink(
+ archive: &mut Builder<File>,
+ path: &Path,
+ target: &Path,
+ metadata: &fs::Metadata,
+) -> Result<()> {
+ let mut header = deterministic_header(metadata, EntryType::Link, 0);
+ archive
+ .append_link(&mut header, path, target)
+ .with_context(|| format!("append hardlink {}", path.display()))
}
fn append_symlink(