Changeset View
Changeset View
Standalone View
Standalone View
sys/fs/fuse/fuse_node.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 All 10 Lines | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
* SUCH DAMAGE. | * SUCH DAMAGE. | ||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <sys/module.h> | |||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/counter.h> | |||||
#include <sys/module.h> | |||||
#include <sys/errno.h> | #include <sys/errno.h> | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/uio.h> | #include <sys/uio.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/queue.h> | #include <sys/queue.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/sx.h> | #include <sys/sx.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
#include <sys/vnode.h> | #include <sys/vnode.h> | ||||
#include <sys/namei.h> | #include <sys/namei.h> | ||||
#include <sys/mount.h> | #include <sys/mount.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/fcntl.h> | #include <sys/fcntl.h> | ||||
#include <sys/fnv_hash.h> | |||||
#include <sys/priv.h> | #include <sys/priv.h> | ||||
#include <sys/buf.h> | |||||
#include <security/mac/mac_framework.h> | #include <security/mac/mac_framework.h> | ||||
#include <vm/vm.h> | #include <vm/vm.h> | ||||
#include <vm/vm_extern.h> | #include <vm/vm_extern.h> | ||||
#include "fuse.h" | #include "fuse.h" | ||||
#include "fuse_node.h" | #include "fuse_node.h" | ||||
#include "fuse_internal.h" | #include "fuse_internal.h" | ||||
#include "fuse_io.h" | #include "fuse_io.h" | ||||
#include "fuse_ipc.h" | #include "fuse_ipc.h" | ||||
SDT_PROVIDER_DECLARE(fuse); | 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, , node, trace, "int", "char*"); | SDT_PROBE_DEFINE2(fusefs, , node, trace, "int", "char*"); | ||||
MALLOC_DEFINE(M_FUSEVN, "fuse_vnode", "fuse vnode private data"); | MALLOC_DEFINE(M_FUSEVN, "fuse_vnode", "fuse vnode private data"); | ||||
static int sysctl_fuse_cache_mode(SYSCTL_HANDLER_ARGS); | static int sysctl_fuse_cache_mode(SYSCTL_HANDLER_ARGS); | ||||
static int fuse_node_count = 0; | static counter_u64_t fuse_node_count; | ||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, node_count, CTLFLAG_RD, | SYSCTL_COUNTER_U64(_vfs_fusefs_stats, OID_AUTO, node_count, CTLFLAG_RD, | ||||
&fuse_node_count, 0, "Count of FUSE vnodes"); | &fuse_node_count, "Count of FUSE vnodes"); | ||||
int fuse_data_cache_mode = FUSE_CACHE_WT; | int fuse_data_cache_mode = FUSE_CACHE_WT; | ||||
/* | |||||
* DEPRECATED | |||||
* This sysctl is no longer needed as of fuse protocol 7.23. Individual | |||||
* servers can select the cache behavior they need for each mountpoint: | |||||
* - writethrough: the default | |||||
* - writeback: set FUSE_WRITEBACK_CACHE in fuse_init_out.flags | |||||
* - uncached: set FOPEN_DIRECT_IO for every file | |||||
* The sysctl is retained primarily for use by jails supporting older FUSE | |||||
* protocols. It may be removed entirely once FreeBSD 11.3 and 12.0 are EOL. | |||||
*/ | |||||
SYSCTL_PROC(_vfs_fusefs, OID_AUTO, data_cache_mode, CTLTYPE_INT|CTLFLAG_RW, | SYSCTL_PROC(_vfs_fusefs, OID_AUTO, data_cache_mode, CTLTYPE_INT|CTLFLAG_RW, | ||||
&fuse_data_cache_mode, 0, sysctl_fuse_cache_mode, "I", | &fuse_data_cache_mode, 0, sysctl_fuse_cache_mode, "I", | ||||
"Zero: disable caching of FUSE file data; One: write-through caching " | "Zero: disable caching of FUSE file data; One: write-through caching " | ||||
"(default); Two: write-back caching (generally unsafe)"); | "(default); Two: write-back caching (generally unsafe)"); | ||||
int fuse_data_cache_invalidate = 0; | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, data_cache_invalidate, CTLFLAG_RW, | |||||
&fuse_data_cache_invalidate, 0, | |||||
"If non-zero, discard cached clean file data when there are no active file" | |||||
" users"); | |||||
int fuse_mmap_enable = 1; | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, mmap_enable, CTLFLAG_RW, | |||||
&fuse_mmap_enable, 0, | |||||
"If non-zero, and data_cache_mode is also non-zero, enable mmap(2) of " | |||||
"FUSE files"); | |||||
int fuse_refresh_size = 0; | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, refresh_size, CTLFLAG_RW, | |||||
&fuse_refresh_size, 0, | |||||
"If non-zero, and no dirty file extension data is buffered, fetch file " | |||||
"size before write operations"); | |||||
int fuse_sync_resize = 1; | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, sync_resize, CTLFLAG_RW, | |||||
&fuse_sync_resize, 0, | |||||
"If a cached write extended a file, inform FUSE filesystem of the changed" | |||||
"size immediately subsequent to the issued writes"); | |||||
int fuse_fix_broken_io = 0; | |||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, fix_broken_io, CTLFLAG_RW, | |||||
&fuse_fix_broken_io, 0, | |||||
"If non-zero, print a diagnostic warning if a userspace filesystem returns" | |||||
" EIO on reads of recently extended portions of files"); | |||||
static int | static int | ||||
sysctl_fuse_cache_mode(SYSCTL_HANDLER_ARGS) | sysctl_fuse_cache_mode(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
int val, error; | int val, error; | ||||
val = *(int *)arg1; | val = *(int *)arg1; | ||||
error = sysctl_handle_int(oidp, &val, 0, req); | error = sysctl_handle_int(oidp, &val, 0, req); | ||||
if (error || !req->newptr) | if (error || !req->newptr) | ||||
Show All 10 Lines | sysctl_fuse_cache_mode(SYSCTL_HANDLER_ARGS) | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
static void | static void | ||||
fuse_vnode_init(struct vnode *vp, struct fuse_vnode_data *fvdat, | fuse_vnode_init(struct vnode *vp, struct fuse_vnode_data *fvdat, | ||||
uint64_t nodeid, enum vtype vtyp) | uint64_t nodeid, enum vtype vtyp) | ||||
{ | { | ||||
int i; | |||||
fvdat->nid = nodeid; | fvdat->nid = nodeid; | ||||
LIST_INIT(&fvdat->handles); | |||||
vattr_null(&fvdat->cached_attrs); | vattr_null(&fvdat->cached_attrs); | ||||
if (nodeid == FUSE_ROOT_ID) { | if (nodeid == FUSE_ROOT_ID) { | ||||
vp->v_vflag |= VV_ROOT; | vp->v_vflag |= VV_ROOT; | ||||
} | } | ||||
vp->v_type = vtyp; | vp->v_type = vtyp; | ||||
vp->v_data = fvdat; | vp->v_data = fvdat; | ||||
for (i = 0; i < FUFH_MAXTYPE; i++) | counter_u64_add(fuse_node_count, 1); | ||||
fvdat->fufh[i].fh_type = FUFH_INVALID; | |||||
atomic_add_acq_int(&fuse_node_count, 1); | |||||
} | } | ||||
void | void | ||||
fuse_vnode_destroy(struct vnode *vp) | fuse_vnode_destroy(struct vnode *vp) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = vp->v_data; | struct fuse_vnode_data *fvdat = vp->v_data; | ||||
vp->v_data = NULL; | vp->v_data = NULL; | ||||
KASSERT(LIST_EMPTY(&fvdat->handles), | |||||
("Destroying fuse vnode with open files!")); | |||||
free(fvdat, M_FUSEVN); | free(fvdat, M_FUSEVN); | ||||
atomic_subtract_acq_int(&fuse_node_count, 1); | counter_u64_add(fuse_node_count, -1); | ||||
} | } | ||||
static int | int | ||||
fuse_vnode_cmp(struct vnode *vp, void *nidp) | fuse_vnode_cmp(struct vnode *vp, void *nidp) | ||||
{ | { | ||||
return (VTOI(vp) != *((uint64_t *)nidp)); | return (VTOI(vp) != *((uint64_t *)nidp)); | ||||
} | } | ||||
static uint32_t inline | SDT_PROBE_DEFINE3(fusefs, , node, stale_vnode, "struct vnode*", "enum vtype", | ||||
fuse_vnode_hash(uint64_t id) | "uint64_t"); | ||||
{ | |||||
return (fnv_32_buf(&id, sizeof(id), FNV1_32_INIT)); | |||||
} | |||||
static int | static int | ||||
fuse_vnode_alloc(struct mount *mp, | fuse_vnode_alloc(struct mount *mp, | ||||
struct thread *td, | struct thread *td, | ||||
uint64_t nodeid, | uint64_t nodeid, | ||||
enum vtype vtyp, | enum vtype vtyp, | ||||
struct vnode **vpp) | struct vnode **vpp) | ||||
{ | { | ||||
struct fuse_data *data; | |||||
struct fuse_vnode_data *fvdat; | struct fuse_vnode_data *fvdat; | ||||
struct vnode *vp2; | struct vnode *vp2; | ||||
int err = 0; | int err = 0; | ||||
data = fuse_get_mpdata(mp); | |||||
if (vtyp == VNON) { | if (vtyp == VNON) { | ||||
return EINVAL; | return EINVAL; | ||||
} | } | ||||
*vpp = NULL; | *vpp = NULL; | ||||
err = vfs_hash_get(mp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, vpp, | err = vfs_hash_get(mp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, vpp, | ||||
fuse_vnode_cmp, &nodeid); | fuse_vnode_cmp, &nodeid); | ||||
if (err) | if (err) | ||||
return (err); | return (err); | ||||
if (*vpp) { | if (*vpp) { | ||||
MPASS((*vpp)->v_type == vtyp && (*vpp)->v_data != NULL); | if ((*vpp)->v_type != vtyp) { | ||||
SDT_PROBE2(fuse, , node, trace, 1, "vnode taken from hash"); | /* | ||||
* STALE vnode! This probably indicates a buggy | |||||
* server, but it could also be the result of a race | |||||
* between FUSE_LOOKUP and another client's | |||||
* FUSE_UNLINK/FUSE_CREATE | |||||
*/ | |||||
SDT_PROBE3(fusefs, , node, stale_vnode, *vpp, vtyp, | |||||
nodeid); | |||||
fuse_internal_vnode_disappear(*vpp); | |||||
lockmgr((*vpp)->v_vnlock, LK_RELEASE, NULL); | |||||
*vpp = NULL; | |||||
return (EAGAIN); | |||||
} | |||||
MPASS((*vpp)->v_data != NULL); | |||||
MPASS(VTOFUD(*vpp)->nid == nodeid); | |||||
SDT_PROBE2(fusefs, , node, trace, 1, "vnode taken from hash"); | |||||
return (0); | return (0); | ||||
} | } | ||||
fvdat = malloc(sizeof(*fvdat), M_FUSEVN, M_WAITOK | M_ZERO); | fvdat = malloc(sizeof(*fvdat), M_FUSEVN, M_WAITOK | M_ZERO); | ||||
switch (vtyp) { | |||||
case VFIFO: | |||||
err = getnewvnode("fuse", mp, &fuse_fifoops, vpp); | |||||
break; | |||||
default: | |||||
err = getnewvnode("fuse", mp, &fuse_vnops, vpp); | err = getnewvnode("fuse", mp, &fuse_vnops, vpp); | ||||
break; | |||||
} | |||||
if (err) { | if (err) { | ||||
free(fvdat, M_FUSEVN); | free(fvdat, M_FUSEVN); | ||||
return (err); | return (err); | ||||
} | } | ||||
lockmgr((*vpp)->v_vnlock, LK_EXCLUSIVE, NULL); | lockmgr((*vpp)->v_vnlock, LK_EXCLUSIVE, NULL); | ||||
fuse_vnode_init(*vpp, fvdat, nodeid, vtyp); | fuse_vnode_init(*vpp, fvdat, nodeid, vtyp); | ||||
err = insmntque(*vpp, mp); | err = insmntque(*vpp, mp); | ||||
ASSERT_VOP_ELOCKED(*vpp, "fuse_vnode_alloc"); | ASSERT_VOP_ELOCKED(*vpp, "fuse_vnode_alloc"); | ||||
if (err) { | if (err) { | ||||
lockmgr((*vpp)->v_vnlock, LK_RELEASE, NULL); | |||||
free(fvdat, M_FUSEVN); | free(fvdat, M_FUSEVN); | ||||
*vpp = NULL; | *vpp = NULL; | ||||
return (err); | return (err); | ||||
} | } | ||||
/* Disallow async reads for fifos because UFS does. I don't know why */ | |||||
if (data->dataflags & FSESS_ASYNC_READ && vtyp != VFIFO) | |||||
VN_LOCK_ASHARE(*vpp); | |||||
err = vfs_hash_insert(*vpp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, | err = vfs_hash_insert(*vpp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, | ||||
td, &vp2, fuse_vnode_cmp, &nodeid); | td, &vp2, fuse_vnode_cmp, &nodeid); | ||||
if (err) | if (err) { | ||||
lockmgr((*vpp)->v_vnlock, LK_RELEASE, NULL); | |||||
free(fvdat, M_FUSEVN); | |||||
*vpp = NULL; | |||||
return (err); | return (err); | ||||
} | |||||
if (vp2 != NULL) { | if (vp2 != NULL) { | ||||
*vpp = vp2; | *vpp = vp2; | ||||
return (0); | return (0); | ||||
} | } | ||||
ASSERT_VOP_ELOCKED(*vpp, "fuse_vnode_alloc"); | ASSERT_VOP_ELOCKED(*vpp, "fuse_vnode_alloc"); | ||||
return (0); | return (0); | ||||
} | } | ||||
int | int | ||||
fuse_vnode_get(struct mount *mp, | fuse_vnode_get(struct mount *mp, | ||||
struct fuse_entry_out *feo, | struct fuse_entry_out *feo, | ||||
uint64_t nodeid, | uint64_t nodeid, | ||||
struct vnode *dvp, | struct vnode *dvp, | ||||
struct vnode **vpp, | struct vnode **vpp, | ||||
struct componentname *cnp, | struct componentname *cnp, | ||||
enum vtype vtyp) | enum vtype vtyp) | ||||
{ | { | ||||
struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread); | struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread); | ||||
/* | |||||
* feo should only be NULL for the root directory, which (when libfuse | |||||
* is used) always has generation 0 | |||||
*/ | |||||
uint64_t generation = feo ? feo->generation : 0; | |||||
int err = 0; | int err = 0; | ||||
err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp); | err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp); | ||||
if (err) { | if (err) { | ||||
return err; | return err; | ||||
} | } | ||||
if (dvp != NULL) { | if (dvp != NULL) { | ||||
MPASS((cnp->cn_flags & ISDOTDOT) == 0); | MPASS(cnp && (cnp->cn_flags & ISDOTDOT) == 0); | ||||
MPASS(!(cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.')); | MPASS(cnp && | ||||
!(cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.')); | |||||
fuse_vnode_setparent(*vpp, dvp); | fuse_vnode_setparent(*vpp, dvp); | ||||
} | } | ||||
if (dvp != NULL && cnp != NULL && (cnp->cn_flags & MAKEENTRY) != 0 && | if (dvp != NULL && cnp != NULL && (cnp->cn_flags & MAKEENTRY) != 0 && | ||||
feo != NULL && | feo != NULL && | ||||
(feo->entry_valid != 0 || feo->entry_valid_nsec != 0)) { | (feo->entry_valid != 0 || feo->entry_valid_nsec != 0)) { | ||||
struct timespec timeout; | |||||
ASSERT_VOP_LOCKED(*vpp, "fuse_vnode_get"); | ASSERT_VOP_LOCKED(*vpp, "fuse_vnode_get"); | ||||
ASSERT_VOP_LOCKED(dvp, "fuse_vnode_get"); | ASSERT_VOP_LOCKED(dvp, "fuse_vnode_get"); | ||||
cache_enter(dvp, *vpp, cnp); | |||||
fuse_validity_2_timespec(feo, &timeout); | |||||
cache_enter_time(dvp, *vpp, cnp, &timeout, NULL); | |||||
} | } | ||||
VTOFUD(*vpp)->generation = generation; | |||||
/* | /* | ||||
* In userland, libfuse uses cached lookups for dot and dotdot entries, | * In userland, libfuse uses cached lookups for dot and dotdot entries, | ||||
* thus it does not really bump the nlookup counter for forget. | * thus it does not really bump the nlookup counter for forget. | ||||
* Follow the same semantic and avoid tu bump it in order to keep | * Follow the same semantic and avoid the bump in order to keep | ||||
* nlookup counters consistent. | * nlookup counters consistent. | ||||
*/ | */ | ||||
if (cnp == NULL || ((cnp->cn_flags & ISDOTDOT) == 0 && | if (cnp == NULL || ((cnp->cn_flags & ISDOTDOT) == 0 && | ||||
(cnp->cn_namelen != 1 || cnp->cn_nameptr[0] != '.'))) | (cnp->cn_namelen != 1 || cnp->cn_nameptr[0] != '.'))) | ||||
VTOFUD(*vpp)->nlookup++; | VTOFUD(*vpp)->nlookup++; | ||||
return 0; | return 0; | ||||
} | } | ||||
/* | |||||
* Called for every fusefs vnode open to initialize the vnode (not | |||||
* fuse_filehandle) for use | |||||
*/ | |||||
void | void | ||||
fuse_vnode_open(struct vnode *vp, int32_t fuse_open_flags, struct thread *td) | fuse_vnode_open(struct vnode *vp, int32_t fuse_open_flags, struct thread *td) | ||||
{ | { | ||||
/* | if (vnode_vtype(vp) == VREG) | ||||
* Funcation is called for every vnode open. | |||||
* Merge fuse_open_flags it may be 0 | |||||
*/ | |||||
/* | |||||
* Ideally speaking, direct io should be enabled on | |||||
* fd's but do not see of any way of providing that | |||||
* this implementation. | |||||
* | |||||
* Also cannot think of a reason why would two | |||||
* different fd's on same vnode would like | |||||
* have DIRECT_IO turned on and off. But linux | |||||
* based implementation works on an fd not an | |||||
* inode and provides such a feature. | |||||
* | |||||
* XXXIP: Handle fd based DIRECT_IO | |||||
*/ | |||||
if (fuse_open_flags & FOPEN_DIRECT_IO) { | |||||
ASSERT_VOP_ELOCKED(vp, __func__); | |||||
VTOFUD(vp)->flag |= FN_DIRECTIO; | |||||
fuse_io_invalbuf(vp, td); | |||||
} else { | |||||
if ((fuse_open_flags & FOPEN_KEEP_CACHE) == 0) | |||||
fuse_io_invalbuf(vp, td); | |||||
VTOFUD(vp)->flag &= ~FN_DIRECTIO; | |||||
} | |||||
if (vnode_vtype(vp) == VREG) { | |||||
/* XXXIP prevent getattr, by using cached node size */ | |||||
vnode_create_vobject(vp, 0, td); | vnode_create_vobject(vp, 0, td); | ||||
} | } | ||||
} | |||||
int | int | ||||
fuse_vnode_savesize(struct vnode *vp, struct ucred *cred) | fuse_vnode_savesize(struct vnode *vp, struct ucred *cred, pid_t pid) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct thread *td = curthread; | struct thread *td = curthread; | ||||
struct fuse_filehandle *fufh = NULL; | struct fuse_filehandle *fufh = NULL; | ||||
struct fuse_dispatcher fdi; | struct fuse_dispatcher fdi; | ||||
struct fuse_setattr_in *fsai; | struct fuse_setattr_in *fsai; | ||||
int err = 0; | int err = 0; | ||||
Show All 12 Lines | if (cred == NULL) { | ||||
cred = td->td_ucred; | cred = td->td_ucred; | ||||
} | } | ||||
fdisp_init(&fdi, sizeof(*fsai)); | fdisp_init(&fdi, sizeof(*fsai)); | ||||
fdisp_make_vp(&fdi, FUSE_SETATTR, vp, td, cred); | fdisp_make_vp(&fdi, FUSE_SETATTR, vp, td, cred); | ||||
fsai = fdi.indata; | fsai = fdi.indata; | ||||
fsai->valid = 0; | fsai->valid = 0; | ||||
/* Truncate to a new value. */ | /* Truncate to a new value. */ | ||||
fsai->size = fvdat->filesize; | MPASS((fvdat->flag & FN_SIZECHANGE) != 0); | ||||
fsai->size = fvdat->cached_attrs.va_size; | |||||
fsai->valid |= FATTR_SIZE; | fsai->valid |= FATTR_SIZE; | ||||
fuse_filehandle_getrw(vp, FUFH_WRONLY, &fufh); | fuse_filehandle_getrw(vp, FWRITE, &fufh, cred, pid); | ||||
if (fufh) { | if (fufh) { | ||||
fsai->fh = fufh->fh_id; | fsai->fh = fufh->fh_id; | ||||
fsai->valid |= FATTR_FH; | fsai->valid |= FATTR_FH; | ||||
} | } | ||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
if (err == 0) | if (err == 0) | ||||
fvdat->flag &= ~FN_SIZECHANGE; | fvdat->flag &= ~FN_SIZECHANGE; | ||||
return err; | return err; | ||||
} | } | ||||
void | /* | ||||
fuse_vnode_refreshsize(struct vnode *vp, struct ucred *cred) | * Adjust the vnode's size to a new value, such as that provided by | ||||
{ | * FUSE_GETATTR. | ||||
*/ | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct vattr va; | |||||
if ((fvdat->flag & FN_SIZECHANGE) != 0 || | |||||
fuse_data_cache_mode == FUSE_CACHE_UC || | |||||
(fuse_refresh_size == 0 && fvdat->filesize != 0)) | |||||
return; | |||||
VOP_GETATTR(vp, &va, cred); | |||||
SDT_PROBE2(fuse, , node, trace, 1, "refreshed file size"); | |||||
} | |||||
int | int | ||||
fuse_vnode_setsize(struct vnode *vp, off_t newsize) | fuse_vnode_setsize(struct vnode *vp, off_t newsize) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct vattr *attrs; | |||||
off_t oldsize; | off_t oldsize; | ||||
size_t iosize; | |||||
struct buf *bp = NULL; | |||||
int err = 0; | int err = 0; | ||||
ASSERT_VOP_ELOCKED(vp, "fuse_vnode_setsize"); | ASSERT_VOP_ELOCKED(vp, "fuse_vnode_setsize"); | ||||
oldsize = fvdat->filesize; | iosize = fuse_iosize(vp); | ||||
fvdat->filesize = newsize; | oldsize = fvdat->cached_attrs.va_size; | ||||
fvdat->flag |= FN_SIZECHANGE; | fvdat->cached_attrs.va_size = newsize; | ||||
if ((attrs = VTOVA(vp)) != NULL) | |||||
attrs->va_size = newsize; | |||||
if (newsize < oldsize) { | if (newsize < oldsize) { | ||||
daddr_t lbn; | |||||
err = vtruncbuf(vp, newsize, fuse_iosize(vp)); | err = vtruncbuf(vp, newsize, fuse_iosize(vp)); | ||||
if (err) | |||||
goto out; | |||||
if (newsize % iosize == 0) | |||||
goto out; | |||||
/* | |||||
* Zero the contents of the last partial block. | |||||
* Sure seems like vtruncbuf should do this for us. | |||||
*/ | |||||
lbn = newsize / iosize; | |||||
bp = getblk(vp, lbn, iosize, PCATCH, 0, 0); | |||||
if (!bp) { | |||||
err = EINTR; | |||||
goto out; | |||||
} | } | ||||
if (!(bp->b_flags & B_CACHE)) | |||||
goto out; /* Nothing to do */ | |||||
MPASS(bp->b_flags & B_VMIO); | |||||
vfs_bio_clrbuf(bp); | |||||
bp->b_dirtyend = MIN(bp->b_dirtyend, newsize - lbn * iosize); | |||||
} | |||||
out: | |||||
if (bp) | |||||
brelse(bp); | |||||
vnode_pager_setsize(vp, newsize); | vnode_pager_setsize(vp, newsize); | ||||
return err; | return err; | ||||
} | |||||
/* Get the current, possibly dirty, size of the file */ | |||||
int | |||||
fuse_vnode_size(struct vnode *vp, off_t *filesize, struct ucred *cred, | |||||
struct thread *td) | |||||
{ | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
int error = 0; | |||||
if (!(fvdat->flag & FN_SIZECHANGE) && | |||||
(VTOVA(vp) == NULL || fvdat->cached_attrs.va_size == VNOVAL)) | |||||
error = fuse_internal_do_getattr(vp, NULL, cred, td); | |||||
if (!error) | |||||
*filesize = fvdat->cached_attrs.va_size; | |||||
return error; | |||||
} | |||||
void | |||||
fuse_vnode_undirty_cached_timestamps(struct vnode *vp) | |||||
{ | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
fvdat->flag &= ~(FN_MTIMECHANGE | FN_CTIMECHANGE); | |||||
} | |||||
/* Update a fuse file's cached timestamps */ | |||||
void | |||||
fuse_vnode_update(struct vnode *vp, int flags) | |||||
{ | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp)); | |||||
struct timespec ts; | |||||
vfs_timestamp(&ts); | |||||
if (data->time_gran > 1) | |||||
ts.tv_nsec = rounddown(ts.tv_nsec, data->time_gran); | |||||
if (flags & FN_MTIMECHANGE) | |||||
fvdat->cached_attrs.va_mtime = ts; | |||||
if (flags & FN_CTIMECHANGE) | |||||
fvdat->cached_attrs.va_ctime = ts; | |||||
fvdat->flag |= flags; | |||||
} | |||||
void | |||||
fuse_node_init(void) | |||||
{ | |||||
fuse_node_count = counter_u64_alloc(M_WAITOK); | |||||
counter_u64_zero(fuse_node_count); | |||||
} | |||||
void | |||||
fuse_node_destroy(void) | |||||
{ | |||||
counter_u64_free(fuse_node_count); | |||||
} | } |