aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Somers <asomers@gmail.com>2020-12-29 01:25:21 +0000
committerAlan Somers <asomers@FreeBSD.org>2021-01-01 17:18:23 +0000
commit92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba (patch)
tree4a5fc1b96281a02f3e9b9c0dc6c1de90fcf06d41
parentae39db74066a0ff1682c1c841be030099d9d4557 (diff)
downloadsrc-92bbfe1f0d1f.tar.gz
src-92bbfe1f0d1f.zip
fusefs: implement FUSE_COPY_FILE_RANGE.
This updates the FUSE protocol to 7.28, though most of the new features are optional and are not yet implemented. MFC after: 2 weeks Relnotes: yes Reviewed by: cem Differential Revision: https://reviews.freebsd.org/D27818
-rw-r--r--sys/fs/fuse/fuse_internal.c48
-rw-r--r--sys/fs/fuse/fuse_internal.h4
-rw-r--r--sys/fs/fuse/fuse_io.c42
-rw-r--r--sys/fs/fuse/fuse_ipc.c4
-rw-r--r--sys/fs/fuse/fuse_kernel.h141
-rw-r--r--sys/fs/fuse/fuse_vnops.c122
-rw-r--r--tests/sys/fs/fusefs/Makefile1
-rw-r--r--tests/sys/fs/fusefs/copy_file_range.cc401
-rw-r--r--tests/sys/fs/fusefs/default_permissions.cc110
-rw-r--r--tests/sys/fs/fusefs/mockfs.cc20
-rw-r--r--tests/sys/fs/fusefs/mockfs.hh1
-rw-r--r--tests/sys/fs/fusefs/write.cc1
12 files changed, 803 insertions, 92 deletions
diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c
index 2faad7cd8651..60f9a7319e00 100644
--- a/sys/fs/fuse/fuse_internal.c
+++ b/sys/fs/fuse/fuse_internal.c
@@ -1054,6 +1054,9 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio)
if (!fuse_libabi_geq(data, 7, 24))
fsess_set_notimpl(data->mp, FUSE_LSEEK);
+ if (!fuse_libabi_geq(data, 7, 28))
+ fsess_set_notimpl(data->mp, FUSE_COPY_FILE_RANGE);
+
out:
if (err) {
fdata_set_dead(data);
@@ -1098,6 +1101,12 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
* FUSE_READDIRPLUS_AUTO: not yet implemented
* FUSE_ASYNC_DIO: not yet implemented
* FUSE_NO_OPEN_SUPPORT: not yet implemented
+ * FUSE_PARALLEL_DIROPS: not yet implemented
+ * FUSE_HANDLE_KILLPRIV: not yet implemented
+ * FUSE_POSIX_ACL: not yet implemented
+ * FUSE_ABORT_ERROR: not yet implemented
+ * FUSE_CACHE_SYMLINKS: not yet implemented
+ * FUSE_MAX_PAGES: not yet implemented
*/
fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE;
@@ -1228,6 +1237,45 @@ out:
return err;
}
+/*
+ * FreeBSD clears the SUID and SGID bits on any write by a non-root user.
+ */
+void
+fuse_internal_clear_suid_on_write(struct vnode *vp, struct ucred *cred,
+ struct thread *td)
+{
+ struct fuse_data *data;
+ struct mount *mp;
+ struct vattr va;
+ int dataflags;
+
+ mp = vnode_mount(vp);
+ data = fuse_get_mpdata(mp);
+ dataflags = data->dataflags;
+
+ ASSERT_VOP_LOCKED(vp, __func__);
+
+ if (dataflags & FSESS_DEFAULT_PERMISSIONS) {
+ if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID)) {
+ fuse_internal_getattr(vp, &va, cred, td);
+ if (va.va_mode & (S_ISUID | S_ISGID)) {
+ mode_t mode = va.va_mode & ~(S_ISUID | S_ISGID);
+ /* Clear all vattr fields except mode */
+ vattr_null(&va);
+ va.va_mode = mode;
+
+ /*
+ * Ignore fuse_internal_setattr's return value,
+ * because at this point the write operation has
+ * already succeeded and we don't want to return
+ * failing status for that.
+ */
+ (void)fuse_internal_setattr(vp, &va, td, NULL);
+ }
+ }
+ }
+}
+
#ifdef ZERO_PAD_INCOMPLETE_BUFS
static int
isbzero(void *buf, size_t len)
diff --git a/sys/fs/fuse/fuse_internal.h b/sys/fs/fuse/fuse_internal.h
index de68861beae2..20a10d7dfda0 100644
--- a/sys/fs/fuse/fuse_internal.h
+++ b/sys/fs/fuse/fuse_internal.h
@@ -274,6 +274,10 @@ void fuse_internal_vnode_disappear(struct vnode *vp);
int fuse_internal_setattr(struct vnode *vp, struct vattr *va,
struct thread *td, struct ucred *cred);
+/* write */
+void fuse_internal_clear_suid_on_write(struct vnode *vp, struct ucred *cred,
+ struct thread *td);
+
/* strategy */
/* entity creation */
diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c
index 4e178bb5340a..3f23a35a8626 100644
--- a/sys/fs/fuse/fuse_io.c
+++ b/sys/fs/fuse/fuse_io.c
@@ -121,9 +121,6 @@ SDT_PROBE_DEFINE2(fusefs, , io, trace, "int", "char*");
static int
fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end);
-static void
-fuse_io_clear_suid_on_write(struct vnode *vp, struct ucred *cred,
- struct thread *td);
static int
fuse_read_directbackend(struct vnode *vp, struct uio *uio,
struct ucred *cred, struct fuse_filehandle *fufh);
@@ -190,43 +187,6 @@ fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end)
return (0);
}
-/*
- * FreeBSD clears the SUID and SGID bits on any write by a non-root user.
- */
-static void
-fuse_io_clear_suid_on_write(struct vnode *vp, struct ucred *cred,
- struct thread *td)
-{
- struct fuse_data *data;
- struct mount *mp;
- struct vattr va;
- int dataflags;
-
- mp = vnode_mount(vp);
- data = fuse_get_mpdata(mp);
- dataflags = data->dataflags;
-
- if (dataflags & FSESS_DEFAULT_PERMISSIONS) {
- if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID)) {
- fuse_internal_getattr(vp, &va, cred, td);
- if (va.va_mode & (S_ISUID | S_ISGID)) {
- mode_t mode = va.va_mode & ~(S_ISUID | S_ISGID);
- /* Clear all vattr fields except mode */
- vattr_null(&va);
- va.va_mode = mode;
-
- /*
- * Ignore fuse_internal_setattr's return value,
- * because at this point the write operation has
- * already succeeded and we don't want to return
- * failing status for that.
- */
- (void)fuse_internal_setattr(vp, &va, td, NULL);
- }
- }
- }
-}
-
SDT_PROBE_DEFINE5(fusefs, , io, io_dispatch, "struct vnode*", "struct uio*",
"int", "struct ucred*", "struct fuse_filehandle*");
SDT_PROBE_DEFINE4(fusefs, , io, io_dispatch_filehandles_closed, "struct vnode*",
@@ -318,7 +278,7 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag,
err = fuse_write_biobackend(vp, uio, cred, fufh, ioflag,
pid);
}
- fuse_io_clear_suid_on_write(vp, cred, uio->uio_td);
+ fuse_internal_clear_suid_on_write(vp, cred, uio->uio_td);
break;
default:
panic("uninterpreted mode passed to fuse_io_dispatch");
diff --git a/sys/fs/fuse/fuse_ipc.c b/sys/fs/fuse/fuse_ipc.c
index d3738da26b34..791ee9f38444 100644
--- a/sys/fs/fuse/fuse_ipc.c
+++ b/sys/fs/fuse/fuse_ipc.c
@@ -855,6 +855,10 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
err = (blen == sizeof(struct fuse_lseek_out)) ? 0 : EINVAL;
break;
+ case FUSE_COPY_FILE_RANGE:
+ err = (blen == sizeof(struct fuse_write_out)) ? 0 : EINVAL;
+ break;
+
default:
panic("FUSE: opcodes out of sync (%d)\n", opcode);
}
diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h
index 6e97b04a733f..14cf4fabac14 100644
--- a/sys/fs/fuse/fuse_kernel.h
+++ b/sys/fs/fuse/fuse_kernel.h
@@ -1,4 +1,6 @@
-/*--
+/*-
+ * SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause)
+ *
* This file defines the kernel interface of FUSE
* Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
*
@@ -105,6 +107,22 @@
*
* 7.24
* - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+ *
+ * 7.25
+ * - add FUSE_PARALLEL_DIROPS
+ *
+ * 7.26
+ * - add FUSE_HANDLE_KILLPRIV
+ * - add FUSE_POSIX_ACL
+ *
+ * 7.27
+ * - add FUSE_ABORT_ERROR
+ *
+ * 7.28
+ * - add FUSE_COPY_FILE_RANGE
+ * - add FOPEN_CACHE_DIR
+ * - add FUSE_MAX_PAGES, add max_pages to init_out
+ * - add FUSE_CACHE_SYMLINKS
*/
#ifndef _FUSE_FUSE_KERNEL_H
@@ -120,7 +138,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 24
+#define FUSE_KERNEL_MINOR_VERSION 28
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -188,10 +206,12 @@ struct fuse_file_lock {
* FOPEN_DIRECT_IO: bypass page cache for this open file
* FOPEN_KEEP_CACHE: don't invalidate the data cache on open
* FOPEN_NONSEEKABLE: the file is not seekable
+ * FOPEN_CACHE_DIR: allow caching this directory
*/
#define FOPEN_DIRECT_IO (1 << 0)
#define FOPEN_KEEP_CACHE (1 << 1)
#define FOPEN_NONSEEKABLE (1 << 2)
+#define FOPEN_CACHE_DIR (1 << 3)
/**
* INIT request/reply flags
@@ -214,6 +234,12 @@ struct fuse_file_lock {
* FUSE_ASYNC_DIO: asynchronous direct I/O submission
* FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
* FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
+ * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir
+ * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
+ * FUSE_POSIX_ACL: filesystem supports posix acls
+ * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED
+ * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages
+ * FUSE_CACHE_SYMLINKS: cache READLINK responses
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -233,6 +259,12 @@ struct fuse_file_lock {
#define FUSE_ASYNC_DIO (1 << 15)
#define FUSE_WRITEBACK_CACHE (1 << 16)
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+#define FUSE_PARALLEL_DIROPS (1 << 18)
+#define FUSE_HANDLE_KILLPRIV (1 << 19)
+#define FUSE_POSIX_ACL (1 << 20)
+#define FUSE_ABORT_ERROR (1 << 21)
+#define FUSE_MAX_PAGES (1 << 22)
+#define FUSE_CACHE_SYMLINKS (1 << 23)
#ifdef linux
/**
@@ -300,54 +332,55 @@ struct fuse_file_lock {
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
enum fuse_opcode {
- FUSE_LOOKUP = 1,
- FUSE_FORGET = 2, /* no reply */
- FUSE_GETATTR = 3,
- FUSE_SETATTR = 4,
- FUSE_READLINK = 5,
- FUSE_SYMLINK = 6,
- FUSE_MKNOD = 8,
- FUSE_MKDIR = 9,
- FUSE_UNLINK = 10,
- FUSE_RMDIR = 11,
- FUSE_RENAME = 12,
- FUSE_LINK = 13,
- FUSE_OPEN = 14,
- FUSE_READ = 15,
- FUSE_WRITE = 16,
- FUSE_STATFS = 17,
- FUSE_RELEASE = 18,
- FUSE_FSYNC = 20,
- FUSE_SETXATTR = 21,
- FUSE_GETXATTR = 22,
- FUSE_LISTXATTR = 23,
- FUSE_REMOVEXATTR = 24,
- FUSE_FLUSH = 25,
- FUSE_INIT = 26,
- FUSE_OPENDIR = 27,
- FUSE_READDIR = 28,
- FUSE_RELEASEDIR = 29,
- FUSE_FSYNCDIR = 30,
- FUSE_GETLK = 31,
- FUSE_SETLK = 32,
- FUSE_SETLKW = 33,
- FUSE_ACCESS = 34,
- FUSE_CREATE = 35,
- FUSE_INTERRUPT = 36,
- FUSE_BMAP = 37,
- FUSE_DESTROY = 38,
- FUSE_IOCTL = 39,
- FUSE_POLL = 40,
- FUSE_NOTIFY_REPLY = 41,
- FUSE_BATCH_FORGET = 42,
- FUSE_FALLOCATE = 43,
- FUSE_READDIRPLUS = 44,
- FUSE_RENAME2 = 45,
- FUSE_LSEEK = 46,
+ FUSE_LOOKUP = 1,
+ FUSE_FORGET = 2, /* no reply */
+ FUSE_GETATTR = 3,
+ FUSE_SETATTR = 4,
+ FUSE_READLINK = 5,
+ FUSE_SYMLINK = 6,
+ FUSE_MKNOD = 8,
+ FUSE_MKDIR = 9,
+ FUSE_UNLINK = 10,
+ FUSE_RMDIR = 11,
+ FUSE_RENAME = 12,
+ FUSE_LINK = 13,
+ FUSE_OPEN = 14,
+ FUSE_READ = 15,
+ FUSE_WRITE = 16,
+ FUSE_STATFS = 17,
+ FUSE_RELEASE = 18,
+ FUSE_FSYNC = 20,
+ FUSE_SETXATTR = 21,
+ FUSE_GETXATTR = 22,
+ FUSE_LISTXATTR = 23,
+ FUSE_REMOVEXATTR = 24,
+ FUSE_FLUSH = 25,
+ FUSE_INIT = 26,
+ FUSE_OPENDIR = 27,
+ FUSE_READDIR = 28,
+ FUSE_RELEASEDIR = 29,
+ FUSE_FSYNCDIR = 30,
+ FUSE_GETLK = 31,
+ FUSE_SETLK = 32,
+ FUSE_SETLKW = 33,
+ FUSE_ACCESS = 34,
+ FUSE_CREATE = 35,
+ FUSE_INTERRUPT = 36,
+ FUSE_BMAP = 37,
+ FUSE_DESTROY = 38,
+ FUSE_IOCTL = 39,
+ FUSE_POLL = 40,
+ FUSE_NOTIFY_REPLY = 41,
+ FUSE_BATCH_FORGET = 42,
+ FUSE_FALLOCATE = 43,
+ FUSE_READDIRPLUS = 44,
+ FUSE_RENAME2 = 45,
+ FUSE_LSEEK = 46,
+ FUSE_COPY_FILE_RANGE = 47,
#ifdef linux
/* CUSE specific operations */
- CUSE_INIT = 4096,
+ CUSE_INIT = 4096,
#endif /* linux */
};
@@ -585,7 +618,9 @@ struct fuse_init_out {
uint16_t congestion_threshold;
uint32_t max_write;
uint32_t time_gran;
- uint32_t unused[9];
+ uint16_t max_pages;
+ uint16_t padding;
+ uint32_t unused[8];
};
#ifdef linux
@@ -766,4 +801,14 @@ struct fuse_lseek_out {
uint64_t offset;
};
+struct fuse_copy_file_range_in {
+ uint64_t fh_in;
+ uint64_t off_in;
+ uint64_t nodeid_out;
+ uint64_t fh_out;
+ uint64_t off_out;
+ uint64_t len;
+ uint64_t flags;
+};
+
#endif /* _FUSE_FUSE_KERNEL_H */
diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index efac7e041cf6..1e9434f9403d 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -130,6 +130,7 @@ static vop_advlock_t fuse_vnop_advlock;
static vop_bmap_t fuse_vnop_bmap;
static vop_close_t fuse_fifo_close;
static vop_close_t fuse_vnop_close;
+static vop_copy_file_range_t fuse_vnop_copy_file_range;
static vop_create_t fuse_vnop_create;
static vop_deleteextattr_t fuse_vnop_deleteextattr;
static vop_fdatasync_t fuse_vnop_fdatasync;
@@ -185,6 +186,7 @@ struct vop_vector fuse_vnops = {
.vop_advlock = fuse_vnop_advlock,
.vop_bmap = fuse_vnop_bmap,
.vop_close = fuse_vnop_close,
+ .vop_copy_file_range = fuse_vnop_copy_file_range,
.vop_create = fuse_vnop_create,
.vop_deleteextattr = fuse_vnop_deleteextattr,
.vop_fsync = fuse_vnop_fsync,
@@ -609,6 +611,126 @@ fuse_vnop_close(struct vop_close_args *ap)
return err;
}
+/*
+ struct vop_copy_file_range_args {
+ struct vop_generic_args a_gen;
+ struct vnode *a_invp;
+ off_t *a_inoffp;
+ struct vnode *a_outvp;
+ off_t *a_outoffp;
+ size_t *a_lenp;
+ unsigned int a_flags;
+ struct ucred *a_incred;
+ struct ucred *a_outcred;
+ struct thread *a_fsizetd;
+}
+ */
+static int
+fuse_vnop_copy_file_range(struct vop_copy_file_range_args *ap)
+{
+ struct vnode *invp = ap->a_invp;
+ struct vnode *outvp = ap->a_outvp;
+ struct mount *mp = vnode_mount(invp);
+ struct fuse_dispatcher fdi;
+ struct fuse_filehandle *infufh, *outfufh;
+ struct fuse_copy_file_range_in *fcfri;
+ struct ucred *incred = ap->a_incred;
+ struct ucred *outcred = ap->a_outcred;
+ struct fuse_write_out *fwo;
+ struct thread *td;
+ struct uio io;
+ pid_t pid;
+ int err;
+
+ if (mp != vnode_mount(outvp))
+ goto fallback;
+
+ if (incred->cr_uid != outcred->cr_uid)
+ goto fallback;
+
+ if (incred->cr_groups[0] != outcred->cr_groups[0])
+ goto fallback;
+
+ if (fsess_not_impl(mp, FUSE_COPY_FILE_RANGE))
+ goto fallback;
+
+ if (ap->a_fsizetd == NULL)
+ td = curthread;
+ else
+ td = ap->a_fsizetd;
+ pid = td->td_proc->p_pid;
+
+ err = fuse_filehandle_getrw(invp, FREAD, &infufh, incred, pid);
+ if (err)
+ return (err);
+
+ err = fuse_filehandle_getrw(outvp, FWRITE, &outfufh, outcred, pid);
+ if (err)
+ return (err);
+
+ /* Lock both vnodes, avoiding risk of deadlock. */
+ do {
+ err = vn_lock(outvp, LK_EXCLUSIVE);
+ if (invp == outvp)
+ break;
+ if (err == 0) {
+ err = vn_lock(invp, LK_SHARED | LK_NOWAIT);
+ if (err == 0)
+ break;
+ VOP_UNLOCK(outvp);
+ err = vn_lock(invp, LK_SHARED);
+ if (err == 0)
+ VOP_UNLOCK(invp);
+ }
+ } while (err == 0);
+ if (err != 0)
+ return (err);
+
+ if (ap->a_fsizetd) {
+ io.uio_offset = *ap->a_outoffp;
+ io.uio_resid = *ap->a_lenp;
+ err = vn_rlimit_fsize(outvp, &io, ap->a_fsizetd);
+ if (err)
+ goto unlock;
+ }
+
+ fdisp_init(&fdi, sizeof(*fcfri));
+ fdisp_make_vp(&fdi, FUSE_COPY_FILE_RANGE, invp, td, incred);
+ fcfri = fdi.indata;
+ fcfri->fh_in = infufh->fh_id;
+ fcfri->off_in = *ap->a_inoffp;
+ fcfri->nodeid_out = VTOI(outvp);
+ fcfri->fh_out = outfufh->fh_id;
+ fcfri->off_out = *ap->a_outoffp;
+ fcfri->len = *ap->a_lenp;
+ fcfri->flags = 0;
+
+ err = fdisp_wait_answ(&fdi);
+ if (err == 0) {
+ fwo = fdi.answ;
+ *ap->a_lenp = fwo->size;
+ *ap->a_inoffp += fwo->size;
+ *ap->a_outoffp += fwo->size;
+ fuse_internal_clear_suid_on_write(outvp, outcred, td);
+ }
+ fdisp_destroy(&fdi);
+
+unlock:
+ if (invp != outvp)
+ VOP_UNLOCK(invp);
+ VOP_UNLOCK(outvp);
+
+ if (err == ENOSYS) {
+ fsess_set_notimpl(mp, FUSE_COPY_FILE_RANGE);
+fallback:
+ err = vn_generic_copy_file_range(ap->a_invp, ap->a_inoffp,
+ ap->a_outvp, ap->a_outoffp, ap->a_lenp, ap->a_flags,
+ ap->a_incred, ap->a_outcred, ap->a_fsizetd);
+ }
+
+ return (err);
+}
+
static void
fdisp_make_mknod_for_fallback(
struct fuse_dispatcher *fdip,
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
index 8d199a53c074..2c858ff42dd1 100644
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -13,6 +13,7 @@ GTESTS+= access
GTESTS+= allow_other
GTESTS+= bmap
GTESTS+= cache
+GTESTS+= copy_file_range
GTESTS+= create
GTESTS+= default_permissions
GTESTS+= default_permissions_privileged
diff --git a/tests/sys/fs/fusefs/copy_file_range.cc b/tests/sys/fs/fusefs/copy_file_range.cc
new file mode 100644
index 000000000000..bb8eecf8b862
--- /dev/null
+++ b/tests/sys/fs/fusefs/copy_file_range.cc
@@ -0,0 +1,401 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 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 <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class CopyFileRange: public FuseTest {
+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);
+
+ FuseTest::TearDown();
+}
+
+void expect_maybe_lseek(uint64_t ino)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(AtMost(1))
+ .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
+}
+
+void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(times)
+ .WillRepeatedly(Invoke(
+ ReturnImmediate([=](auto in __unused, auto& out) {
+ out.header.len = sizeof(out.header);
+ SET_OUT_HEADER_LEN(out, open);
+ out.body.open.fh = fh;
+ out.body.open.open_flags = flags;
+ })));
+}
+
+void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
+ uint64_t osize, const void *contents)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ const char *buf = (const char*)in.body.bytes +
+ sizeof(struct fuse_write_in);
+
+ return (in.header.opcode == FUSE_WRITE &&
+ in.header.nodeid == ino &&
+ in.body.write.offset == offset &&
+ in.body.write.size == isize &&
+ 0 == bcmp(buf, contents, isize));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = osize;
+ })));
+}
+
+};
+
+sig_atomic_t CopyFileRange::s_sigxfsz = 0;
+
+void sigxfsz_handler(int __unused sig) {
+ CopyFileRange::s_sigxfsz = 1;
+}
+
+
+class CopyFileRange_7_27: public CopyFileRange {
+public:
+virtual void SetUp() {
+ m_kernel_minor_version = 27;
+ CopyFileRange::SetUp();
+}
+};
+
+TEST_F(CopyFileRange, eio)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = 3 << 17;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(EIO)));
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+ EXPECT_EQ(EIO, errno);
+}
+
+/*
+ * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
+ * fallback to a read/write based implementation.
+ */
+TEST_F(CopyFileRange, fallback)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize2 = 0;
+ off_t start1 = 0;
+ off_t start2 = 0;
+ const char *contents = "Hello, world!";
+ ssize_t len;
+ int fd1, fd2;
+
+ len = strlen(contents);
+
+ /*
+ * Ensure that we read to EOF, just so the buffer cache's read size is
+ * predictable.
+ */
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ expect_maybe_lseek(ino1);
+ expect_read(ino1, start1, len, len, contents, 0);
+ expect_write(ino2, start2, len, len, contents);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ ASSERT_GE(fd1, 0);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_GE(fd2, 0);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+/* fusefs should respect RLIMIT_FSIZE */
+TEST_F(CopyFileRange, rlimit_fsize)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ struct rlimit rl;
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = fsize2;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+
+ rl.rlim_cur = fsize2;
+ rl.rlim_max = 10 * fsize2;
+ ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
+ ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+ EXPECT_EQ(EFBIG, errno);
+ EXPECT_EQ(1, s_sigxfsz);
+}
+
+TEST_F(CopyFileRange, ok)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = 3 << 17;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+/*
+ * copy_file_range can make copies within a single file, as long as the ranges
+ * don't overlap.
+ * */
+TEST_F(CopyFileRange, same_file)
+{
+ const char FULLPATH[] = "mountpoint/src.txt";
+ const char RELPATH[] = "src.txt";
+ const uint64_t ino = 4;
+ const uint64_t fh = 0xdeadbeefa7ebabe;
+ off_t fsize = 1 << 20; /* 1 MiB */
+ off_t off_in = 1 << 18;
+ off_t off_out = 3 << 17;
+ ssize_t len = 65536;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1, fh);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino &&
+ in.body.copy_file_range.fh_in == fh &&
+ (off_t)in.body.copy_file_range.off_in == off_in &&
+ in.body.copy_file_range.nodeid_out == ino &&
+ in.body.copy_file_range.fh_out == fh &&
+ (off_t)in.body.copy_file_range.off_out == off_out &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+
+ fd = open(FULLPATH, O_RDWR);
+ ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
+}
+
+/* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
+TEST_F(CopyFileRange_7_27, fallback)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize2 = 0;
+ off_t start1 = 0;
+ off_t start2 = 0;
+ const char *contents = "Hello, world!";
+ ssize_t len;
+ int fd1, fd2;
+
+ len = strlen(contents);
+
+ /*
+ * Ensure that we read to EOF, just so the buffer cache's read size is
+ * predictable.
+ */
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+ expect_maybe_lseek(ino1);
+ expect_read(ino1, start1, len, len, contents, 0);
+ expect_write(ino2, start2, len, len, contents);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ ASSERT_GE(fd1, 0);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_GE(fd2, 0);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+
diff --git a/tests/sys/fs/fusefs/default_permissions.cc b/tests/sys/fs/fusefs/default_permissions.cc
index 368c28bbcb3f..6401f926bb49 100644
--- a/tests/sys/fs/fusefs/default_permissions.cc
+++ b/tests/sys/fs/fusefs/default_permissions.cc
@@ -109,6 +109,25 @@ void expect_create(const char *relpath, uint64_t ino)
})));
}
+void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
+ uint64_t off_out, uint64_t len)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino_in &&
+ in.body.copy_file_range.off_in == off_in &&
+ in.body.copy_file_range.nodeid_out == ino_out &&
+ in.body.copy_file_range.off_out == off_out &&
+ in.body.copy_file_range.len == len);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+}
+
void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
uid_t uid = 0, gid_t gid = 0)
{
@@ -141,6 +160,7 @@ void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
class Access: public DefaultPermissions {};
class Chown: public DefaultPermissions {};
class Chgrp: public DefaultPermissions {};
+class CopyFileRange: public DefaultPermissions {};
class Lookup: public DefaultPermissions {};
class Open: public DefaultPermissions {};
class Setattr: public DefaultPermissions {};
@@ -477,6 +497,94 @@ TEST_F(Chgrp, ok)
EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
}
+/* A write by a non-owner should clear a file's SGID bit */
+TEST_F(CopyFileRange, clear_guid)
+{
+ const char FULLPATH_IN[] = "mountpoint/in.txt";
+ const char RELPATH_IN[] = "in.txt";
+ const char FULLPATH_OUT[] = "mountpoint/out.txt";
+ const char RELPATH_OUT[] = "out.txt";
+ struct stat sb;
+ uint64_t ino_in = 42;
+ uint64_t ino_out = 43;
+ mode_t oldmode = 02777;
+ mode_t newmode = 0777;
+ off_t fsize = 16;
+ off_t off_in = 0;
+ off_t off_out = 8;
+ off_t len = 8;
+ int fd_in, fd_out;
+
+ expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
+ FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
+ UINT64_MAX, 0, 0);
+ expect_open(ino_in, 0, 1);
+ FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
+ 1, UINT64_MAX, 0, 0);
+ expect_open(ino_out, 0, 1);
+ expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
+ expect_chmod(ino_out, newmode, fsize);
+
+ fd_in = open(FULLPATH_IN, O_RDONLY);
+ ASSERT_LE(0, fd_in) << strerror(errno);
+ fd_out = open(FULLPATH_OUT, O_WRONLY);
+ ASSERT_LE(0, fd_out) << strerror(errno);
+ ASSERT_EQ(len,
+ copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
+ << strerror(errno);
+ ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
+ ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
+
+ leak(fd_in);
+ leak(fd_out);
+}
+
+/* A write by a non-owner should clear a file's SUID bit */
+TEST_F(CopyFileRange, clear_suid)
+{
+ const char FULLPATH_IN[] = "mountpoint/in.txt";
+ const char RELPATH_IN[] = "in.txt";
+ const char FULLPATH_OUT[] = "mountpoint/out.txt";
+ const char RELPATH_OUT[] = "out.txt";
+ struct stat sb;
+ uint64_t ino_in = 42;
+ uint64_t ino_out = 43;
+ mode_t oldmode = 04777;
+ mode_t newmode = 0777;
+ off_t fsize = 16;
+ off_t off_in = 0;
+ off_t off_out = 8;
+ off_t len = 8;
+ int fd_in, fd_out;
+
+ expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
+ FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
+ UINT64_MAX, 0, 0);
+ expect_open(ino_in, 0, 1);
+ FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
+ 1, UINT64_MAX, 0, 0);
+ expect_open(ino_out, 0, 1);
+ expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
+ expect_chmod(ino_out, newmode, fsize);
+
+ fd_in = open(FULLPATH_IN, O_RDONLY);
+ ASSERT_LE(0, fd_in) << strerror(errno);
+ fd_out = open(FULLPATH_OUT, O_WRONLY);
+ ASSERT_LE(0, fd_out) << strerror(errno);
+ ASSERT_EQ(len,
+ copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
+ << strerror(errno);
+ ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
+ ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
+
+ leak(fd_in);
+ leak(fd_out);
+}
+
TEST_F(Create, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
@@ -1311,5 +1419,3 @@ TEST_F(Write, recursion_panic_while_clearing_suid)
ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
leak(fd);
}
-
-
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index 0124d8765eb1..7e4991fb7bb9 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -110,6 +110,7 @@ const char* opcode2opname(uint32_t opcode)
"READDIRPLUS",
"RENAME2",
"LSEEK",
+ "COPY_FILE_RANGE",
};
if (opcode >= nitems(table))
return ("Unknown (opcode > max)");
@@ -182,6 +183,20 @@ void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
printf(" block=%" PRIx64 " blocksize=%#x",
in.body.bmap.block, in.body.bmap.blocksize);
break;
+ case FUSE_COPY_FILE_RANGE:
+ printf(" off_in=%" PRIu64 " ino_out=%" PRIu64
+ " off_out=%" PRIu64 " size=%" PRIu64,
+ in.body.copy_file_range.off_in,
+ in.body.copy_file_range.nodeid_out,
+ in.body.copy_file_range.off_out,
+ in.body.copy_file_range.len);
+ if (verbosity > 1)
+ printf(" fh_in=%" PRIu64 " fh_out=%" PRIu64
+ " flags=%" PRIx64,
+ in.body.copy_file_range.fh_in,
+ in.body.copy_file_range.fh_out,
+ in.body.copy_file_range.flags);
+ break;
case FUSE_CREATE:
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
@@ -663,6 +678,11 @@ void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
EXPECT_EQ(inlen, fih + sizeof(in.body.lseek));
EXPECT_EQ((size_t)buflen, inlen);
break;
+ case FUSE_COPY_FILE_RANGE:
+ EXPECT_EQ(inlen, fih + sizeof(in.body.copy_file_range));
+ EXPECT_EQ(0ul, in.body.copy_file_range.flags);
+ EXPECT_EQ((size_t)buflen, inlen);
+ break;
case FUSE_NOTIFY_REPLY:
case FUSE_BATCH_FORGET:
case FUSE_FALLOCATE:
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
index 6fb15089bc23..a7a6a55922c7 100644
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -157,6 +157,7 @@ union fuse_payloads_in {
uint8_t bytes[
max_max_write + 0x1000 - sizeof(struct fuse_in_header)
];
+ fuse_copy_file_range_in copy_file_range;
fuse_create_in create;
fuse_flush_in flush;
fuse_fsync_in fsync;
diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc
index 7529b76847d7..f0f9685000fb 100644
--- a/tests/sys/fs/fusefs/write.cc
+++ b/tests/sys/fs/fusefs/write.cc
@@ -511,7 +511,6 @@ TEST_F(Write, eof_during_rmw)
ssize_t bufsize = strlen(CONTENTS) + 1;
off_t orig_fsize = 10;
off_t truncated_fsize = 5;
- off_t final_fsize = bufsize;
int fd;
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, orig_fsize, 1);