[PATCH] erofs-utils: tests: add a splice() test for inode_share mode
Zhan Xusheng <[email protected]>
| Newsgroups | org.ozlabs.lists.linux-erofs |
|---|---|
| Message-ID | <[email protected]> |
From: Zhan Xusheng <[email protected]> From: Zhan Xusheng <[email protected]> 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 <[email protected]> --- 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