From dee25c497c017c2b88d618ec1ce034f86af5c452 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 12 Aug 2026 02:21:21 +0000 Subject: Normalize legacy archive comparison inputs --- src/manifest.rs | 11 ++++++++++ tests/compare.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/manifest.rs b/src/manifest.rs index 57e57c5..c25f0a8 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -96,6 +96,9 @@ impl ArtifactManifest { let mut entry = entry .with_context(|| format!("read archive member from {}", archive_path.display()))?; let path = portable_path(&entry.path()?)?; + if is_archive_root_path(&path) || is_internal_metadata_path(&path) { + continue; + } let entry_type = entry.header().entry_type(); let kind = match entry_type { EntryType::Regular => "file", @@ -235,6 +238,14 @@ fn is_enabled_service(path: &str) -> bool { && path.ends_with(".service") } +fn is_internal_metadata_path(path: &str) -> bool { + matches!(path.split('/').next(), Some(".host" | ".fakedata")) +} + +fn is_archive_root_path(path: &str) -> bool { + path == "." || path == "./" +} + fn service_name(path: &str) -> Result<&str> { path.rsplit('/') .next() diff --git a/tests/compare.rs b/tests/compare.rs index 0853572..6dd7317 100644 --- a/tests/compare.rs +++ b/tests/compare.rs @@ -4,6 +4,7 @@ use alt_controller_image::manifest::{ ArchiveMemberRecord, ArtifactManifest, FileRecord, InitrdRecord, PackageRecord, ServiceRecord, }; use std::fs; +use std::io::Cursor; use std::os::unix::fs::symlink; use tempfile::tempdir; @@ -183,3 +184,64 @@ fn collects_comparable_semantic_facts_directly_from_a_tar_artifact() { ); assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]); } + +#[test] +fn normalizes_legacy_dot_prefixed_archive_paths_before_comparison() { + let fixture = tempdir().expect("temporary directory"); + let artifact = fixture.path().join("legacy.tar"); + let file = fs::File::create(&artifact).expect("create legacy archive"); + let mut archive = tar::Builder::new(file); + let mut header = tar::Header::new_gnu(); + header.set_size(11); + header.set_mode(0o644); + header.set_cksum(); + archive + .append_data(&mut header, "./etc/controller.conf", Cursor::new(b"controller\n")) + .expect("append legacy-style member"); + archive.finish().expect("finish legacy archive"); + + let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts"); + let expected = ArtifactManifest::new( + vec![], + vec![FileRecord::file( + "etc/controller.conf", + "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b", + )], + None, + vec![], + vec![ArchiveMemberRecord::new("etc/controller.conf", "file")], + ) + .expect("valid normalized manifest"); + + assert!(compare(&manifest, &expected).is_equivalent()); +} + +#[test] +fn excludes_legacy_internal_host_and_fakedata_archive_members() { + let fixture = tempdir().expect("temporary directory"); + let rootfs = fixture.path().join("rootfs"); + fs::create_dir_all(rootfs.join(".host/private")).expect("create host metadata"); + fs::create_dir_all(rootfs.join(".fakedata")).expect("create fakeroot metadata"); + fs::write(rootfs.join(".host/private/secret"), "metadata").expect("write host metadata"); + fs::write(rootfs.join(".fakedata/owner"), "metadata").expect("write fakeroot metadata"); + fs::create_dir_all(rootfs.join("etc")).expect("create visible directory"); + fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file"); + let legacy = fixture.path().join("legacy.tar"); + let native = fixture.path().join("native.tar"); + let mut legacy_writer = tar::Builder::new(fs::File::create(&legacy).expect("create legacy archive")); + legacy_writer + .append_dir_all(".", &rootfs) + .expect("write legacy archive with internal metadata"); + legacy_writer.finish().expect("finish legacy archive"); + NativeTarWriter::new().write(&rootfs, &native).expect("write native archive"); + + let legacy_manifest = ArtifactManifest::collect_archive(&legacy).expect("collect legacy facts"); + let native_manifest = ArtifactManifest::collect_archive(&native).expect("collect native facts"); + + assert!(legacy_manifest + .archive_members + .iter() + .all(|record| !record.path.starts_with(".host/") && !record.path.starts_with(".fakedata/"))); + let report = compare(&legacy_manifest, &native_manifest); + assert!(report.is_equivalent(), "{}", report.render()); +} -- cgit v1.2.3