git: 311f68079ff2 - stable/12 - fusefs: After successful F_GETLK, l_whence should be SEEK_SET

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Thu, 20 Oct 2022 16:25:38 UTC
The branch stable/12 has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=311f68079ff29aabd594c6fbdc97c2bff5d91ba5

commit 311f68079ff29aabd594c6fbdc97c2bff5d91ba5
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2022-10-11 23:00:07 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2022-10-20 16:24:19 +0000

    fusefs: After successful F_GETLK, l_whence should be SEEK_SET
    
    PR:             266886
    Reported by:    John Millikin <jmillikin@gmail.com>
    Reviewed by:    emaste
    Differential Revision: https://reviews.freebsd.org/D37014
    
    (cherry picked from commit 3c3b906b54236841d813fd9a01b1e090f39558ea)
    
    fusefs: fix VOP_ADVLOCK with SEEK_END
    
    When the user specifies SEEK_END, unlike SEEK_CUR, VOP_ADVLOCK must
    adjust lock offsets itself.
    
    Sort-of related to bug 266886.
    
    Reviewed by:    emaste
    Differential Revision: https://reviews.freebsd.org/D37040
    
    (cherry picked from commit f6e5319550f60170840f1a07a9cbdd45b5014a21)
    
    Approved by:    kib (re)
