summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/manifest.rs39
-rw-r--r--tests/compare.rs37
2 files changed, 68 insertions, 8 deletions
diff --git a/src/manifest.rs b/src/manifest.rs
index 3c34ade..5b7ae77 100644
--- a/src/manifest.rs
+++ b/src/manifest.rs
@@ -131,7 +131,13 @@ impl ArtifactManifest {
}
}
EntryType::Directory => {}
- EntryType::Link => {}
+ EntryType::Link => {
+ let target = entry
+ .link_name()
+ .with_context(|| format!("read archive hardlink target for {path}"))?
+ .ok_or_else(|| anyhow::anyhow!("archive hardlink has no target: {path}"))?;
+ files.push(FileRecord::hardlink(path, portable_path(&target)?));
+ }
_ => unreachable!("entry type was checked above"),
}
}
@@ -247,16 +253,29 @@ fn portable_path(path: &Path) -> Result<String> {
bail!("absolute path is not valid in an artifact manifest: {}", path.display());
}
let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
- if value.is_empty() || value == "." {
+ if value.is_empty() {
bail!("empty path is not valid in an artifact manifest");
}
- if path
- .components()
- .any(|component| matches!(component, std::path::Component::ParentDir))
- {
- bail!("parent path is not valid in an artifact manifest: {}", path.display());
+ let mut normalized = PathBuf::new();
+ for component in path.components() {
+ match component {
+ std::path::Component::Normal(part) => normalized.push(part),
+ std::path::Component::CurDir => {}
+ std::path::Component::ParentDir => {
+ bail!("parent path is not valid in an artifact manifest: {}", path.display());
+ }
+ std::path::Component::RootDir | std::path::Component::Prefix(_) => {
+ bail!("absolute path is not valid in an artifact manifest: {}", path.display());
+ }
+ }
}
- Ok(value.to_owned())
+ if normalized.as_os_str().is_empty() {
+ return Ok(".".into());
+ }
+ let normalized = normalized
+ .to_str()
+ .ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
+ Ok(normalized.to_owned())
}
fn is_enabled_service(path: &str) -> bool {
@@ -326,6 +345,10 @@ impl FileRecord {
pub fn symlink(path: impl Into<String>, target: impl Into<String>) -> Self {
Self { path: path.into(), kind: "symlink".into(), digest: None, target: Some(target.into()) }
}
+
+ pub fn hardlink(path: impl Into<String>, target: impl Into<String>) -> Self {
+ Self { path: path.into(), kind: "hardlink".into(), digest: None, target: Some(target.into()) }
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
diff --git a/tests/compare.rs b/tests/compare.rs
index 37135a7..63e4a4f 100644
--- a/tests/compare.rs
+++ b/tests/compare.rs
@@ -217,6 +217,43 @@ fn normalizes_legacy_dot_prefixed_archive_paths_before_comparison() {
}
#[test]
+fn collects_hardlink_targets_from_an_archive_for_semantic_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, "./usr/bin/controller", Cursor::new(b"controller\n"))
+ .expect("append regular member");
+ let mut link_header = tar::Header::new_gnu();
+ link_header.set_entry_type(tar::EntryType::Link);
+ link_header.set_size(0);
+ link_header.set_cksum();
+ archive
+ .append_link(
+ &mut link_header,
+ "./usr/bin/controller-link",
+ "./usr/bin/controller",
+ )
+ .expect("append hardlink member");
+ archive.finish().expect("finish legacy archive");
+
+ let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
+
+ assert!(
+ manifest.files.iter().any(|record| {
+ record == &FileRecord::hardlink("usr/bin/controller-link", "usr/bin/controller")
+ }),
+ "collected files: {:?}",
+ manifest.files
+ );
+}
+
+#[test]
fn normalizes_dot_prefixed_paths_from_a_legacy_tree_archive() {
let fixture = tempdir().expect("temporary directory");
let rootfs = fixture.path().join("rootfs");