Linux-EROFS Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2] erofs-utils: tests: check SEEK_HOLE/SEEK_DATA in inode_share mode
@ 2026-09-09  9:34 Jingbo Xu
  0 siblings, 0 replies; only message in thread
From: Jingbo Xu @ 2026-09-09  9:34 UTC (permalink / raw)
  To: xiang, linux-erofs

In inode_share mode erofs_ishare_fops used to fall back to
generic_file_llseek(), which considers the whole file to be data, so
SEEK_HOLE always returned i_size and the real holes of sparse files
were hidden.  This is fixed by kernel commit 8ebf95897885 ("erofs:
support SEEK_HOLE/SEEK_DATA in inode_share mode").

Add a regression test for it.

Signed-off-by: Jingbo Xu <jefflexu@linux.alibaba.com>
---
changes since v1:
- add a minimal util probing SEEK_HOLE/SEEK_DATA, rather than relying
  on xfs_io
---
 tests/Makefile.am     |   3 +
 tests/erofs/033       | 151 ++++++++++++++++++++++++++++++++++++++++++
 tests/erofs/033.out   |   2 +
 tests/src/Makefile.am |   5 +-
 tests/src/lseek.c     |  65 ++++++++++++++++++
 5 files changed, 225 insertions(+), 1 deletion(-)
 create mode 100755 tests/erofs/033
 create mode 100644 tests/erofs/033.out
 create mode 100644 tests/src/lseek.c

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 363b93dc..b60a6cc9 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 - regression test for SEEK_HOLE/SEEK_DATA in inode_share mode
+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 00000000..b3e58628
--- /dev/null
+++ b/tests/erofs/033
@@ -0,0 +1,151 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Check that SEEK_HOLE/SEEK_DATA see the real holes in inode_share mode
+#
+# In an inode_share mount regular files are served by erofs_ishare_fops,
+# whose .llseek used to be generic_file_llseek().  That one considers the
+# whole file to be data, so SEEK_HOLE always returned i_size and SEEK_DATA
+# always returned the offset it was handed.
+#
+# Regression test for Linux commit 8ebf95897885 ("erofs: support
+# SEEK_HOLE/SEEK_DATA in inode_share mode").
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
+
+# get standard environment, filters and checks
+. "${srcdir}/common/rc"
+
+cleanup()
+{
+	cd /
+	rm -rf $tmp.*
+}
+
+_require_erofs
+_require_erofs_inode_sharing
+_require_mkfs_blksize
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+echo "QA output created by $seq"
+
+if [ -z $SCRATCH_DEV ]; then
+	SCRATCH_DEV=$tmp/erofs_$seq.img
+	rm -f $SCRATCH_DEV
+fi
+
+localdir="$tmp/$seq"
+rm -rf $localdir
+mkdir -p $localdir
+
+# eight chunks, of which chunk 1, 3, 4, 6, 7 stay holes
+chunksize=4096
+truncate -s $((8 * chunksize)) $localdir/sparse ||
+	_fail "failed to truncate $localdir/sparse"
+for i in 0 2 5; do
+	dd if=/dev/urandom of=$localdir/sparse bs=$chunksize seek=$i count=1 \
+		conv=notrunc >> $seqres.full 2>&1 ||
+		_fail "failed to write chunk $i of $localdir/sparse"
+done
+
+# the same size without any hole at all
+dd if=/dev/urandom of=$localdir/dense bs=$chunksize count=8 \
+	>> $seqres.full 2>&1 || _fail "failed to write $localdir/dense"
+
+sparse_layout="data 0 4096
+hole 4096 4096
+data 8192 4096
+hole 12288 8192
+data 20480 4096
+hole 24576 8192"
+dense_layout="data 0 32768"
+
+# seek to the next data or hole, the result ends up in $seek_result
+_seek()
+{
+	local whence=$1
+	local offset=$2
+	local file=$3
+
+	seek_result=`${PWD}/src/lseek "$file" $whence $offset` ||
+		_fail "lseek failed to seek $whence at $offset in $file"
+}
+
+# write the data/hole layout of $1 to $tmp.layout as "<type> <off> <len>"
+_seek_layout()
+{
+	local file="$1"
+	local size=`_get_filesize "$file"`
+	local pos=0
+
+	: > $tmp.layout
+	while [ $pos -lt $size ]; do
+		_seek data $pos "$file"
+		if [ "$seek_result" = "EOF" ]; then
+			echo "hole $pos $((size - pos))" >> $tmp.layout
+			break
+		fi
+		if [ $seek_result -gt $pos ]; then
+			echo "hole $pos $((seek_result - pos))" >> $tmp.layout
+		fi
+		pos=$seek_result
+
+		_seek hole $pos "$file"
+		[ "$seek_result" = "EOF" ] && seek_result=$size
+		echo "data $pos $((seek_result - pos))" >> $tmp.layout
+		pos=$seek_result
+	done
+}
+
+_check_layout()
+{
+	local tag="$1"
+	local file="$2"
+	local expected="$3"
+
+	_seek_layout "$file"
+	if [ "`cat $tmp.layout`" != "$expected" ]; then
+		{
+			echo "--- $tag expected ---"
+			echo "$expected"
+			echo "--- $tag reported ---"
+			cat $tmp.layout
+		} >> $seqres.full
+		_fail "$tag: SEEK_DATA/SEEK_HOLE reported an unexpected layout"
+	fi
+}
+
+for format in force-chunk-indexes force-inode-blockmap; do
+	echo "=== -E$format ===" >> $seqres.full
+
+	_scratch_mkfs -b$chunksize --chunksize=$chunksize -E$format \
+		--xattr-inode-digest=system.erofs.fingerprint \
+		$localdir >> $seqres.full 2>&1 ||
+		_fail "failed to mkfs (-E$format)"
+
+	# make sure that mkfs wrote ishare_xattrs feature bit
+	$DUMP_EROFS_PROG -s $SCRATCH_DEV | grep -q ishare_xattrs ||
+		_fail "the image carries no inode fingerprints (-E$format)"
+
+	# the very same image without inode_share tells them apart
+	_scratch_mount 2>> $seqres.full
+	_check_layout "sparse (-E$format)" \
+		$SCRATCH_MNT/sparse "$sparse_layout"
+	_check_layout "dense (-E$format)" $SCRATCH_MNT/dense "$dense_layout"
+	_scratch_unmount
+
+	_scratch_mount -oinode_share,domain_id=erofs-$seq-$format 2>> $seqres.full
+	_check_layout "sparse (-E$format, inode_share)" \
+		$SCRATCH_MNT/sparse "$sparse_layout"
+	_check_layout "dense (-E$format, inode_share)" \
+		$SCRATCH_MNT/dense "$dense_layout"
+	_scratch_unmount
+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 00000000..c5d29149
--- /dev/null
+++ b/tests/erofs/033.out
@@ -0,0 +1,2 @@
+QA output created by 033
+Silence is golden
diff --git a/tests/src/Makefile.am b/tests/src/Makefile.am
index 16de41a1..4d9975e0 100644
--- a/tests/src/Makefile.am
+++ b/tests/src/Makefile.am
@@ -3,12 +3,15 @@
 
 AUTOMAKE_OPTIONS	= foreign
 check_PROGRAMS		=
-noinst_PROGRAMS		= fssum
+noinst_PROGRAMS	= fssum lseek
 
 fssum_CFLAGS = -Wall -I$(top_srcdir)/include
 fssum_LDADD = $(top_builddir)/lib/liberofs.la
 fssum_SOURCES = fssum.c
 
+lseek_CFLAGS = -Wall
+lseek_SOURCES = lseek.c
+
 if ENABLE_LZ4
 check_PROGRAMS += badlz4
 badlz4_SOURCES = badlz4.c
diff --git a/tests/src/lseek.c b/tests/src/lseek.c
new file mode 100644
index 00000000..c8bd2998
--- /dev/null
+++ b/tests/src/lseek.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs-utils/tests/src/lseek.c
+ *
+ * Minimal SEEK_DATA/SEEK_HOLE probe: report the offset the kernel
+ * resolves a seek to, or EOF if there is nothing left to find.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char *argv[])
+{
+	const char *file, *whence_name;
+	char *end;
+	long long offset, ret;
+	int whence, fd;
+
+	if (argc != 4) {
+		fprintf(stderr, "Usage: %s FILE data|hole OFFSET\n", argv[0]);
+		return 2;
+	}
+	file = argv[1];
+	whence_name = argv[2];
+	if (!strcmp(whence_name, "data")) {
+		whence = SEEK_DATA;
+	} else if (!strcmp(whence_name, "hole")) {
+		whence = SEEK_HOLE;
+	} else {
+		fprintf(stderr, "bad whence: %s\n", whence_name);
+		return 2;
+	}
+	errno = 0;
+	offset = strtoll(argv[3], &end, 0);
+	if (*argv[3] == '\0' || *end != '\0' || errno) {
+		fprintf(stderr, "bad offset: %s\n", argv[3]);
+		return 2;
+	}
+
+	fd = open(file, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "failed to open %s: %s\n", file, strerror(errno));
+		return 2;
+	}
+	/* the kernel reports -ENXIO when no data/hole is left past EOF */
+	ret = lseek(fd, offset, whence);
+	if (ret < 0) {
+		if (errno == ENXIO) {
+			printf("EOF\n");
+			close(fd);
+			return 0;
+		}
+		fprintf(stderr, "lseek %s %lld in %s: %s\n", whence_name,
+			offset, file, strerror(errno));
+		close(fd);
+		return 2;
+	}
+	printf("%lld\n", ret);
+	close(fd);
+	return 0;
+}
-- 
2.19.1.6.gb485710b



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-09  9:34 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09  9:34 [PATCH v2] erofs-utils: tests: check SEEK_HOLE/SEEK_DATA in inode_share mode Jingbo Xu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox