[PATCH] erofs-utils: fix opaque handling for merged dirs in rebuild mode

Gao Xiang <[email protected]> Sat, 25 Jul 2026 10:53:28 +0800
Newsgroups org.ozlabs.lists.linux-erofs
Message-ID <[email protected]>
As cayoub-oai reports,

A three-layer opaque-directory case still fails (oldest to newest):
L1: dir/old.txt
L2: dir/.wh..wh..opq
L3: dir/new.txt

After converting each layer with `mkfs.erofs --tar=f --aufs` and
fsmerging with `--aufs --ovlfs-strip=1`, the expected result is
only `dir/new.txt`. However, `dir/old.txt` incorrectly reappears.

Reported-by: cayoub-oai <[email protected]>
Fixes: 7fbec7d32ce3 ("erofs-utils: lib: honor rebuild whiteouts for recreated dirs")
Fixes: 39147b48b76d ("erofs-utils: lib: add erofs_rebuild_load_tree() helper")
Signed-off-by: Gao Xiang <[email protected]>
---
 include/erofs/xattr.h |  2 ++
 lib/inode.c           |  1 +
 lib/rebuild.c         |  6 ++++--
 lib/xattr.c           | 16 ++++++++++++++++
 4 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/include/erofs/xattr.h b/include/erofs/xattr.h
index 5fe3e91..d12c292 100644
--- a/include/erofs/xattr.h
+++ b/include/erofs/xattr.h
@@ -47,12 +47,14 @@ int erofs_vfs_setxattr(struct erofs_inode *inode, const char *name,
 		       const void *value, size_t size);
 int erofs_set_opaque_xattr(struct erofs_inode *inode);
 void erofs_clear_opaque_xattr(struct erofs_inode *inode);
+bool erofs_get_opaque_from_disk(struct erofs_inode *inode);
 int erofs_set_origin_xattr(struct erofs_inode *inode);
 int erofs_read_xattrs_from_disk(struct erofs_inode *inode);
 
 bool erofs_xattr_prefix_matches(const char *key, unsigned int *index,
 				unsigned int *len);
 void erofs_xattr_exit(struct erofs_sb_info *sbi);
+void erofs_inode_free_xattrs(struct erofs_inode *inode);
 
 #ifdef __cplusplus
 }
diff --git a/lib/inode.c b/lib/inode.c
index 267694f..9f4d14c 100644
--- a/lib/inode.c
+++ b/lib/inode.c
@@ -166,6 +166,7 @@ unsigned int erofs_iput(struct erofs_inode *inode)
 
 	if (inode->datalayout == EROFS_INODE_CHUNK_BASED)
 		free(inode->chunkindexes);
+	erofs_inode_free_xattrs(inode);
 	free(inode);
 	return 0;
 }
diff --git a/lib/rebuild.c b/lib/rebuild.c
index 108a464..a5308dc 100644
--- a/lib/rebuild.c
+++ b/lib/rebuild.c
@@ -403,13 +403,15 @@ static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx)
 		ret = erofs_read_inode_from_disk(&src);
 		if (ret)
 			goto out;
+		mergedir = d->inode;
 		if (erofs_inode_is_whiteout(&src)) {
-			d->inode->opaque = true;
+			mergedir->opaque = true;
 			goto out;
 		}
 		if (!S_ISDIR(src.i_mode))
 			goto out;
-		mergedir = d->inode;
+		mergedir->opaque |= erofs_get_opaque_from_disk(&src);
+		erofs_inode_free_xattrs(&src);
 		inode = dir = &src;
 	} else {
 		u64 nid;
diff --git a/lib/xattr.c b/lib/xattr.c
index 051fdd8..a9486e4 100644
--- a/lib/xattr.c
+++ b/lib/xattr.c
@@ -576,6 +576,11 @@ void erofs_clear_opaque_xattr(struct erofs_inode *inode)
 	erofs_removexattr(inode, OVL_XATTR_OPAQUE);
 }
 
+bool erofs_get_opaque_from_disk(struct erofs_inode *inode)
+{
+	return (erofs_getxattr(inode, OVL_XATTR_OPAQUE, NULL, 0) >= 0);
+}
+
 int erofs_set_origin_xattr(struct erofs_inode *inode)
 {
 	return erofs_vfs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0);
@@ -1123,6 +1128,17 @@ char *erofs_export_xattr_ibody(struct erofs_inode *inode)
 	return buf;
 }
 
+void erofs_inode_free_xattrs(struct erofs_inode *inode)
+{
+	DBG_BUGON(inode->i_count > 0);
+
+	if (erofs_atomic_read(&inode->flags) & EROFS_I_EA_INITED) {
+		free(inode->xattr_shared_xattrs);
+		inode->xattr_shared_xattrs = NULL;
+		inode->xattr_shared_count = 0;
+	}
+}
+
 struct erofs_xattr_iter {
 	struct erofs_sb_info *sbi;
 	struct erofs_buf buf;
-- 
2.47.3