summaryrefslogtreecommitdiff
path: root/src/manifest.rs
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 03:36:47 +0000
committerHermes Agent <hermes@localhost>2026-08-12 03:36:47 +0000
commit754972b95ba2fabc082aed387c246e632a8d7434 (patch)
tree9a86dc5c927ea6dfea5d381ad1d9ccdcf882c2bd /src/manifest.rs
parent434fc29ab867d630185b632a43e82513ac918fe5 (diff)
Preserve rootfs hardlink facts in manifests
Diffstat (limited to 'src/manifest.rs')
-rw-r--r--src/manifest.rs23
1 files changed, 19 insertions, 4 deletions
diff --git a/src/manifest.rs b/src/manifest.rs
index 307913e..6168706 100644
--- a/src/manifest.rs
+++ b/src/manifest.rs
@@ -1,6 +1,7 @@
-use std::collections::HashSet;
+use std::collections::{BTreeMap, HashSet};
use std::fs;
use std::io::Read;
+use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
@@ -154,8 +155,15 @@ impl ArtifactManifest {
fn collect_files(rootfs: &Path) -> Result<Vec<FileRecord>> {
let mut files = Vec::new();
- for entry in WalkDir::new(rootfs).follow_links(false).min_depth(1) {
- let entry = entry.with_context(|| format!("walk rootfs {}", rootfs.display()))?;
+ let mut entries = WalkDir::new(rootfs)
+ .follow_links(false)
+ .min_depth(1)
+ .into_iter()
+ .collect::<std::result::Result<Vec<_>, _>>()
+ .with_context(|| format!("walk rootfs {}", rootfs.display()))?;
+ entries.sort_by_key(|entry| entry.path().to_path_buf());
+ let mut hardlink_targets: BTreeMap<(u64, u64), String> = BTreeMap::new();
+ for entry in entries {
let relative = entry.path().strip_prefix(rootfs).expect("walk entry is below rootfs");
if is_internal_metadata_path(&portable_path(relative)?) {
continue;
@@ -163,7 +171,14 @@ fn collect_files(rootfs: &Path) -> Result<Vec<FileRecord>> {
let path = portable_path(relative)?;
let file_type = entry.file_type();
if file_type.is_file() {
- files.push(FileRecord::file(path, sha256_file(entry.path())?));
+ let metadata = entry.metadata().with_context(|| format!("inspect rootfs file {}", entry.path().display()))?;
+ let key = (metadata.dev(), metadata.ino());
+ if let Some(target) = hardlink_targets.get(&key) {
+ files.push(FileRecord::hardlink(path, target.clone()));
+ } else {
+ hardlink_targets.insert(key, path.clone());
+ files.push(FileRecord::file(path, sha256_file(entry.path())?));
+ }
} else if file_type.is_symlink() {
let target = fs::read_link(entry.path())
.with_context(|| format!("read rootfs symlink {}", entry.path().display()))?;