git: 6d408ac49073 - main - fusefs: add a regression test for a cluster_read bug

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Thu, 23 Oct 2025 13:41:05 UTC
The branch main has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=6d408ac490730614b3ed0ebd3caffcd23f303fb4

commit 6d408ac490730614b3ed0ebd3caffcd23f303fb4
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2025-10-23 13:40:56 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2025-10-23 13:40:56 +0000

    fusefs: add a regression test for a cluster_read bug
    
    VOP_BMAP is purely advisory.  If VOP_BMAP returns an error during
    readahead, cluster_read should still succeed, because the actual data
    was still read just fine.
    
    Add a regression test for PR 264196, wherein cluster_read would fail if
    VOP_BMAP did.
    
    PR:             264196
    MFC with:       62aef3f73f38db9fb68bffc12cc8900fecd58f0e
    Reported by:    danfe
    Reviewed by:    arrowd
    Differential Revision: https://reviews.freebsd.org/D51316
---
 tests/sys/fs/fusefs/bmap.cc | 87 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/tests/sys/fs/fusefs/bmap.cc b/tests/sys/fs/fusefs/bmap.cc
index 30612079657d..e61dadb6d79e 100644
--- a/tests/sys/fs/fusefs/bmap.cc
+++ b/tests/sys/fs/fusefs/bmap.cc
@@ -177,6 +177,93 @@ TEST_F(Bmap, default_)
 	leak(fd);
 }
 
+/*
+ * The server returns an error for some reason for FUSE_BMAP.  fusefs should
+ * faithfully report that error up to the caller.
+ */
+TEST_F(Bmap, einval)
+{
+	struct fiobmap2_arg arg;
+	const off_t filesize = 1 << 30;
+	int64_t lbn = 100;
+	const ino_t ino = 42;
+	int fd;
+
+	expect_lookup(RELPATH, 42, filesize);
+	expect_open(ino, 0, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in.header.opcode == FUSE_BMAP &&
+				in.header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnErrno(EINVAL)));
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+
+	arg.bn = lbn;
+	arg.runp = -1;
+	arg.runb = -1;
+	ASSERT_EQ(-1, ioctl(fd, FIOBMAP2, &arg));
+	EXPECT_EQ(EINVAL, errno);
+
+	leak(fd);
+}
+
+/*
+ * Even if the server returns EINVAL during VOP_BMAP, we should still be able
+ * to successfully read a block.  This is a regression test for
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264196 .  The bug did not
+ * lie in fusefs, but this is a convenient place for a regression test.
+ */
+TEST_F(Bmap, spurious_einval)
+{
+	const off_t filesize = 4ull << 30;
+	const ino_t ino = 42;
+	int fd, r;
+	char buf[1];
+
+	expect_lookup(RELPATH, 42, filesize);
+	expect_open(ino, 0, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in.header.opcode == FUSE_BMAP &&
+				in.header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke(ReturnErrno(EINVAL)));
+	EXPECT_CALL(*m_mock, process(
+	ResultOf([=](auto in) {
+		return (in.header.opcode == FUSE_READ &&
+			in.header.nodeid == ino &&
+			in.body.read.offset == 0 &&
+			in.body.read.size == (uint64_t)m_maxbcachebuf);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
+		size_t osize = in.body.read.size;
+
+		assert(osize < sizeof(out.body.bytes));
+		out.header.len = sizeof(struct fuse_out_header) + osize;
+		bzero(out.body.bytes, osize);
+	})));
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+
+	/*
+	 * Read the same block multiple times.  On a system affected by PR
+	 * 264196 , the second read will fail.
+	 */
+	r = read(fd, buf, sizeof(buf));
+	EXPECT_EQ(r, 1) << strerror(errno);
+	r = read(fd, buf, sizeof(buf));
+	EXPECT_EQ(r, 1) << strerror(errno);
+	r = read(fd, buf, sizeof(buf));
+	EXPECT_EQ(r, 1) << strerror(errno);
+}
+
 /*
  * VOP_BMAP should not query the server for the file's size, even if its cached
  * attributes have expired.