Skip to content

fix: a delta layer is the final tree diff against its parent - #18

Merged
marcos-mendez merged 1 commit into
19.xfrom
fix/delta-layer-final-diff
Oct 8, 2026
Merged

marcos-mendez merged 1 commit into
19.xfrom
fix/delta-layer-final-diff

Conversation

@marcos-mendez

Copy link
Copy Markdown
Collaborator

A child layer was packed as the two deck upper directories in turn (root.build, then root.patched). A file created in stage 1 and deleted in stage 2 is content in one and a whiteout in the other, so the tarball shipped its bytes followed by the whiteout. The unpacked tree was right; the bytes were still published. This is how the postgresql layer of 2026-10-08 (not published) carried the snakeoil key ssl-cert makes; layer_audit_keys reads the final tree, where the whiteout already hides it.

  • layer_delta_lists computes the members from the final tree and the parent: paths either upper holds that root.patched still has (packed from root.patched); a whiteout only for a path the parent has and the final tree lacks; an opaque directory over a parent directory taken from its upper so the mark survives. Created-then-deleted leaves nothing.
  • NUL-separated sorted lists read with --null --no-recursion: reproducible (the -u re-export stays byte identical).
  • bt-layer passes the parent rootfs and fails if the members cannot be listed.

Tests (tests/layer, section 10a): a fake key created in stage 1 and whited out in stage 2 is neither named nor present byte for byte in a real tarball; parent file removed = whiteout; file re-created packed once; opaque directory from its upper; refusals; the real predicates. layer-lib 99.54 percent locally (gate 99); the one new uncovered line is a loop's done.

bt-layer packed a child layer as the two deck upper directories one after
the other, root.build's then root.patched's. A file the first stage created
and the second deleted is content in one and a whiteout in the other, so
the tarball carried its bytes and then the whiteout: the tree a layer
unpacks to was right and the bytes were still published. That is how the
postgresql layer of 2026-10-08 carried the snakeoil key and certificate
ssl-cert makes at installation and removelists-final takes out; layer_
audit_keys reads the final tree, where the whiteout had already hidden
them, so it passed.

layer_delta_lists computes the members from the final tree and the parent
instead: every path either upper holds that root.patched still has, packed
from root.patched; a whiteout only for a path the parent has and the final
tree does not; an opaque directory over a parent directory from its upper,
so the mark is kept. A file created and deleted inside the layer leaves
nothing. The lists are NUL separated and sorted, and tar reads them with
--null --no-recursion, so the tarball stays reproducible (the -u re-export
test is byte identical). bt-layer passes the parent's rootfs and fails when
the members cannot be listed.

tests/layer: a fake key created in stage 1 and whited out in stage 2 is
neither named nor present byte for byte in a real tarball; a parent file
removed is a whiteout; a file removed by stage 1 and re-created by stage 2
is packed once; an opaque directory comes from its upper; the refusals; the
real predicates on what an unprivileged run can make. The fake product's
final tree now holds what its uppers add, as an overlay mount shows it.

Opaque marks are read or the build stops: layer_is_opaque returns 2 when
getfattr is missing or fails with anything but "No such attribute", and
layer_delta_lists refuses to run without getfattr (package attr, now in
docs/setup and in CI). Before, a getfattr error read as "not opaque" and a
parent file the layer deleted came back.

No test of the lists follows a symlink: layer_exists_in and
layer_real_dir_in walk REL component by component with lstat semantics, so
a stage 1 file under a directory stage 2 replaced with a link to the build
host is not packed through the link, and a parent link is neither a
whiteout target nor a directory to be opaque over.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant