* [PATCH] erofs-utils: tests: add a splice() test for inode_share mode
@ 2026-08-21 1:57 Zhan Xusheng
2026-08-21 6:36 ` Gao Xiang
0 siblings, 1 reply; 2+ messages in thread
From: Zhan Xusheng @ 2026-08-21 1:57 UTC (permalink / raw)
To: Gao Xiang; +Cc: Jingbo Xu, zhanxusheng, linux-erofs, Zhan Xusheng
From: Zhan Xusheng <zhanxusheng1024@gmail.com>
From: Zhan Xusheng <zhanxusheng@xiaomi.com>
splice() and sendfile() went through filemap_splice_read() on the user
file, so they populated the per-inode page cache instead of the shared one
that read() and mmap() use. The content was correct either way, which is
why nothing caught it: only which mapping the pages land in changes.
Observe that instead. cachestat() works on the file's own mapping, while
fincore's mmap()+mincore() fallback goes through erofs_ishare_mmap(), which
redirects the vma to the backing file and so reports the shared mapping.
After a splice-only read the first must be empty and the second must not,
which is the same pair of observations erofs/028 already relies on.
Measured on 7.2 with two identical 8 MiB files under inode_share, before
and after the kernel change, as pages of the 8 MiB file:
before own 2048 shared 0
after own 0 shared 2048
spliceread is a small helper because no common tool reads a file purely
with splice(2); dd and cat both use read(2).
Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
---
tests/Makefile.am | 3 ++
tests/common/rc | 6 ++++
tests/erofs/032 | 82 ++++++++++++++++++++++++++++++++++++++++++
tests/erofs/032.out | 2 ++
tests/src/Makefile.am | 5 ++-
tests/src/spliceread.c | 61 +++++++++++++++++++++++++++++++
6 files changed, 158 insertions(+), 1 deletion(-)
create mode 100755 tests/erofs/032
create mode 100644 tests/erofs/032.out
create mode 100644 tests/src/spliceread.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c0291ac..d7674fe 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -132,6 +132,9 @@ TESTS += erofs/030
# 031 - test chunk-based mapping with shared chunks across inodes
TESTS += erofs/031
+# 032 - test that splice() uses the shared page cache in inode_share mode
+TESTS += erofs/032
+
# NEW TEST CASE HERE
# TESTS += erofs/999
diff --git a/tests/common/rc b/tests/common/rc
index aa7ef99..32484ee 100644
--- a/tests/common/rc
+++ b/tests/common/rc
@@ -357,6 +357,12 @@ _require_fssum()
[ -x $FSSUM_PROG ] || _notrun "fssum not built"
}
+_require_spliceread()
+{
+ SPLICEREAD_PROG=${PWD}/src/spliceread
+ [ -x $SPLICEREAD_PROG ] || _notrun "spliceread not built"
+}
+
# generate random string with maximum $1 length
_random()
{
diff --git a/tests/erofs/032 b/tests/erofs/032
new file mode 100755
index 0000000..aef2164
--- /dev/null
+++ b/tests/erofs/032
@@ -0,0 +1,82 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Test that splice() reads through the shared page cache 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 -f $tmp.*
+}
+
+# Resident bytes as reported through cachestat(), which works on the file's
+# own mapping. In inode_share mode that is the per-inode one.
+own_cached()
+{
+ fincore -oRES -bnr "$1"
+}
+
+# The same, with cachestat() made to fail so that fincore falls back to
+# mmap()+mincore(). mmap is redirected to the backing file, so this reports
+# the shared mapping instead.
+shared_cached()
+{
+ strace -efault=cachestat -o/dev/null fincore -oRES -bnr "$1"
+}
+
+_require_erofs
+_require_erofs_inode_sharing
+_require_spliceread
+command -v strace > /dev/null 2>&1 || _notrun "strace is not found"
+command -v fincore > /dev/null 2>&1 || _notrun "fincore is not found"
+command -v fadvise > /dev/null 2>&1 || _notrun "fadvise is not found"
+
+# 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
+
+# two files with identical content, so that they end up sharing one inode
+dd if=/dev/urandom of=$localdir/a bs=1M count=8 2>/dev/null
+cp $localdir/a $localdir/b
+
+_scratch_mkfs --xattr-inode-digest=system.erofs.fingerprint $localdir \
+ >> $seqres.full 2>&1 || _fail "failed to mkfs"
+_scratch_mount -oinode_share,domain_id=share_$seq 2>> $seqres.full
+
+f=$SCRATCH_MNT/b
+
+# fadvise is redirected to the backing file, so this empties the shared
+# mapping. The per-inode mapping is empty anyway on a fresh mount.
+fadvise -a dontneed $f
+
+$SPLICEREAD_PROG $f >> $seqres.full 2>&1 || _fail "splice failed"
+
+echo "own $(own_cached $f) shared $(shared_cached $f)" >> $seqres.full
+
+[ "$(own_cached $f)" -eq 0 ] || \
+ _fail "splice populated the per-inode page cache"
+[ "$(shared_cached $f)" -gt 0 ] || \
+ _fail "splice did not populate the shared page cache"
+
+_scratch_unmount
+
+echo Silence is golden
+status=0
+exit 0
diff --git a/tests/erofs/032.out b/tests/erofs/032.out
new file mode 100644
index 0000000..34e059f
--- /dev/null
+++ b/tests/erofs/032.out
@@ -0,0 +1,2 @@
+QA output created by 032
+Silence is golden
diff --git a/tests/src/Makefile.am b/tests/src/Makefile.am
index 16de41a..f51be82 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 spliceread
fssum_CFLAGS = -Wall -I$(top_srcdir)/include
fssum_LDADD = $(top_builddir)/lib/liberofs.la
fssum_SOURCES = fssum.c
+spliceread_CFLAGS = -Wall
+spliceread_SOURCES = spliceread.c
+
if ENABLE_LZ4
check_PROGRAMS += badlz4
badlz4_SOURCES = badlz4.c
diff --git a/tests/src/spliceread.c b/tests/src/spliceread.c
new file mode 100644
index 0000000..cb9f09d
--- /dev/null
+++ b/tests/src/spliceread.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * spliceread.c
+ *
+ * Read a file into a pipe with splice(2) and discard it, so that a test can
+ * exercise ->splice_read without any read(2) touching the file.
+ */
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define CHUNK 65536
+
+int main(int argc, char *argv[])
+{
+ static char buf[CHUNK];
+ int fd, p[2];
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s FILE\n", argv[0]);
+ return 2;
+ }
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+ if (pipe(p)) {
+ perror("pipe");
+ return 1;
+ }
+
+ for (;;) {
+ ssize_t n = splice(fd, NULL, p[1], NULL, CHUNK, 0);
+
+ if (n < 0) {
+ perror("splice");
+ return 1;
+ }
+ if (!n)
+ break;
+
+ /* drain the pipe so the next splice() has room */
+ while (n > 0) {
+ ssize_t m = read(p[0], buf, n > CHUNK ? CHUNK : n);
+
+ if (m <= 0) {
+ perror("read");
+ return 1;
+ }
+ n -= m;
+ }
+ }
+
+ close(p[0]);
+ close(p[1]);
+ close(fd);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH] erofs-utils: tests: add a splice() test for inode_share mode
2026-08-21 1:57 [PATCH] erofs-utils: tests: add a splice() test for inode_share mode Zhan Xusheng
@ 2026-08-21 6:36 ` Gao Xiang
0 siblings, 0 replies; 2+ messages in thread
From: Gao Xiang @ 2026-08-21 6:36 UTC (permalink / raw)
To: Zhan Xusheng; +Cc: Gao Xiang, Jingbo Xu, zhanxusheng, linux-erofs
Hi Xusheng,
On Fri, Aug 21, 2026 at 09:57:22AM +0800, Zhan Xusheng wrote:
> From: Zhan Xusheng <zhanxusheng1024@gmail.com>
>
> From: Zhan Xusheng <zhanxusheng@xiaomi.com>
>
> splice() and sendfile() went through filemap_splice_read() on the user
> file, so they populated the per-inode page cache instead of the shared one
> that read() and mmap() use. The content was correct either way, which is
> why nothing caught it: only which mapping the pages land in changes.
>
> Observe that instead. cachestat() works on the file's own mapping, while
> fincore's mmap()+mincore() fallback goes through erofs_ishare_mmap(), which
Actually we shouldn't rely on the fact that cachestat() works on the
file's own mapping since it's another misfeature, similar to:
https://lore.kernel.org/linux-unionfs/20260623111533.2285005-1-ptikhomirov@virtuozzo.com/
we should find another way to replace the cachestat() usage here.
Thanks,
Gao Xiang
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-21 6:36 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 1:57 [PATCH] erofs-utils: tests: add a splice() test for inode_share mode Zhan Xusheng
2026-08-21 6:36 ` Gao Xiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox