Skip to content

Commit

Permalink
tar: Handle absolute hardlinked paths to sysroot
Browse files Browse the repository at this point in the history
See containers/bootc#856

Basically we need to handle absolute paths here too.

This currently fails tests as we need to normalize in multiple
places.
  • Loading branch information
cgwalters committed Nov 1, 2024
1 parent 05dd65f commit 9481ab8
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 4 deletions.
48 changes: 46 additions & 2 deletions lib/src/tar/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,23 @@ fn remap_etc_path(path: &Utf8Path) -> Cow<Utf8Path> {
}
}

/// Checks whether the given path has `prefix` as a prefix, ignoring or relative path
/// prefixes. The input paths are also normalized.
fn path_starts_with_ignoring_absolute(
path: impl AsRef<Utf8Path>,
prefix: impl AsRef<Utf8Path>,
) -> bool {
fn filter_prefix(c: &Utf8Component) -> bool {
!matches!(
c,
camino::Utf8Component::RootDir | camino::Utf8Component::CurDir,
)
}
let path = path.as_ref().components().filter(filter_prefix);
let prefix = prefix.as_ref().components().filter(filter_prefix);
path.zip(prefix).all(|(a, b)| a == b)
}

fn normalize_validate_path<'a>(
path: &'a Utf8Path,
config: &'_ TarImportConfig,
Expand Down Expand Up @@ -266,7 +283,7 @@ pub(crate) fn filter_tar(

let is_modified = header.mtime().unwrap_or_default() > 0;
let is_regular = header.entry_type() == tar::EntryType::Regular;
if path.strip_prefix(crate::tar::REPO_PREFIX).is_ok() {
if path_starts_with_ignoring_absolute(&path, crate::tar::REPO_PREFIX) {
// If it's a modified file in /sysroot, it may be a target for future hardlinks.
// In that case, we copy the data off to a temporary file. Then the first hardlink
// to it becomes instead the real file, and any *further* hardlinks refer to that
Expand Down Expand Up @@ -294,7 +311,7 @@ pub(crate) fn filter_tar(
.ok_or_else(|| anyhow!("Invalid empty hardlink"))?;
let target: &Utf8Path = (&*target).try_into()?;
// If this is a hardlink into /sysroot...
if target.strip_prefix(crate::tar::REPO_PREFIX).is_ok() {
if path_starts_with_ignoring_absolute(&target, crate::tar::REPO_PREFIX) {
// And we found a previously processed modified file there
if let Some((mut header, data)) = changed_sysroot_objects.remove(target) {
tracing::debug!("Making {path} canonical for sysroot link {target}");
Expand Down Expand Up @@ -509,6 +526,33 @@ mod tests {
}
}

#[test]
fn test_path_match_ignoring_absolute() {
for (path, prefix) in [
("foo", "foo"),
("foo", "/"),
("foo/bar/baz", "foo"),
("/foo", "foo"),
("/foo/bar", "foo"),
("/foo/bar", "/foo"),
("/foo/bar", "/foo/bar"),
("///foo", "foo"),
("///foo", "foo"),
] {
assert!(path_starts_with_ignoring_absolute(path, prefix));
}

for (path, prefix) in [
("bar", "foo"),
("foo/bar/baz", "foo/baz"),
("/bar", "foo"),
("/foo/bar", "baz"),
("/foo/bar", "/baz"),
] {
assert!(!path_starts_with_ignoring_absolute(path, prefix));
}
}

#[test]
fn test_normalize_path() {
let imp_default = &TarImportConfig {
Expand Down
19 changes: 17 additions & 2 deletions lib/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,16 @@ async fn test_container_var_content() -> Result<()> {
}

#[tokio::test]
async fn test_container_etc_hardlinked() -> Result<()> {
async fn test_container_etc_hardlinked_absolute() -> Result<()> {
test_container_etc_hardlinked(true).await
}

#[tokio::test]
async fn test_container_etc_hardlinked_relative() -> Result<()> {
test_container_etc_hardlinked(false).await
}

async fn test_container_etc_hardlinked(absolute: bool) -> Result<()> {
let fixture = Fixture::new_v1()?;

let imgref = fixture.export_container().await.unwrap().0;
Expand Down Expand Up @@ -1186,7 +1195,13 @@ async fn test_container_etc_hardlinked() -> Result<()> {
h.set_entry_type(tar::EntryType::Link);
h.set_mtime(42);
h.set_size(0);
layer_tar.append_link(&mut h.clone(), "sysroot/ostree/repo/objects/45/7279b28b541ca20358bec8487c81baac6a3d5ed3cea019aee675137fab53cb.file", "etc/dnf.conf")?;
let path = "sysroot/ostree/repo/objects/45/7279b28b541ca20358bec8487c81baac6a3d5ed3cea019aee675137fab53cb.file";
let path = if absolute {
Cow::Owned(format!("/{path}"))
} else {
Cow::Borrowed(path)
};
layer_tar.append_link(&mut h.clone(), &*path, "etc/dnf.conf")?;
layer_tar.finish()?;
Ok(())
},
Expand Down

0 comments on commit 9481ab8

Please sign in to comment.