git: 4a0c5d391f8a - stable/13 - fusefs: fix VOP_ADVLOCK with SEEK_END

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Wed, 23 Nov 2022 20:55:26 UTC
The branch stable/13 has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=4a0c5d391f8a586ea0af4e18b6451ac2e84b6639

commit 4a0c5d391f8a586ea0af4e18b6451ac2e84b6639
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2022-10-11 23:00:07 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2022-11-23 20:55:15 +0000

    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)
---
 sys/fs/fuse/fuse_vnops.c      |  33 +++++++++++-
 tests/sys/fs/fusefs/locks.cc  | 122 ++++++++++++++++++++++++++++++++++++++++++
 tests/sys/fs/fusefs/mockfs.cc |  14 ++++-
 3 files changed, 166 insertions(+), 3 deletions(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index fb94be178743..eaace113d67e 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -476,7 +476,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;
 
@@ -511,6 +513,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;
@@ -521,9 +550,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;
diff --git a/tests/sys/fs/fusefs/locks.cc b/tests/sys/fs/fusefs/locks.cc
index f637c65cff24..c7c652dd573b 100644
--- a/tests/sys/fs/fusefs/locks.cc
+++ b/tests/sys/fs/fusefs/locks.cc
@@ -420,6 +420,71 @@ TEST_F(Getlk, seek_cur)
 	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.
@@ -525,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 9a197d8a80f0..e012df8c3488 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -229,6 +229,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;
@@ -338,7 +350,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);
 			}