diff options
| author | Hermes Agent <hermes@localhost> | 2026-08-12 03:27:18 +0000 |
|---|---|---|
| committer | Hermes Agent <hermes@localhost> | 2026-08-12 03:27:18 +0000 |
| commit | 434fc29ab867d630185b632a43e82513ac918fe5 (patch) | |
| tree | d6fd932fa082b6eebe5701e46fd9fee42b675f0b /src | |
| parent | 47f461b35fe77c9454488b64fddc2576ff2561be (diff) | |
Preserve hardlinks in native tar archives
Diffstat (limited to 'src')
| -rw-r--r-- | src/archive.rs | 36 |
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( |