diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -1074,6 +1074,10 @@ fsess_set_notimpl(data->mp, FUSE_DESTROY); } + if (!fuse_libabi_geq(data, 7, 19)) { + fsess_set_notimpl(data->mp, FUSE_FALLOCATE); + } + if (fuse_libabi_geq(data, 7, 23) && fiio->time_gran >= 1 && fiio->time_gran <= 1000000000) data->time_gran = fiio->time_gran; diff --git a/sys/fs/fuse/fuse_ipc.c b/sys/fs/fuse/fuse_ipc.c --- a/sys/fs/fuse/fuse_ipc.c +++ b/sys/fs/fuse/fuse_ipc.c @@ -845,6 +845,10 @@ err = (blen == 0) ? 0 : EINVAL; break; + case FUSE_FALLOCATE: + err = (blen == 0) ? 0 : EINVAL; + break; + case FUSE_LSEEK: err = (blen == sizeof(struct fuse_lseek_out)) ? 0 : EINVAL; break; diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -127,6 +127,7 @@ /* vnode ops */ static vop_access_t fuse_vnop_access; static vop_advlock_t fuse_vnop_advlock; +static vop_allocate_t fuse_vnop_allocate; static vop_bmap_t fuse_vnop_bmap; static vop_close_t fuse_fifo_close; static vop_close_t fuse_vnop_close; @@ -180,7 +181,7 @@ VFS_VOP_VECTOR_REGISTER(fuse_fifoops); struct vop_vector fuse_vnops = { - .vop_allocate = VOP_EINVAL, + .vop_allocate = fuse_vnop_allocate, .vop_default = &default_vnodeops, .vop_access = fuse_vnop_access, .vop_advlock = fuse_vnop_advlock, @@ -550,6 +551,96 @@ return err; } +static int +fuse_vnop_allocate(struct vop_allocate_args *ap) +{ + struct vnode *vp = ap->a_vp; + off_t *len = ap->a_len; + off_t *offset = ap->a_offset; + struct ucred *cred = ap->a_cred; + struct fuse_filehandle *fufh; + struct mount *mp = vnode_mount(vp); + struct fuse_dispatcher fdi; + struct fuse_fallocate_in *ffi; + struct uio io; + pid_t pid = curthread->td_proc->p_pid; + struct fuse_vnode_data *fvdat = VTOFUD(vp); + off_t filesize; + int err; + + if (fuse_isdeadfs(vp)) + return (ENXIO); + + switch (vp->v_type) { + case VFIFO: + return (ESPIPE); + case VLNK: + case VREG: + if (vfs_isrdonly(mp)) + return (EROFS); + break; + default: + return (ENODEV); + } + + if (vfs_isrdonly(mp)) + return (EROFS); + + if (fsess_not_impl(mp, FUSE_FALLOCATE)) + return (EINVAL); + + io.uio_offset = *offset; + io.uio_resid = *len; + err = vn_rlimit_fsize(vp, &io, curthread); + if (err) + return (err); + + err = fuse_filehandle_getrw(vp, FWRITE, &fufh, cred, pid); + if (err) + return (err); + + fuse_vnode_update(vp, FN_MTIMECHANGE | FN_CTIMECHANGE); + + err = fuse_vnode_size(vp, &filesize, cred, curthread); + if (err) + return (err); + fuse_inval_buf_range(vp, filesize, *offset, *offset + *len); + + fdisp_init(&fdi, sizeof(*ffi)); + fdisp_make_vp(&fdi, FUSE_FALLOCATE, vp, curthread, cred); + ffi = fdi.indata; + ffi->fh = fufh->fh_id; + ffi->offset = *offset; + ffi->length = *len; + ffi->mode = 0; + err = fdisp_wait_answ(&fdi); + + if (err == ENOSYS) { + fsess_set_notimpl(mp, FUSE_FALLOCATE); + err = EINVAL; + } else if (err == EOPNOTSUPP) { + /* + * The file system server does not support FUSE_FALLOCATE with + * the supplied mode. That's effectively the same thing as + * ENOSYS since we only ever issue mode=0. + * TODO: revise this section once we support fspacectl. + */ + fsess_set_notimpl(mp, FUSE_FALLOCATE); + err = EINVAL; + } else if (!err) { + *offset += *len; + *len = 0; + fuse_vnode_undirty_cached_timestamps(vp, false); + fuse_internal_clear_suid_on_write(vp, cred, curthread); + if (*offset > fvdat->cached_attrs.va_size) { + fuse_vnode_setsize(vp, *offset, false); + getnanouptime(&fvdat->last_local_modify); + } + } + + return (err); +} + /* { struct vnode *a_vp; daddr_t a_bn; diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile --- a/tests/sys/fs/fusefs/Makefile +++ b/tests/sys/fs/fusefs/Makefile @@ -19,6 +19,7 @@ GTESTS+= default_permissions_privileged GTESTS+= destroy GTESTS+= dev_fuse_poll +GTESTS+= fallocate GTESTS+= fifo GTESTS+= flush GTESTS+= forget diff --git a/tests/sys/fs/fusefs/default_permissions.cc b/tests/sys/fs/fusefs/default_permissions.cc --- a/tests/sys/fs/fusefs/default_permissions.cc +++ b/tests/sys/fs/fusefs/default_permissions.cc @@ -163,6 +163,7 @@ class CopyFileRange: public DefaultPermissions {}; class Lookup: public DefaultPermissions {}; class Open: public DefaultPermissions {}; +class PosixFallocate: public DefaultPermissions {}; class Setattr: public DefaultPermissions {}; class Unlink: public DefaultPermissions {}; class Utimensat: public DefaultPermissions {}; @@ -498,7 +499,7 @@ } /* A write by a non-owner should clear a file's SGID bit */ -TEST_F(CopyFileRange, clear_guid) +TEST_F(CopyFileRange, clear_sgid) { const char FULLPATH_IN[] = "mountpoint/in.txt"; const char RELPATH_IN[] = "in.txt"; @@ -877,6 +878,92 @@ leak(fd); } +/* A write by a non-owner should clear a file's SGID bit */ +TEST_F(PosixFallocate, clear_sgid) +{ + const char FULLPATH[] = "mountpoint/file.txt"; + const char RELPATH[] = "file.txt"; + struct stat sb; + uint64_t ino = 42; + mode_t oldmode = 02777; + mode_t newmode = 0777; + off_t fsize = 16; + off_t off = 8; + off_t len = 8; + int fd; + + expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, + 1, UINT64_MAX, 0, 0); + expect_open(ino, 0, 1); + expect_fallocate(ino, off, len, 0, 0); + expect_chmod(ino, newmode, fsize); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); + ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); + EXPECT_EQ(S_IFREG | newmode, sb.st_mode); + + leak(fd); +} + +/* A write by a non-owner should clear a file's SUID bit */ +TEST_F(PosixFallocate, clear_suid) +{ + const char FULLPATH[] = "mountpoint/file.txt"; + const char RELPATH[] = "file.txt"; + struct stat sb; + uint64_t ino = 42; + mode_t oldmode = 04777; + mode_t newmode = 0777; + off_t fsize = 16; + off_t off = 8; + off_t len = 8; + int fd; + + expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, + 1, UINT64_MAX, 0, 0); + expect_open(ino, 0, 1); + expect_fallocate(ino, off, len, 0, 0); + expect_chmod(ino, newmode, fsize); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); + ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); + EXPECT_EQ(S_IFREG | newmode, sb.st_mode); + + leak(fd); +} + +/* + * posix_fallcoate() of a file without writable permissions should succeed as + * long as the file descriptor is writable. This is important when combined + * with O_CREAT + */ +TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + off_t off = 8; + off_t len = 8; + int fd; + + expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnErrno(ENOENT))); + expect_create(RELPATH, ino); + expect_fallocate(ino, off, len, 0, 0); + + fd = open(FULLPATH, O_CREAT | O_RDWR, 0); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); + leak(fd); +} + TEST_F(Rename, eacces_on_srcdir) { const char FULLDST[] = "mountpoint/d/dst"; diff --git a/tests/sys/fs/fusefs/fallocate.cc b/tests/sys/fs/fusefs/fallocate.cc new file mode 100644 --- /dev/null +++ b/tests/sys/fs/fusefs/fallocate.cc @@ -0,0 +1,297 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Alan Somers + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +extern "C" { +#include +#include +#include +#include + +#include +#include +#include + +#include "mntopts.h" // for build_iovec +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Fallocate: public FuseTest{}; + +class PosixFallocate: public Fallocate { +public: +static sig_atomic_t s_sigxfsz; + +void SetUp() { + s_sigxfsz = 0; + FuseTest::SetUp(); +} + +void TearDown() { + struct sigaction sa; + + bzero(&sa, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGXFSZ, &sa, NULL); + + Fallocate::TearDown(); +} + +}; + +sig_atomic_t PosixFallocate::s_sigxfsz = 0; + +void sigxfsz_handler(int __unused sig) { + PosixFallocate::s_sigxfsz = 1; +} + +class PosixFallocate_7_18: public PosixFallocate { +public: +virtual void SetUp() { + m_kernel_minor_version = 18; + PosixFallocate::SetUp(); +} +}; + + +/* + * If the server returns ENOSYS, it indicates that the server does not support + * FUSE_FALLOCATE. This and future calls should return EINVAL. + */ +TEST_F(PosixFallocate, enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + expect_fallocate(ino, offset, length, 0, ENOSYS); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); + + /* Subsequent calls shouldn't query the daemon*/ + EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); + + leak(fd); +} + +/* + * EOPNOTSUPP means either "the file system does not support fallocate" or "the + * file system does not support fallocate with the supplied mode". fusefs + * should conservatively assume the latter, and not issue any more fallocate + * operations with the same mode. + */ +TEST_F(PosixFallocate, eopnotsupp) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + expect_fallocate(ino, offset, length, 0, EOPNOTSUPP); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); + + /* Subsequent calls shouldn't query the daemon*/ + EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); + + leak(fd); +} + +/* EIO is not a permanent error, and may be retried */ +TEST_F(PosixFallocate, eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + expect_fallocate(ino, offset, length, 0, EIO); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(EIO, posix_fallocate(fd, offset, length)); + + expect_fallocate(ino, offset, length, 0, 0); + + EXPECT_EQ(0, posix_fallocate(fd, offset, length)); + + leak(fd); +} + +TEST_F(PosixFallocate, erofs) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct statfs statbuf; + struct iovec *iov = NULL; + int iovlen = 0; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + int newflags; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in.header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) + { + /* + * All of the fields except f_flags are don't care, and f_flags + * is set by the VFS + */ + SET_OUT_HEADER_LEN(out, statfs); + }))); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + + /* Remount read-only */ + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; + build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); + build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); + build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); + ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); + + EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length)); + + leak(fd); +} + +TEST_F(PosixFallocate, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct stat sb0, sb1; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = S_IFREG | 0644; + out.body.entry.nodeid = ino; + out.body.entry.entry_valid = UINT64_MAX; + out.body.entry.attr_valid = UINT64_MAX; + }))); + expect_open(ino, 0, 1); + expect_fallocate(ino, offset, length, 0, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); + EXPECT_EQ(0, posix_fallocate(fd, offset, length)); + /* + * Despite the originally cached file size of zero, stat should now + * return either the new size or requery the daemon. + */ + EXPECT_EQ(0, stat(FULLPATH, &sb1)); + EXPECT_EQ(length, (uint64_t)sb1.st_size); + + /* mtime and ctime should be updated */ + EXPECT_EQ(sb0.st_atime, sb1.st_atime); + EXPECT_NE(sb0.st_mtime, sb1.st_mtime); + EXPECT_NE(sb0.st_ctime, sb1.st_ctime); + + leak(fd); +} + +/* fusefs should respect RLIMIT_FSIZE */ +TEST_F(PosixFallocate, rlimit_fsize) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct rlimit rl; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1'000'000; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + + rl.rlim_cur = length / 2; + rl.rlim_max = 10 * length; + ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); + ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length)); + EXPECT_EQ(1, s_sigxfsz); + + leak(fd); +} + +/* With older servers, no FUSE_FALLOCATE should be attempted */ +TEST_F(PosixFallocate_7_18, einval) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + uint64_t offset = 0; + uint64_t length = 1000; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); + + leak(fd); +} diff --git a/tests/sys/fs/fusefs/last_local_modify.cc b/tests/sys/fs/fusefs/last_local_modify.cc --- a/tests/sys/fs/fusefs/last_local_modify.cc +++ b/tests/sys/fs/fusefs/last_local_modify.cc @@ -62,9 +62,10 @@ */ enum Mutator { + VOP_ALLOCATE, + VOP_COPY_FILE_RANGE, VOP_SETATTR, VOP_WRITE, - VOP_COPY_FILE_RANGE }; /* @@ -73,22 +74,26 @@ * --gtest_list_tests */ enum Mutator writer_from_str(const char* s) { - if (0 == strcmp("VOP_SETATTR", s)) + if (0 == strcmp("VOP_ALLOCATE", s)) + return VOP_ALLOCATE; + else if (0 == strcmp("VOP_COPY_FILE_RANGE", s)) + return VOP_COPY_FILE_RANGE; + else if (0 == strcmp("VOP_SETATTR", s)) return VOP_SETATTR; - else if (0 == strcmp("VOP_WRITE", s)) - return VOP_WRITE; else - return VOP_COPY_FILE_RANGE; + return VOP_WRITE; } uint32_t fuse_op_from_mutator(enum Mutator mutator) { switch(mutator) { + case VOP_ALLOCATE: + return(FUSE_FALLOCATE); + case VOP_COPY_FILE_RANGE: + return(FUSE_COPY_FILE_RANGE); case VOP_SETATTR: return(FUSE_SETATTR); case VOP_WRITE: return(FUSE_WRITE); - case VOP_COPY_FILE_RANGE: - return(FUSE_COPY_FILE_RANGE); } } @@ -101,6 +106,25 @@ } }; +static void* allocate_th(void* arg) { + int fd; + ssize_t r; + sem_t *sem = (sem_t*) arg; + + if (sem) + sem_wait(sem); + + fd = open("mountpoint/some_file.txt", O_RDWR); + if (fd < 0) + return (void*)(intptr_t)errno; + + r = posix_fallocate(fd, 0, 15); + if (r >= 0) + return 0; + else + return (void*)(intptr_t)errno; +} + static void* copy_file_range_th(void* arg) { ssize_t r; int fd; @@ -261,6 +285,14 @@ /* Then, respond to the mutator request */ out1->header.unique = mutator_unique; switch(mutator) { + case VOP_ALLOCATE: + out1->header.error = 0; + out1->header.len = sizeof(out1->header); + break; + case VOP_COPY_FILE_RANGE: + SET_OUT_HEADER_LEN(*out1, write); + out1->body.write.size = mutator_size; + break; case VOP_SETATTR: SET_OUT_HEADER_LEN(*out1, attr); out1->body.attr.attr.ino = ino; @@ -272,16 +304,20 @@ SET_OUT_HEADER_LEN(*out1, write); out1->body.write.size = mutator_size; break; - case VOP_COPY_FILE_RANGE: - SET_OUT_HEADER_LEN(*out1, write); - out1->body.write.size = mutator_size; - break; } out.push_back(std::move(out1)); })); /* Start the mutator thread */ switch(mutator) { + case VOP_ALLOCATE: + ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, + NULL)) << strerror(errno); + break; + case VOP_COPY_FILE_RANGE: + ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, + NULL)) << strerror(errno); + break; case VOP_SETATTR: ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL)) << strerror(errno); @@ -290,10 +326,6 @@ ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL)) << strerror(errno); break; - case VOP_COPY_FILE_RANGE: - ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, - NULL)) << strerror(errno); - break; } @@ -415,6 +447,14 @@ /* Then, respond to the mutator request */ out1->header.unique = in.header.unique; switch(mutator) { + case VOP_ALLOCATE: + out1->header.error = 0; + out1->header.len = sizeof(out1->header); + break; + case VOP_COPY_FILE_RANGE: + SET_OUT_HEADER_LEN(*out1, write); + out1->body.write.size = in.body.copy_file_range.len; + break; case VOP_SETATTR: SET_OUT_HEADER_LEN(*out1, attr); out1->body.attr.attr.ino = ino; @@ -426,10 +466,6 @@ SET_OUT_HEADER_LEN(*out1, write); out1->body.write.size = in.body.write.size; break; - case VOP_COPY_FILE_RANGE: - SET_OUT_HEADER_LEN(*out1, write); - out1->body.write.size = in.body.copy_file_range.len; - break; } out.push_back(std::move(out1)); })); @@ -439,6 +475,14 @@ /* Start the mutator thread */ switch(mutator) { + case VOP_ALLOCATE: + ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th, + (void*)&sem)) << strerror(errno); + break; + case VOP_COPY_FILE_RANGE: + ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, + (void*)&sem)) << strerror(errno); + break; case VOP_SETATTR: ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, (void*)&sem)) << strerror(errno); @@ -447,10 +491,6 @@ ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem)) << strerror(errno); break; - case VOP_COPY_FILE_RANGE: - ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th, - (void*)&sem)) << strerror(errno); - break; } /* Lookup again, which will race with setattr */ @@ -465,5 +505,10 @@ INSTANTIATE_TEST_CASE_P(LLM, LastLocalModify, - Values("VOP_SETATTR", "VOP_WRITE", "VOP_COPY_FILE_RANGE") + Values( + "VOP_ALLOCATE", + "VOP_COPY_FILE_RANGE", + "VOP_SETATTR", + "VOP_WRITE" + ) ); diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -159,6 +159,7 @@ ]; fuse_copy_file_range_in copy_file_range; fuse_create_in create; + fuse_fallocate_in fallocate; fuse_flush_in flush; fuse_fsync_in fsync; fuse_fsync_in fsyncdir; diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -207,6 +207,14 @@ printf(" flags=%#x name=%s", in.body.open.flags, name); break; + case FUSE_FALLOCATE: + printf(" fh=%#" PRIx64 " offset=%" PRIu64 + " length=%" PRIx64 " mode=%#x", + in.body.fallocate.fh, + in.body.fallocate.offset, + in.body.fallocate.length, + in.body.fallocate.mode); + break; case FUSE_FLUSH: printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64, in.body.flush.fh, @@ -684,6 +692,10 @@ EXPECT_EQ(inlen, fih + sizeof(in.body.interrupt)); EXPECT_EQ((size_t)buflen, inlen); break; + case FUSE_FALLOCATE: + EXPECT_EQ(inlen, fih + sizeof(in.body.fallocate)); + EXPECT_EQ((size_t)buflen, inlen); + break; case FUSE_BMAP: EXPECT_EQ(inlen, fih + sizeof(in.body.bmap)); EXPECT_EQ((size_t)buflen, inlen); @@ -699,7 +711,6 @@ break; case FUSE_NOTIFY_REPLY: case FUSE_BATCH_FORGET: - case FUSE_FALLOCATE: case FUSE_IOCTL: case FUSE_POLL: case FUSE_READDIRPLUS: diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -114,6 +114,14 @@ /* Expect FUSE_DESTROY and shutdown the daemon */ void expect_destroy(int error); + /* + * Create an expectation that FUSE_FALLOCATE will be called with the + * given inode, offset, length, and mode, exactly times times and + * returning error + */ + void expect_fallocate(uint64_t ino, uint64_t offset, uint64_t length, + uint32_t mode, int error, int times=1); + /* * Create an expectation that FUSE_FLUSH will be called times times for * the given inode diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -225,6 +225,23 @@ }))); } +void +FuseTest::expect_fallocate(uint64_t ino, uint64_t offset, uint64_t length, + uint32_t mode, int error, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_FALLOCATE && + in.header.nodeid == ino && + in.body.fallocate.offset == offset && + in.body.fallocate.length == length && + in.body.fallocate.mode == mode); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(ReturnErrno(error))); +} + void FuseTest::expect_flush(uint64_t ino, int times, ProcessMockerT r) {