All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gao Xiang <xiang@kernel.org>
To: Wenyuan Xu <sereno.xu@deepseek.com>
Cc: xiang@kernel.org, linux-erofs@lists.ozlabs.org
Subject: Re: [PATCH 2/2] erofs-utils: mkfs: fix origin xattrs when merging layers
Date: Mon, 9 Nov 2026 09:51:01 +0100	[thread overview]
Message-ID: <avGJdXYTfqiRDPRP@MacBookPro.fritz.box> (raw)
In-Reply-To: <20261107042703.3392000-2-sereno.xu@deepseek.com>

Hi Wenyuan,

On Sat, Nov 07, 2026 at 04:27:03AM +0000, Wenyuan Xu wrote:
> Only supplement empty trusted.overlay.origin xattrs on whiteout parents
> during incremental builds or multi-source rebuilds. Ordinary local
> directory builds and single-source rebuilds should not synthesize this
> merge marker.
> 
> Preserve any existing origin xattr instead of appending a duplicate
> empty entry.
> 
> Fixes: 8680f3cf94ef ("erofs-utils: mkfs: enable incremental builds for local files")
> Assisted-by: LLM
> Signed-off-by: Wenyuan Xu <sereno.xu@deepseek.com>

Can the following diff resolve the issue as well? (untested)
If possible, could you ask LLM to refine this way?

Thanks,
Gao Xiang


diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index 19e38fb..1eeb78f 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -269,6 +269,7 @@ struct erofs_inode {
 	char idata_type;
 	bool lazy_tailblock;
 	bool opaque;
+	bool origin;
 	/* OVL: non-merge dir that may contain whiteout entries */
 	bool whiteouts;
 	bool dot_omitted;
diff --git a/lib/tar.c b/lib/tar.c
index 72d867c..2f3e189 100644
--- a/lib/tar.c
+++ b/lib/tar.c
@@ -1080,7 +1080,6 @@ out_eot:
 		 * needed if the tar tree is used soon, thus we have no chance
 		 * to generate it from xattrs.  No impact to mergefs.
 		 */
-		d->inode->opaque = true;
 		ret = erofs_set_opaque_xattr(d->inode);
 		goto out;
 	} else if (th->typeflag == '1') {	/* hard link cases */
diff --git a/lib/xattr.c b/lib/xattr.c
index c670b12..59281d3 100644
--- a/lib/xattr.c
+++ b/lib/xattr.c
@@ -452,7 +452,7 @@ static bool erofs_is_skipped_xattr(const char *key)
 }
 
 static int read_xattrs_from_file(struct erofs_sb_info *sbi, const char *path,
-				 mode_t mode, struct list_head *ixattrs)
+				 mode_t mode, struct erofs_inode *inode)
 {
 	ssize_t kllen = erofs_sys_llistxattr(path, NULL, 0);
 	char *keylst, *key, *klend;
@@ -507,10 +507,16 @@ static int read_xattrs_from_file(struct erofs_sb_info *sbi, const char *path,
 			goto err;
 		}
 
-		if (ixattrs) {
-			ret = erofs_inode_xattr_add(ixattrs, item);
+		if (inode) {
+			ret = erofs_inode_xattr_add(&inode->i_xattrs, item);
 			if (ret < 0)
 				goto err;
+			if (!strcmp(key, OVL_XATTR_OPAQUE))
+				inode->opaque = S_ISDIR(inode->i_mode);
+			if (!strcmp(key, OVL_XATTR_ORIGIN)) {
+				inode->origin = true;
+				inode->whiteouts |= S_ISDIR(inode->i_mode);
+			}
 		}
 	}
 	free(keylst);
@@ -520,8 +526,8 @@ out:
 	item = erofs_get_selabel_xattr(sbi, path, mode);
 	if (IS_ERR(item))
 		return PTR_ERR(item);
-	if (item && ixattrs)
-		ret = erofs_inode_xattr_add(ixattrs, item);
+	if (item && inode)
+		ret = erofs_inode_xattr_add(&inode->i_xattrs, item);
 	return ret;
 
 err:
@@ -595,12 +601,18 @@ static void erofs_removexattr(struct erofs_inode *inode, const char *key)
 
 int erofs_set_opaque_xattr(struct erofs_inode *inode)
 {
+	if (inode->opaque)
+		return 0;
+	inode->opaque = true;
 	return erofs_vfs_setxattr(inode, OVL_XATTR_OPAQUE, "y", 1);
 }
 
 void erofs_clear_opaque_xattr(struct erofs_inode *inode)
 {
+	if (!inode->opaque)
+		return;
 	erofs_removexattr(inode, OVL_XATTR_OPAQUE);
+	inode->opaque = false;
 }
 
 bool erofs_get_opaque_from_disk(struct erofs_inode *inode)
@@ -610,6 +622,9 @@ bool erofs_get_opaque_from_disk(struct erofs_inode *inode)
 
 int erofs_set_origin_xattr(struct erofs_inode *inode)
 {
+	if (inode->origin)
+		return 0;
+	inode->origin = true;
 	return erofs_vfs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0);
 }
 