---
 sys/fs/fuse/fuse_vnops.c      |  34 +++++++-
 tests/sys/fs/fusefs/locks.cc  | 191 +++++++++++++++++++++++++++++++++++++++++-
 tests/sys/fs/fusefs/mockfs.cc |  14 +++-
 3 files changed, 234 insertions(+), 5 deletions(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index d143f4390887..0780c20c9308 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -385,7 +385,9 @@ fuse_vnop_advlock(struct vop_advlock_args *ap)
 	struct fuse_dispatcher fdi;
 	struct fuse_lk_in *fli;
 	struct fuse_lk_out *flo;
+	struct vattr vattr;
 	enum fuse_opcode op;
+	off_t size, start;
 	int dataflags, err;
 	int flags = ap->a_flags;
 
@@ -420,6 +422,33 @@ fuse_vnop_advlock(struct vop_advlock_args *ap)
 
 	vn_lock(vp, LK_SHARED | LK_RETRY);
 
+	switch (fl->l_whence) {
+	case SEEK_SET:
+	case SEEK_CUR:
+		/*
+		 * Caller is responsible for adding any necessary offset
+		 * when SEEK_CUR is used.
+		 */
+		start = fl->l_start;
+		break;
+
+	case SEEK_END:
+		err = fuse_internal_getattr(vp, &vattr, cred, td);
+		if (err)
+			goto out;
+		size = vattr.va_size;
+		if (size > OFF_MAX ||
+		    (fl->l_start > 0 && size > OFF_MAX - fl->l_start)) {
+			err = EOVERFLOW;
+			goto out;
+		}
+		start = size + fl->l_start;
+		break;
+
+	default:
+		return (EINVAL);
+	}
+
 	err = fuse_filehandle_get_anyflags(vp, &fufh, cred, pid);
 	if (err)
 		goto out;
@@ -430,9 +459,9 @@ fuse_vnop_advlock(struct vop_advlock_args *ap)
 	fli = fdi.indata;
 	fli->fh = fufh->fh_id;
 	fli->owner = td->td_proc->p_pid;
-	fli->lk.start = fl->l_start;
+	fli->lk.start = start;
 	if (fl->l_len != 0)
-		fli->lk.end = fl->l_start + fl->l_len - 1;
+		fli->lk.end = start + fl->l_len - 1;
 	else
 		fli->lk.end = INT64_MAX;
 	fli->lk.type = fl->l_type;
@@ -444,6 +473,7 @@ fuse_vnop_advlock(struct vop_advlock_args *ap)
 	if (err == 0 && op == FUSE_GETLK) {
 		flo = fdi.answ;
 		fl->l_type = flo->lk.type;
+		fl->l_whence = SEEK_SET;
 		if (flo->lk.type != F_UNLCK) {
 			fl->l_pid = flo->lk.pid;
 			fl->l_start = flo->lk.start;
diff --git a/tests/sys/fs/fusefs/locks.cc b/tests/sys/fs/fusefs/locks.cc
index aeb24704624b..c7c652dd573b 100644
--- a/tests/sys/fs/fusefs/locks.cc
+++ b/tests/sys/fs/fusefs/locks.cc
@@ -47,9 +47,9 @@ using namespace testing;
 class Fallback: public FuseTest {
 public:
 
-void expect_lookup(const char *relpath, uint64_t ino)
+void expect_lookup(const char *relpath, uint64_t ino, uint64_t size = 0)
 {
-	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
+	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
 }
 
 };
@@ -355,6 +355,136 @@ TEST_F(Getlk, lock_exists)
 	leak(fd);
 }
 
+/*
+ * F_GETLK with SEEK_CUR
+ */
+TEST_F(Getlk, seek_cur)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	uint64_t ino = 42;
+	struct flock fl;
+	int fd;
+	pid_t pid = getpid();
+
+	expect_lookup(RELPATH, ino, 1024);
+	expect_open(ino, 0, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in.header.opcode == FUSE_GETLK &&
+				in.header.nodeid == ino &&
+				in.body.getlk.fh == FH &&
+				/*
+				 * Though it seems useless, libfuse expects the
+				 * owner and pid fields to be set during
+				 * FUSE_GETLK.
+				 */
+				in.body.getlk.owner == (uint32_t)pid &&
+				in.body.getlk.lk.pid == (uint64_t)pid &&
+				in.body.getlk.lk.start == 500 &&
+				in.body.getlk.lk.end == 509 &&
+				in.body.getlk.lk.type == F_RDLCK);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+		SET_OUT_HEADER_LEN(out, getlk);
+		out.body.getlk.lk.start = 400;
+		out.body.getlk.lk.end = 499;
+		out.body.getlk.lk.type = F_WRLCK;
+		out.body.getlk.lk.pid = (uint32_t)pid + 1;
+	})));
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+	ASSERT_NE(-1, lseek(fd, 500, SEEK_SET));
+
+	fl.l_start = 0;
+	fl.l_len = 10;
+	fl.l_pid = 42;
+	fl.l_type = F_RDLCK;
+	fl.l_whence = SEEK_CUR;
+	fl.l_sysid = 0;
+	ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
+
+	/*
+	 * After a successful F_GETLK request, the value of l_whence is
+	 * SEEK_SET.
+	 */
+	EXPECT_EQ(F_WRLCK, fl.l_type);
+	EXPECT_EQ(fl.l_pid, pid + 1);
+	EXPECT_EQ(fl.l_start, 400);
+	EXPECT_EQ(fl.l_len, 100);
+	EXPECT_EQ(fl.l_whence, SEEK_SET);
+	ASSERT_EQ(fl.l_sysid, 0);
+
+	leak(fd);
+}
+
+/*
+ * F_GETLK with SEEK_END
+ */
+TEST_F(Getlk, seek_end)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	uint64_t ino = 42;
+	struct flock fl;
+	int fd;
+	pid_t pid = getpid();
+
+	expect_lookup(RELPATH, ino, 1024);
+	expect_open(ino, 0, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in.header.opcode == FUSE_GETLK &&
+				in.header.nodeid == ino &&
+				in.body.getlk.fh == FH &&
+				/*
+				 * Though it seems useless, libfuse expects the
+				 * owner and pid fields to be set during
+				 * FUSE_GETLK.
+				 */
+				in.body.getlk.owner == (uint32_t)pid &&
+				in.body.getlk.lk.pid == (uint64_t)pid &&
+				in.body.getlk.lk.start == 512 &&
+				in.body.getlk.lk.end == 1023 &&
+				in.body.getlk.lk.type == F_RDLCK);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+		SET_OUT_HEADER_LEN(out, getlk);
+		out.body.getlk.lk.start = 400;
+		out.body.getlk.lk.end = 499;
+		out.body.getlk.lk.type = F_WRLCK;
+		out.body.getlk.lk.pid = (uint32_t)pid + 1;
+	})));
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+	ASSERT_NE(-1, lseek(fd, 500, SEEK_SET));
+
+	fl.l_start = -512;
+	fl.l_len = 512;
+	fl.l_pid = 42;
+	fl.l_type = F_RDLCK;
+	fl.l_whence = SEEK_END;
+	fl.l_sysid = 0;
+	ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
+
+	/*
+	 * After a successful F_GETLK request, the value of l_whence is
+	 * SEEK_SET.
+	 */
+	EXPECT_EQ(F_WRLCK, fl.l_type);
+	EXPECT_EQ(fl.l_pid, pid + 1);
+	EXPECT_EQ(fl.l_start, 400);
+	EXPECT_EQ(fl.l_len, 100);
+	EXPECT_EQ(fl.l_whence, SEEK_SET);
+	ASSERT_EQ(fl.l_sysid, 0);
+
+	leak(fd);
+}
+
 /*
  * If the fuse filesystem does not support posix file locks, then the kernel
  * should fall back to local locks.
@@ -460,6 +590,63 @@ TEST_F(Setlk, set_eof)
 	leak(fd);
 }
 
+/* Set a new lock with FUSE_SETLK, using SEEK_CUR for l_whence */
+TEST_F(Setlk, set_seek_cur)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	uint64_t ino = 42;
+	struct flock fl;
+	int fd;
+	pid_t pid = getpid();
+
+	expect_lookup(RELPATH, ino, 1024);
+	expect_open(ino, 0, 1);
+	expect_setlk(ino, pid, 500, 509, F_RDLCK, 0);
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+	ASSERT_NE(-1, lseek(fd, 500, SEEK_SET));
+
+	fl.l_start = 0;
+	fl.l_len = 10;
+	fl.l_pid = 0;
+	fl.l_type = F_RDLCK;
+	fl.l_whence = SEEK_CUR;
+	fl.l_sysid = 0;
+	ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
+
+	leak(fd);
+}
+
+/* Set a new lock with FUSE_SETLK, using SEEK_END for l_whence */
+TEST_F(Setlk, set_seek_end)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	uint64_t ino = 42;
+	struct flock fl;
+	int fd;
+	pid_t pid = getpid();
+
+	expect_lookup(RELPATH, ino, 1024);
+	expect_open(ino, 0, 1);
+	expect_setlk(ino, pid, 1000, 1009, F_RDLCK, 0);
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+
+	fl.l_start = -24;
+	fl.l_len = 10;
+	fl.l_pid = 0;
+	fl.l_type = F_RDLCK;
+	fl.l_whence = SEEK_END;
+	fl.l_sysid = 0;
+	ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
+
+	leak(fd);
+}
+
 /* Fail to set a new lock with FUSE_SETLK due to a conflict */
 TEST_F(Setlk, eagain)
 {
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index 383bea65e764..baadb335f041 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -199,6 +199,18 @@ void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
 		case FUSE_FSYNCDIR:
 			printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
 			break;
+		case FUSE_GETLK:
+			printf(" fh=%#" PRIx64
+				" type=%u pid=%u",
+				in.body.getlk.fh,
+				in.body.getlk.lk.type,
+				in.body.getlk.lk.pid);
+			if (verbosity >= 2) {
+				printf(" range=[%" PRIi64 ":%" PRIi64 "]",
+					in.body.getlk.lk.start,
+					in.body.getlk.lk.end);
+			}
+			break;
 		case FUSE_INTERRUPT:
 			printf(" unique=%" PRIu64, in.body.interrupt.unique);
 			break;
@@ -292,7 +304,7 @@ void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
 				in.body.setlk.lk.type,
 				in.body.setlk.lk.pid);
 			if (verbosity >= 2) {
-				printf(" range=[%" PRIu64 "-%" PRIu64 "]",
+				printf(" range=[%" PRIi64 ":%" PRIi64 "]",
 					in.body.setlk.lk.start,
 					in.body.setlk.lk.end);
 			}