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
This commit is contained in:
parent
ae39db7406
commit
92bbfe1f0d
@ -1054,6 +1054,9 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio)
|
|||||||
if (!fuse_libabi_geq(data, 7, 24))
|
if (!fuse_libabi_geq(data, 7, 24))
|
||||||
fsess_set_notimpl(data->mp, FUSE_LSEEK);
|
fsess_set_notimpl(data->mp, FUSE_LSEEK);
|
||||||
|
|
||||||
|
if (!fuse_libabi_geq(data, 7, 28))
|
||||||
|
fsess_set_notimpl(data->mp, FUSE_COPY_FILE_RANGE);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (err) {
|
if (err) {
|
||||||
fdata_set_dead(data);
|
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_READDIRPLUS_AUTO: not yet implemented
|
||||||
* FUSE_ASYNC_DIO: not yet implemented
|
* FUSE_ASYNC_DIO: not yet implemented
|
||||||
* FUSE_NO_OPEN_SUPPORT: 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
|
fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
|
||||||
| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE;
|
| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE;
|
||||||
@ -1228,6 +1237,45 @@ int fuse_internal_setattr(struct vnode *vp, struct vattr *vap,
|
|||||||
return err;
|
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
|
#ifdef ZERO_PAD_INCOMPLETE_BUFS
|
||||||
static int
|
static int
|
||||||
isbzero(void *buf, size_t len)
|
isbzero(void *buf, size_t len)
|
||||||
|
@ -274,6 +274,10 @@ void fuse_internal_vnode_disappear(struct vnode *vp);
|
|||||||
int fuse_internal_setattr(struct vnode *vp, struct vattr *va,
|
int fuse_internal_setattr(struct vnode *vp, struct vattr *va,
|
||||||
struct thread *td, struct ucred *cred);
|
struct thread *td, struct ucred *cred);
|
||||||
|
|
||||||
|
/* write */
|
||||||
|
void fuse_internal_clear_suid_on_write(struct vnode *vp, struct ucred *cred,
|
||||||
|
struct thread *td);
|
||||||
|
|
||||||
/* strategy */
|
/* strategy */
|
||||||
|
|
||||||
/* entity creation */
|
/* entity creation */
|
||||||
|
@ -121,9 +121,6 @@ SDT_PROBE_DEFINE2(fusefs, , io, trace, "int", "char*");
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end);
|
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
|
static int
|
||||||
fuse_read_directbackend(struct vnode *vp, struct uio *uio,
|
fuse_read_directbackend(struct vnode *vp, struct uio *uio,
|
||||||
struct ucred *cred, struct fuse_filehandle *fufh);
|
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);
|
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*",
|
SDT_PROBE_DEFINE5(fusefs, , io, io_dispatch, "struct vnode*", "struct uio*",
|
||||||
"int", "struct ucred*", "struct fuse_filehandle*");
|
"int", "struct ucred*", "struct fuse_filehandle*");
|
||||||
SDT_PROBE_DEFINE4(fusefs, , io, io_dispatch_filehandles_closed, "struct vnode*",
|
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,
|
err = fuse_write_biobackend(vp, uio, cred, fufh, ioflag,
|
||||||
pid);
|
pid);
|
||||||
}
|
}
|
||||||
fuse_io_clear_suid_on_write(vp, cred, uio->uio_td);
|
fuse_internal_clear_suid_on_write(vp, cred, uio->uio_td);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
panic("uninterpreted mode passed to fuse_io_dispatch");
|
panic("uninterpreted mode passed to fuse_io_dispatch");
|
||||||
|
@ -855,6 +855,10 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
|
|||||||
err = (blen == sizeof(struct fuse_lseek_out)) ? 0 : EINVAL;
|
err = (blen == sizeof(struct fuse_lseek_out)) ? 0 : EINVAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FUSE_COPY_FILE_RANGE:
|
||||||
|
err = (blen == sizeof(struct fuse_write_out)) ? 0 : EINVAL;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("FUSE: opcodes out of sync (%d)\n", opcode);
|
panic("FUSE: opcodes out of sync (%d)\n", opcode);
|
||||||
}
|
}
|
||||||
|
@ -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
|
* This file defines the kernel interface of FUSE
|
||||||
* Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
|
* Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
|
||||||
*
|
*
|
||||||
@ -105,6 +107,22 @@
|
|||||||
*
|
*
|
||||||
* 7.24
|
* 7.24
|
||||||
* - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
|
* - 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
|
#ifndef _FUSE_FUSE_KERNEL_H
|
||||||
@ -120,7 +138,7 @@
|
|||||||
#define FUSE_KERNEL_VERSION 7
|
#define FUSE_KERNEL_VERSION 7
|
||||||
|
|
||||||
/** Minor version number of this interface */
|
/** 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 */
|
/** The node ID of the root inode */
|
||||||
#define FUSE_ROOT_ID 1
|
#define FUSE_ROOT_ID 1
|
||||||
@ -188,10 +206,12 @@ struct fuse_file_lock {
|
|||||||
* FOPEN_DIRECT_IO: bypass page cache for this open file
|
* FOPEN_DIRECT_IO: bypass page cache for this open file
|
||||||
* FOPEN_KEEP_CACHE: don't invalidate the data cache on open
|
* FOPEN_KEEP_CACHE: don't invalidate the data cache on open
|
||||||
* FOPEN_NONSEEKABLE: the file is not seekable
|
* FOPEN_NONSEEKABLE: the file is not seekable
|
||||||
|
* FOPEN_CACHE_DIR: allow caching this directory
|
||||||
*/
|
*/
|
||||||
#define FOPEN_DIRECT_IO (1 << 0)
|
#define FOPEN_DIRECT_IO (1 << 0)
|
||||||
#define FOPEN_KEEP_CACHE (1 << 1)
|
#define FOPEN_KEEP_CACHE (1 << 1)
|
||||||
#define FOPEN_NONSEEKABLE (1 << 2)
|
#define FOPEN_NONSEEKABLE (1 << 2)
|
||||||
|
#define FOPEN_CACHE_DIR (1 << 3)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INIT request/reply flags
|
* INIT request/reply flags
|
||||||
@ -214,6 +234,12 @@ struct fuse_file_lock {
|
|||||||
* FUSE_ASYNC_DIO: asynchronous direct I/O submission
|
* FUSE_ASYNC_DIO: asynchronous direct I/O submission
|
||||||
* FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
|
* FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
|
||||||
* FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
|
* 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_ASYNC_READ (1 << 0)
|
||||||
#define FUSE_POSIX_LOCKS (1 << 1)
|
#define FUSE_POSIX_LOCKS (1 << 1)
|
||||||
@ -233,6 +259,12 @@ struct fuse_file_lock {
|
|||||||
#define FUSE_ASYNC_DIO (1 << 15)
|
#define FUSE_ASYNC_DIO (1 << 15)
|
||||||
#define FUSE_WRITEBACK_CACHE (1 << 16)
|
#define FUSE_WRITEBACK_CACHE (1 << 16)
|
||||||
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
|
#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
|
#ifdef linux
|
||||||
/**
|
/**
|
||||||
@ -300,54 +332,55 @@ struct fuse_file_lock {
|
|||||||
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
|
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
|
||||||
|
|
||||||
enum fuse_opcode {
|
enum fuse_opcode {
|
||||||
FUSE_LOOKUP = 1,
|
FUSE_LOOKUP = 1,
|
||||||
FUSE_FORGET = 2, /* no reply */
|
FUSE_FORGET = 2, /* no reply */
|
||||||
FUSE_GETATTR = 3,
|
FUSE_GETATTR = 3,
|
||||||
FUSE_SETATTR = 4,
|
FUSE_SETATTR = 4,
|
||||||
FUSE_READLINK = 5,
|
FUSE_READLINK = 5,
|
||||||
FUSE_SYMLINK = 6,
|
FUSE_SYMLINK = 6,
|
||||||
FUSE_MKNOD = 8,
|
FUSE_MKNOD = 8,
|
||||||
FUSE_MKDIR = 9,
|
FUSE_MKDIR = 9,
|
||||||
FUSE_UNLINK = 10,
|
FUSE_UNLINK = 10,
|
||||||
FUSE_RMDIR = 11,
|
FUSE_RMDIR = 11,
|
||||||
FUSE_RENAME = 12,
|
FUSE_RENAME = 12,
|
||||||
FUSE_LINK = 13,
|
FUSE_LINK = 13,
|
||||||
FUSE_OPEN = 14,
|
FUSE_OPEN = 14,
|
||||||
FUSE_READ = 15,
|
FUSE_READ = 15,
|
||||||
FUSE_WRITE = 16,
|
FUSE_WRITE = 16,
|
||||||
FUSE_STATFS = 17,
|
FUSE_STATFS = 17,
|
||||||
FUSE_RELEASE = 18,
|
FUSE_RELEASE = 18,
|
||||||
FUSE_FSYNC = 20,
|
FUSE_FSYNC = 20,
|
||||||
FUSE_SETXATTR = 21,
|
FUSE_SETXATTR = 21,
|
||||||
FUSE_GETXATTR = 22,
|
FUSE_GETXATTR = 22,
|
||||||
FUSE_LISTXATTR = 23,
|
FUSE_LISTXATTR = 23,
|
||||||
FUSE_REMOVEXATTR = 24,
|
FUSE_REMOVEXATTR = 24,
|
||||||
FUSE_FLUSH = 25,
|
FUSE_FLUSH = 25,
|
||||||
FUSE_INIT = 26,
|
FUSE_INIT = 26,
|
||||||
FUSE_OPENDIR = 27,
|
FUSE_OPENDIR = 27,
|
||||||
FUSE_READDIR = 28,
|
FUSE_READDIR = 28,
|
||||||
FUSE_RELEASEDIR = 29,
|
FUSE_RELEASEDIR = 29,
|
||||||
FUSE_FSYNCDIR = 30,
|
FUSE_FSYNCDIR = 30,
|
||||||
FUSE_GETLK = 31,
|
FUSE_GETLK = 31,
|
||||||
FUSE_SETLK = 32,
|
FUSE_SETLK = 32,
|
||||||
FUSE_SETLKW = 33,
|
FUSE_SETLKW = 33,
|
||||||
FUSE_ACCESS = 34,
|
FUSE_ACCESS = 34,
|
||||||
FUSE_CREATE = 35,
|
FUSE_CREATE = 35,
|
||||||
FUSE_INTERRUPT = 36,
|
FUSE_INTERRUPT = 36,
|
||||||
FUSE_BMAP = 37,
|
FUSE_BMAP = 37,
|
||||||
FUSE_DESTROY = 38,
|
FUSE_DESTROY = 38,
|
||||||
FUSE_IOCTL = 39,
|
FUSE_IOCTL = 39,
|
||||||
FUSE_POLL = 40,
|
FUSE_POLL = 40,
|
||||||
FUSE_NOTIFY_REPLY = 41,
|
FUSE_NOTIFY_REPLY = 41,
|
||||||
FUSE_BATCH_FORGET = 42,
|
FUSE_BATCH_FORGET = 42,
|
||||||
FUSE_FALLOCATE = 43,
|
FUSE_FALLOCATE = 43,
|
||||||
FUSE_READDIRPLUS = 44,
|
FUSE_READDIRPLUS = 44,
|
||||||
FUSE_RENAME2 = 45,
|
FUSE_RENAME2 = 45,
|
||||||
FUSE_LSEEK = 46,
|
FUSE_LSEEK = 46,
|
||||||
|
FUSE_COPY_FILE_RANGE = 47,
|
||||||
|
|
||||||
#ifdef linux
|
#ifdef linux
|
||||||
/* CUSE specific operations */
|
/* CUSE specific operations */
|
||||||
CUSE_INIT = 4096,
|
CUSE_INIT = 4096,
|
||||||
#endif /* linux */
|
#endif /* linux */
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -585,7 +618,9 @@ struct fuse_init_out {
|
|||||||
uint16_t congestion_threshold;
|
uint16_t congestion_threshold;
|
||||||
uint32_t max_write;
|
uint32_t max_write;
|
||||||
uint32_t time_gran;
|
uint32_t time_gran;
|
||||||
uint32_t unused[9];
|
uint16_t max_pages;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t unused[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef linux
|
#ifdef linux
|
||||||
@ -766,4 +801,14 @@ struct fuse_lseek_out {
|
|||||||
uint64_t offset;
|
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 */
|
#endif /* _FUSE_FUSE_KERNEL_H */
|
||||||
|
@ -130,6 +130,7 @@ static vop_advlock_t fuse_vnop_advlock;
|
|||||||
static vop_bmap_t fuse_vnop_bmap;
|
static vop_bmap_t fuse_vnop_bmap;
|
||||||
static vop_close_t fuse_fifo_close;
|
static vop_close_t fuse_fifo_close;
|
||||||
static vop_close_t fuse_vnop_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_create_t fuse_vnop_create;
|
||||||
static vop_deleteextattr_t fuse_vnop_deleteextattr;
|
static vop_deleteextattr_t fuse_vnop_deleteextattr;
|
||||||
static vop_fdatasync_t fuse_vnop_fdatasync;
|
static vop_fdatasync_t fuse_vnop_fdatasync;
|
||||||
@ -185,6 +186,7 @@ struct vop_vector fuse_vnops = {
|
|||||||
.vop_advlock = fuse_vnop_advlock,
|
.vop_advlock = fuse_vnop_advlock,
|
||||||
.vop_bmap = fuse_vnop_bmap,
|
.vop_bmap = fuse_vnop_bmap,
|
||||||
.vop_close = fuse_vnop_close,
|
.vop_close = fuse_vnop_close,
|
||||||
|
.vop_copy_file_range = fuse_vnop_copy_file_range,
|
||||||
.vop_create = fuse_vnop_create,
|
.vop_create = fuse_vnop_create,
|
||||||
.vop_deleteextattr = fuse_vnop_deleteextattr,
|
.vop_deleteextattr = fuse_vnop_deleteextattr,
|
||||||
.vop_fsync = fuse_vnop_fsync,
|
.vop_fsync = fuse_vnop_fsync,
|
||||||
@ -609,6 +611,126 @@ fuse_vnop_close(struct vop_close_args *ap)
|
|||||||
return err;
|
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
|
static void
|
||||||
fdisp_make_mknod_for_fallback(
|
fdisp_make_mknod_for_fallback(
|
||||||
struct fuse_dispatcher *fdip,
|
struct fuse_dispatcher *fdip,
|
||||||
|
@ -13,6 +13,7 @@ GTESTS+= access
|
|||||||
GTESTS+= allow_other
|
GTESTS+= allow_other
|
||||||
GTESTS+= bmap
|
GTESTS+= bmap
|
||||||
GTESTS+= cache
|
GTESTS+= cache
|
||||||
|
GTESTS+= copy_file_range
|
||||||
GTESTS+= create
|
GTESTS+= create
|
||||||
GTESTS+= default_permissions
|
GTESTS+= default_permissions
|
||||||
GTESTS+= default_permissions_privileged
|
GTESTS+= default_permissions_privileged
|
||||||
|
401
tests/sys/fs/fusefs/copy_file_range.cc
Normal file
401
tests/sys/fs/fusefs/copy_file_range.cc
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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,
|
void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
|
||||||
uid_t uid = 0, gid_t gid = 0)
|
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 Access: public DefaultPermissions {};
|
||||||
class Chown: public DefaultPermissions {};
|
class Chown: public DefaultPermissions {};
|
||||||
class Chgrp: public DefaultPermissions {};
|
class Chgrp: public DefaultPermissions {};
|
||||||
|
class CopyFileRange: public DefaultPermissions {};
|
||||||
class Lookup: public DefaultPermissions {};
|
class Lookup: public DefaultPermissions {};
|
||||||
class Open: public DefaultPermissions {};
|
class Open: public DefaultPermissions {};
|
||||||
class Setattr: public DefaultPermissions {};
|
class Setattr: public DefaultPermissions {};
|
||||||
@ -477,6 +497,94 @@ TEST_F(Chgrp, ok)
|
|||||||
EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
|
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)
|
TEST_F(Create, ok)
|
||||||
{
|
{
|
||||||
const char FULLPATH[] = "mountpoint/some_file.txt";
|
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);
|
ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
|
||||||
leak(fd);
|
leak(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,6 +110,7 @@ const char* opcode2opname(uint32_t opcode)
|
|||||||
"READDIRPLUS",
|
"READDIRPLUS",
|
||||||
"RENAME2",
|
"RENAME2",
|
||||||
"LSEEK",
|
"LSEEK",
|
||||||
|
"COPY_FILE_RANGE",
|
||||||
};
|
};
|
||||||
if (opcode >= nitems(table))
|
if (opcode >= nitems(table))
|
||||||
return ("Unknown (opcode > max)");
|
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",
|
printf(" block=%" PRIx64 " blocksize=%#x",
|
||||||
in.body.bmap.block, in.body.bmap.blocksize);
|
in.body.bmap.block, in.body.bmap.blocksize);
|
||||||
break;
|
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:
|
case FUSE_CREATE:
|
||||||
if (m_kernel_minor_version >= 12)
|
if (m_kernel_minor_version >= 12)
|
||||||
name = (const char*)in.body.bytes +
|
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(inlen, fih + sizeof(in.body.lseek));
|
||||||
EXPECT_EQ((size_t)buflen, inlen);
|
EXPECT_EQ((size_t)buflen, inlen);
|
||||||
break;
|
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_NOTIFY_REPLY:
|
||||||
case FUSE_BATCH_FORGET:
|
case FUSE_BATCH_FORGET:
|
||||||
case FUSE_FALLOCATE:
|
case FUSE_FALLOCATE:
|
||||||
|
@ -157,6 +157,7 @@ union fuse_payloads_in {
|
|||||||
uint8_t bytes[
|
uint8_t bytes[
|
||||||
max_max_write + 0x1000 - sizeof(struct fuse_in_header)
|
max_max_write + 0x1000 - sizeof(struct fuse_in_header)
|
||||||
];
|
];
|
||||||
|
fuse_copy_file_range_in copy_file_range;
|
||||||
fuse_create_in create;
|
fuse_create_in create;
|
||||||
fuse_flush_in flush;
|
fuse_flush_in flush;
|
||||||
fuse_fsync_in fsync;
|
fuse_fsync_in fsync;
|
||||||
|
@ -511,7 +511,6 @@ TEST_F(Write, eof_during_rmw)
|
|||||||
ssize_t bufsize = strlen(CONTENTS) + 1;
|
ssize_t bufsize = strlen(CONTENTS) + 1;
|
||||||
off_t orig_fsize = 10;
|
off_t orig_fsize = 10;
|
||||||
off_t truncated_fsize = 5;
|
off_t truncated_fsize = 5;
|
||||||
off_t final_fsize = bufsize;
|
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, orig_fsize, 1);
|
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, orig_fsize, 1);
|
||||||
|
Loading…
Reference in New Issue
Block a user