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 f6e5319550)
This commit is contained in:
Alan Somers 2022-10-11 17:00:07 -06:00
parent 7ea6564c24
commit 4a0c5d391f
3 changed files with 166 additions and 3 deletions

View file

@ -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;

View file

@ -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)
{

View file

@ -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);
}