Changeset View
Changeset View
Standalone View
Standalone View
sys/fs/fuse/fuse_vnops.c
Show All 27 Lines | |||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
* | * | ||||
* Copyright (C) 2005 Csaba Henk. | * Copyright (C) 2005 Csaba Henk. | ||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* Copyright (c) 2019 The FreeBSD Foundation | |||||
* | |||||
* Portions of this software were developed by BFF Storage Systems, LLC under | |||||
* sponsorship from the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
* documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | |||||
#include <vm/vnode_pager.h> | #include <vm/vnode_pager.h> | ||||
#include <vm/vm_object.h> | #include <vm/vm_object.h> | ||||
#include "fuse.h" | #include "fuse.h" | ||||
#include "fuse_file.h" | #include "fuse_file.h" | ||||
#include "fuse_internal.h" | #include "fuse_internal.h" | ||||
#include "fuse_ipc.h" | #include "fuse_ipc.h" | ||||
#include "fuse_node.h" | #include "fuse_node.h" | ||||
#include "fuse_param.h" | |||||
#include "fuse_io.h" | #include "fuse_io.h" | ||||
#include <sys/priv.h> | #include <sys/priv.h> | ||||
SDT_PROVIDER_DECLARE(fuse); | /* Maximum number of hardlinks to a single FUSE file */ | ||||
#define FUSE_LINK_MAX UINT32_MAX | |||||
SDT_PROVIDER_DECLARE(fusefs); | |||||
/* | /* | ||||
* Fuse trace probe: | * Fuse trace probe: | ||||
* arg0: verbosity. Higher numbers give more verbose messages | * arg0: verbosity. Higher numbers give more verbose messages | ||||
* arg1: Textual message | * arg1: Textual message | ||||
*/ | */ | ||||
SDT_PROBE_DEFINE2(fuse, , vnops, trace, "int", "char*"); | SDT_PROBE_DEFINE2(fusefs, , vnops, trace, "int", "char*"); | ||||
/* vnode ops */ | /* vnode ops */ | ||||
static vop_access_t fuse_vnop_access; | static vop_access_t fuse_vnop_access; | ||||
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_close_t fuse_vnop_close; | ||||
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_fsync_t fuse_vnop_fsync; | static vop_fsync_t fuse_vnop_fsync; | ||||
static vop_getattr_t fuse_vnop_getattr; | static vop_getattr_t fuse_vnop_getattr; | ||||
static vop_getextattr_t fuse_vnop_getextattr; | static vop_getextattr_t fuse_vnop_getextattr; | ||||
static vop_inactive_t fuse_vnop_inactive; | static vop_inactive_t fuse_vnop_inactive; | ||||
static vop_link_t fuse_vnop_link; | static vop_link_t fuse_vnop_link; | ||||
static vop_listextattr_t fuse_vnop_listextattr; | static vop_listextattr_t fuse_vnop_listextattr; | ||||
static vop_lookup_t fuse_vnop_lookup; | static vop_lookup_t fuse_vnop_lookup; | ||||
static vop_mkdir_t fuse_vnop_mkdir; | static vop_mkdir_t fuse_vnop_mkdir; | ||||
static vop_mknod_t fuse_vnop_mknod; | static vop_mknod_t fuse_vnop_mknod; | ||||
static vop_open_t fuse_vnop_open; | static vop_open_t fuse_vnop_open; | ||||
static vop_pathconf_t fuse_vnop_pathconf; | static vop_pathconf_t fuse_vnop_pathconf; | ||||
static vop_read_t fuse_vnop_read; | static vop_read_t fuse_vnop_read; | ||||
static vop_readdir_t fuse_vnop_readdir; | static vop_readdir_t fuse_vnop_readdir; | ||||
static vop_readlink_t fuse_vnop_readlink; | static vop_readlink_t fuse_vnop_readlink; | ||||
static vop_reclaim_t fuse_vnop_reclaim; | static vop_reclaim_t fuse_vnop_reclaim; | ||||
static vop_remove_t fuse_vnop_remove; | static vop_remove_t fuse_vnop_remove; | ||||
static vop_rename_t fuse_vnop_rename; | static vop_rename_t fuse_vnop_rename; | ||||
static vop_rmdir_t fuse_vnop_rmdir; | static vop_rmdir_t fuse_vnop_rmdir; | ||||
static vop_setattr_t fuse_vnop_setattr; | static vop_setattr_t fuse_vnop_setattr; | ||||
static vop_setextattr_t fuse_vnop_setextattr; | static vop_setextattr_t fuse_vnop_setextattr; | ||||
static vop_strategy_t fuse_vnop_strategy; | static vop_strategy_t fuse_vnop_strategy; | ||||
static vop_symlink_t fuse_vnop_symlink; | static vop_symlink_t fuse_vnop_symlink; | ||||
static vop_write_t fuse_vnop_write; | static vop_write_t fuse_vnop_write; | ||||
static vop_getpages_t fuse_vnop_getpages; | static vop_getpages_t fuse_vnop_getpages; | ||||
static vop_putpages_t fuse_vnop_putpages; | |||||
static vop_print_t fuse_vnop_print; | static vop_print_t fuse_vnop_print; | ||||
static vop_vptofh_t fuse_vnop_vptofh; | |||||
struct vop_vector fuse_fifoops = { | |||||
.vop_default = &fifo_specops, | |||||
.vop_access = fuse_vnop_access, | |||||
.vop_close = fuse_fifo_close, | |||||
.vop_fsync = fuse_vnop_fsync, | |||||
.vop_getattr = fuse_vnop_getattr, | |||||
.vop_inactive = fuse_vnop_inactive, | |||||
.vop_pathconf = fuse_vnop_pathconf, | |||||
.vop_print = fuse_vnop_print, | |||||
.vop_read = VOP_PANIC, | |||||
.vop_reclaim = fuse_vnop_reclaim, | |||||
.vop_setattr = fuse_vnop_setattr, | |||||
.vop_write = VOP_PANIC, | |||||
.vop_vptofh = fuse_vnop_vptofh, | |||||
}; | |||||
struct vop_vector fuse_vnops = { | struct vop_vector fuse_vnops = { | ||||
.vop_allocate = VOP_EINVAL, | |||||
.vop_default = &default_vnodeops, | .vop_default = &default_vnodeops, | ||||
.vop_access = fuse_vnop_access, | .vop_access = fuse_vnop_access, | ||||
.vop_advlock = fuse_vnop_advlock, | |||||
.vop_bmap = fuse_vnop_bmap, | |||||
.vop_close = fuse_vnop_close, | .vop_close = fuse_vnop_close, | ||||
.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, | ||||
.vop_fdatasync = fuse_vnop_fdatasync, | |||||
.vop_getattr = fuse_vnop_getattr, | .vop_getattr = fuse_vnop_getattr, | ||||
.vop_getextattr = fuse_vnop_getextattr, | .vop_getextattr = fuse_vnop_getextattr, | ||||
.vop_inactive = fuse_vnop_inactive, | .vop_inactive = fuse_vnop_inactive, | ||||
/* | |||||
* TODO: implement vop_ioctl after upgrading to protocol 7.16. | |||||
* FUSE_IOCTL was added in 7.11, but 32-bit compat is broken until | |||||
* 7.16. | |||||
*/ | |||||
.vop_link = fuse_vnop_link, | .vop_link = fuse_vnop_link, | ||||
.vop_listextattr = fuse_vnop_listextattr, | .vop_listextattr = fuse_vnop_listextattr, | ||||
.vop_lookup = fuse_vnop_lookup, | .vop_lookup = fuse_vnop_lookup, | ||||
.vop_mkdir = fuse_vnop_mkdir, | .vop_mkdir = fuse_vnop_mkdir, | ||||
.vop_mknod = fuse_vnop_mknod, | .vop_mknod = fuse_vnop_mknod, | ||||
.vop_open = fuse_vnop_open, | .vop_open = fuse_vnop_open, | ||||
.vop_pathconf = fuse_vnop_pathconf, | .vop_pathconf = fuse_vnop_pathconf, | ||||
/* | |||||
* TODO: implement vop_poll after upgrading to protocol 7.21. | |||||
* FUSE_POLL was added in protocol 7.11, but it's kind of broken until | |||||
* 7.21, which adds the ability for the client to choose which poll | |||||
* events it wants, and for a client to deregister a file handle | |||||
*/ | |||||
.vop_read = fuse_vnop_read, | .vop_read = fuse_vnop_read, | ||||
.vop_readdir = fuse_vnop_readdir, | .vop_readdir = fuse_vnop_readdir, | ||||
.vop_readlink = fuse_vnop_readlink, | .vop_readlink = fuse_vnop_readlink, | ||||
.vop_reclaim = fuse_vnop_reclaim, | .vop_reclaim = fuse_vnop_reclaim, | ||||
.vop_remove = fuse_vnop_remove, | .vop_remove = fuse_vnop_remove, | ||||
.vop_rename = fuse_vnop_rename, | .vop_rename = fuse_vnop_rename, | ||||
.vop_rmdir = fuse_vnop_rmdir, | .vop_rmdir = fuse_vnop_rmdir, | ||||
.vop_setattr = fuse_vnop_setattr, | .vop_setattr = fuse_vnop_setattr, | ||||
.vop_setextattr = fuse_vnop_setextattr, | .vop_setextattr = fuse_vnop_setextattr, | ||||
.vop_strategy = fuse_vnop_strategy, | .vop_strategy = fuse_vnop_strategy, | ||||
.vop_symlink = fuse_vnop_symlink, | .vop_symlink = fuse_vnop_symlink, | ||||
.vop_write = fuse_vnop_write, | .vop_write = fuse_vnop_write, | ||||
.vop_getpages = fuse_vnop_getpages, | .vop_getpages = fuse_vnop_getpages, | ||||
.vop_putpages = fuse_vnop_putpages, | |||||
.vop_print = fuse_vnop_print, | .vop_print = fuse_vnop_print, | ||||
.vop_vptofh = fuse_vnop_vptofh, | |||||
}; | }; | ||||
static u_long fuse_lookup_cache_hits = 0; | uma_zone_t fuse_pbuf_zone; | ||||
SYSCTL_ULONG(_vfs_fusefs, OID_AUTO, lookup_cache_hits, CTLFLAG_RD, | #define fuse_vm_page_lock(m) vm_page_lock((m)); | ||||
&fuse_lookup_cache_hits, 0, "number of positive cache hits in lookup"); | #define fuse_vm_page_unlock(m) vm_page_unlock((m)); | ||||
#define fuse_vm_page_lock_queues() ((void)0) | |||||
#define fuse_vm_page_unlock_queues() ((void)0) | |||||
static u_long fuse_lookup_cache_misses = 0; | /* Check permission for extattr operations, much like extattr_check_cred */ | ||||
static int | |||||
fuse_extattr_check_cred(struct vnode *vp, int ns, struct ucred *cred, | |||||
struct thread *td, accmode_t accmode) | |||||
{ | |||||
struct mount *mp = vnode_mount(vp); | |||||
struct fuse_data *data = fuse_get_mpdata(mp); | |||||
SYSCTL_ULONG(_vfs_fusefs, OID_AUTO, lookup_cache_misses, CTLFLAG_RD, | /* | ||||
&fuse_lookup_cache_misses, 0, "number of cache misses in lookup"); | * Kernel-invoked always succeeds. | ||||
*/ | |||||
if (cred == NOCRED) | |||||
return (0); | |||||
int fuse_lookup_cache_enable = 1; | /* | ||||
* Do not allow privileged processes in jail to directly manipulate | |||||
* system attributes. | |||||
*/ | |||||
switch (ns) { | |||||
case EXTATTR_NAMESPACE_SYSTEM: | |||||
if (data->dataflags & FSESS_DEFAULT_PERMISSIONS) { | |||||
return (priv_check_cred(cred, PRIV_VFS_EXTATTR_SYSTEM)); | |||||
} | |||||
/* FALLTHROUGH */ | |||||
case EXTATTR_NAMESPACE_USER: | |||||
return (fuse_internal_access(vp, accmode, td, cred)); | |||||
default: | |||||
return (EPERM); | |||||
} | |||||
} | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, lookup_cache_enable, CTLFLAG_RW, | /* Get a filehandle for a directory */ | ||||
&fuse_lookup_cache_enable, 0, "if non-zero, enable lookup cache"); | static int | ||||
fuse_filehandle_get_dir(struct vnode *vp, struct fuse_filehandle **fufhp, | |||||
struct ucred *cred, pid_t pid) | |||||
{ | |||||
if (fuse_filehandle_get(vp, FREAD, fufhp, cred, pid) == 0) | |||||
return 0; | |||||
return fuse_filehandle_get(vp, FEXEC, fufhp, cred, pid); | |||||
} | |||||
/* Send FUSE_FLUSH for this vnode */ | |||||
static int | |||||
fuse_flush(struct vnode *vp, struct ucred *cred, pid_t pid, int fflag) | |||||
{ | |||||
struct fuse_flush_in *ffi; | |||||
struct fuse_filehandle *fufh; | |||||
struct fuse_dispatcher fdi; | |||||
struct thread *td = curthread; | |||||
struct mount *mp = vnode_mount(vp); | |||||
int err; | |||||
if (!fsess_isimpl(vnode_mount(vp), FUSE_FLUSH)) | |||||
return 0; | |||||
err = fuse_filehandle_getrw(vp, fflag, &fufh, cred, pid); | |||||
if (err) | |||||
return err; | |||||
fdisp_init(&fdi, sizeof(*ffi)); | |||||
fdisp_make_vp(&fdi, FUSE_FLUSH, vp, td, cred); | |||||
ffi = fdi.indata; | |||||
ffi->fh = fufh->fh_id; | |||||
/* | /* | ||||
* XXX: This feature is highly experimental and can bring to instabilities, | * If the file has a POSIX lock then we're supposed to set lock_owner. | ||||
* needs revisiting before to be enabled by default. | * If not, then lock_owner is undefined. So we may as well always set | ||||
* it. | |||||
*/ | */ | ||||
static int fuse_reclaim_revoked = 0; | ffi->lock_owner = td->td_proc->p_pid; | ||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, reclaim_revoked, CTLFLAG_RW, | err = fdisp_wait_answ(&fdi); | ||||
&fuse_reclaim_revoked, 0, ""); | if (err == ENOSYS) { | ||||
fsess_set_notimpl(mp, FUSE_FLUSH); | |||||
err = 0; | |||||
} | |||||
fdisp_destroy(&fdi); | |||||
return err; | |||||
} | |||||
uma_zone_t fuse_pbuf_zone; | /* Close wrapper for fifos. */ | ||||
static int | |||||
fuse_fifo_close(struct vop_close_args *ap) | |||||
{ | |||||
return (fifo_specops.vop_close(ap)); | |||||
} | |||||
#define fuse_vm_page_lock(m) vm_page_lock((m)); | |||||
#define fuse_vm_page_unlock(m) vm_page_unlock((m)); | |||||
#define fuse_vm_page_lock_queues() ((void)0) | |||||
#define fuse_vm_page_unlock_queues() ((void)0) | |||||
/* | /* | ||||
struct vnop_access_args { | struct vnop_access_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
#if VOP_ACCESS_TAKES_ACCMODE_T | #if VOP_ACCESS_TAKES_ACCMODE_T | ||||
accmode_t a_accmode; | accmode_t a_accmode; | ||||
#else | #else | ||||
int a_mode; | int a_mode; | ||||
#endif | #endif | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_access(struct vop_access_args *ap) | fuse_vnop_access(struct vop_access_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
int accmode = ap->a_accmode; | int accmode = ap->a_accmode; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
struct fuse_access_param facp; | |||||
struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp)); | struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp)); | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
if (vnode_isvroot(vp)) { | if (vnode_isvroot(vp)) { | ||||
return 0; | return 0; | ||||
} | } | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (!(data->dataflags & FSESS_INITED)) { | if (!(data->dataflags & FSESS_INITED)) { | ||||
if (vnode_isvroot(vp)) { | if (vnode_isvroot(vp)) { | ||||
if (priv_check_cred(cred, PRIV_VFS_ADMIN) || | if (priv_check_cred(cred, PRIV_VFS_ADMIN) || | ||||
(fuse_match_cred(data->daemoncred, cred) == 0)) { | (fuse_match_cred(data->daemoncred, cred) == 0)) { | ||||
return 0; | return 0; | ||||
} | } | ||||
} | } | ||||
return EBADF; | return EBADF; | ||||
} | } | ||||
if (vnode_islnk(vp)) { | if (vnode_islnk(vp)) { | ||||
return 0; | return 0; | ||||
} | } | ||||
bzero(&facp, sizeof(facp)); | |||||
err = fuse_internal_access(vp, accmode, &facp, ap->a_td, ap->a_cred); | err = fuse_internal_access(vp, accmode, ap->a_td, ap->a_cred); | ||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_close_args { | * struct vop_advlock_args { | ||||
* struct vop_generic_args a_gen; | |||||
* struct vnode *a_vp; | |||||
* void *a_id; | |||||
* int a_op; | |||||
* struct flock *a_fl; | |||||
* int a_flags; | |||||
* } | |||||
*/ | |||||
static int | |||||
fuse_vnop_advlock(struct vop_advlock_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct flock *fl = ap->a_fl; | |||||
struct thread *td = curthread; | |||||
struct ucred *cred = td->td_ucred; | |||||
pid_t pid = td->td_proc->p_pid; | |||||
struct fuse_filehandle *fufh; | |||||
struct fuse_dispatcher fdi; | |||||
struct fuse_lk_in *fli; | |||||
struct fuse_lk_out *flo; | |||||
enum fuse_opcode op; | |||||
int dataflags, err; | |||||
int flags = ap->a_flags; | |||||
dataflags = fuse_get_mpdata(vnode_mount(vp))->dataflags; | |||||
if (fuse_isdeadfs(vp)) { | |||||
return ENXIO; | |||||
} | |||||
if (!(dataflags & FSESS_POSIX_LOCKS)) | |||||
return vop_stdadvlock(ap); | |||||
/* FUSE doesn't properly support flock until protocol 7.17 */ | |||||
if (flags & F_FLOCK) | |||||
return vop_stdadvlock(ap); | |||||
err = fuse_filehandle_get_anyflags(vp, &fufh, cred, pid); | |||||
if (err) | |||||
return err; | |||||
fdisp_init(&fdi, sizeof(*fli)); | |||||
switch(ap->a_op) { | |||||
case F_GETLK: | |||||
op = FUSE_GETLK; | |||||
break; | |||||
case F_SETLK: | |||||
op = FUSE_SETLK; | |||||
break; | |||||
case F_SETLKW: | |||||
op = FUSE_SETLKW; | |||||
break; | |||||
default: | |||||
return EINVAL; | |||||
} | |||||
fdisp_make_vp(&fdi, op, vp, td, cred); | |||||
fli = fdi.indata; | |||||
fli->fh = fufh->fh_id; | |||||
fli->owner = fl->l_pid; | |||||
fli->lk.start = fl->l_start; | |||||
if (fl->l_len != 0) | |||||
fli->lk.end = fl->l_start + fl->l_len - 1; | |||||
else | |||||
fli->lk.end = INT64_MAX; | |||||
fli->lk.type = fl->l_type; | |||||
fli->lk.pid = fl->l_pid; | |||||
err = fdisp_wait_answ(&fdi); | |||||
fdisp_destroy(&fdi); | |||||
if (err == 0 && op == FUSE_GETLK) { | |||||
flo = fdi.answ; | |||||
fl->l_type = flo->lk.type; | |||||
fl->l_pid = flo->lk.pid; | |||||
if (flo->lk.type != F_UNLCK) { | |||||
fl->l_start = flo->lk.start; | |||||
if (flo->lk.end == INT64_MAX) | |||||
fl->l_len = 0; | |||||
else | |||||
fl->l_len = flo->lk.end - flo->lk.start + 1; | |||||
fl->l_start = flo->lk.start; | |||||
} | |||||
} | |||||
return err; | |||||
} | |||||
/* { | |||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
daddr_t a_bn; | |||||
struct bufobj **a_bop; | |||||
daddr_t *a_bnp; | |||||
int *a_runp; | |||||
int *a_runb; | |||||
} */ | |||||
static int | |||||
fuse_vnop_bmap(struct vop_bmap_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct bufobj **bo = ap->a_bop; | |||||
struct thread *td = curthread; | |||||
struct mount *mp; | |||||
struct fuse_dispatcher fdi; | |||||
struct fuse_bmap_in *fbi; | |||||
struct fuse_bmap_out *fbo; | |||||
struct fuse_data *data; | |||||
uint64_t biosize; | |||||
off_t filesize; | |||||
daddr_t lbn = ap->a_bn; | |||||
daddr_t *pbn = ap->a_bnp; | |||||
int *runp = ap->a_runp; | |||||
int *runb = ap->a_runb; | |||||
int error = 0; | |||||
int maxrun; | |||||
if (fuse_isdeadfs(vp)) { | |||||
return ENXIO; | |||||
} | |||||
mp = vnode_mount(vp); | |||||
data = fuse_get_mpdata(mp); | |||||
biosize = fuse_iosize(vp); | |||||
maxrun = MIN(vp->v_mount->mnt_iosize_max / biosize - 1, | |||||
data->max_readahead_blocks); | |||||
if (bo != NULL) | |||||
*bo = &vp->v_bufobj; | |||||
/* | |||||
* The FUSE_BMAP operation does not include the runp and runb | |||||
* variables, so we must guess. Report nonzero contiguous runs so | |||||
* cluster_read will combine adjacent reads. It's worthwhile to reduce | |||||
* upcalls even if we don't know the true physical layout of the file. | |||||
* | |||||
* FUSE file systems may opt out of read clustering in two ways: | |||||
* * mounting with -onoclusterr | |||||
* * Setting max_readahead <= maxbcachebuf during FUSE_INIT | |||||
*/ | |||||
if (runb != NULL) | |||||
*runb = MIN(lbn, maxrun); | |||||
if (runp != NULL) { | |||||
error = fuse_vnode_size(vp, &filesize, td->td_ucred, td); | |||||
if (error == 0) | |||||
*runp = MIN(MAX(0, filesize / biosize - lbn - 1), | |||||
maxrun); | |||||
else | |||||
*runp = 0; | |||||
} | |||||
if (fsess_isimpl(mp, FUSE_BMAP)) { | |||||
fdisp_init(&fdi, sizeof(*fbi)); | |||||
fdisp_make_vp(&fdi, FUSE_BMAP, vp, td, td->td_ucred); | |||||
fbi = fdi.indata; | |||||
fbi->block = lbn; | |||||
fbi->blocksize = biosize; | |||||
error = fdisp_wait_answ(&fdi); | |||||
if (error == ENOSYS) { | |||||
fdisp_destroy(&fdi); | |||||
fsess_set_notimpl(mp, FUSE_BMAP); | |||||
error = 0; | |||||
} else { | |||||
fbo = fdi.answ; | |||||
if (error == 0 && pbn != NULL) | |||||
*pbn = fbo->block; | |||||
fdisp_destroy(&fdi); | |||||
return error; | |||||
} | |||||
} | |||||
/* If the daemon doesn't support BMAP, make up a sensible default */ | |||||
if (pbn != NULL) | |||||
*pbn = lbn * btodb(biosize); | |||||
return (error); | |||||
} | |||||
/* | |||||
struct vop_close_args { | |||||
struct vnode *a_vp; | |||||
int a_fflag; | int a_fflag; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_close(struct vop_close_args *ap) | fuse_vnop_close(struct vop_close_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
int fflag = ap->a_fflag; | int fflag = ap->a_fflag; | ||||
fufh_type_t fufh_type; | struct thread *td = ap->a_td; | ||||
pid_t pid = td->td_proc->p_pid; | |||||
int err = 0; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) | ||||
return 0; | return 0; | ||||
} | if (vnode_isdir(vp)) | ||||
if (vnode_isdir(vp)) { | |||||
if (fuse_filehandle_valid(vp, FUFH_RDONLY)) { | |||||
fuse_filehandle_close(vp, FUFH_RDONLY, NULL, cred); | |||||
} | |||||
return 0; | return 0; | ||||
} | if (fflag & IO_NDELAY) | ||||
if (fflag & IO_NDELAY) { | |||||
return 0; | return 0; | ||||
} | |||||
fufh_type = fuse_filehandle_xlate_from_fflags(fflag); | |||||
if (!fuse_filehandle_valid(vp, fufh_type)) { | err = fuse_flush(vp, cred, pid, fflag); | ||||
int i; | /* TODO: close the file handle, if we're sure it's no longer used */ | ||||
for (i = 0; i < FUFH_MAXTYPE; i++) | |||||
if (fuse_filehandle_valid(vp, i)) | |||||
break; | |||||
if (i == FUFH_MAXTYPE) | |||||
panic("FUSE: fufh type %d found to be invalid in close" | |||||
" (fflag=0x%x)\n", | |||||
fufh_type, fflag); | |||||
} | |||||
if ((VTOFUD(vp)->flag & FN_SIZECHANGE) != 0) { | if ((VTOFUD(vp)->flag & FN_SIZECHANGE) != 0) { | ||||
fuse_vnode_savesize(vp, cred); | fuse_vnode_savesize(vp, cred, td->td_proc->p_pid); | ||||
} | } | ||||
return 0; | return err; | ||||
} | } | ||||
static void | |||||
fdisp_make_mknod_for_fallback( | |||||
struct fuse_dispatcher *fdip, | |||||
struct componentname *cnp, | |||||
struct vnode *dvp, | |||||
uint64_t parentnid, | |||||
struct thread *td, | |||||
struct ucred *cred, | |||||
mode_t mode, | |||||
enum fuse_opcode *op) | |||||
{ | |||||
struct fuse_mknod_in *fmni; | |||||
fdisp_init(fdip, sizeof(*fmni) + cnp->cn_namelen + 1); | |||||
*op = FUSE_MKNOD; | |||||
fdisp_make(fdip, *op, vnode_mount(dvp), parentnid, td, cred); | |||||
fmni = fdip->indata; | |||||
fmni->mode = mode; | |||||
fmni->rdev = 0; | |||||
memcpy((char *)fdip->indata + sizeof(*fmni), cnp->cn_nameptr, | |||||
cnp->cn_namelen); | |||||
((char *)fdip->indata)[sizeof(*fmni) + cnp->cn_namelen] = '\0'; | |||||
} | |||||
/* | /* | ||||
struct vnop_create_args { | struct vnop_create_args { | ||||
struct vnode *a_dvp; | struct vnode *a_dvp; | ||||
struct vnode **a_vpp; | struct vnode **a_vpp; | ||||
struct componentname *a_cnp; | struct componentname *a_cnp; | ||||
struct vattr *a_vap; | struct vattr *a_vap; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_create(struct vop_create_args *ap) | fuse_vnop_create(struct vop_create_args *ap) | ||||
{ | { | ||||
struct vnode *dvp = ap->a_dvp; | struct vnode *dvp = ap->a_dvp; | ||||
struct vnode **vpp = ap->a_vpp; | struct vnode **vpp = ap->a_vpp; | ||||
struct componentname *cnp = ap->a_cnp; | struct componentname *cnp = ap->a_cnp; | ||||
struct vattr *vap = ap->a_vap; | struct vattr *vap = ap->a_vap; | ||||
struct thread *td = cnp->cn_thread; | struct thread *td = cnp->cn_thread; | ||||
struct ucred *cred = cnp->cn_cred; | struct ucred *cred = cnp->cn_cred; | ||||
struct fuse_open_in *foi; | struct fuse_data *data; | ||||
struct fuse_create_in *fci; | |||||
struct fuse_entry_out *feo; | struct fuse_entry_out *feo; | ||||
struct fuse_dispatcher fdi; | struct fuse_open_out *foo; | ||||
struct fuse_dispatcher fdi, fdi2; | |||||
struct fuse_dispatcher *fdip = &fdi; | struct fuse_dispatcher *fdip = &fdi; | ||||
struct fuse_dispatcher *fdip2 = NULL; | |||||
int err; | int err; | ||||
struct mount *mp = vnode_mount(dvp); | struct mount *mp = vnode_mount(dvp); | ||||
data = fuse_get_mpdata(mp); | |||||
uint64_t parentnid = VTOFUD(dvp)->nid; | uint64_t parentnid = VTOFUD(dvp)->nid; | ||||
mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode); | mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode); | ||||
uint64_t x_fh_id; | enum fuse_opcode op; | ||||
uint32_t x_open_flags; | int flags; | ||||
if (fuse_isdeadfs(dvp)) { | if (fuse_isdeadfs(dvp)) | ||||
return ENXIO; | return ENXIO; | ||||
} | |||||
/* FUSE expects sockets to be created with FUSE_MKNOD */ | |||||
if (vap->va_type == VSOCK) | |||||
return fuse_internal_mknod(dvp, vpp, cnp, vap); | |||||
/* | |||||
* VOP_CREATE doesn't tell us the open(2) flags, so we guess. Only a | |||||
* writable mode makes sense, and we might as well include readability | |||||
* too. | |||||
*/ | |||||
flags = O_RDWR; | |||||
bzero(&fdi, sizeof(fdi)); | bzero(&fdi, sizeof(fdi)); | ||||
/* XXX: Will we ever want devices ? */ | if (vap->va_type != VREG) | ||||
if ((vap->va_type != VREG)) { | |||||
printf("fuse_vnop_create: unsupported va_type %d\n", | |||||
vap->va_type); | |||||
return (EINVAL); | return (EINVAL); | ||||
} | |||||
fdisp_init(fdip, sizeof(*foi) + cnp->cn_namelen + 1); | if (!fsess_isimpl(mp, FUSE_CREATE) || vap->va_type == VSOCK) { | ||||
if (!fsess_isimpl(mp, FUSE_CREATE)) { | /* Fallback to FUSE_MKNOD/FUSE_OPEN */ | ||||
SDT_PROBE2(fuse, , vnops, trace, 1, | fdisp_make_mknod_for_fallback(fdip, cnp, dvp, parentnid, td, | ||||
"eh, daemon doesn't implement create?"); | cred, mode, &op); | ||||
return (EINVAL); | } else { | ||||
/* Use FUSE_CREATE */ | |||||
size_t insize; | |||||
op = FUSE_CREATE; | |||||
fdisp_init(fdip, sizeof(*fci) + cnp->cn_namelen + 1); | |||||
fdisp_make(fdip, op, vnode_mount(dvp), parentnid, td, cred); | |||||
fci = fdip->indata; | |||||
fci->mode = mode; | |||||
fci->flags = O_CREAT | flags; | |||||
if (fuse_libabi_geq(data, 7, 12)) { | |||||
insize = sizeof(*fci); | |||||
fci->umask = td->td_proc->p_fd->fd_cmask; | |||||
} else { | |||||
insize = sizeof(struct fuse_open_in); | |||||
} | } | ||||
fdisp_make(fdip, FUSE_CREATE, vnode_mount(dvp), parentnid, td, cred); | |||||
foi = fdip->indata; | memcpy((char *)fdip->indata + insize, cnp->cn_nameptr, | ||||
foi->mode = mode; | |||||
foi->flags = O_CREAT | O_RDWR; | |||||
memcpy((char *)fdip->indata + sizeof(*foi), cnp->cn_nameptr, | |||||
cnp->cn_namelen); | cnp->cn_namelen); | ||||
((char *)fdip->indata)[sizeof(*foi) + cnp->cn_namelen] = '\0'; | ((char *)fdip->indata)[insize + cnp->cn_namelen] = '\0'; | ||||
} | |||||
err = fdisp_wait_answ(fdip); | err = fdisp_wait_answ(fdip); | ||||
if (err) { | if (err) { | ||||
if (err == ENOSYS) | if (err == ENOSYS && op == FUSE_CREATE) { | ||||
fsess_set_notimpl(mp, FUSE_CREATE); | fsess_set_notimpl(mp, FUSE_CREATE); | ||||
fdisp_destroy(fdip); | |||||
fdisp_make_mknod_for_fallback(fdip, cnp, dvp, | |||||
parentnid, td, cred, mode, &op); | |||||
err = fdisp_wait_answ(fdip); | |||||
} | |||||
if (err) | |||||
goto out; | goto out; | ||||
} | } | ||||
feo = fdip->answ; | feo = fdip->answ; | ||||
if ((err = fuse_internal_checkentry(feo, VREG))) { | if ((err = fuse_internal_checkentry(feo, vap->va_type))) { | ||||
goto out; | goto out; | ||||
} | } | ||||
err = fuse_vnode_get(mp, feo, feo->nodeid, dvp, vpp, cnp, VREG); | |||||
if (op == FUSE_CREATE) { | |||||
foo = (struct fuse_open_out*)(feo + 1); | |||||
} else { | |||||
/* Issue a separate FUSE_OPEN */ | |||||
struct fuse_open_in *foi; | |||||
fdip2 = &fdi2; | |||||
fdisp_init(fdip2, sizeof(*foi)); | |||||
fdisp_make(fdip2, FUSE_OPEN, vnode_mount(dvp), feo->nodeid, td, | |||||
cred); | |||||
foi = fdip2->indata; | |||||
foi->flags = flags; | |||||
err = fdisp_wait_answ(fdip2); | |||||
if (err) | |||||
goto out; | |||||
foo = fdip2->answ; | |||||
} | |||||
err = fuse_vnode_get(mp, feo, feo->nodeid, dvp, vpp, cnp, vap->va_type); | |||||
if (err) { | if (err) { | ||||
struct fuse_release_in *fri; | struct fuse_release_in *fri; | ||||
uint64_t nodeid = feo->nodeid; | uint64_t nodeid = feo->nodeid; | ||||
uint64_t fh_id = ((struct fuse_open_out *)(feo + 1))->fh; | uint64_t fh_id = foo->fh; | ||||
fdisp_init(fdip, sizeof(*fri)); | fdisp_init(fdip, sizeof(*fri)); | ||||
fdisp_make(fdip, FUSE_RELEASE, mp, nodeid, td, cred); | fdisp_make(fdip, FUSE_RELEASE, mp, nodeid, td, cred); | ||||
fri = fdip->indata; | fri = fdip->indata; | ||||
fri->fh = fh_id; | fri->fh = fh_id; | ||||
fri->flags = OFLAGS(mode); | fri->flags = flags; | ||||
fuse_insert_callback(fdip->tick, fuse_internal_forget_callback); | fuse_insert_callback(fdip->tick, fuse_internal_forget_callback); | ||||
fuse_insert_message(fdip->tick); | fuse_insert_message(fdip->tick, false); | ||||
return err; | goto out; | ||||
} | } | ||||
ASSERT_VOP_ELOCKED(*vpp, "fuse_vnop_create"); | ASSERT_VOP_ELOCKED(*vpp, "fuse_vnop_create"); | ||||
fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid, | |||||
feo->attr_valid_nsec, NULL); | |||||
fdip->answ = feo + 1; | fuse_filehandle_init(*vpp, FUFH_RDWR, NULL, td, cred, foo); | ||||
fuse_vnode_open(*vpp, foo->open_flags, td); | |||||
x_fh_id = ((struct fuse_open_out *)(feo + 1))->fh; | /* | ||||
x_open_flags = ((struct fuse_open_out *)(feo + 1))->open_flags; | * Purge the parent's attribute cache because the daemon should've | ||||
fuse_filehandle_init(*vpp, FUFH_RDWR, NULL, x_fh_id); | * updated its mtime and ctime | ||||
fuse_vnode_open(*vpp, x_open_flags, td); | */ | ||||
fuse_vnode_clear_attr_cache(dvp); | |||||
cache_purge_negative(dvp); | cache_purge_negative(dvp); | ||||
out: | out: | ||||
if (fdip2) | |||||
fdisp_destroy(fdip2); | |||||
fdisp_destroy(fdip); | fdisp_destroy(fdip); | ||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
* Our vnop_fsync roughly corresponds to the FUSE_FSYNC method. The Linux | struct vnop_fdatasync_args { | ||||
* version of FUSE also has a FUSE_FLUSH method. | struct vop_generic_args a_gen; | ||||
* | struct vnode * a_vp; | ||||
* On Linux, fsync() synchronizes a file's complete in-core state with that | struct thread * a_td; | ||||
* on disk. The call is not supposed to return until the system has completed | }; | ||||
* that action or until an error is detected. | |||||
* | |||||
* Linux also has an fdatasync() call that is similar to fsync() but is not | |||||
* required to update the metadata such as access time and modification time. | |||||
*/ | */ | ||||
static int | |||||
fuse_vnop_fdatasync(struct vop_fdatasync_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct thread *td = ap->a_td; | |||||
int waitfor = MNT_WAIT; | |||||
int err = 0; | |||||
if (fuse_isdeadfs(vp)) { | |||||
return 0; | |||||
} | |||||
if ((err = vop_stdfdatasync_buf(ap))) | |||||
return err; | |||||
return fuse_internal_fsync(vp, td, waitfor, true); | |||||
} | |||||
/* | /* | ||||
struct vnop_fsync_args { | struct vnop_fsync_args { | ||||
struct vnodeop_desc *a_desc; | struct vop_generic_args a_gen; | ||||
struct vnode * a_vp; | struct vnode * a_vp; | ||||
struct ucred * a_cred; | |||||
int a_waitfor; | int a_waitfor; | ||||
struct thread * a_td; | struct thread * a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_fsync(struct vop_fsync_args *ap) | fuse_vnop_fsync(struct vop_fsync_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct thread *td = ap->a_td; | struct thread *td = ap->a_td; | ||||
int waitfor = ap->a_waitfor; | |||||
int err = 0; | |||||
struct fuse_filehandle *fufh; | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
int type, err = 0; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return 0; | return 0; | ||||
} | } | ||||
if ((err = vop_stdfsync(ap))) | if ((err = vop_stdfsync(ap))) | ||||
return err; | return err; | ||||
if (!fsess_isimpl(vnode_mount(vp), | return fuse_internal_fsync(vp, td, waitfor, false); | ||||
(vnode_vtype(vp) == VDIR ? FUSE_FSYNCDIR : FUSE_FSYNC))) { | |||||
goto out; | |||||
} | } | ||||
for (type = 0; type < FUFH_MAXTYPE; type++) { | |||||
fufh = &(fvdat->fufh[type]); | |||||
if (FUFH_IS_VALID(fufh)) { | |||||
fuse_internal_fsync(vp, td, NULL, fufh); | |||||
} | |||||
} | |||||
out: | |||||
return 0; | |||||
} | |||||
/* | /* | ||||
struct vnop_getattr_args { | struct vnop_getattr_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct vattr *a_vap; | struct vattr *a_vap; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_getattr(struct vop_getattr_args *ap) | fuse_vnop_getattr(struct vop_getattr_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct vattr *vap = ap->a_vap; | struct vattr *vap = ap->a_vap; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
struct thread *td = curthread; | struct thread *td = curthread; | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct fuse_attr_out *fao; | |||||
int err = 0; | int err = 0; | ||||
int dataflags; | int dataflags; | ||||
struct fuse_dispatcher fdi; | |||||
dataflags = fuse_get_mpdata(vnode_mount(vp))->dataflags; | dataflags = fuse_get_mpdata(vnode_mount(vp))->dataflags; | ||||
/* Note that we are not bailing out on a dead file system just yet. */ | /* Note that we are not bailing out on a dead file system just yet. */ | ||||
if (!(dataflags & FSESS_INITED)) { | if (!(dataflags & FSESS_INITED)) { | ||||
if (!vnode_isvroot(vp)) { | if (!vnode_isvroot(vp)) { | ||||
fdata_set_dead(fuse_get_mpdata(vnode_mount(vp))); | fdata_set_dead(fuse_get_mpdata(vnode_mount(vp))); | ||||
err = ENOTCONN; | err = ENOTCONN; | ||||
return err; | return err; | ||||
} else { | } else { | ||||
goto fake; | goto fake; | ||||
} | } | ||||
} | } | ||||
fdisp_init(&fdi, 0); | err = fuse_internal_getattr(vp, vap, cred, td); | ||||
if ((err = fdisp_simple_putget_vp(&fdi, FUSE_GETATTR, vp, td, cred))) { | if (err == ENOTCONN && vnode_isvroot(vp)) { | ||||
if ((err == ENOTCONN) && vnode_isvroot(vp)) { | |||||
/* see comment in fuse_vfsop_statfs() */ | /* see comment in fuse_vfsop_statfs() */ | ||||
fdisp_destroy(&fdi); | |||||
goto fake; | goto fake; | ||||
} else { | |||||
return err; | |||||
} | } | ||||
if (err == ENOENT) { | |||||
fuse_internal_vnode_disappear(vp); | |||||
} | |||||
goto out; | |||||
} | |||||
fao = (struct fuse_attr_out *)fdi.answ; | |||||
fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid, | |||||
fao->attr_valid_nsec, vap); | |||||
if (vap->va_type != vnode_vtype(vp)) { | |||||
fuse_internal_vnode_disappear(vp); | |||||
err = ENOENT; | |||||
goto out; | |||||
} | |||||
if ((fvdat->flag & FN_SIZECHANGE) != 0) | |||||
vap->va_size = fvdat->filesize; | |||||
if (vnode_isreg(vp) && (fvdat->flag & FN_SIZECHANGE) == 0) { | |||||
/* | |||||
* This is for those cases when the file size changed without us | |||||
* knowing, and we want to catch up. | |||||
*/ | |||||
off_t new_filesize = ((struct fuse_attr_out *) | |||||
fdi.answ)->attr.size; | |||||
if (fvdat->filesize != new_filesize) { | |||||
fuse_vnode_setsize(vp, new_filesize); | |||||
fvdat->flag &= ~FN_SIZECHANGE; | |||||
} | |||||
} | |||||
out: | |||||
fdisp_destroy(&fdi); | |||||
return err; | |||||
fake: | fake: | ||||
bzero(vap, sizeof(*vap)); | bzero(vap, sizeof(*vap)); | ||||
vap->va_type = vnode_vtype(vp); | vap->va_type = vnode_vtype(vp); | ||||
return 0; | return 0; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_inactive_args { | struct vnop_inactive_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_inactive(struct vop_inactive_args *ap) | fuse_vnop_inactive(struct vop_inactive_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct thread *td = ap->a_td; | struct thread *td = ap->a_td; | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh = NULL; | struct fuse_filehandle *fufh, *fufh_tmp; | ||||
int type, need_flush = 1; | int need_flush = 1; | ||||
for (type = 0; type < FUFH_MAXTYPE; type++) { | LIST_FOREACH_SAFE(fufh, &fvdat->handles, next, fufh_tmp) { | ||||
fufh = &(fvdat->fufh[type]); | |||||
if (FUFH_IS_VALID(fufh)) { | |||||
if (need_flush && vp->v_type == VREG) { | if (need_flush && vp->v_type == VREG) { | ||||
if ((VTOFUD(vp)->flag & FN_SIZECHANGE) != 0) { | if ((VTOFUD(vp)->flag & FN_SIZECHANGE) != 0) { | ||||
fuse_vnode_savesize(vp, NULL); | fuse_vnode_savesize(vp, NULL, 0); | ||||
} | } | ||||
if (fuse_data_cache_invalidate || | if ((fvdat->flag & FN_REVOKED) != 0) | ||||
(fvdat->flag & FN_REVOKED) != 0) | |||||
fuse_io_invalbuf(vp, td); | fuse_io_invalbuf(vp, td); | ||||
else | else | ||||
fuse_io_flushbuf(vp, MNT_WAIT, td); | fuse_io_flushbuf(vp, MNT_WAIT, td); | ||||
need_flush = 0; | need_flush = 0; | ||||
} | } | ||||
fuse_filehandle_close(vp, type, td, NULL); | fuse_filehandle_close(vp, fufh, td, NULL); | ||||
} | } | ||||
} | |||||
if ((fvdat->flag & FN_REVOKED) != 0 && fuse_reclaim_revoked) { | if ((fvdat->flag & FN_REVOKED) != 0) | ||||
vrecycle(vp); | vrecycle(vp); | ||||
} | |||||
return 0; | return 0; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_link_args { | struct vnop_link_args { | ||||
struct vnode *a_tdvp; | struct vnode *a_tdvp; | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct componentname *a_cnp; | struct componentname *a_cnp; | ||||
Show All 35 Lines | fuse_vnop_link(struct vop_link_args *ap) | ||||
fuse_internal_newentry_makerequest(vnode_mount(tdvp), VTOI(tdvp), cnp, | fuse_internal_newentry_makerequest(vnode_mount(tdvp), VTOI(tdvp), cnp, | ||||
FUSE_LINK, &fli, sizeof(fli), &fdi); | FUSE_LINK, &fli, sizeof(fli), &fdi); | ||||
if ((err = fdisp_wait_answ(&fdi))) { | if ((err = fdisp_wait_answ(&fdi))) { | ||||
goto out; | goto out; | ||||
} | } | ||||
feo = fdi.answ; | feo = fdi.answ; | ||||
err = fuse_internal_checkentry(feo, vnode_vtype(vp)); | err = fuse_internal_checkentry(feo, vnode_vtype(vp)); | ||||
if (!err) { | |||||
/* | |||||
* Purge the parent's attribute cache because the daemon | |||||
* should've updated its mtime and ctime | |||||
*/ | |||||
fuse_vnode_clear_attr_cache(tdvp); | |||||
fuse_internal_cache_attrs(vp, &feo->attr, feo->attr_valid, | |||||
feo->attr_valid_nsec, NULL); | |||||
} | |||||
out: | out: | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return err; | return err; | ||||
} | } | ||||
struct fuse_lookup_alloc_arg { | |||||
struct fuse_entry_out *feo; | |||||
struct componentname *cnp; | |||||
uint64_t nid; | |||||
enum vtype vtyp; | |||||
}; | |||||
/* Callback for vn_get_ino */ | |||||
static int | |||||
fuse_lookup_alloc(struct mount *mp, void *arg, int lkflags, struct vnode **vpp) | |||||
{ | |||||
struct fuse_lookup_alloc_arg *flaa = arg; | |||||
return fuse_vnode_get(mp, flaa->feo, flaa->nid, NULL, vpp, flaa->cnp, | |||||
flaa->vtyp); | |||||
} | |||||
SDT_PROBE_DEFINE3(fusefs, , vnops, cache_lookup, | |||||
"int", "struct timespec*", "struct timespec*"); | |||||
/* | /* | ||||
struct vnop_lookup_args { | struct vnop_lookup_args { | ||||
struct vnodeop_desc *a_desc; | struct vnodeop_desc *a_desc; | ||||
struct vnode *a_dvp; | struct vnode *a_dvp; | ||||
struct vnode **a_vpp; | struct vnode **a_vpp; | ||||
struct componentname *a_cnp; | struct componentname *a_cnp; | ||||
}; | }; | ||||
*/ | */ | ||||
Show All 12 Lines | fuse_vnop_lookup(struct vop_lookup_args *ap) | ||||
int islastcn = flags & ISLASTCN; | int islastcn = flags & ISLASTCN; | ||||
struct mount *mp = vnode_mount(dvp); | struct mount *mp = vnode_mount(dvp); | ||||
int err = 0; | int err = 0; | ||||
int lookup_err = 0; | int lookup_err = 0; | ||||
struct vnode *vp = NULL; | struct vnode *vp = NULL; | ||||
struct fuse_dispatcher fdi; | struct fuse_dispatcher fdi; | ||||
enum fuse_opcode op; | bool did_lookup = false; | ||||
struct fuse_entry_out *feo = NULL; | |||||
enum vtype vtyp; /* vnode type of target */ | |||||
off_t filesize; /* filesize of target */ | |||||
uint64_t nid; | uint64_t nid; | ||||
struct fuse_access_param facp; | |||||
if (fuse_isdeadfs(dvp)) { | if (fuse_isdeadfs(dvp)) { | ||||
*vpp = NULL; | *vpp = NULL; | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (!vnode_isdir(dvp)) { | if (!vnode_isdir(dvp)) | ||||
return ENOTDIR; | return ENOTDIR; | ||||
} | |||||
if (islastcn && vfs_isrdonly(mp) && (nameiop != LOOKUP)) { | if (islastcn && vfs_isrdonly(mp) && (nameiop != LOOKUP)) | ||||
return EROFS; | return EROFS; | ||||
} | |||||
/* | |||||
* We do access check prior to doing anything else only in the case | |||||
* when we are at fs root (we'd like to say, "we are at the first | |||||
* component", but that's not exactly the same... nevermind). | |||||
* See further comments at further access checks. | |||||
*/ | |||||
bzero(&facp, sizeof(facp)); | if ((err = fuse_internal_access(dvp, VEXEC, td, cred))) | ||||
if (vnode_isvroot(dvp)) { /* early permission check hack */ | |||||
if ((err = fuse_internal_access(dvp, VEXEC, &facp, td, cred))) { | |||||
return err; | return err; | ||||
} | |||||
} | |||||
if (flags & ISDOTDOT) { | if (flags & ISDOTDOT) { | ||||
KASSERT(VTOFUD(dvp)->flag & FN_PARENT_NID, | |||||
("Looking up .. is TODO")); | |||||
nid = VTOFUD(dvp)->parent_nid; | nid = VTOFUD(dvp)->parent_nid; | ||||
if (nid == 0) { | if (nid == 0) | ||||
return ENOENT; | return ENOENT; | ||||
} | /* .. is obviously a directory */ | ||||
fdisp_init(&fdi, 0); | vtyp = VDIR; | ||||
op = FUSE_GETATTR; | filesize = 0; | ||||
goto calldaemon; | |||||
} else if (cnp->cn_namelen == 1 && *(cnp->cn_nameptr) == '.') { | } else if (cnp->cn_namelen == 1 && *(cnp->cn_nameptr) == '.') { | ||||
nid = VTOI(dvp); | nid = VTOI(dvp); | ||||
fdisp_init(&fdi, 0); | /* . is obviously a directory */ | ||||
op = FUSE_GETATTR; | vtyp = VDIR; | ||||
goto calldaemon; | filesize = 0; | ||||
} else if (fuse_lookup_cache_enable) { | } else { | ||||
err = cache_lookup(dvp, vpp, cnp, NULL, NULL); | struct timespec now, timeout; | ||||
switch (err) { | |||||
err = cache_lookup(dvp, vpp, cnp, &timeout, NULL); | |||||
getnanouptime(&now); | |||||
SDT_PROBE3(fusefs, , vnops, cache_lookup, err, &timeout, &now); | |||||
switch (err) { | |||||
case -1: /* positive match */ | case -1: /* positive match */ | ||||
atomic_add_acq_long(&fuse_lookup_cache_hits, 1); | if (timespeccmp(&timeout, &now, >)) { | ||||
counter_u64_add(fuse_lookup_cache_hits, 1); | |||||
} else { | |||||
/* Cache timeout */ | |||||
counter_u64_add(fuse_lookup_cache_misses, 1); | |||||
bintime_clear( | |||||
&VTOFUD(*vpp)->entry_cache_timeout); | |||||
cache_purge(*vpp); | |||||
if (dvp != *vpp) | |||||
vput(*vpp); | |||||
else | |||||
vrele(*vpp); | |||||
*vpp = NULL; | |||||
break; | |||||
} | |||||
return 0; | return 0; | ||||
case 0: /* no match in cache */ | case 0: /* no match in cache */ | ||||
atomic_add_acq_long(&fuse_lookup_cache_misses, 1); | counter_u64_add(fuse_lookup_cache_misses, 1); | ||||
break; | break; | ||||
case ENOENT: /* negative match */ | case ENOENT: /* negative match */ | ||||
getnanouptime(&now); | |||||
if (timespeccmp(&timeout, &now, <=)) { | |||||
/* Cache timeout */ | |||||
cache_purge_negative(dvp); | |||||
break; | |||||
} | |||||
/* fall through */ | /* fall through */ | ||||
default: | default: | ||||
return err; | return err; | ||||
} | } | ||||
} | |||||
nid = VTOI(dvp); | nid = VTOI(dvp); | ||||
fdisp_init(&fdi, cnp->cn_namelen + 1); | fdisp_init(&fdi, cnp->cn_namelen + 1); | ||||
op = FUSE_LOOKUP; | fdisp_make(&fdi, FUSE_LOOKUP, mp, nid, td, cred); | ||||
calldaemon: | |||||
fdisp_make(&fdi, op, mp, nid, td, cred); | |||||
if (op == FUSE_LOOKUP) { | |||||
memcpy(fdi.indata, cnp->cn_nameptr, cnp->cn_namelen); | memcpy(fdi.indata, cnp->cn_nameptr, cnp->cn_namelen); | ||||
((char *)fdi.indata)[cnp->cn_namelen] = '\0'; | ((char *)fdi.indata)[cnp->cn_namelen] = '\0'; | ||||
} | |||||
lookup_err = fdisp_wait_answ(&fdi); | lookup_err = fdisp_wait_answ(&fdi); | ||||
did_lookup = true; | |||||
if ((op == FUSE_LOOKUP) && !lookup_err) { /* lookup call succeeded */ | if (!lookup_err) { | ||||
nid = ((struct fuse_entry_out *)fdi.answ)->nodeid; | /* lookup call succeeded */ | ||||
if (!nid) { | feo = (struct fuse_entry_out *)fdi.answ; | ||||
/* | nid = feo->nodeid; | ||||
* zero nodeid is the same as "not found", | if (nid == 0) { | ||||
* but it's also cacheable (which we keep | /* zero nodeid means ENOENT and cache it */ | ||||
* keep on doing not as of writing this) | struct timespec timeout; | ||||
*/ | |||||
fdi.answ_stat = ENOENT; | |||||
lookup_err = ENOENT; | lookup_err = ENOENT; | ||||
if (cnp->cn_flags & MAKEENTRY) { | |||||
fuse_validity_2_timespec(feo, &timeout); | |||||
cache_enter_time(dvp, *vpp, cnp, | |||||
&timeout, NULL); | |||||
} | |||||
} else if (nid == FUSE_ROOT_ID) { | } else if (nid == FUSE_ROOT_ID) { | ||||
lookup_err = EINVAL; | lookup_err = EINVAL; | ||||
} | } | ||||
vtyp = IFTOVT(feo->attr.mode); | |||||
filesize = feo->attr.size; | |||||
} | } | ||||
if (lookup_err && | if (lookup_err && (!fdi.answ_stat || lookup_err != ENOENT)) { | ||||
(!fdi.answ_stat || lookup_err != ENOENT || op != FUSE_LOOKUP)) { | |||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return lookup_err; | return lookup_err; | ||||
} | } | ||||
} | |||||
/* lookup_err, if non-zero, must be ENOENT at this point */ | /* lookup_err, if non-zero, must be ENOENT at this point */ | ||||
if (lookup_err) { | if (lookup_err) { | ||||
/* Entry not found */ | |||||
if ((nameiop == CREATE || nameiop == RENAME) && islastcn | if ((nameiop == CREATE || nameiop == RENAME) && islastcn) { | ||||
/* && directory dvp has not been removed */ ) { | err = fuse_internal_access(dvp, VWRITE, td, cred); | ||||
if (!err) { | |||||
if (vfs_isrdonly(mp)) { | |||||
err = EROFS; | |||||
goto out; | |||||
} | |||||
#if 0 /* THINK_ABOUT_THIS */ | |||||
if ((err = fuse_internal_access(dvp, VWRITE, cred, td, &facp))) { | |||||
goto out; | |||||
} | |||||
#endif | |||||
/* | /* | ||||
* Possibly record the position of a slot in the | * Set the SAVENAME flag to hold onto the | ||||
* directory large enough for the new component name. | * pathname for use later in VOP_CREATE or | ||||
* This can be recorded in the vnode private data for | * VOP_RENAME. | ||||
* dvp. Set the SAVENAME flag to hold onto the | |||||
* pathname for use later in VOP_CREATE or VOP_RENAME. | |||||
*/ | */ | ||||
cnp->cn_flags |= SAVENAME; | cnp->cn_flags |= SAVENAME; | ||||
err = EJUSTRETURN; | err = EJUSTRETURN; | ||||
goto out; | |||||
} | } | ||||
/* Consider inserting name into cache. */ | |||||
/* | |||||
* No we can't use negative caching, as the fs | |||||
* changes are out of our control. | |||||
* False positives' falseness turns out just as things | |||||
* go by, but false negatives' falseness doesn't. | |||||
* (and aiding the caching mechanism with extra control | |||||
* mechanisms comes quite close to beating the whole purpose | |||||
* caching...) | |||||
*/ | |||||
#if 0 | |||||
if ((cnp->cn_flags & MAKEENTRY) != 0) { | |||||
SDT_PROBE2(fuse, , vnops, trace, 1, | |||||
"inserting NULL into cache"); | |||||
cache_enter(dvp, NULL, cnp); | |||||
} | |||||
#endif | |||||
err = ENOENT; | |||||
goto out; | |||||
} else { | } else { | ||||
err = ENOENT; | |||||
/* !lookup_err */ | |||||
struct fuse_entry_out *feo = NULL; | |||||
struct fuse_attr *fattr = NULL; | |||||
if (op == FUSE_GETATTR) { | |||||
fattr = &((struct fuse_attr_out *)fdi.answ)->attr; | |||||
} else { | |||||
feo = (struct fuse_entry_out *)fdi.answ; | |||||
fattr = &(feo->attr); | |||||
} | } | ||||
/* | |||||
* If deleting, and at end of pathname, return parameters | |||||
* which can be used to remove file. If the wantparent flag | |||||
* isn't set, we return only the directory, otherwise we go on | |||||
* and lock the inode, being careful with ".". | |||||
*/ | |||||
if (nameiop == DELETE && islastcn) { | |||||
/* | |||||
* Check for write access on directory. | |||||
*/ | |||||
facp.xuid = fattr->uid; | |||||
facp.facc_flags |= FACCESS_STICKY; | |||||
err = fuse_internal_access(dvp, VWRITE, &facp, td, cred); | |||||
facp.facc_flags &= ~FACCESS_XQUERIES; | |||||
if (err) { | |||||
goto out; | |||||
} | |||||
if (nid == VTOI(dvp)) { | |||||
vref(dvp); | |||||
*vpp = dvp; | |||||
} else { | } else { | ||||
err = fuse_vnode_get(dvp->v_mount, feo, nid, | /* Entry was found */ | ||||
dvp, &vp, cnp, IFTOVT(fattr->mode)); | |||||
if (err) | |||||
goto out; | |||||
*vpp = vp; | |||||
} | |||||
/* | |||||
* Save the name for use in VOP_RMDIR and VOP_REMOVE | |||||
* later. | |||||
*/ | |||||
cnp->cn_flags |= SAVENAME; | |||||
goto out; | |||||
} | |||||
/* | |||||
* If rewriting (RENAME), return the inode and the | |||||
* information required to rewrite the present directory | |||||
* Must get inode of directory entry to verify it's a | |||||
* regular file, or empty directory. | |||||
*/ | |||||
if (nameiop == RENAME && wantparent && islastcn) { | |||||
#if 0 /* THINK_ABOUT_THIS */ | |||||
if ((err = fuse_internal_access(dvp, VWRITE, cred, td, &facp))) { | |||||
goto out; | |||||
} | |||||
#endif | |||||
/* | |||||
* Check for "." | |||||
*/ | |||||
if (nid == VTOI(dvp)) { | |||||
err = EISDIR; | |||||
goto out; | |||||
} | |||||
err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp, | |||||
&vp, cnp, IFTOVT(fattr->mode)); | |||||
if (err) { | |||||
goto out; | |||||
} | |||||
*vpp = vp; | |||||
/* | |||||
* Save the name for use in VOP_RENAME later. | |||||
*/ | |||||
cnp->cn_flags |= SAVENAME; | |||||
goto out; | |||||
} | |||||
if (flags & ISDOTDOT) { | if (flags & ISDOTDOT) { | ||||
struct mount *mp; | struct fuse_lookup_alloc_arg flaa; | ||||
int ltype; | |||||
/* | flaa.nid = nid; | ||||
* Expanded copy of vn_vget_ino() so that | flaa.feo = feo; | ||||
* fuse_vnode_get() can be used. | flaa.cnp = cnp; | ||||
*/ | flaa.vtyp = vtyp; | ||||
mp = dvp->v_mount; | err = vn_vget_ino_gen(dvp, fuse_lookup_alloc, &flaa, 0, | ||||
ltype = VOP_ISLOCKED(dvp); | &vp); | ||||
err = vfs_busy(mp, MBF_NOWAIT); | |||||
if (err != 0) { | |||||
vfs_ref(mp); | |||||
VOP_UNLOCK(dvp, 0); | |||||
err = vfs_busy(mp, 0); | |||||
vn_lock(dvp, ltype | LK_RETRY); | |||||
vfs_rel(mp); | |||||
if (err) | |||||
goto out; | |||||
if ((dvp->v_iflag & VI_DOOMED) != 0) { | |||||
err = ENOENT; | |||||
vfs_unbusy(mp); | |||||
goto out; | |||||
} | |||||
} | |||||
VOP_UNLOCK(dvp, 0); | |||||
err = fuse_vnode_get(vnode_mount(dvp), feo, nid, NULL, | |||||
&vp, cnp, IFTOVT(fattr->mode)); | |||||
vfs_unbusy(mp); | |||||
vn_lock(dvp, ltype | LK_RETRY); | |||||
if ((dvp->v_iflag & VI_DOOMED) != 0) { | |||||
if (err == 0) | |||||
vput(vp); | |||||
err = ENOENT; | |||||
} | |||||
if (err) | |||||
goto out; | |||||
*vpp = vp; | *vpp = vp; | ||||
} else if (nid == VTOI(dvp)) { | } else if (nid == VTOI(dvp)) { | ||||
vref(dvp); | vref(dvp); | ||||
*vpp = dvp; | *vpp = dvp; | ||||
} else { | } else { | ||||
struct fuse_vnode_data *fvdat; | struct fuse_vnode_data *fvdat; | ||||
err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp, | err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp, | ||||
&vp, cnp, IFTOVT(fattr->mode)); | &vp, cnp, vtyp); | ||||
if (err) { | if (err) | ||||
goto out; | goto out; | ||||
} | *vpp = vp; | ||||
fuse_vnode_setparent(vp, dvp); | |||||
/* | /* | ||||
* In the case where we are looking up a FUSE node | * In the case where we are looking up a FUSE node | ||||
* represented by an existing cached vnode, and the | * represented by an existing cached vnode, and the | ||||
* true size reported by FUSE_LOOKUP doesn't match | * true size reported by FUSE_LOOKUP doesn't match | ||||
* the vnode's cached size, fix the vnode cache to | * the vnode's cached size, then any cached writes | ||||
* match the real object size. | * beyond the file's current size are lost. | ||||
* | * | ||||
* This can occur via FUSE distributed filesystems, | * We can get here: | ||||
* irregular files, etc. | * * following attribute cache expiration, or | ||||
* * due a bug in the daemon, or | |||||
*/ | */ | ||||
fvdat = VTOFUD(vp); | fvdat = VTOFUD(vp); | ||||
if (vnode_isreg(vp) && | if (vnode_isreg(vp) && | ||||
fattr->size != fvdat->filesize) { | filesize != fvdat->cached_attrs.va_size && | ||||
fvdat->flag & FN_SIZECHANGE) { | |||||
/* | /* | ||||
* The FN_SIZECHANGE flag reflects a dirty | * The FN_SIZECHANGE flag reflects a dirty | ||||
* append. If userspace lets us know our cache | * append. If userspace lets us know our cache | ||||
* is invalid, that write was lost. (Dirty | * is invalid, that write was lost. (Dirty | ||||
* writes that do not cause append are also | * writes that do not cause append are also | ||||
* lost, but we don't detect them here.) | * lost, but we don't detect them here.) | ||||
* | * | ||||
* XXX: Maybe disable WB caching on this mount. | * XXX: Maybe disable WB caching on this mount. | ||||
*/ | */ | ||||
if (fvdat->flag & FN_SIZECHANGE) | printf("%s: WB cache incoherent on %s!\n", | ||||
printf("%s: WB cache incoherent on " | __func__, | ||||
"%s!\n", __func__, | |||||
vnode_mount(vp)->mnt_stat.f_mntonname); | vnode_mount(vp)->mnt_stat.f_mntonname); | ||||
(void)fuse_vnode_setsize(vp, fattr->size); | |||||
fvdat->flag &= ~FN_SIZECHANGE; | fvdat->flag &= ~FN_SIZECHANGE; | ||||
} | } | ||||
*vpp = vp; | |||||
} | |||||
if (op == FUSE_GETATTR) { | MPASS(feo != NULL); | ||||
struct fuse_attr_out *fao = | fuse_internal_cache_attrs(*vpp, &feo->attr, | ||||
(struct fuse_attr_out*)fdi.answ; | feo->attr_valid, feo->attr_valid_nsec, NULL); | ||||
fuse_internal_cache_attrs(*vpp, | fuse_validity_2_bintime(feo->entry_valid, | ||||
&fao->attr, fao->attr_valid, | feo->entry_valid_nsec, | ||||
fao->attr_valid_nsec, NULL); | &fvdat->entry_cache_timeout); | ||||
} else { | |||||
struct fuse_entry_out *feo = | |||||
(struct fuse_entry_out*)fdi.answ; | |||||
fuse_internal_cache_attrs(*vpp, | |||||
&feo->attr, feo->attr_valid, | |||||
feo->attr_valid_nsec, NULL); | |||||
} | |||||
/* Insert name into cache if appropriate. */ | if ((nameiop == DELETE || nameiop == RENAME) && | ||||
islastcn) | |||||
{ | |||||
struct vattr dvattr; | |||||
err = fuse_internal_access(dvp, VWRITE, td, | |||||
cred); | |||||
if (err != 0) | |||||
goto out; | |||||
/* | /* | ||||
* Nooo, caching is evil. With caching, we can't avoid stale | * if the parent's sticky bit is set, check | ||||
* information taking over the playground (cached info is not | * whether we're allowed to remove the file. | ||||
* just positive/negative, it does have qualitative aspects, | * Need to figure out the vnode locking to make | ||||
* too). And a (VOP/FUSE)_GETATTR is always thrown anyway, when | * this work. | ||||
* walking down along cached path components, and that's not | |||||
* any cheaper than FUSE_LOOKUP. This might change with | |||||
* implementing kernel side attr caching, but... In Linux, | |||||
* lookup results are not cached, and the daemon is bombarded | |||||
* with FUSE_LOOKUPS on and on. This shows that by design, the | |||||
* daemon is expected to handle frequent lookup queries | |||||
* efficiently, do its caching in userspace, and so on. | |||||
* | |||||
* So just leave the name cache alone. | |||||
*/ | */ | ||||
fuse_internal_getattr(dvp, &dvattr, cred, td); | |||||
/* | if ((dvattr.va_mode & S_ISTXT) && | ||||
* Well, now I know, Linux caches lookups, but with a | fuse_internal_access(dvp, VADMIN, td, | ||||
* timeout... So it's the same thing as attribute caching: | cred) && | ||||
* we can deal with it when implement timeouts. | fuse_internal_access(*vpp, VADMIN, td, | ||||
*/ | cred)) { | ||||
#if 0 | err = EPERM; | ||||
if (cnp->cn_flags & MAKEENTRY) { | goto out; | ||||
cache_enter(dvp, *vpp, cnp); | |||||
} | } | ||||
#endif | |||||
} | } | ||||
out: | |||||
if (!lookup_err) { | |||||
/* No lookup error; need to clean up. */ | if (islastcn && ( | ||||
(nameiop == DELETE) || | |||||
if (err) { /* Found inode; exit with no vnode. */ | (nameiop == RENAME && wantparent))) { | ||||
if (op == FUSE_LOOKUP) { | cnp->cn_flags |= SAVENAME; | ||||
fuse_internal_forget_send(vnode_mount(dvp), td, cred, | |||||
nid, 1); | |||||
} | } | ||||
fdisp_destroy(&fdi); | |||||
return err; | |||||
} else { | |||||
#ifndef NO_EARLY_PERM_CHECK_HACK | |||||
if (!islastcn) { | |||||
/* | |||||
* We have the attributes of the next item | |||||
* *now*, and it's a fact, and we do not | |||||
* have to do extra work for it (ie, beg the | |||||
* daemon), and it neither depends on such | |||||
* accidental things like attr caching. So | |||||
* the big idea: check credentials *now*, | |||||
* not at the beginning of the next call to | |||||
* lookup. | |||||
* | |||||
* The first item of the lookup chain (fs root) | |||||
* won't be checked then here, of course, as | |||||
* its never "the next". But go and see that | |||||
* the root is taken care about at the very | |||||
* beginning of this function. | |||||
* | |||||
* Now, given we want to do the access check | |||||
* this way, one might ask: so then why not | |||||
* do the access check just after fetching | |||||
* the inode and its attributes from the | |||||
* daemon? Why bother with producing the | |||||
* corresponding vnode at all if something | |||||
* is not OK? We know what's the deal as | |||||
* soon as we get those attrs... There is | |||||
* one bit of info though not given us by | |||||
* the daemon: whether his response is | |||||
* authoritative or not... His response should | |||||
* be ignored if something is mounted over | |||||
* the dir in question. But that can be | |||||
* known only by having the vnode... | |||||
*/ | |||||
int tmpvtype = vnode_vtype(*vpp); | |||||
bzero(&facp, sizeof(facp)); | |||||
/*the early perm check hack */ | |||||
facp.facc_flags |= FACCESS_VA_VALID; | |||||
if ((tmpvtype != VDIR) && (tmpvtype != VLNK)) { | |||||
err = ENOTDIR; | |||||
} | } | ||||
if (!err && !vnode_mountedhere(*vpp)) { | |||||
err = fuse_internal_access(*vpp, VEXEC, &facp, td, cred); | |||||
} | } | ||||
out: | |||||
if (err) { | if (err) { | ||||
if (tmpvtype == VLNK) | if (vp != NULL && dvp != vp) | ||||
SDT_PROBE2(fuse, , vnops, trace, | vput(vp); | ||||
1, "weird, permission " | else if (vp != NULL) | ||||
"error with a symlink?"); | vrele(vp); | ||||
vput(*vpp); | |||||
*vpp = NULL; | *vpp = NULL; | ||||
} | } | ||||
} | if (did_lookup) | ||||
#endif | |||||
} | |||||
} | |||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_mkdir_args { | struct vnop_mkdir_args { | ||||
struct vnode *a_dvp; | struct vnode *a_dvp; | ||||
struct vnode **a_vpp; | struct vnode **a_vpp; | ||||
Show All 10 Lines | fuse_vnop_mkdir(struct vop_mkdir_args *ap) | ||||
struct vattr *vap = ap->a_vap; | struct vattr *vap = ap->a_vap; | ||||
struct fuse_mkdir_in fmdi; | struct fuse_mkdir_in fmdi; | ||||
if (fuse_isdeadfs(dvp)) { | if (fuse_isdeadfs(dvp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
fmdi.mode = MAKEIMODE(vap->va_type, vap->va_mode); | fmdi.mode = MAKEIMODE(vap->va_type, vap->va_mode); | ||||
fmdi.umask = curthread->td_proc->p_fd->fd_cmask; | |||||
return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKDIR, &fmdi, | return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKDIR, &fmdi, | ||||
sizeof(fmdi), VDIR)); | sizeof(fmdi), VDIR)); | ||||
} | } | ||||
/* | /* | ||||
struct vnop_mknod_args { | struct vnop_mknod_args { | ||||
struct vnode *a_dvp; | struct vnode *a_dvp; | ||||
struct vnode **a_vpp; | struct vnode **a_vpp; | ||||
struct componentname *a_cnp; | struct componentname *a_cnp; | ||||
struct vattr *a_vap; | struct vattr *a_vap; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_mknod(struct vop_mknod_args *ap) | fuse_vnop_mknod(struct vop_mknod_args *ap) | ||||
{ | { | ||||
return (EINVAL); | struct vnode *dvp = ap->a_dvp; | ||||
} | struct vnode **vpp = ap->a_vpp; | ||||
struct componentname *cnp = ap->a_cnp; | |||||
struct vattr *vap = ap->a_vap; | |||||
if (fuse_isdeadfs(dvp)) | |||||
return ENXIO; | |||||
return fuse_internal_mknod(dvp, vpp, cnp, vap); | |||||
} | |||||
/* | /* | ||||
struct vnop_open_args { | struct vop_open_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
int a_mode; | int a_mode; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
int a_fdidx; / struct file *a_fp; | int a_fdidx; / struct file *a_fp; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_open(struct vop_open_args *ap) | fuse_vnop_open(struct vop_open_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
int mode = ap->a_mode; | int a_mode = ap->a_mode; | ||||
struct thread *td = ap->a_td; | struct thread *td = ap->a_td; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
pid_t pid = td->td_proc->p_pid; | |||||
fufh_type_t fufh_type; | |||||
struct fuse_vnode_data *fvdat; | struct fuse_vnode_data *fvdat; | ||||
int error, isdir = 0; | if (fuse_isdeadfs(vp)) | ||||
int32_t fuse_open_flags; | |||||
if (fuse_isdeadfs(vp)) { | |||||
return ENXIO; | return ENXIO; | ||||
} | if (vp->v_type == VCHR || vp->v_type == VBLK || vp->v_type == VFIFO) | ||||
if ((mode & (FREAD | FWRITE)) == 0) | return (EOPNOTSUPP); | ||||
if ((a_mode & (FREAD | FWRITE | FEXEC)) == 0) | |||||
return EINVAL; | return EINVAL; | ||||
fvdat = VTOFUD(vp); | fvdat = VTOFUD(vp); | ||||
if (vnode_isdir(vp)) { | if (fuse_filehandle_validrw(vp, a_mode, cred, pid)) { | ||||
isdir = 1; | fuse_vnode_open(vp, 0, td); | ||||
} | |||||
fuse_open_flags = 0; | |||||
if (isdir) { | |||||
fufh_type = FUFH_RDONLY; | |||||
} else { | |||||
fufh_type = fuse_filehandle_xlate_from_fflags(mode); | |||||
/* | |||||
* For WRONLY opens, force DIRECT_IO. This is necessary | |||||
* since writing a partial block through the buffer cache | |||||
* will result in a read of the block and that read won't | |||||
* be allowed by the WRONLY open. | |||||
*/ | |||||
if (fufh_type == FUFH_WRONLY || | |||||
(fvdat->flag & FN_DIRECTIO) != 0) | |||||
fuse_open_flags = FOPEN_DIRECT_IO; | |||||
} | |||||
if (fuse_filehandle_validrw(vp, fufh_type) != FUFH_INVALID) { | |||||
fuse_vnode_open(vp, fuse_open_flags, td); | |||||
return 0; | return 0; | ||||
} | } | ||||
error = fuse_filehandle_open(vp, fufh_type, NULL, td, cred); | |||||
return error; | return fuse_filehandle_open(vp, a_mode, NULL, td, cred); | ||||
} | } | ||||
static int | static int | ||||
fuse_vnop_pathconf(struct vop_pathconf_args *ap) | fuse_vnop_pathconf(struct vop_pathconf_args *ap) | ||||
{ | { | ||||
switch (ap->a_name) { | switch (ap->a_name) { | ||||
case _PC_FILESIZEBITS: | case _PC_FILESIZEBITS: | ||||
Show All 26 Lines | |||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_read(struct vop_read_args *ap) | fuse_vnop_read(struct vop_read_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct uio *uio = ap->a_uio; | struct uio *uio = ap->a_uio; | ||||
int ioflag = ap->a_ioflag; | int ioflag = ap->a_ioflag; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
pid_t pid = curthread->td_proc->p_pid; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (VTOFUD(vp)->flag & FN_DIRECTIO) { | if (VTOFUD(vp)->flag & FN_DIRECTIO) { | ||||
ioflag |= IO_DIRECT; | ioflag |= IO_DIRECT; | ||||
} | } | ||||
return fuse_io_dispatch(vp, uio, ioflag, cred); | return fuse_io_dispatch(vp, uio, ioflag, cred, pid); | ||||
} | } | ||||
/* | /* | ||||
struct vnop_readdir_args { | struct vnop_readdir_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct uio *a_uio; | struct uio *a_uio; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
int *a_eofflag; | int *a_eofflag; | ||||
int *ncookies; | int *a_ncookies; | ||||
u_long **a_cookies; | u_long **a_cookies; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_readdir(struct vop_readdir_args *ap) | fuse_vnop_readdir(struct vop_readdir_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct uio *uio = ap->a_uio; | struct uio *uio = ap->a_uio; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
struct fuse_filehandle *fufh = NULL; | struct fuse_filehandle *fufh = NULL; | ||||
struct fuse_iov cookediov; | struct fuse_iov cookediov; | ||||
int err = 0; | int err = 0; | ||||
int freefufh = 0; | u_long *cookies; | ||||
off_t startoff; | |||||
ssize_t tresid; | |||||
int ncookies; | |||||
bool closefufh = false; | |||||
pid_t pid = curthread->td_proc->p_pid; | |||||
if (ap->a_eofflag) | |||||
*ap->a_eofflag = 0; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if ( /* XXXIP ((uio_iovcnt(uio) > 1)) || */ | if ( /* XXXIP ((uio_iovcnt(uio) > 1)) || */ | ||||
(uio_resid(uio) < sizeof(struct dirent))) { | (uio_resid(uio) < sizeof(struct dirent))) { | ||||
return EINVAL; | return EINVAL; | ||||
} | } | ||||
if (!fuse_filehandle_valid(vp, FUFH_RDONLY)) { | tresid = uio->uio_resid; | ||||
SDT_PROBE2(fuse, , vnops, trace, 1, | startoff = uio->uio_offset; | ||||
"calling readdir() before open()"); | err = fuse_filehandle_get_dir(vp, &fufh, cred, pid); | ||||
err = fuse_filehandle_open(vp, FUFH_RDONLY, &fufh, NULL, cred); | if (err == EBADF && vnode_mount(vp)->mnt_flag & MNT_EXPORTED) { | ||||
freefufh = 1; | /* | ||||
} else { | * nfsd will do VOP_READDIR without first doing VOP_OPEN. We | ||||
err = fuse_filehandle_get(vp, FUFH_RDONLY, &fufh); | * must implicitly open the directory here | ||||
*/ | |||||
err = fuse_filehandle_open(vp, FREAD, &fufh, curthread, cred); | |||||
if (err == 0) { | |||||
/* | |||||
* When a directory is opened, it must be read from | |||||
* the beginning. Hopefully, the "startoff" still | |||||
* exists as an offset cookie for the directory. | |||||
* If not, it will read the entire directory without | |||||
* returning any entries and just return eof. | |||||
*/ | |||||
uio->uio_offset = 0; | |||||
} | } | ||||
if (err) { | closefufh = true; | ||||
} | |||||
if (err) | |||||
return (err); | return (err); | ||||
if (ap->a_ncookies != NULL) { | |||||
ncookies = uio->uio_resid / | |||||
(offsetof(struct dirent, d_name) + 4) + 1; | |||||
cookies = malloc(ncookies * sizeof(*cookies), M_TEMP, M_WAITOK); | |||||
*ap->a_ncookies = ncookies; | |||||
*ap->a_cookies = cookies; | |||||
} else { | |||||
ncookies = 0; | |||||
cookies = NULL; | |||||
} | } | ||||
#define DIRCOOKEDSIZE FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + MAXNAMLEN + 1) | #define DIRCOOKEDSIZE FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + MAXNAMLEN + 1) | ||||
fiov_init(&cookediov, DIRCOOKEDSIZE); | fiov_init(&cookediov, DIRCOOKEDSIZE); | ||||
err = fuse_internal_readdir(vp, uio, fufh, &cookediov); | err = fuse_internal_readdir(vp, uio, startoff, fufh, &cookediov, | ||||
&ncookies, cookies); | |||||
fiov_teardown(&cookediov); | fiov_teardown(&cookediov); | ||||
if (freefufh) { | if (closefufh) | ||||
fuse_filehandle_close(vp, FUFH_RDONLY, NULL, cred); | fuse_filehandle_close(vp, fufh, curthread, cred); | ||||
if (ap->a_ncookies != NULL) { | |||||
if (err == 0) { | |||||
*ap->a_ncookies -= ncookies; | |||||
} else { | |||||
free(*ap->a_cookies, M_TEMP); | |||||
*ap->a_ncookies = 0; | |||||
*ap->a_cookies = NULL; | |||||
} | } | ||||
} | |||||
if (err == 0 && tresid == uio->uio_resid) | |||||
*ap->a_eofflag = 1; | |||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_readlink_args { | struct vnop_readlink_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct uio *a_uio; | struct uio *a_uio; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
Show All 40 Lines | /* | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_reclaim(struct vop_reclaim_args *ap) | fuse_vnop_reclaim(struct vop_reclaim_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct thread *td = ap->a_td; | struct thread *td = ap->a_td; | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh = NULL; | struct fuse_filehandle *fufh, *fufh_tmp; | ||||
int type; | |||||
if (!fvdat) { | if (!fvdat) { | ||||
panic("FUSE: no vnode data during recycling"); | panic("FUSE: no vnode data during recycling"); | ||||
} | } | ||||
for (type = 0; type < FUFH_MAXTYPE; type++) { | LIST_FOREACH_SAFE(fufh, &fvdat->handles, next, fufh_tmp) { | ||||
fufh = &(fvdat->fufh[type]); | printf("FUSE: vnode being reclaimed with open fufh " | ||||
if (FUFH_IS_VALID(fufh)) { | "(type=%#x)", fufh->fufh_type); | ||||
printf("FUSE: vnode being reclaimed but fufh (type=%d) is valid", | fuse_filehandle_close(vp, fufh, td, NULL); | ||||
type); | |||||
fuse_filehandle_close(vp, type, td, NULL); | |||||
} | } | ||||
} | |||||
if ((!fuse_isdeadfs(vp)) && (fvdat->nlookup)) { | if ((!fuse_isdeadfs(vp)) && (fvdat->nlookup)) { | ||||
fuse_internal_forget_send(vnode_mount(vp), td, NULL, VTOI(vp), | fuse_internal_forget_send(vnode_mount(vp), td, NULL, VTOI(vp), | ||||
fvdat->nlookup); | fvdat->nlookup); | ||||
} | } | ||||
fuse_vnode_setparent(vp, NULL); | fuse_vnode_setparent(vp, NULL); | ||||
cache_purge(vp); | cache_purge(vp); | ||||
vfs_hash_remove(vp); | vfs_hash_remove(vp); | ||||
Show All 20 Lines | fuse_vnop_remove(struct vop_remove_args *ap) | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (vnode_isdir(vp)) { | if (vnode_isdir(vp)) { | ||||
return EPERM; | return EPERM; | ||||
} | } | ||||
cache_purge(vp); | |||||
err = fuse_internal_remove(dvp, vp, cnp, FUSE_UNLINK); | err = fuse_internal_remove(dvp, vp, cnp, FUSE_UNLINK); | ||||
if (err == 0) | |||||
fuse_internal_vnode_disappear(vp); | |||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_rename_args { | struct vnop_rename_args { | ||||
struct vnode *a_fdvp; | struct vnode *a_fdvp; | ||||
struct vnode *a_fvp; | struct vnode *a_fvp; | ||||
struct componentname *a_fcnp; | struct componentname *a_fcnp; | ||||
struct vnode *a_tdvp; | struct vnode *a_tdvp; | ||||
struct vnode *a_tvp; | struct vnode *a_tvp; | ||||
struct componentname *a_tcnp; | struct componentname *a_tcnp; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_rename(struct vop_rename_args *ap) | fuse_vnop_rename(struct vop_rename_args *ap) | ||||
{ | { | ||||
struct vnode *fdvp = ap->a_fdvp; | struct vnode *fdvp = ap->a_fdvp; | ||||
struct vnode *fvp = ap->a_fvp; | struct vnode *fvp = ap->a_fvp; | ||||
struct componentname *fcnp = ap->a_fcnp; | struct componentname *fcnp = ap->a_fcnp; | ||||
struct vnode *tdvp = ap->a_tdvp; | struct vnode *tdvp = ap->a_tdvp; | ||||
struct vnode *tvp = ap->a_tvp; | struct vnode *tvp = ap->a_tvp; | ||||
struct componentname *tcnp = ap->a_tcnp; | struct componentname *tcnp = ap->a_tcnp; | ||||
struct fuse_data *data; | struct fuse_data *data; | ||||
bool newparent = fdvp != tdvp; | |||||
bool isdir = fvp->v_type == VDIR; | |||||
int err = 0; | int err = 0; | ||||
if (fuse_isdeadfs(fdvp)) { | if (fuse_isdeadfs(fdvp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (fvp->v_mount != tdvp->v_mount || | if (fvp->v_mount != tdvp->v_mount || | ||||
(tvp && fvp->v_mount != tvp->v_mount)) { | (tvp && fvp->v_mount != tvp->v_mount)) { | ||||
SDT_PROBE2(fuse, , vnops, trace, 1, "cross-device rename"); | SDT_PROBE2(fusefs, , vnops, trace, 1, "cross-device rename"); | ||||
err = EXDEV; | err = EXDEV; | ||||
goto out; | goto out; | ||||
} | } | ||||
cache_purge(fvp); | cache_purge(fvp); | ||||
/* | /* | ||||
* FUSE library is expected to check if target directory is not | * FUSE library is expected to check if target directory is not | ||||
* under the source directory in the file system tree. | * under the source directory in the file system tree. | ||||
* Linux performs this check at VFS level. | * Linux performs this check at VFS level. | ||||
*/ | */ | ||||
/* | |||||
* If source is a directory, and it will get a new parent, user must | |||||
* have write permission to it, so ".." can be modified. | |||||
*/ | |||||
data = fuse_get_mpdata(vnode_mount(tdvp)); | data = fuse_get_mpdata(vnode_mount(tdvp)); | ||||
if (data->dataflags & FSESS_DEFAULT_PERMISSIONS && isdir && newparent) { | |||||
err = fuse_internal_access(fvp, VWRITE, | |||||
tcnp->cn_thread, tcnp->cn_cred); | |||||
if (err) | |||||
goto out; | |||||
} | |||||
sx_xlock(&data->rename_lock); | sx_xlock(&data->rename_lock); | ||||
err = fuse_internal_rename(fdvp, fcnp, tdvp, tcnp); | err = fuse_internal_rename(fdvp, fcnp, tdvp, tcnp); | ||||
if (err == 0) { | if (err == 0) { | ||||
if (tdvp != fdvp) | if (tdvp != fdvp) | ||||
fuse_vnode_setparent(fvp, tdvp); | fuse_vnode_setparent(fvp, tdvp); | ||||
if (tvp != NULL) | if (tvp != NULL) | ||||
fuse_vnode_setparent(tvp, NULL); | fuse_vnode_setparent(tvp, NULL); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | fuse_vnop_rmdir(struct vop_rmdir_args *ap) | ||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
if (VTOFUD(vp) == VTOFUD(dvp)) { | if (VTOFUD(vp) == VTOFUD(dvp)) { | ||||
return EINVAL; | return EINVAL; | ||||
} | } | ||||
err = fuse_internal_remove(dvp, vp, ap->a_cnp, FUSE_RMDIR); | err = fuse_internal_remove(dvp, vp, ap->a_cnp, FUSE_RMDIR); | ||||
if (err == 0) | |||||
fuse_internal_vnode_disappear(vp); | |||||
return err; | return err; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_setattr_args { | struct vnop_setattr_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct vattr *a_vap; | struct vattr *a_vap; | ||||
struct ucred *a_cred; | struct ucred *a_cred; | ||||
struct thread *a_td; | struct thread *a_td; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_setattr(struct vop_setattr_args *ap) | fuse_vnop_setattr(struct vop_setattr_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct vattr *vap = ap->a_vap; | struct vattr *vap = ap->a_vap; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
struct thread *td = curthread; | struct thread *td = curthread; | ||||
struct mount *mp; | |||||
struct fuse_data *data; | |||||
struct vattr old_va; | |||||
int dataflags; | |||||
int err = 0, err2; | |||||
accmode_t accmode = 0; | |||||
bool checkperm; | |||||
bool drop_suid = false; | |||||
gid_t cr_gid; | |||||
struct fuse_dispatcher fdi; | mp = vnode_mount(vp); | ||||
struct fuse_setattr_in *fsai; | data = fuse_get_mpdata(mp); | ||||
struct fuse_access_param facp; | dataflags = data->dataflags; | ||||
checkperm = dataflags & FSESS_DEFAULT_PERMISSIONS; | |||||
if (cred->cr_ngroups > 0) | |||||
cr_gid = cred->cr_groups[0]; | |||||
else | |||||
cr_gid = 0; | |||||
int err = 0; | |||||
enum vtype vtyp; | |||||
int sizechanged = 0; | |||||
uint64_t newsize = 0; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
fdisp_init(&fdi, sizeof(*fsai)); | |||||
fdisp_make_vp(&fdi, FUSE_SETATTR, vp, td, cred); | |||||
fsai = fdi.indata; | |||||
fsai->valid = 0; | |||||
bzero(&facp, sizeof(facp)); | |||||
facp.xuid = vap->va_uid; | |||||
facp.xgid = vap->va_gid; | |||||
if (vap->va_uid != (uid_t)VNOVAL) { | if (vap->va_uid != (uid_t)VNOVAL) { | ||||
facp.facc_flags |= FACCESS_CHOWN; | if (checkperm) { | ||||
fsai->uid = vap->va_uid; | /* Only root may change a file's owner */ | ||||
fsai->valid |= FATTR_UID; | err = priv_check_cred(cred, PRIV_VFS_CHOWN); | ||||
if (err) { | |||||
/* As a special case, allow the null chown */ | |||||
err2 = fuse_internal_getattr(vp, &old_va, cred, | |||||
td); | |||||
if (err2) | |||||
return (err2); | |||||
if (vap->va_uid != old_va.va_uid) | |||||
return err; | |||||
else | |||||
accmode |= VADMIN; | |||||
drop_suid = true; | |||||
} else | |||||
accmode |= VADMIN; | |||||
} else | |||||
accmode |= VADMIN; | |||||
} | } | ||||
if (vap->va_gid != (gid_t)VNOVAL) { | if (vap->va_gid != (gid_t)VNOVAL) { | ||||
facp.facc_flags |= FACCESS_CHOWN; | if (checkperm && priv_check_cred(cred, PRIV_VFS_CHOWN)) | ||||
fsai->gid = vap->va_gid; | drop_suid = true; | ||||
fsai->valid |= FATTR_GID; | if (checkperm && !groupmember(vap->va_gid, cred)) | ||||
{ | |||||
/* | |||||
* Non-root users may only chgrp to one of their own | |||||
* groups | |||||
*/ | |||||
err = priv_check_cred(cred, PRIV_VFS_CHOWN); | |||||
if (err) { | |||||
/* As a special case, allow the null chgrp */ | |||||
err2 = fuse_internal_getattr(vp, &old_va, cred, | |||||
td); | |||||
if (err2) | |||||
return (err2); | |||||
if (vap->va_gid != old_va.va_gid) | |||||
return err; | |||||
accmode |= VADMIN; | |||||
} else | |||||
accmode |= VADMIN; | |||||
} else | |||||
accmode |= VADMIN; | |||||
} | } | ||||
if (vap->va_size != VNOVAL) { | if (vap->va_size != VNOVAL) { | ||||
switch (vp->v_type) { | |||||
struct fuse_filehandle *fufh = NULL; | case VDIR: | ||||
return (EISDIR); | |||||
/*Truncate to a new value. */ | case VLNK: | ||||
fsai->size = vap->va_size; | case VREG: | ||||
sizechanged = 1; | if (vfs_isrdonly(mp)) | ||||
newsize = vap->va_size; | return (EROFS); | ||||
fsai->valid |= FATTR_SIZE; | break; | ||||
default: | |||||
fuse_filehandle_getrw(vp, FUFH_WRONLY, &fufh); | /* | ||||
if (fufh) { | * According to POSIX, the result is unspecified | ||||
fsai->fh = fufh->fh_id; | * for file types other than regular files, | ||||
fsai->valid |= FATTR_FH; | * directories and shared memory objects. We | ||||
* don't support shared memory objects in the file | |||||
* system, and have dubious support for truncating | |||||
* symlinks. Just ignore the request in other cases. | |||||
*/ | |||||
return (0); | |||||
} | } | ||||
/* Don't set accmode. Permission to trunc is checked upstack */ | |||||
} | } | ||||
if (vap->va_atime.tv_sec != VNOVAL) { | if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { | ||||
fsai->atime = vap->va_atime.tv_sec; | if (vap->va_vaflags & VA_UTIMES_NULL) | ||||
fsai->atimensec = vap->va_atime.tv_nsec; | accmode |= VWRITE; | ||||
fsai->valid |= FATTR_ATIME; | else | ||||
accmode |= VADMIN; | |||||
} | } | ||||
if (vap->va_mtime.tv_sec != VNOVAL) { | if (drop_suid) { | ||||
fsai->mtime = vap->va_mtime.tv_sec; | if (vap->va_mode != (mode_t)VNOVAL) | ||||
fsai->mtimensec = vap->va_mtime.tv_nsec; | vap->va_mode &= ~(S_ISUID | S_ISGID); | ||||
fsai->valid |= FATTR_MTIME; | else { | ||||
err = fuse_internal_getattr(vp, &old_va, cred, td); | |||||
if (err) | |||||
return (err); | |||||
vap->va_mode = old_va.va_mode & ~(S_ISUID | S_ISGID); | |||||
} | } | ||||
if (vap->va_mode != (mode_t)VNOVAL) { | |||||
fsai->mode = vap->va_mode & ALLPERMS; | |||||
fsai->valid |= FATTR_MODE; | |||||
} | } | ||||
if (!fsai->valid) { | if (vap->va_mode != (mode_t)VNOVAL) { | ||||
goto out; | /* Only root may set the sticky bit on non-directories */ | ||||
} | if (checkperm && vp->v_type != VDIR && (vap->va_mode & S_ISTXT) | ||||
vtyp = vnode_vtype(vp); | && priv_check_cred(cred, PRIV_VFS_STICKYFILE)) | ||||
return EFTYPE; | |||||
if (fsai->valid & FATTR_SIZE && vtyp == VDIR) { | if (checkperm && (vap->va_mode & S_ISGID)) { | ||||
err = EISDIR; | err = fuse_internal_getattr(vp, &old_va, cred, td); | ||||
goto out; | |||||
} | |||||
if (vfs_isrdonly(vnode_mount(vp)) && (fsai->valid & ~FATTR_SIZE || vtyp == VREG)) { | |||||
err = EROFS; | |||||
goto out; | |||||
} | |||||
if (fsai->valid & ~FATTR_SIZE) { | |||||
/*err = fuse_internal_access(vp, VADMIN, context, &facp); */ | |||||
/*XXX */ | |||||
err = 0; | |||||
} | |||||
facp.facc_flags &= ~FACCESS_XQUERIES; | |||||
if (err && !(fsai->valid & ~(FATTR_ATIME | FATTR_MTIME)) && | |||||
vap->va_vaflags & VA_UTIMES_NULL) { | |||||
err = fuse_internal_access(vp, VWRITE, &facp, td, cred); | |||||
} | |||||
if (err) | if (err) | ||||
goto out; | return (err); | ||||
if ((err = fdisp_wait_answ(&fdi))) | if (!groupmember(old_va.va_gid, cred)) { | ||||
goto out; | err = priv_check_cred(cred, PRIV_VFS_SETGID); | ||||
vtyp = IFTOVT(((struct fuse_attr_out *)fdi.answ)->attr.mode); | if (err) | ||||
return (err); | |||||
if (vnode_vtype(vp) != vtyp) { | |||||
if (vnode_vtype(vp) == VNON && vtyp != VNON) { | |||||
SDT_PROBE2(fuse, , vnops, trace, 1, "FUSE: Dang! " | |||||
"vnode_vtype is VNON and vtype isn't."); | |||||
} else { | |||||
/* | |||||
* STALE vnode, ditch | |||||
* | |||||
* The vnode has changed its type "behind our back". | |||||
* There's nothing really we can do, so let us just | |||||
* force an internal revocation and tell the caller to | |||||
* try again, if interested. | |||||
*/ | |||||
fuse_internal_vnode_disappear(vp); | |||||
err = EAGAIN; | |||||
} | } | ||||
} | } | ||||
if (err == 0) { | accmode |= VADMIN; | ||||
struct fuse_attr_out *fao = (struct fuse_attr_out*)fdi.answ; | |||||
fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid, | |||||
fao->attr_valid_nsec, NULL); | |||||
} | } | ||||
out: | if (vfs_isrdonly(mp)) | ||||
fdisp_destroy(&fdi); | return EROFS; | ||||
if (!err && sizechanged) { | |||||
fuse_vnode_setsize(vp, newsize); | err = fuse_internal_access(vp, accmode, td, cred); | ||||
VTOFUD(vp)->flag &= ~FN_SIZECHANGE; | if (err) | ||||
} | |||||
return err; | return err; | ||||
else | |||||
return fuse_internal_setattr(vp, vap, td, cred); | |||||
} | } | ||||
/* | /* | ||||
struct vnop_strategy_args { | struct vnop_strategy_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
struct buf *a_bp; | struct buf *a_bp; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_strategy(struct vop_strategy_args *ap) | fuse_vnop_strategy(struct vop_strategy_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct buf *bp = ap->a_bp; | struct buf *bp = ap->a_bp; | ||||
if (!vp || fuse_isdeadfs(vp)) { | if (!vp || fuse_isdeadfs(vp)) { | ||||
bp->b_ioflags |= BIO_ERROR; | bp->b_ioflags |= BIO_ERROR; | ||||
bp->b_error = ENXIO; | bp->b_error = ENXIO; | ||||
bufdone(bp); | bufdone(bp); | ||||
return ENXIO; | return 0; | ||||
} | } | ||||
if (bp->b_iocmd == BIO_WRITE) | |||||
fuse_vnode_refreshsize(vp, NOCRED); | |||||
(void)fuse_io_strategy(vp, bp); | |||||
/* | /* | ||||
* This is a dangerous function. If returns error, that might mean a | * VOP_STRATEGY always returns zero and signals error via bp->b_ioflags. | ||||
* panic. We prefer pretty much anything over being forced to panic | * fuse_io_strategy sets bp's error fields | ||||
* by a malicious daemon (a demon?). So we just return 0 anyway. You | |||||
* should never mind this: this function has its own error | |||||
* propagation mechanism via the argument buffer, so | |||||
* not-that-melodramatic residents of the call chain still will be | |||||
* able to know what to do. | |||||
*/ | */ | ||||
(void)fuse_io_strategy(vp, bp); | |||||
return 0; | return 0; | ||||
} | } | ||||
/* | /* | ||||
struct vnop_symlink_args { | struct vnop_symlink_args { | ||||
struct vnode *a_dvp; | struct vnode *a_dvp; | ||||
struct vnode **a_vpp; | struct vnode **a_vpp; | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_write(struct vop_write_args *ap) | fuse_vnop_write(struct vop_write_args *ap) | ||||
{ | { | ||||
struct vnode *vp = ap->a_vp; | struct vnode *vp = ap->a_vp; | ||||
struct uio *uio = ap->a_uio; | struct uio *uio = ap->a_uio; | ||||
int ioflag = ap->a_ioflag; | int ioflag = ap->a_ioflag; | ||||
struct ucred *cred = ap->a_cred; | struct ucred *cred = ap->a_cred; | ||||
pid_t pid = curthread->td_proc->p_pid; | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
return ENXIO; | return ENXIO; | ||||
} | } | ||||
fuse_vnode_refreshsize(vp, cred); | |||||
if (VTOFUD(vp)->flag & FN_DIRECTIO) { | if (VTOFUD(vp)->flag & FN_DIRECTIO) { | ||||
ioflag |= IO_DIRECT; | ioflag |= IO_DIRECT; | ||||
} | } | ||||
return fuse_io_dispatch(vp, uio, ioflag, cred); | return fuse_io_dispatch(vp, uio, ioflag, cred, pid); | ||||
} | } | ||||
SDT_PROBE_DEFINE1(fuse, , vnops, vnop_getpages_error, "int"); | static daddr_t | ||||
/* | fuse_gbp_getblkno(struct vnode *vp, vm_ooffset_t off) | ||||
struct vnop_getpages_args { | |||||
struct vnode *a_vp; | |||||
vm_page_t *a_m; | |||||
int a_count; | |||||
int a_reqpage; | |||||
}; | |||||
*/ | |||||
static int | |||||
fuse_vnop_getpages(struct vop_getpages_args *ap) | |||||
{ | { | ||||
int i, error, nextoff, size, toff, count, npages; | const int biosize = fuse_iosize(vp); | ||||
struct uio uio; | |||||
struct iovec iov; | |||||
vm_offset_t kva; | |||||
struct buf *bp; | |||||
struct vnode *vp; | |||||
struct thread *td; | |||||
struct ucred *cred; | |||||
vm_page_t *pages; | |||||
vp = ap->a_vp; | return (off / biosize); | ||||
KASSERT(vp->v_object, ("objectless vp passed to getpages")); | |||||
td = curthread; /* XXX */ | |||||
cred = curthread->td_ucred; /* XXX */ | |||||
pages = ap->a_m; | |||||
npages = ap->a_count; | |||||
if (!fsess_opt_mmap(vnode_mount(vp))) { | |||||
SDT_PROBE2(fuse, , vnops, trace, 1, | |||||
"called on non-cacheable vnode??\n"); | |||||
return (VM_PAGER_ERROR); | |||||
} | } | ||||
/* | static int | ||||
* If the last page is partially valid, just return it and allow | fuse_gbp_getblksz(struct vnode *vp, daddr_t lbn) | ||||
* the pager to zero-out the blanks. Partially valid pages can | { | ||||
* only occur at the file EOF. | off_t filesize; | ||||
* | int blksz, err; | ||||
* XXXGL: is that true for FUSE, which is a local filesystem, | const int biosize = fuse_iosize(vp); | ||||
* but still somewhat disconnected from the kernel? | |||||
*/ | |||||
VM_OBJECT_WLOCK(vp->v_object); | |||||
if (pages[npages - 1]->valid != 0 && --npages == 0) | |||||
goto out; | |||||
VM_OBJECT_WUNLOCK(vp->v_object); | |||||
/* | err = fuse_vnode_size(vp, &filesize, NULL, NULL); | ||||
* We use only the kva address for the buffer, but this is extremely | KASSERT(err == 0, ("vfs_bio_getpages can't handle errors here")); | ||||
* convenient and fast. | if (err) | ||||
*/ | return biosize; | ||||
bp = uma_zalloc(fuse_pbuf_zone, M_WAITOK); | |||||
kva = (vm_offset_t)bp->b_data; | if ((off_t)lbn * biosize >= filesize) { | ||||
pmap_qenter(kva, pages, npages); | blksz = 0; | ||||
VM_CNT_INC(v_vnodein); | } else if ((off_t)(lbn + 1) * biosize > filesize) { | ||||
VM_CNT_ADD(v_vnodepgsin, npages); | blksz = filesize - (off_t)lbn *biosize; | ||||
count = npages << PAGE_SHIFT; | |||||
iov.iov_base = (caddr_t)kva; | |||||
iov.iov_len = count; | |||||
uio.uio_iov = &iov; | |||||
uio.uio_iovcnt = 1; | |||||
uio.uio_offset = IDX_TO_OFF(pages[0]->pindex); | |||||
uio.uio_resid = count; | |||||
uio.uio_segflg = UIO_SYSSPACE; | |||||
uio.uio_rw = UIO_READ; | |||||
uio.uio_td = td; | |||||
error = fuse_io_dispatch(vp, &uio, IO_DIRECT, cred); | |||||
pmap_qremove(kva, npages); | |||||
uma_zfree(fuse_pbuf_zone, bp); | |||||
if (error && (uio.uio_resid == count)) { | |||||
SDT_PROBE1(fuse, , vnops, vnop_getpages_error, error); | |||||
return VM_PAGER_ERROR; | |||||
} | |||||
/* | |||||
* Calculate the number of bytes read and validate only that number | |||||
* of bytes. Note that due to pending writes, size may be 0. This | |||||
* does not mean that the remaining data is invalid! | |||||
*/ | |||||
size = count - uio.uio_resid; | |||||
VM_OBJECT_WLOCK(vp->v_object); | |||||
fuse_vm_page_lock_queues(); | |||||
for (i = 0, toff = 0; i < npages; i++, toff = nextoff) { | |||||
vm_page_t m; | |||||
nextoff = toff + PAGE_SIZE; | |||||
m = pages[i]; | |||||
if (nextoff <= size) { | |||||
/* | |||||
* Read operation filled an entire page | |||||
*/ | |||||
m->valid = VM_PAGE_BITS_ALL; | |||||
KASSERT(m->dirty == 0, | |||||
("fuse_getpages: page %p is dirty", m)); | |||||
} else if (size > toff) { | |||||
/* | |||||
* Read operation filled a partial page. | |||||
*/ | |||||
m->valid = 0; | |||||
vm_page_set_valid_range(m, 0, size - toff); | |||||
KASSERT(m->dirty == 0, | |||||
("fuse_getpages: page %p is dirty", m)); | |||||
} else { | } else { | ||||
/* | blksz = biosize; | ||||
* Read operation was short. If no error occurred | |||||
* we may have hit a zero-fill section. We simply | |||||
* leave valid set to 0. | |||||
*/ | |||||
; | |||||
} | } | ||||
return (blksz); | |||||
} | } | ||||
fuse_vm_page_unlock_queues(); | |||||
out: | |||||
VM_OBJECT_WUNLOCK(vp->v_object); | |||||
if (ap->a_rbehind) | |||||
*ap->a_rbehind = 0; | |||||
if (ap->a_rahead) | |||||
*ap->a_rahead = 0; | |||||
return (VM_PAGER_OK); | |||||
} | |||||
/* | /* | ||||
struct vnop_putpages_args { | struct vnop_getpages_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
vm_page_t *a_m; | vm_page_t *a_m; | ||||
int a_count; | int a_count; | ||||
int a_sync; | int a_reqpage; | ||||
int *a_rtvals; | |||||
vm_ooffset_t a_offset; | |||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_putpages(struct vop_putpages_args *ap) | fuse_vnop_getpages(struct vop_getpages_args *ap) | ||||
{ | { | ||||
struct uio uio; | struct vnode *vp = ap->a_vp; | ||||
struct iovec iov; | |||||
vm_offset_t kva; | |||||
struct buf *bp; | |||||
int i, error, npages, count; | |||||
off_t offset; | |||||
int *rtvals; | |||||
struct vnode *vp; | |||||
struct thread *td; | |||||
struct ucred *cred; | |||||
vm_page_t *pages; | |||||
vm_ooffset_t fsize; | |||||
vp = ap->a_vp; | |||||
KASSERT(vp->v_object, ("objectless vp passed to putpages")); | |||||
fsize = vp->v_object->un_pager.vnp.vnp_size; | |||||
td = curthread; /* XXX */ | |||||
cred = curthread->td_ucred; /* XXX */ | |||||
pages = ap->a_m; | |||||
count = ap->a_count; | |||||
rtvals = ap->a_rtvals; | |||||
npages = btoc(count); | |||||
offset = IDX_TO_OFF(pages[0]->pindex); | |||||
if (!fsess_opt_mmap(vnode_mount(vp))) { | if (!fsess_opt_mmap(vnode_mount(vp))) { | ||||
SDT_PROBE2(fuse, , vnops, trace, 1, | SDT_PROBE2(fusefs, , vnops, trace, 1, | ||||
"called on non-cacheable vnode??\n"); | "called on non-cacheable vnode??\n"); | ||||
return (VM_PAGER_ERROR); | |||||
} | } | ||||
for (i = 0; i < npages; i++) | |||||
rtvals[i] = VM_PAGER_AGAIN; | |||||
/* | return (vfs_bio_getpages(vp, ap->a_m, ap->a_count, ap->a_rbehind, | ||||
* When putting pages, do not extend file past EOF. | ap->a_rahead, fuse_gbp_getblkno, fuse_gbp_getblksz)); | ||||
*/ | |||||
if (offset + count > fsize) { | |||||
count = fsize - offset; | |||||
if (count < 0) | |||||
count = 0; | |||||
} | } | ||||
/* | |||||
* We use only the kva address for the buffer, but this is extremely | |||||
* convenient and fast. | |||||
*/ | |||||
bp = uma_zalloc(fuse_pbuf_zone, M_WAITOK); | |||||
kva = (vm_offset_t)bp->b_data; | |||||
pmap_qenter(kva, pages, npages); | |||||
VM_CNT_INC(v_vnodeout); | |||||
VM_CNT_ADD(v_vnodepgsout, count); | |||||
iov.iov_base = (caddr_t)kva; | |||||
iov.iov_len = count; | |||||
uio.uio_iov = &iov; | |||||
uio.uio_iovcnt = 1; | |||||
uio.uio_offset = offset; | |||||
uio.uio_resid = count; | |||||
uio.uio_segflg = UIO_SYSSPACE; | |||||
uio.uio_rw = UIO_WRITE; | |||||
uio.uio_td = td; | |||||
error = fuse_io_dispatch(vp, &uio, IO_DIRECT, cred); | |||||
pmap_qremove(kva, npages); | |||||
uma_zfree(fuse_pbuf_zone, bp); | |||||
if (!error) { | |||||
int nwritten = round_page(count - uio.uio_resid) / PAGE_SIZE; | |||||
for (i = 0; i < nwritten; i++) { | |||||
rtvals[i] = VM_PAGER_OK; | |||||
VM_OBJECT_WLOCK(pages[i]->object); | |||||
vm_page_undirty(pages[i]); | |||||
VM_OBJECT_WUNLOCK(pages[i]->object); | |||||
} | |||||
} | |||||
return rtvals[0]; | |||||
} | |||||
static const char extattr_namespace_separator = '.'; | static const char extattr_namespace_separator = '.'; | ||||
/* | /* | ||||
struct vop_getextattr_args { | struct vop_getextattr_args { | ||||
struct vop_generic_args a_gen; | struct vop_generic_args a_gen; | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
int a_attrnamespace; | int a_attrnamespace; | ||||
const char *a_name; | const char *a_name; | ||||
Show All 17 Lines | fuse_vnop_getextattr(struct vop_getextattr_args *ap) | ||||
char *prefix; | char *prefix; | ||||
char *attr_str; | char *attr_str; | ||||
size_t len; | size_t len; | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) | if (fuse_isdeadfs(vp)) | ||||
return (ENXIO); | return (ENXIO); | ||||
if (!fsess_isimpl(mp, FUSE_GETXATTR)) | |||||
return EOPNOTSUPP; | |||||
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, VREAD); | |||||
if (err) | |||||
return err; | |||||
/* Default to looking for user attributes. */ | /* Default to looking for user attributes. */ | ||||
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | ||||
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | ||||
else | else | ||||
prefix = EXTATTR_NAMESPACE_USER_STRING; | prefix = EXTATTR_NAMESPACE_USER_STRING; | ||||
len = strlen(prefix) + sizeof(extattr_namespace_separator) + | len = strlen(prefix) + sizeof(extattr_namespace_separator) + | ||||
strlen(ap->a_name) + 1; | strlen(ap->a_name) + 1; | ||||
Show All 14 Lines | else | ||||
get_xattr_in->size = uio->uio_resid; | get_xattr_in->size = uio->uio_resid; | ||||
attr_str = (char *)fdi.indata + sizeof(*get_xattr_in); | attr_str = (char *)fdi.indata + sizeof(*get_xattr_in); | ||||
snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator, | snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator, | ||||
ap->a_name); | ap->a_name); | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
if (err != 0) { | if (err != 0) { | ||||
if (err == ENOSYS) | if (err == ENOSYS) { | ||||
fsess_set_notimpl(mp, FUSE_GETXATTR); | fsess_set_notimpl(mp, FUSE_GETXATTR); | ||||
err = EOPNOTSUPP; | |||||
} | |||||
goto out; | goto out; | ||||
} | } | ||||
get_xattr_out = fdi.answ; | get_xattr_out = fdi.answ; | ||||
if (ap->a_size != NULL) | if (ap->a_size != NULL) | ||||
*ap->a_size = get_xattr_out->size; | *ap->a_size = get_xattr_out->size; | ||||
Show All 29 Lines | fuse_vnop_setextattr(struct vop_setextattr_args *ap) | ||||
char *prefix; | char *prefix; | ||||
size_t len; | size_t len; | ||||
char *attr_str; | char *attr_str; | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) | if (fuse_isdeadfs(vp)) | ||||
return (ENXIO); | return (ENXIO); | ||||
if (!fsess_isimpl(mp, FUSE_SETXATTR)) | |||||
return EOPNOTSUPP; | |||||
if (vfs_isrdonly(mp)) | |||||
return EROFS; | |||||
/* Deleting xattrs must use VOP_DELETEEXTATTR instead */ | |||||
if (ap->a_uio == NULL) { | |||||
/* | |||||
* If we got here as fallback from VOP_DELETEEXTATTR, then | |||||
* return EOPNOTSUPP. | |||||
*/ | |||||
if (!fsess_isimpl(mp, FUSE_REMOVEXATTR)) | |||||
return (EOPNOTSUPP); | |||||
else | |||||
return (EINVAL); | |||||
} | |||||
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, | |||||
VWRITE); | |||||
if (err) | |||||
return err; | |||||
/* Default to looking for user attributes. */ | /* Default to looking for user attributes. */ | ||||
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | ||||
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | ||||
else | else | ||||
prefix = EXTATTR_NAMESPACE_USER_STRING; | prefix = EXTATTR_NAMESPACE_USER_STRING; | ||||
len = strlen(prefix) + sizeof(extattr_namespace_separator) + | len = strlen(prefix) + sizeof(extattr_namespace_separator) + | ||||
strlen(ap->a_name) + 1; | strlen(ap->a_name) + 1; | ||||
Show All 11 Lines | fuse_vnop_setextattr(struct vop_setextattr_args *ap) | ||||
err = uiomove((char *)fdi.indata + sizeof(*set_xattr_in) + len, | err = uiomove((char *)fdi.indata + sizeof(*set_xattr_in) + len, | ||||
uio->uio_resid, uio); | uio->uio_resid, uio); | ||||
if (err != 0) { | if (err != 0) { | ||||
goto out; | goto out; | ||||
} | } | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
if (err != 0) { | if (err == ENOSYS) { | ||||
if (err == ENOSYS) | |||||
fsess_set_notimpl(mp, FUSE_SETXATTR); | fsess_set_notimpl(mp, FUSE_SETXATTR); | ||||
goto out; | err = EOPNOTSUPP; | ||||
} | } | ||||
if (err == ERESTART) { | |||||
/* Can't restart after calling uiomove */ | |||||
err = EINTR; | |||||
} | |||||
out: | out: | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return (err); | return (err); | ||||
} | } | ||||
/* | /* | ||||
* The Linux / FUSE extended attribute list is simply a collection of | * The Linux / FUSE extended attribute list is simply a collection of | ||||
▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | fuse_vnop_listextattr(struct vop_listextattr_args *ap) | ||||
char *linux_list; | char *linux_list; | ||||
int bsd_list_len; | int bsd_list_len; | ||||
int linux_list_len; | int linux_list_len; | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) | if (fuse_isdeadfs(vp)) | ||||
return (ENXIO); | return (ENXIO); | ||||
if (!fsess_isimpl(mp, FUSE_LISTXATTR)) | |||||
return EOPNOTSUPP; | |||||
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, VREAD); | |||||
if (err) | |||||
return err; | |||||
/* | /* | ||||
* Add space for a NUL and the period separator if enabled. | * Add space for a NUL and the period separator if enabled. | ||||
* Default to looking for user attributes. | * Default to looking for user attributes. | ||||
*/ | */ | ||||
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | ||||
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | ||||
else | else | ||||
prefix = EXTATTR_NAMESPACE_USER_STRING; | prefix = EXTATTR_NAMESPACE_USER_STRING; | ||||
len = strlen(prefix) + sizeof(extattr_namespace_separator) + 1; | len = strlen(prefix) + sizeof(extattr_namespace_separator) + 1; | ||||
fdisp_init(&fdi, sizeof(*list_xattr_in) + len); | fdisp_init(&fdi, sizeof(*list_xattr_in) + len); | ||||
fdisp_make_vp(&fdi, FUSE_LISTXATTR, vp, td, cred); | fdisp_make_vp(&fdi, FUSE_LISTXATTR, vp, td, cred); | ||||
/* | /* | ||||
* Retrieve Linux / FUSE compatible list size. | * Retrieve Linux / FUSE compatible list size. | ||||
*/ | */ | ||||
list_xattr_in = fdi.indata; | list_xattr_in = fdi.indata; | ||||
list_xattr_in->size = 0; | list_xattr_in->size = 0; | ||||
attr_str = (char *)fdi.indata + sizeof(*list_xattr_in); | attr_str = (char *)fdi.indata + sizeof(*list_xattr_in); | ||||
snprintf(attr_str, len, "%s%c", prefix, extattr_namespace_separator); | snprintf(attr_str, len, "%s%c", prefix, extattr_namespace_separator); | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
if (err != 0) { | if (err != 0) { | ||||
if (err == ENOSYS) | if (err == ENOSYS) { | ||||
fsess_set_notimpl(mp, FUSE_LISTXATTR); | fsess_set_notimpl(mp, FUSE_LISTXATTR); | ||||
err = EOPNOTSUPP; | |||||
} | |||||
goto out; | goto out; | ||||
} | } | ||||
list_xattr_out = fdi.answ; | list_xattr_out = fdi.answ; | ||||
linux_list_len = list_xattr_out->size; | linux_list_len = list_xattr_out->size; | ||||
if (linux_list_len == 0) { | if (linux_list_len == 0) { | ||||
if (ap->a_size != NULL) | if (ap->a_size != NULL) | ||||
*ap->a_size = linux_list_len; | *ap->a_size = linux_list_len; | ||||
goto out; | goto out; | ||||
} | } | ||||
/* | /* | ||||
* Retrieve Linux / FUSE compatible list values. | * Retrieve Linux / FUSE compatible list values. | ||||
*/ | */ | ||||
fdisp_make_vp(&fdi, FUSE_LISTXATTR, vp, td, cred); | fdisp_refresh_vp(&fdi, FUSE_LISTXATTR, vp, td, cred); | ||||
list_xattr_in = fdi.indata; | list_xattr_in = fdi.indata; | ||||
list_xattr_in->size = linux_list_len + sizeof(*list_xattr_out); | list_xattr_in->size = linux_list_len + sizeof(*list_xattr_out); | ||||
attr_str = (char *)fdi.indata + sizeof(*list_xattr_in); | attr_str = (char *)fdi.indata + sizeof(*list_xattr_in); | ||||
snprintf(attr_str, len, "%s%c", prefix, extattr_namespace_separator); | snprintf(attr_str, len, "%s%c", prefix, extattr_namespace_separator); | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
if (err != 0) | if (err != 0) | ||||
goto out; | goto out; | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | fuse_vnop_deleteextattr(struct vop_deleteextattr_args *ap) | ||||
char *prefix; | char *prefix; | ||||
size_t len; | size_t len; | ||||
char *attr_str; | char *attr_str; | ||||
int err; | int err; | ||||
if (fuse_isdeadfs(vp)) | if (fuse_isdeadfs(vp)) | ||||
return (ENXIO); | return (ENXIO); | ||||
if (!fsess_isimpl(mp, FUSE_REMOVEXATTR)) | |||||
return EOPNOTSUPP; | |||||
if (vfs_isrdonly(mp)) | |||||
return EROFS; | |||||
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, | |||||
VWRITE); | |||||
if (err) | |||||
return err; | |||||
/* Default to looking for user attributes. */ | /* Default to looking for user attributes. */ | ||||
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) | ||||
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; | ||||
else | else | ||||
prefix = EXTATTR_NAMESPACE_USER_STRING; | prefix = EXTATTR_NAMESPACE_USER_STRING; | ||||
len = strlen(prefix) + sizeof(extattr_namespace_separator) + | len = strlen(prefix) + sizeof(extattr_namespace_separator) + | ||||
strlen(ap->a_name) + 1; | strlen(ap->a_name) + 1; | ||||
fdisp_init(&fdi, len); | fdisp_init(&fdi, len); | ||||
fdisp_make_vp(&fdi, FUSE_REMOVEXATTR, vp, td, cred); | fdisp_make_vp(&fdi, FUSE_REMOVEXATTR, vp, td, cred); | ||||
attr_str = fdi.indata; | attr_str = fdi.indata; | ||||
snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator, | snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator, | ||||
ap->a_name); | ap->a_name); | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
if (err != 0) { | if (err == ENOSYS) { | ||||
if (err == ENOSYS) | |||||
fsess_set_notimpl(mp, FUSE_REMOVEXATTR); | fsess_set_notimpl(mp, FUSE_REMOVEXATTR); | ||||
err = EOPNOTSUPP; | |||||
} | } | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return (err); | return (err); | ||||
} | } | ||||
/* | /* | ||||
struct vnop_print_args { | struct vnop_print_args { | ||||
struct vnode *a_vp; | struct vnode *a_vp; | ||||
}; | }; | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_vnop_print(struct vop_print_args *ap) | fuse_vnop_print(struct vop_print_args *ap) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(ap->a_vp); | struct fuse_vnode_data *fvdat = VTOFUD(ap->a_vp); | ||||
printf("nodeid: %ju, parent nodeid: %ju, nlookup: %ju, flag: %#x\n", | printf("nodeid: %ju, parent nodeid: %ju, nlookup: %ju, flag: %#x\n", | ||||
(uintmax_t)VTOILLU(ap->a_vp), (uintmax_t)fvdat->parent_nid, | (uintmax_t)VTOILLU(ap->a_vp), (uintmax_t)fvdat->parent_nid, | ||||
(uintmax_t)fvdat->nlookup, | (uintmax_t)fvdat->nlookup, | ||||
fvdat->flag); | fvdat->flag); | ||||
return 0; | return 0; | ||||
} | } | ||||
/* | |||||
* Get an NFS filehandle for a FUSE file. | |||||
* | |||||
* This will only work for FUSE file systems that guarantee the uniqueness of | |||||
* nodeid:generation, which most don't. | |||||
*/ | |||||
/* | |||||
vop_vptofh { | |||||
IN struct vnode *a_vp; | |||||
IN struct fid *a_fhp; | |||||
}; | |||||
*/ | |||||
static int | |||||
fuse_vnop_vptofh(struct vop_vptofh_args *ap) | |||||
{ | |||||
struct vnode *vp = ap->a_vp; | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct fuse_fid *fhp = (struct fuse_fid *)(ap->a_fhp); | |||||
_Static_assert(sizeof(struct fuse_fid) <= sizeof(struct fid), | |||||
"FUSE fid type is too big"); | |||||
struct mount *mp = vnode_mount(vp); | |||||
struct fuse_data *data = fuse_get_mpdata(mp); | |||||
struct vattr va; | |||||
int err; | |||||
if (!(data->dataflags & FSESS_EXPORT_SUPPORT)) | |||||
return EOPNOTSUPP; | |||||
err = fuse_internal_getattr(vp, &va, curthread->td_ucred, curthread); | |||||
if (err) | |||||
return err; | |||||
/*ip = VTOI(ap->a_vp);*/ | |||||
/*ufhp = (struct ufid *)ap->a_fhp;*/ | |||||
fhp->len = sizeof(struct fuse_fid); | |||||
fhp->nid = fvdat->nid; | |||||
if (fvdat->generation <= UINT32_MAX) | |||||
fhp->gen = fvdat->generation; | |||||
else | |||||
return EOVERFLOW; | |||||
return (0); | |||||
} | |||||