@@ -659,10 +674,9 @@ static int erofs_droid_xattr_set_caps(struct erofs_inode *inode)
 int erofs_scan_file_xattrs(struct erofs_inode *inode)
 {
 	int ret;
-	struct list_head *ixattrs = &inode->i_xattrs;
 
 	ret = read_xattrs_from_file(inode->sbi, inode->i_srcpath,
-				    inode->i_mode, ixattrs);
+				    inode->i_mode, inode);
 	if (ret < 0)
 		return ret;
 
@@ -721,11 +735,9 @@ int erofs_read_xattrs_from_disk(struct erofs_inode *inode)
 				goto out;
 			}
 			DBG_BUGON(ret != size);
-		} else if (S_ISDIR(inode->i_mode) &&
-			   !strcmp(key, OVL_XATTR_ORIGIN)) {
-			ret = 0;
-			inode->whiteouts = true;
-			continue;
+		} else if (!strcmp(key, OVL_XATTR_ORIGIN)) {
+			inode->origin = true;
+			inode->whiteouts |= S_ISDIR(inode->i_mode);
 		}
 
 		ret = erofs_vfs_setxattr(inode, key, value, size);
-- 
2.50.1 (Apple Git-155)


> ---
>  include/erofs/importer.h |   1 +
>  lib/inode.c              |   2 +-
>  lib/xattr.c              |   2 +
>  mkfs/main.c              |   2 +
>  tests/Makefile.am        |   3 +
>  tests/erofs/033          | 122 +++++++++++++++++++++++++++++++++++++++
>  tests/erofs/033.out      |   2 +
>  7 files changed, 133 insertions(+), 1 deletion(-)
>  create mode 100755 tests/erofs/033
>  create mode 100644 tests/erofs/033.out
> 
> diff --git a/include/erofs/importer.h b/include/erofs/importer.h
> index 5141965..b8dcaf8 100644
> --- a/include/erofs/importer.h
> +++ b/include/erofs/importer.h
> @@ -54,6 +54,7 @@ struct erofs_importer_params {
>  	bool dirdata_in_metazone;
>  	bool hard_dereference;
>  	bool ovlfs_strip;
> +	bool ovlfs_merge;		/* build involves overlayfs-style merging */
>  	bool dot_omitted;
>  	bool no_xattrs;			/* don't store extended attributes */
>  	bool no_zcompact;
> diff --git a/lib/inode.c b/lib/inode.c
> index da29b5a..185bcfc 100644
> --- a/lib/inode.c
> +++ b/lib/inode.c
> @@ -2134,7 +2134,7 @@ static int erofs_mkfs_handle_inode(const struct erofs_mkfs_btctx *ctx,
>  	/* strip all unnecessary overlayfs xattrs when ovlfs_strip is enabled */
>  	if (params->ovlfs_strip)
>  		erofs_clear_opaque_xattr(inode);
> -	else if (inode->whiteouts)
> +	else if (params->ovlfs_merge && inode->whiteouts)
>  		erofs_set_origin_xattr(inode);
>  
>  	if (!S_ISDIR(inode->i_mode)) {
> diff --git a/lib/xattr.c b/lib/xattr.c
> index a0ac76f..01a8547 100644
> --- a/lib/xattr.c
> +++ b/lib/xattr.c
> @@ -619,6 +619,8 @@ bool erofs_get_opaque_from_disk(struct erofs_inode *inode)
>  
>  int erofs_set_origin_xattr(struct erofs_inode *inode)
>  {
> +	if (erofs_inode_has_xattr(inode, OVL_XATTR_ORIGIN))
> +		return 0;
>  	return erofs_vfs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0);
>  }
>  
> diff --git a/mkfs/main.c b/mkfs/main.c
> index 93123c1..06d868a 100644
> --- a/mkfs/main.c
> +++ b/mkfs/main.c
> @@ -1899,6 +1899,8 @@ int main(int argc, char **argv)
>  	importer_params.source = cfg.c_src_path;
>  	importer_params.no_datainline = mkfs_no_datainline;
>  	importer_params.dot_omitted = mkfs_dot_omitted;
> +	importer_params.ovlfs_merge = incremental_mode ||
> +		(source_mode == EROFS_MKFS_SOURCE_REBUILD && rebuild_src_count > 1);
>  	if (importer_params.dedupe == EROFS_DEDUPE_FORCE_ON &&
>  	    !mkfscfg.chunkbits && !mkfscfg.total_zcfgs) {
>  		erofs_err("Compression is not enabled.  Turn on chunk-based data deduplication instead.");
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index 363b93d..bfd1fc2 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -135,6 +135,9 @@ TESTS += erofs/031
>  # 032 - check that the xattr layout does not depend on listxattr(2) order
>  TESTS += erofs/032
>  
> +# 033 - preserve existing origin and only add empty origin when merging
> +TESTS += erofs/033
> +
>  # NEW TEST CASE HERE
>  # TESTS += erofs/999
>  
> diff --git a/tests/erofs/033 b/tests/erofs/033
> new file mode 100755
> index 0000000..1da7bca
> --- /dev/null
> +++ b/tests/erofs/033
> @@ -0,0 +1,122 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Only supplement missing origin xattrs on whiteout parents when merging.
> +# Existing origin values must survive without duplicate xattr names.
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
> +
> +. "${srcdir}/common/rc"
> +
> +cleanup()
> +{
> +	cd /
> +	rm -rf $tmp.*
> +}
> +
> +_require_root
> +_require_erofs
> +_require_xattr
> +command -v getfattr >/dev/null 2>&1 || _notrun "getfattr isn't installed"
> +
> +rm -f $seqres.full
> +
> +localdir=$tmp/$seq
> +mkdir -p "$localdir/source" "$localdir/base" || _fail "mkdir failed"
> +
> +# Probe capabilities as root alone does not guarantee CAP_MKNOD/SYS_ADMIN.
> +mknod "$localdir/probe" c 0 0 >> $seqres.full 2>&1 ||
> +	_notrun "cannot create whiteouts"
> +setfattr -n trusted.overlay.origin -v xxx "$localdir/source" \
> +	>> $seqres.full 2>&1 || _notrun "cannot set trusted xattrs"
> +getfattr -n trusted.overlay.origin --only-values "$localdir/source" \
> +	> "$localdir/probe-value" 2>> $seqres.full ||
> +	_notrun "cannot read trusted xattrs"
> +[ "$(cat "$localdir/probe-value")" = xxx ] ||
> +	_notrun "trusted xattrs are not visible"
> +setfattr -x trusted.overlay.origin "$localdir/source" \
> +	>> $seqres.full 2>&1 || _fail "cannot remove probe xattr"
> +
> +echo "QA output created by $seq"
> +
> +for state in absent empty value; do
> +	mkdir "$localdir/source/$state" || _fail "mkdir failed"
> +	mknod "$localdir/source/$state/whiteout" c 0 0 \
> +		>> $seqres.full 2>&1 || _fail "cannot create whiteout"
> +done
> +setfattr -n trusted.overlay.origin -v '' "$localdir/source/empty" \
> +	>> $seqres.full 2>&1 || _fail "cannot set empty origin"
> +setfattr -n trusted.overlay.origin -v xxx "$localdir/source/value" \
> +	>> $seqres.full 2>&1 || _fail "cannot set nonempty origin"
> +echo base > "$localdir/base/file"
> +
> +# Dumping all matching names also catches duplicate names: getxattr alone
> +# would retrieve the same first value for each occurrence of the name.
> +check_origin()
> +{
> +	getfattr --absolute-names -d -m '^trusted\.overlay\.origin$' \
> +		"$SCRATCH_MNT/$1" > "$localdir/attrs" 2>> $seqres.full ||
> +		_fail "cannot read $case_name/$1 xattrs"
> +	cat "$localdir/attrs" >> $seqres.full
> +	actual=$(sed -n '/^trusted\.overlay\.origin=/p' "$localdir/attrs")
> +	[ "$actual" = "$2" ] || _fail "unexpected origin in $case_name/$1"
> +	[ -c "$SCRATCH_MNT/$1/whiteout" ] ||
> +		_fail "whiteout lost in $case_name/$1"
> +}
> +
> +build_image()
> +{
> +	$MKFS_EROFS_PROG "$@" >> $seqres.full 2>&1 ||
> +		_fail "mkfs failed for $case_name"
> +}
> +
> +case_name=localdir
> +SCRATCH_DEV=$localdir/source.erofs
> +build_image "$SCRATCH_DEV" "$localdir/source"
> +_scratch_mount >> $seqres.full 2>&1
> +check_origin absent ''
> +check_origin empty 'trusted.overlay.origin=""'
> +check_origin value 'trusted.overlay.origin="xxx"'
> +_scratch_unmount >> $seqres.full 2>&1 || _fail "unmount failed"
> +
> +case_name=single-source
> +SCRATCH_DEV=$localdir/single.erofs
> +build_image --clean=data "$SCRATCH_DEV" "$localdir/source.erofs"
> +_scratch_mount >> $seqres.full 2>&1
> +check_origin absent ''
> +check_origin value 'trusted.overlay.origin="xxx"'
> +_scratch_unmount >> $seqres.full 2>&1 || _fail "unmount failed"
> +
> +case_name=base
> +build_image "$localdir/base.erofs" "$localdir/base"
> +
> +for case_name in multi-source incremental-localdir incremental-rebuild; do
> +	SCRATCH_DEV=$localdir/$case_name.erofs
> +	case $case_name in
> +	multi-source)
> +		build_image --clean=data "$SCRATCH_DEV" \
> +			"$localdir/base.erofs" "$localdir/source.erofs"
> +		;;
> +	incremental-*)
> +		cp "$localdir/base.erofs" "$SCRATCH_DEV" || _fail "copy failed"
> +		if [ "$case_name" = incremental-localdir ]; then
> +			source=$localdir/source
> +		else
> +			source=$localdir/source.erofs
> +		fi
> +		build_image --incremental=data "$SCRATCH_DEV" "$source"
> +		;;
> +	esac
> +	_scratch_mount >> $seqres.full 2>&1
> +	check_origin absent 'trusted.overlay.origin=""'
> +	check_origin empty 'trusted.overlay.origin=""'
> +	check_origin value 'trusted.overlay.origin="xxx"'
> +	[ "$(cat "$SCRATCH_MNT/file")" = base ] ||
> +		_fail "base file lost in $case_name"
> +	_scratch_unmount >> $seqres.full 2>&1 || _fail "unmount failed"
> +done
> +
> +echo Silence is golden
> +status=0
> +exit 0
> diff --git a/tests/erofs/033.out b/tests/erofs/033.out
> new file mode 100644
> index 0000000..c5d2914
> --- /dev/null
> +++ b/tests/erofs/033.out
> @@ -0,0 +1,2 @@
> +QA output created by 033
> +Silence is golden
> -- 
> 2.43.0
> 
> 


      reply	other threads:[~2026-11-09  8:51 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  4:27 [PATCH 1/2] erofs-utils: lib: add erofs_inode_has_xattr helper Wenyuan Xu
2026-11-07  4:27 ` [PATCH 2/2] erofs-utils: mkfs: fix origin xattrs when merging layers Wenyuan Xu
2026-11-09  8:51   ` Gao Xiang [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=avGJdXYTfqiRDPRP@MacBookPro.fritz.box \
    --to=xiang@kernel.org \
    --cc=linux-erofs@lists.ozlabs.org \
    --cc=sereno.xu@deepseek.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.