You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Partial work. The run was stopped at its timeout before the task was done; this is what it handed back.
Research on bootc-dev/bootc#2571 at PR head 7b970f0a: why chmod 0555 /usr in the Dockerfile has no effect on the deployed system.
Short answer: reproduced. There are two separate bugs in the merge commit, and /usr/bin 0755 isn't a bootc problem.
What I ran (verified)
In a Fedora 44 KVM VM: I applied the chmod 0555 /usr Dockerfile patch posted above to the PR head and ran just build. The ELN grub2-efi-x64-cc download was stubbed out because that package isn't available right now; it's unrelated to this issue. The built image has bootc-202610151811.g7b970f0a39.
I had to run setenforce 0 in the VM for the pull, because the image's SELinux labels aren't in the host policy.
Root cause
A later layer's directory metadata is dropped (an existing bug).write_merge_commit_impl does a union checkout of each layer (store.rs#L1252-L1285) with UNION_FILES. In ostree, when the directory already exists it sets did_exist, and fchmod, fchown, xattrs and mtime are all skipped (ostree-repo-checkout.c#L1096-L1110, L1200-L1229). So for directories the first layer wins. OCI and composefs-rs do the opposite: the last layer wins (generic_tree.rs#L561-L567). Verified: the layer commit holds /usr 0555, but the merge has 0755.
The PR's normalize_root_dirmeta does nothing.mt.walk(&["usr"], 0) (store.rs#L634) returns the root itself, because ostree_mutable_tree_walk returns self when start == len-1 (ostree-mutable-tree.c#L552-L556). So the root's checksum is copied onto the root. Verified: the merge root's dirmeta checksum equals the layer roots' and differs from /usr's. The new test can't catch this, because in the fixtures / and /usr already have the same dirmeta. Even if walk worked, copying the whole dirmeta checksum after labelling would give / the label usr_t.
/usr/bin 755 is in the image itself. It's 755 in layer 1 and in stock quay.io/centos-bootc/centos-bootc:stream10, while rpm -ql --dump filesystem says 040555. That comes from base image build tooling, not from the bootc import (verified 755 in the image; where it comes from is inferred).
Proposed patch (not compiled or tested: the run ran out of time before the build)
Reapply directory mode and owner from the layers in order (last wins) to the checkout. Then copy /usr's mode and owner to /, matching composefs-rs copy_root_metadata_from_usr. Both happen beforewrite_dfd_to_mtree, so SELinux labelling still gives /root_t. No hardcoded 0o555.
On "use the image's root when present": --tar-autocreate-parents synthesizes a 0755 root for every layer commit, so at merge time ostree-ext can't tell whether the image had a ./ entry. composefs-rs' transform_for_oci copies /usr over the root unconditionally anyway. Honoring ./ would need filter_tar to record whether it saw one.
The test helper assert_root_dirmeta_matches_usr should also compare mode, uid and gid instead of the dirmeta checksum, and a fixture needs a derived layer that chmods /usr.
diff --git a/crates/ostree-ext/src/container/store.rs b/crates/ostree-ext/src/container/store.rs
index 64943c5b..59904e97 100644
--- a/crates/ostree-ext/src/container/store.rs+++ b/crates/ostree-ext/src/container/store.rs@@ -624,22 +624,107 @@ fn cleanup_root(root: &Dir) -> Result<()> {
Ok(())
}
-/// Copy `/usr` directory metadata to the root, matching the composefs-rs-/// approach since OCI images don't reliably include root dir metadata.+/// Mode, uid and gid of a directory.+type DirOwnerMode = (u32, u32, u32);++/// Record the mode and ownership of every directory in `dir` into `out`,+/// keyed by path relative to the commit root.+fn collect_dirmeta(+ dir: &ostree::RepoFile,+ path: &Utf8Path,+ out: &mut HashMap<Utf8PathBuf, DirOwnerMode>,+ cancellable: Option<&gio::Cancellable>,+) -> Result<()> {+ let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;+ let info = dir.query_info("unix::mode,unix::uid,unix::gid", queryflags, cancellable)?;+ out.insert(+ path.to_owned(),+ (+ info.attribute_uint32("unix::mode"),+ info.attribute_uint32("unix::uid"),+ info.attribute_uint32("unix::gid"),+ ),+ );+ let e = dir.enumerate_children("standard::name,standard::type", queryflags, cancellable)?;+ while let Some(child_info) = e.next_file(cancellable)? {+ if child_info.file_type() != gio::FileType::Directory {+ continue;+ }+ let name = child_info.name();+ let name = name.to_str().expect("UTF-8 ostree name");+ let child = e.child(&child_info).downcast::<ostree::RepoFile>().unwrap();+ collect_dirmeta(&child, &path.join(name), out, cancellable)?;+ }+ Ok(())+}++/// Apply directory mode (and ownership, if `chown` is set) to `dir`.+fn apply_dirmeta(+ dir: impl rustix::fd::AsFd,+ (mode, uid, gid): DirOwnerMode,+ chown: bool,+) -> Result<()> {+ use rustix::fs::{Gid, Mode, Uid};+ if chown {+ rustix::fs::fchown(&dir, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid)))+ .context("fchown")?;+ }+ rustix::fs::fchmod(&dir, Mode::from_raw_mode(mode & 0o7777)).context("fchmod")?;+ Ok(())+}++/// A `UnionFiles` checkout leaves a directory that an earlier layer created+/// untouched, so a later layer changing only a directory's metadata (e.g.+/// `RUN chmod 0555 /usr`) would be lost. OCI semantics (and composefs-rs) are+/// that the last layer wins, so reapply directory metadata from the layers+/// in order once they have all been checked out.+fn apply_layer_dirmeta(+ repo: &ostree::Repo,+ layer_commits: &[LayerRef],+ root: &Dir,+ chown: bool,+ cancellable: Option<&gio::Cancellable>,+) -> Result<()> {+ let mut dirmeta = HashMap::new();+ for lc in layer_commits {+ let (commit_root, _) = repo.read_commit(&lc.commit, cancellable)?;+ let commit_root = commit_root.downcast::<ostree::RepoFile>().unwrap();+ collect_dirmeta(&commit_root, Utf8Path::new("."), &mut dirmeta, cancellable)+ .with_context(|| format!("Reading directory metadata of {}", lc.commit))?;+ }+ for (path, meta) in dirmeta {+ use rustix::fs::{Mode, OFlags, ResolveFlags};+ let flags = OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC;+ let resolve = ResolveFlags::BENEATH | ResolveFlags::NO_SYMLINKS;+ let fd = match rustix::fs::openat2(root, path.as_std_path(), flags, Mode::empty(), resolve)+ {+ Ok(fd) => fd,+ // The directory was removed by a later layer, or it or a parent+ // was replaced by a non-directory.+ Err(rustix::io::Errno::NOENT | rustix::io::Errno::NOTDIR | rustix::io::Errno::LOOP) => {+ continue;+ }+ Err(e) => return Err(e).with_context(|| format!("Opening {path}")),+ };+ apply_dirmeta(fd, meta, chown).with_context(|| format!("Updating {path}"))?;+ }+ Ok(())+}++/// Copy the `/usr` directory mode and ownership to the root, matching+/// composefs-rs, since OCI images don't reliably include root dir metadata.+/// This happens before the merged tree is committed, so the root still gets+/// its own SELinux label.
///
/// See:
/// - <https://github.com/coreos/fedora-coreos-tracker/issues/2238>
/// - <https://github.com/coreos/fedora-coreos-tracker/issues/2238#issuecomment-6082074286>
-fn normalize_root_dirmeta(mt: &ostree::MutableTree) -> Result<()> {- let usr = mt.walk(&["usr"], 0).context("looking up /usr in mtree")?;-- let usr_meta_checksum = usr.metadata_checksum();- if usr_meta_checksum.is_empty() {- anyhow::bail!("/usr has no metadata checksum in mtree");+fn normalize_root_dirmeta(root: &Dir, chown: bool) -> Result<()> {+ let usr = root.symlink_metadata("usr").context("looking up /usr")?;+ if !usr.is_dir() {+ anyhow::bail!("/usr is not a directory");
}
-- mt.set_metadata_checksum(&usr_meta_checksum);- Ok(())+ apply_dirmeta(root, (usr.mode(), usr.uid(), usr.gid()), chown)
}
impl ImageImporter {
@@ -1315,6 +1400,11 @@ impl ImageImporter {
cleanup_root(&root_dir)?;
+ let chown = checkout_mode == ostree::RepoCheckoutMode::None;+ apply_layer_dirmeta(repo, layer_commits, &root_dir, chown, cancellable)+ .context("Applying layer directory metadata")?;+ normalize_root_dirmeta(&root_dir, chown).context("normalizing root dir metadata")?;+
let mt = ostree::MutableTree::new();
repo.write_dfd_to_mtree(
(*td).as_raw_fd(),
@@ -1325,8 +1415,6 @@ impl ImageImporter {
)
.context("Writing merged filesystem to mtree")?;
- normalize_root_dirmeta(&mt).context("normalizing root dir metadata on mtree")?;-
let merged_root = repo
.write_mtree(&mt, cancellable)
.context("Writing mtree")?;
Follow-up on bootc-dev/bootc#2571: I built and tested the patch proposed in the previous comment on PR head 7b970f0a.
Short answer: the patch as posted compiled but broke 15 of the 30 ostree-ext integration tests. After a one-line-scope fixup, all tests pass. The VM repro now gives / 0555 and /usr 0555 in the merge commit; unpatched bootc on the same image gives 0755 for both.
Environment: Fedora 44 Cloud VM under KVM, Fedora rust/cargo 1.99. Every result below was run and observed (verified) unless it is marked inferred.
cargo test -p ostree-ext --test it, unpatched PR head (baseline)
29 passed, 1 ignored
cargo test -p ostree-ext --test it, patch as proposed
15 failed, 14 passed: Importing: normalizing root dir metadata: fchmod: Bad file descriptor (os error 9)
cargo test -p ostree-ext --test it, patch + fixup
29 passed, 1 ignored
cargo clippy -p ostree-ext --all-targets
exits 101 both with and without the patch, because of a pre-existing disallowed_methods error in fixture.rs:274 (newer clippy). store.rs has the same 5 pre-existing lints before and after, so the patch adds none
The bug in the proposed patch.normalize_root_dirmeta called fchmod on root_dir, which comes from cap-std open_dir and is an O_PATH fd. fchmod/fchown on an O_PATH fd fail with EBADF. The fixup reopens . with O_RDONLY|O_DIRECTORY first. apply_layer_dirmeta already used openat2(O_RDONLY...), so it wasn't affected. (Verified that the error came from this call, and that the fixup makes it go away. That O_PATH is the cause is inferred from cap-std's behaviour and the EBADF.)
VM repro (verified)
The PR head plus the chmod 0555 /usr Dockerfile change, built with just build. As in the previous run, the ELN grub2-efi-x64-cc download was stubbed out (touch stub.rpm) because ELN doesn't have the package; it's only used when bootloader=grub-cc. The image's /usr/bin/bootc contains the patched code (the string Applying layer directory metadata is present). Then ostree init --mode=bare and bootc internals ostree-container image pull <repo> ostree-unverified-image:containers-storage:localhost/bootc as root (privileged, setenforce 0).
The 0755 on /usr/bin matches the image (it's 755 in podman run too), so it isn't an import problem, as found before.
Not covered (gaps / inferred)
Only the bare repo path (root, chown = true) went through the VM pull. The it tests run unprivileged, so they presumably exercise only the chmod path (inferred, not instrumented).
No booted deployment: I checked the merge commit, not bootc install plus a boot, or just test-tmt readonly.
No new test was added. The existing assert_root_dirmeta_matches_usr still compares dirmeta checksums. That passes only because the fixtures' / and /usr are identical, and it can't hold for real images where / gets root_t and /usr gets usr_t. It should compare mode/uid/gid, and a fixture needs a derived layer that chmods /usr.
apply_layer_dirmeta carries mode/uid/gid, not xattrs, so a later layer that changes only a directory's xattrs is still lost (inferred from the code). I didn't measure the extra cost of walking every layer commit's directories; pulling this 7-layer, 1.7 GB image went through without trouble.
Final diff (proposed patch + fixup, cargo fmt)
diff --git a/crates/ostree-ext/src/container/store.rs b/crates/ostree-ext/src/container/store.rs
index 64943c5b..1dad077a 100644
--- a/crates/ostree-ext/src/container/store.rs+++ b/crates/ostree-ext/src/container/store.rs@@ -624,22 +624,112 @@ fn cleanup_root(root: &Dir) -> Result<()> {
Ok(())
}
-/// Copy `/usr` directory metadata to the root, matching the composefs-rs-/// approach since OCI images don't reliably include root dir metadata.+/// Mode, uid and gid of a directory.+type DirOwnerMode = (u32, u32, u32);++/// Record the mode and ownership of every directory in `dir` into `out`,+/// keyed by path relative to the commit root.+fn collect_dirmeta(+ dir: &ostree::RepoFile,+ path: &Utf8Path,+ out: &mut HashMap<Utf8PathBuf, DirOwnerMode>,+ cancellable: Option<&gio::Cancellable>,+) -> Result<()> {+ let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;+ let info = dir.query_info("unix::mode,unix::uid,unix::gid", queryflags, cancellable)?;+ out.insert(+ path.to_owned(),+ (+ info.attribute_uint32("unix::mode"),+ info.attribute_uint32("unix::uid"),+ info.attribute_uint32("unix::gid"),+ ),+ );+ let e = dir.enumerate_children("standard::name,standard::type", queryflags, cancellable)?;+ while let Some(child_info) = e.next_file(cancellable)? {+ if child_info.file_type() != gio::FileType::Directory {+ continue;+ }+ let name = child_info.name();+ let name = name.to_str().expect("UTF-8 ostree name");+ let child = e.child(&child_info).downcast::<ostree::RepoFile>().unwrap();+ collect_dirmeta(&child, &path.join(name), out, cancellable)?;+ }+ Ok(())+}++/// Apply directory mode (and ownership, if `chown` is set) to `dir`.+fn apply_dirmeta(+ dir: impl rustix::fd::AsFd,+ (mode, uid, gid): DirOwnerMode,+ chown: bool,+) -> Result<()> {+ use rustix::fs::{Gid, Mode, Uid};+ if chown {+ rustix::fs::fchown(&dir, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid)))+ .context("fchown")?;+ }+ rustix::fs::fchmod(&dir, Mode::from_raw_mode(mode & 0o7777)).context("fchmod")?;+ Ok(())+}++/// A `UnionFiles` checkout leaves a directory that an earlier layer created+/// untouched, so a later layer changing only a directory's metadata (e.g.+/// `RUN chmod 0555 /usr`) would be lost. OCI semantics (and composefs-rs) are+/// that the last layer wins, so reapply directory metadata from the layers+/// in order once they have all been checked out.+fn apply_layer_dirmeta(+ repo: &ostree::Repo,+ layer_commits: &[LayerRef],+ root: &Dir,+ chown: bool,+ cancellable: Option<&gio::Cancellable>,+) -> Result<()> {+ let mut dirmeta = HashMap::new();+ for lc in layer_commits {+ let (commit_root, _) = repo.read_commit(&lc.commit, cancellable)?;+ let commit_root = commit_root.downcast::<ostree::RepoFile>().unwrap();+ collect_dirmeta(&commit_root, Utf8Path::new("."), &mut dirmeta, cancellable)+ .with_context(|| format!("Reading directory metadata of {}", lc.commit))?;+ }+ for (path, meta) in dirmeta {+ use rustix::fs::{Mode, OFlags, ResolveFlags};+ let flags = OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC;+ let resolve = ResolveFlags::BENEATH | ResolveFlags::NO_SYMLINKS;+ let fd = match rustix::fs::openat2(root, path.as_std_path(), flags, Mode::empty(), resolve)+ {+ Ok(fd) => fd,+ // The directory was removed by a later layer, or it or a parent+ // was replaced by a non-directory.+ Err(rustix::io::Errno::NOENT | rustix::io::Errno::NOTDIR | rustix::io::Errno::LOOP) => {+ continue;+ }+ Err(e) => return Err(e).with_context(|| format!("Opening {path}")),+ };+ apply_dirmeta(fd, meta, chown).with_context(|| format!("Updating {path}"))?;+ }+ Ok(())+}++/// Copy the `/usr` directory mode and ownership to the root, matching+/// composefs-rs, since OCI images don't reliably include root dir metadata.+/// This happens before the merged tree is committed, so the root still gets+/// its own SELinux label.
///
/// See:
/// - <https://github.com/coreos/fedora-coreos-tracker/issues/2238>
/// - <https://github.com/coreos/fedora-coreos-tracker/issues/2238#issuecomment-6082074286>
-fn normalize_root_dirmeta(mt: &ostree::MutableTree) -> Result<()> {- let usr = mt.walk(&["usr"], 0).context("looking up /usr in mtree")?;-- let usr_meta_checksum = usr.metadata_checksum();- if usr_meta_checksum.is_empty() {- anyhow::bail!("/usr has no metadata checksum in mtree");+fn normalize_root_dirmeta(root: &Dir, chown: bool) -> Result<()> {+ let usr = root.symlink_metadata("usr").context("looking up /usr")?;+ if !usr.is_dir() {+ anyhow::bail!("/usr is not a directory");
}
-- mt.set_metadata_checksum(&usr_meta_checksum);- Ok(())+ // `root` may be an `O_PATH` fd, on which fchmod/fchown fail with EBADF.+ let flags =+ rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::DIRECTORY | rustix::fs::OFlags::CLOEXEC;+ let rootfd =+ rustix::fs::openat(root, ".", flags, rustix::fs::Mode::empty()).context("opening root")?;+ apply_dirmeta(rootfd, (usr.mode(), usr.uid(), usr.gid()), chown)
}
impl ImageImporter {
@@ -1315,6 +1405,11 @@ impl ImageImporter {
cleanup_root(&root_dir)?;
+ let chown = checkout_mode == ostree::RepoCheckoutMode::None;+ apply_layer_dirmeta(repo, layer_commits, &root_dir, chown, cancellable)+ .context("Applying layer directory metadata")?;+ normalize_root_dirmeta(&root_dir, chown).context("normalizing root dir metadata")?;+
let mt = ostree::MutableTree::new();
repo.write_dfd_to_mtree(
(*td).as_raw_fd(),
@@ -1325,8 +1420,6 @@ impl ImageImporter {
)
.context("Writing merged filesystem to mtree")?;
- normalize_root_dirmeta(&mt).context("normalizing root dir metadata on mtree")?;-
let merged_root = repo
.write_mtree(&mt, cancellable)
.context("Writing mtree")?;
Activity
cgwalters-bot commentedon Oct 20, 2026
✅ agent ended well. Result: #22 (comment)
cgwalters-bot commentedon Oct 20, 2026
Partial work. The run was stopped at its timeout before the task was done; this is what it handed back.
Research on bootc-dev/bootc#2571 at PR head
7b970f0a: whychmod 0555 /usrin the Dockerfile has no effect on the deployed system.Short answer: reproduced. There are two separate bugs in the merge commit, and
/usr/bin0755 isn't a bootc problem.What I ran (verified)
In a Fedora 44 KVM VM: I applied the
chmod 0555 /usrDockerfile patch posted above to the PR head and ranjust build. The ELNgrub2-efi-x64-ccdownload was stubbed out because that package isn't available right now; it's unrelated to this issue. The built image hasbootc-202610151811.g7b970f0a39.I had to run
setenforce 0in the VM for the pull, because the image's SELinux labels aren't in the host policy.Root cause
write_merge_commit_impldoes a union checkout of each layer (store.rs#L1252-L1285) withUNION_FILES. In ostree, when the directory already exists it setsdid_exist, and fchmod, fchown, xattrs and mtime are all skipped (ostree-repo-checkout.c#L1096-L1110, L1200-L1229). So for directories the first layer wins. OCI and composefs-rs do the opposite: the last layer wins (generic_tree.rs#L561-L567). Verified: the layer commit holds/usr0555, but the merge has 0755.normalize_root_dirmetadoes nothing.mt.walk(&["usr"], 0)(store.rs#L634) returns the root itself, becauseostree_mutable_tree_walkreturnsselfwhenstart == len-1(ostree-mutable-tree.c#L552-L556). So the root's checksum is copied onto the root. Verified: the merge root's dirmeta checksum equals the layer roots' and differs from/usr's. The new test can't catch this, because in the fixtures/and/usralready have the same dirmeta. Even ifwalkworked, copying the whole dirmeta checksum after labelling would give/the labelusr_t./usr/bin755 is in the image itself. It's 755 in layer 1 and in stockquay.io/centos-bootc/centos-bootc:stream10, whilerpm -ql --dump filesystemsays 040555. That comes from base image build tooling, not from the bootc import (verified 755 in the image; where it comes from is inferred).Proposed patch (not compiled or tested: the run ran out of time before the build)
Reapply directory mode and owner from the layers in order (last wins) to the checkout. Then copy
/usr's mode and owner to/, matching composefs-rscopy_root_metadata_from_usr. Both happen beforewrite_dfd_to_mtree, so SELinux labelling still gives/root_t. No hardcoded 0o555.On "use the image's root when present":
--tar-autocreate-parentssynthesizes a 0755 root for every layer commit, so at merge time ostree-ext can't tell whether the image had a./entry. composefs-rs'transform_for_ocicopies/usrover the root unconditionally anyway. Honoring./would needfilter_tarto record whether it saw one.The test helper
assert_root_dirmeta_matches_usrshould also compare mode, uid and gid instead of the dirmeta checksum, and a fixture needs a derived layer that chmods/usr.cgwalters-bot commentedon Oct 20, 2026
Follow-up on bootc-dev/bootc#2571: I built and tested the patch proposed in the previous comment on PR head
7b970f0a.Short answer: the patch as posted compiled but broke 15 of the 30 ostree-ext integration tests. After a one-line-scope fixup, all tests pass. The VM repro now gives
/0555 and/usr0555 in the merge commit; unpatched bootc on the same image gives 0755 for both.Environment: Fedora 44 Cloud VM under KVM, Fedora rust/cargo 1.99. Every result below was run and observed (verified) unless it is marked inferred.
Build and unit tests (verified)
git applyof the proposed patch on7b970f0acargo build -p ostree-ext --all-targetscargo test -p ostree-ext --lib container::storetest_ref_for_descriptor,test_cleanup_root)cargo test -p ostree-ext --test it, unpatched PR head (baseline)cargo test -p ostree-ext --test it, patch as proposedImporting: normalizing root dir metadata: fchmod: Bad file descriptor (os error 9)cargo test -p ostree-ext --test it, patch + fixupcargo clippy -p ostree-ext --all-targetsdisallowed_methodserror infixture.rs:274(newer clippy). store.rs has the same 5 pre-existing lints before and after, so the patch adds noneThe bug in the proposed patch.
normalize_root_dirmetacalledfchmodonroot_dir, which comes from cap-stdopen_dirand is anO_PATHfd.fchmod/fchownon anO_PATHfd fail withEBADF. The fixup reopens.withO_RDONLY|O_DIRECTORYfirst.apply_layer_dirmetaalready usedopenat2(O_RDONLY...), so it wasn't affected. (Verified that the error came from this call, and that the fixup makes it go away. ThatO_PATHis the cause is inferred from cap-std's behaviour and theEBADF.)VM repro (verified)
The PR head plus the
chmod 0555 /usrDockerfile change, built withjust build. As in the previous run, the ELNgrub2-efi-x64-ccdownload was stubbed out (touch stub.rpm) because ELN doesn't have the package; it's only used whenbootloader=grub-cc. The image's/usr/bin/bootccontains the patched code (the stringApplying layer directory metadatais present). Thenostree init --mode=bareandbootc internals ostree-container image pull <repo> ostree-unverified-image:containers-storage:localhost/bootcas root (privileged,setenforce 0).The 0755 on
/usr/binmatches the image (it's 755 inpodman runtoo), so it isn't an import problem, as found before.Not covered (gaps / inferred)
barerepo path (root,chown = true) went through the VM pull. Theittests run unprivileged, so they presumably exercise only thechmodpath (inferred, not instrumented).bootc installplus a boot, orjust test-tmt readonly.assert_root_dirmeta_matches_usrstill compares dirmeta checksums. That passes only because the fixtures'/and/usrare identical, and it can't hold for real images where/getsroot_tand/usrgetsusr_t. It should compare mode/uid/gid, and a fixture needs a derived layer that chmods/usr.apply_layer_dirmetacarries mode/uid/gid, not xattrs, so a later layer that changes only a directory's xattrs is still lost (inferred from the code). I didn't measure the extra cost of walking every layer commit's directories; pulling this 7-layer, 1.7 GB image went through without trouble.Final diff (proposed patch + fixup,
cargo fmt)Generated-by: https://github.com/cgwalters/#llms