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))
|
||||
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 @@ int fuse_internal_setattr(struct vnode *vp, struct vattr *vap,
|
||||
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)
|
||||
|
@ -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 */
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
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,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user