* [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 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.