Linux-EROFS Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [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

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