Changeset View
Changeset View
Standalone View
Standalone View
sys/fs/fuse/fuse_file.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/param.h> | #include <sys/param.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/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/mount.h> | #include <sys/mount.h> | ||||
#include <sys/vnode.h> | #include <sys/vnode.h> | ||||
#include <sys/sdt.h> | #include <sys/sdt.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.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_io.h" | |||||
#include "fuse_ipc.h" | #include "fuse_ipc.h" | ||||
#include "fuse_node.h" | #include "fuse_node.h" | ||||
SDT_PROVIDER_DECLARE(fuse); | MALLOC_DEFINE(M_FUSE_FILEHANDLE, "fuse_filefilehandle", "FUSE file handle"); | ||||
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, , file, trace, "int", "char*"); | SDT_PROBE_DEFINE2(fusefs, , file, trace, "int", "char*"); | ||||
static int fuse_fh_count = 0; | static counter_u64_t fuse_fh_count; | ||||
SYSCTL_INT(_vfs_fusefs, OID_AUTO, filehandle_count, CTLFLAG_RD, | SYSCTL_COUNTER_U64(_vfs_fusefs_stats, OID_AUTO, filehandle_count, CTLFLAG_RD, | ||||
&fuse_fh_count, 0, "number of open FUSE filehandles"); | &fuse_fh_count, "number of open FUSE filehandles"); | ||||
/* Get the FUFH type for a particular access mode */ | |||||
static inline fufh_type_t | |||||
fflags_2_fufh_type(int fflags) | |||||
{ | |||||
if ((fflags & FREAD) && (fflags & FWRITE)) | |||||
return FUFH_RDWR; | |||||
else if (fflags & (FWRITE)) | |||||
return FUFH_WRONLY; | |||||
else if (fflags & (FREAD)) | |||||
return FUFH_RDONLY; | |||||
else if (fflags & (FEXEC)) | |||||
return FUFH_EXEC; | |||||
else | |||||
panic("FUSE: What kind of a flag is this (%x)?", fflags); | |||||
} | |||||
int | int | ||||
fuse_filehandle_open(struct vnode *vp, fufh_type_t fufh_type, | fuse_filehandle_open(struct vnode *vp, int a_mode, | ||||
struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred) | struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred) | ||||
{ | { | ||||
struct fuse_dispatcher fdi; | struct fuse_dispatcher fdi; | ||||
struct fuse_open_in *foi; | struct fuse_open_in *foi; | ||||
struct fuse_open_out *foo; | struct fuse_open_out *foo; | ||||
fufh_type_t fufh_type; | |||||
int err = 0; | int err = 0; | ||||
int oflags = 0; | int oflags = 0; | ||||
int op = FUSE_OPEN; | int op = FUSE_OPEN; | ||||
if (fuse_filehandle_valid(vp, fufh_type)) { | fufh_type = fflags_2_fufh_type(a_mode); | ||||
panic("FUSE: filehandle_open called despite valid fufh (type=%d)", | oflags = fufh_type_2_fflags(fufh_type); | ||||
fufh_type); | |||||
/* NOTREACHED */ | |||||
} | |||||
/* | |||||
* Note that this means we are effectively FILTERING OUT open() flags. | |||||
*/ | |||||
oflags = fuse_filehandle_xlate_to_oflags(fufh_type); | |||||
if (vnode_isdir(vp)) { | if (vnode_isdir(vp)) { | ||||
op = FUSE_OPENDIR; | op = FUSE_OPENDIR; | ||||
if (fufh_type != FUFH_RDONLY) { | /* vn_open_vnode already rejects FWRITE on directories */ | ||||
SDT_PROBE2(fuse, , file, trace, 1, | MPASS(fufh_type == FUFH_RDONLY || fufh_type == FUFH_EXEC); | ||||
"non-rdonly fh requested for a directory?"); | |||||
printf("FUSE:non-rdonly fh requested for a directory?\n"); | |||||
fufh_type = FUFH_RDONLY; | |||||
} | } | ||||
} | |||||
fdisp_init(&fdi, sizeof(*foi)); | fdisp_init(&fdi, sizeof(*foi)); | ||||
fdisp_make_vp(&fdi, op, vp, td, cred); | fdisp_make_vp(&fdi, op, vp, td, cred); | ||||
foi = fdi.indata; | foi = fdi.indata; | ||||
foi->flags = oflags; | foi->flags = oflags; | ||||
if ((err = fdisp_wait_answ(&fdi))) { | if ((err = fdisp_wait_answ(&fdi))) { | ||||
SDT_PROBE2(fuse, , file, trace, 1, | SDT_PROBE2(fusefs, , file, trace, 1, | ||||
"OUCH ... daemon didn't give fh"); | "OUCH ... daemon didn't give fh"); | ||||
if (err == ENOENT) { | if (err == ENOENT) { | ||||
fuse_internal_vnode_disappear(vp); | fuse_internal_vnode_disappear(vp); | ||||
} | } | ||||
goto out; | goto out; | ||||
} | } | ||||
foo = fdi.answ; | foo = fdi.answ; | ||||
fuse_filehandle_init(vp, fufh_type, fufhp, foo->fh); | fuse_filehandle_init(vp, fufh_type, fufhp, td, cred, foo); | ||||
/* | |||||
* 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) | |||||
fuse_vnode_open(vp, foo->open_flags | FOPEN_DIRECT_IO, td); | |||||
else | |||||
fuse_vnode_open(vp, foo->open_flags, td); | fuse_vnode_open(vp, foo->open_flags, td); | ||||
out: | out: | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
return err; | return err; | ||||
} | } | ||||
int | int | ||||
fuse_filehandle_close(struct vnode *vp, fufh_type_t fufh_type, | fuse_filehandle_close(struct vnode *vp, struct fuse_filehandle *fufh, | ||||
struct thread *td, struct ucred *cred) | struct thread *td, struct ucred *cred) | ||||
{ | { | ||||
struct fuse_dispatcher fdi; | struct fuse_dispatcher fdi; | ||||
struct fuse_release_in *fri; | struct fuse_release_in *fri; | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct fuse_filehandle *fufh = NULL; | |||||
int err = 0; | int err = 0; | ||||
int op = FUSE_RELEASE; | int op = FUSE_RELEASE; | ||||
fufh = &(fvdat->fufh[fufh_type]); | |||||
if (!FUFH_IS_VALID(fufh)) { | |||||
panic("FUSE: filehandle_put called on invalid fufh (type=%d)", | |||||
fufh_type); | |||||
/* NOTREACHED */ | |||||
} | |||||
if (fuse_isdeadfs(vp)) { | if (fuse_isdeadfs(vp)) { | ||||
goto out; | goto out; | ||||
} | } | ||||
if (vnode_isdir(vp)) | if (vnode_isdir(vp)) | ||||
op = FUSE_RELEASEDIR; | op = FUSE_RELEASEDIR; | ||||
fdisp_init(&fdi, sizeof(*fri)); | fdisp_init(&fdi, sizeof(*fri)); | ||||
fdisp_make_vp(&fdi, op, vp, td, cred); | fdisp_make_vp(&fdi, op, vp, td, cred); | ||||
fri = fdi.indata; | fri = fdi.indata; | ||||
fri->fh = fufh->fh_id; | fri->fh = fufh->fh_id; | ||||
fri->flags = fuse_filehandle_xlate_to_oflags(fufh_type); | fri->flags = fufh_type_2_fflags(fufh->fufh_type); | ||||
/* | |||||
* If the file has a POSIX lock then we're supposed to set lock_owner. | |||||
* If not, then lock_owner is undefined. So we may as well always set | |||||
* it. | |||||
*/ | |||||
fri->lock_owner = td->td_proc->p_pid; | |||||
err = fdisp_wait_answ(&fdi); | err = fdisp_wait_answ(&fdi); | ||||
fdisp_destroy(&fdi); | fdisp_destroy(&fdi); | ||||
out: | out: | ||||
atomic_subtract_acq_int(&fuse_fh_count, 1); | counter_u64_add(fuse_fh_count, -1); | ||||
fufh->fh_id = (uint64_t)-1; | LIST_REMOVE(fufh, next); | ||||
fufh->fh_type = FUFH_INVALID; | free(fufh, M_FUSE_FILEHANDLE); | ||||
return err; | return err; | ||||
} | } | ||||
int | |||||
fuse_filehandle_valid(struct vnode *vp, fufh_type_t fufh_type) | |||||
{ | |||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | |||||
struct fuse_filehandle *fufh; | |||||
fufh = &(fvdat->fufh[fufh_type]); | |||||
return FUFH_IS_VALID(fufh); | |||||
} | |||||
/* | /* | ||||
* Check for a valid file handle, first the type requested, but if that | * Check for a valid file handle, first the type requested, but if that | ||||
* isn't valid, try for FUFH_RDWR. | * isn't valid, try for FUFH_RDWR. | ||||
* Return the FUFH type that is valid or FUFH_INVALID if there are none. | * Return true if there is any file handle with the correct credentials and | ||||
* This is a variant of fuse_filehandle_vaild() analogous to | * a fufh type that includes the provided one. | ||||
* fuse_filehandle_getrw(). | * A pid of 0 means "don't care" | ||||
*/ | */ | ||||
fufh_type_t | bool | ||||
fuse_filehandle_validrw(struct vnode *vp, fufh_type_t fufh_type) | fuse_filehandle_validrw(struct vnode *vp, int mode, | ||||
struct ucred *cred, pid_t pid) | |||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh; | struct fuse_filehandle *fufh; | ||||
fufh_type_t fufh_type = fflags_2_fufh_type(mode); | |||||
fufh = &fvdat->fufh[fufh_type]; | /* | ||||
if (FUFH_IS_VALID(fufh) != 0) | * Unlike fuse_filehandle_get, we want to search for a filehandle with | ||||
return (fufh_type); | * the exact cred, and no fallback | ||||
fufh = &fvdat->fufh[FUFH_RDWR]; | */ | ||||
if (FUFH_IS_VALID(fufh) != 0) | LIST_FOREACH(fufh, &fvdat->handles, next) { | ||||
return (FUFH_RDWR); | if (fufh->fufh_type == fufh_type && | ||||
return (FUFH_INVALID); | fufh->uid == cred->cr_uid && | ||||
fufh->gid == cred->cr_rgid && | |||||
(pid == 0 || fufh->pid == pid)) | |||||
return true; | |||||
} | } | ||||
if (fufh_type == FUFH_EXEC) | |||||
return false; | |||||
/* Fallback: find a RDWR list entry with the right cred */ | |||||
LIST_FOREACH(fufh, &fvdat->handles, next) { | |||||
if (fufh->fufh_type == FUFH_RDWR && | |||||
fufh->uid == cred->cr_uid && | |||||
fufh->gid == cred->cr_rgid && | |||||
(pid == 0 || fufh->pid == pid)) | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
int | int | ||||
fuse_filehandle_get(struct vnode *vp, fufh_type_t fufh_type, | fuse_filehandle_get(struct vnode *vp, int fflag, | ||||
struct fuse_filehandle **fufhp) | struct fuse_filehandle **fufhp, struct ucred *cred, pid_t pid) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh; | struct fuse_filehandle *fufh; | ||||
fufh_type_t fufh_type; | |||||
fufh = &(fvdat->fufh[fufh_type]); | fufh_type = fflags_2_fufh_type(fflag); | ||||
if (!FUFH_IS_VALID(fufh)) | /* cred can be NULL for in-kernel clients */ | ||||
if (cred == NULL) | |||||
goto fallback; | |||||
LIST_FOREACH(fufh, &fvdat->handles, next) { | |||||
if (fufh->fufh_type == fufh_type && | |||||
fufh->uid == cred->cr_uid && | |||||
fufh->gid == cred->cr_rgid && | |||||
(pid == 0 || fufh->pid == pid)) | |||||
goto found; | |||||
} | |||||
fallback: | |||||
/* Fallback: find a list entry with the right flags */ | |||||
LIST_FOREACH(fufh, &fvdat->handles, next) { | |||||
if (fufh->fufh_type == fufh_type) | |||||
break; | |||||
} | |||||
if (fufh == NULL) | |||||
return EBADF; | return EBADF; | ||||
found: | |||||
if (fufhp != NULL) | if (fufhp != NULL) | ||||
*fufhp = fufh; | *fufhp = fufh; | ||||
return 0; | return 0; | ||||
} | } | ||||
/* Get a file handle with any kind of flags */ | |||||
int | int | ||||
fuse_filehandle_getrw(struct vnode *vp, fufh_type_t fufh_type, | fuse_filehandle_get_anyflags(struct vnode *vp, | ||||
struct fuse_filehandle **fufhp) | struct fuse_filehandle **fufhp, struct ucred *cred, pid_t pid) | ||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh; | struct fuse_filehandle *fufh; | ||||
fufh = &(fvdat->fufh[fufh_type]); | if (cred == NULL) | ||||
if (!FUFH_IS_VALID(fufh)) { | goto fallback; | ||||
fufh_type = FUFH_RDWR; | |||||
LIST_FOREACH(fufh, &fvdat->handles, next) { | |||||
if (fufh->uid == cred->cr_uid && | |||||
fufh->gid == cred->cr_rgid && | |||||
(pid == 0 || fufh->pid == pid)) | |||||
goto found; | |||||
} | } | ||||
return fuse_filehandle_get(vp, fufh_type, fufhp); | |||||
fallback: | |||||
/* Fallback: find any list entry */ | |||||
fufh = LIST_FIRST(&fvdat->handles); | |||||
if (fufh == NULL) | |||||
return EBADF; | |||||
found: | |||||
if (fufhp != NULL) | |||||
*fufhp = fufh; | |||||
return 0; | |||||
} | } | ||||
int | |||||
fuse_filehandle_getrw(struct vnode *vp, int fflag, | |||||
struct fuse_filehandle **fufhp, struct ucred *cred, pid_t pid) | |||||
{ | |||||
int err; | |||||
err = fuse_filehandle_get(vp, fflag, fufhp, cred, pid); | |||||
if (err) | |||||
err = fuse_filehandle_get(vp, FREAD | FWRITE, fufhp, cred, pid); | |||||
return err; | |||||
} | |||||
void | void | ||||
fuse_filehandle_init(struct vnode *vp, fufh_type_t fufh_type, | fuse_filehandle_init(struct vnode *vp, fufh_type_t fufh_type, | ||||
struct fuse_filehandle **fufhp, uint64_t fh_id) | struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred, | ||||
struct fuse_open_out *foo) | |||||
{ | { | ||||
struct fuse_vnode_data *fvdat = VTOFUD(vp); | struct fuse_vnode_data *fvdat = VTOFUD(vp); | ||||
struct fuse_filehandle *fufh; | struct fuse_filehandle *fufh; | ||||
fufh = &(fvdat->fufh[fufh_type]); | fufh = malloc(sizeof(struct fuse_filehandle), M_FUSE_FILEHANDLE, | ||||
MPASS(!FUFH_IS_VALID(fufh)); | M_WAITOK); | ||||
fufh->fh_id = fh_id; | MPASS(fufh != NULL); | ||||
fufh->fh_type = fufh_type; | fufh->fh_id = foo->fh; | ||||
fufh->fufh_type = fufh_type; | |||||
fufh->gid = cred->cr_rgid; | |||||
fufh->uid = cred->cr_uid; | |||||
fufh->pid = td->td_proc->p_pid; | |||||
fufh->fuse_open_flags = foo->open_flags; | |||||
if (!FUFH_IS_VALID(fufh)) { | if (!FUFH_IS_VALID(fufh)) { | ||||
panic("FUSE: init: invalid filehandle id (type=%d)", fufh_type); | panic("FUSE: init: invalid filehandle id (type=%d)", fufh_type); | ||||
} | } | ||||
LIST_INSERT_HEAD(&fvdat->handles, fufh, next); | |||||
if (fufhp != NULL) | if (fufhp != NULL) | ||||
*fufhp = fufh; | *fufhp = fufh; | ||||
atomic_add_acq_int(&fuse_fh_count, 1); | counter_u64_add(fuse_fh_count, 1); | ||||
if (foo->open_flags & FOPEN_DIRECT_IO) { | |||||
ASSERT_VOP_ELOCKED(vp, __func__); | |||||
VTOFUD(vp)->flag |= FN_DIRECTIO; | |||||
fuse_io_invalbuf(vp, td); | |||||
} else { | |||||
if ((foo->open_flags & FOPEN_KEEP_CACHE) == 0) | |||||
fuse_io_invalbuf(vp, td); | |||||
VTOFUD(vp)->flag &= ~FN_DIRECTIO; | |||||
} | |||||
} | |||||
void | |||||
fuse_file_init(void) | |||||
{ | |||||
fuse_fh_count = counter_u64_alloc(M_WAITOK); | |||||
} | |||||
void | |||||
fuse_file_destroy(void) | |||||
{ | |||||
counter_u64_free(fuse_fh_count); | |||||
} | } |