fusefs: Make fuse file systems NFS-exportable
This commit adds the VOPs needed by userspace NFS servers (tested with net/unfs3). More work is needed to make the in-kernel nfsd work, because of its stateless nature. It doesn't open files prior to doing I/O. Also, the NFS-related VOPs currently ignore the entry cache. Sponsored by: The FreeBSD Foundation
This commit is contained in:
parent
2013b723d3
commit
e5b50fe736
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=348135
@ -727,6 +727,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio)
|
|||||||
data->max_write = fiio->max_write;
|
data->max_write = fiio->max_write;
|
||||||
if (fiio->flags & FUSE_POSIX_LOCKS)
|
if (fiio->flags & FUSE_POSIX_LOCKS)
|
||||||
data->dataflags |= FSESS_POSIX_LOCKS;
|
data->dataflags |= FSESS_POSIX_LOCKS;
|
||||||
|
if (fiio->flags & FUSE_EXPORT_SUPPORT)
|
||||||
|
data->dataflags |= FSESS_EXPORT_SUPPORT;
|
||||||
} else {
|
} else {
|
||||||
err = EINVAL;
|
err = EINVAL;
|
||||||
}
|
}
|
||||||
@ -764,7 +766,7 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
|
|||||||
* the size of a buffer cache block.
|
* the size of a buffer cache block.
|
||||||
*/
|
*/
|
||||||
fiii->max_readahead = maxbcachebuf;
|
fiii->max_readahead = maxbcachebuf;
|
||||||
fiii->flags = FUSE_POSIX_LOCKS;
|
fiii->flags = FUSE_EXPORT_SUPPORT | FUSE_POSIX_LOCKS;
|
||||||
|
|
||||||
fuse_insert_callback(fdi.tick, fuse_internal_init_callback);
|
fuse_insert_callback(fdi.tick, fuse_internal_init_callback);
|
||||||
fuse_insert_message(fdi.tick, false);
|
fuse_insert_message(fdi.tick, false);
|
||||||
|
@ -222,6 +222,7 @@ struct fuse_data {
|
|||||||
#define FSESS_NO_NAMECACHE 0x0400 /* disable name cache */
|
#define FSESS_NO_NAMECACHE 0x0400 /* disable name cache */
|
||||||
#define FSESS_NO_MMAP 0x0800 /* disable mmap */
|
#define FSESS_NO_MMAP 0x0800 /* disable mmap */
|
||||||
#define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */
|
#define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */
|
||||||
|
#define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */
|
||||||
#define FSESS_MNTOPTS_MASK ( \
|
#define FSESS_MNTOPTS_MASK ( \
|
||||||
FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \
|
FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \
|
||||||
FSESS_DEFAULT_PERMISSIONS | FSESS_NO_ATTRCACHE | \
|
FSESS_DEFAULT_PERMISSIONS | FSESS_NO_ATTRCACHE | \
|
||||||
|
@ -77,7 +77,6 @@ __FBSDID("$FreeBSD$");
|
|||||||
#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 <sys/buf.h>
|
||||||
#include <security/mac/mac_framework.h>
|
#include <security/mac/mac_framework.h>
|
||||||
@ -165,18 +164,12 @@ fuse_vnode_destroy(struct vnode *vp)
|
|||||||
atomic_subtract_acq_int(&fuse_node_count, 1);
|
atomic_subtract_acq_int(&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
|
|
||||||
fuse_vnode_hash(uint64_t id)
|
|
||||||
{
|
|
||||||
return (fnv_32_buf(&id, sizeof(id), FNV1_32_INIT));
|
|
||||||
}
|
|
||||||
|
|
||||||
SDT_PROBE_DEFINE3(fusefs, , node, stale_vnode, "struct vnode*", "enum vtype",
|
SDT_PROBE_DEFINE3(fusefs, , node, stale_vnode, "struct vnode*", "enum vtype",
|
||||||
"uint64_t");
|
"uint64_t");
|
||||||
static int
|
static int
|
||||||
@ -215,6 +208,7 @@ fuse_vnode_alloc(struct mount *mp,
|
|||||||
return (EAGAIN);
|
return (EAGAIN);
|
||||||
}
|
}
|
||||||
MPASS((*vpp)->v_data != NULL);
|
MPASS((*vpp)->v_data != NULL);
|
||||||
|
MPASS(VTOFUD(*vpp)->nid == nodeid);
|
||||||
SDT_PROBE2(fusefs, , node, trace, 1, "vnode taken from hash");
|
SDT_PROBE2(fusefs, , node, trace, 1, "vnode taken from hash");
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
@ -269,6 +263,7 @@ fuse_vnode_get(struct mount *mp,
|
|||||||
enum vtype vtyp)
|
enum vtype vtyp)
|
||||||
{
|
{
|
||||||
struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread);
|
struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread);
|
||||||
|
uint64_t generation = feo ? feo->generation : 1;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp);
|
err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp);
|
||||||
@ -293,10 +288,11 @@ fuse_vnode_get(struct mount *mp,
|
|||||||
cache_enter_time(dvp, *vpp, cnp, &timeout, NULL);
|
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 &&
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
#ifndef _FUSE_NODE_H_
|
#ifndef _FUSE_NODE_H_
|
||||||
#define _FUSE_NODE_H_
|
#define _FUSE_NODE_H_
|
||||||
|
|
||||||
|
#include <sys/fnv_hash.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/mutex.h>
|
#include <sys/mutex.h>
|
||||||
|
|
||||||
@ -75,10 +76,13 @@
|
|||||||
*/
|
*/
|
||||||
#define FN_SIZECHANGE 0x00000100
|
#define FN_SIZECHANGE 0x00000100
|
||||||
#define FN_DIRECTIO 0x00000200
|
#define FN_DIRECTIO 0x00000200
|
||||||
|
/* Indicates that parent_nid is valid */
|
||||||
|
#define FN_PARENT_NID 0x00000400
|
||||||
|
|
||||||
struct fuse_vnode_data {
|
struct fuse_vnode_data {
|
||||||
/** self **/
|
/** self **/
|
||||||
uint64_t nid;
|
uint64_t nid;
|
||||||
|
uint64_t generation;
|
||||||
|
|
||||||
/** parent **/
|
/** parent **/
|
||||||
uint64_t parent_nid;
|
uint64_t parent_nid;
|
||||||
@ -98,6 +102,17 @@ struct fuse_vnode_data {
|
|||||||
enum vtype vtype;
|
enum vtype vtype;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This overlays the fid structure (see mount.h). Mostly the same as the types
|
||||||
|
* used by UFS and ext2.
|
||||||
|
*/
|
||||||
|
struct fuse_fid {
|
||||||
|
uint16_t len; /* Length of structure. */
|
||||||
|
uint16_t pad; /* Force 32-bit alignment. */
|
||||||
|
uint32_t gen; /* Generation number. */
|
||||||
|
uint64_t nid; /* FUSE node id. */
|
||||||
|
};
|
||||||
|
|
||||||
#define VTOFUD(vp) \
|
#define VTOFUD(vp) \
|
||||||
((struct fuse_vnode_data *)((vp)->v_data))
|
((struct fuse_vnode_data *)((vp)->v_data))
|
||||||
#define VTOI(vp) (VTOFUD(vp)->nid)
|
#define VTOI(vp) (VTOFUD(vp)->nid)
|
||||||
@ -119,6 +134,12 @@ fuse_vnode_clear_attr_cache(struct vnode *vp)
|
|||||||
bintime_clear(&VTOFUD(vp)->attr_cache_timeout);
|
bintime_clear(&VTOFUD(vp)->attr_cache_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t inline
|
||||||
|
fuse_vnode_hash(uint64_t id)
|
||||||
|
{
|
||||||
|
return (fnv_32_buf(&id, sizeof(id), FNV1_32_INIT));
|
||||||
|
}
|
||||||
|
|
||||||
#define VTOILLU(vp) ((uint64_t)(VTOFUD(vp) ? VTOI(vp) : 0))
|
#define VTOILLU(vp) ((uint64_t)(VTOFUD(vp) ? VTOI(vp) : 0))
|
||||||
|
|
||||||
#define FUSE_NULL_ID 0
|
#define FUSE_NULL_ID 0
|
||||||
@ -126,12 +147,15 @@ fuse_vnode_clear_attr_cache(struct vnode *vp)
|
|||||||
extern struct vop_vector fuse_fifoops;
|
extern struct vop_vector fuse_fifoops;
|
||||||
extern struct vop_vector fuse_vnops;
|
extern struct vop_vector fuse_vnops;
|
||||||
|
|
||||||
|
int fuse_vnode_cmp(struct vnode *vp, void *nidp);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
fuse_vnode_setparent(struct vnode *vp, struct vnode *dvp)
|
fuse_vnode_setparent(struct vnode *vp, struct vnode *dvp)
|
||||||
{
|
{
|
||||||
if (dvp != NULL && vp->v_type == VDIR) {
|
if (dvp != NULL && vp->v_type == VDIR) {
|
||||||
MPASS(dvp->v_type == VDIR);
|
MPASS(dvp->v_type == VDIR);
|
||||||
VTOFUD(vp)->parent_nid = VTOI(dvp);
|
VTOFUD(vp)->parent_nid = VTOI(dvp);
|
||||||
|
VTOFUD(vp)->flag |= FN_PARENT_NID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,16 +107,20 @@ SDT_PROBE_DEFINE2(fusefs, , vfsops, trace, "int", "char*");
|
|||||||
#define PRIV_VFS_FUSE_SYNC_UNMOUNT PRIV_VFS_MOUNT_NONUSER
|
#define PRIV_VFS_FUSE_SYNC_UNMOUNT PRIV_VFS_MOUNT_NONUSER
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static vfs_fhtovp_t fuse_vfsop_fhtovp;
|
||||||
static vfs_mount_t fuse_vfsop_mount;
|
static vfs_mount_t fuse_vfsop_mount;
|
||||||
static vfs_unmount_t fuse_vfsop_unmount;
|
static vfs_unmount_t fuse_vfsop_unmount;
|
||||||
static vfs_root_t fuse_vfsop_root;
|
static vfs_root_t fuse_vfsop_root;
|
||||||
static vfs_statfs_t fuse_vfsop_statfs;
|
static vfs_statfs_t fuse_vfsop_statfs;
|
||||||
|
static vfs_vget_t fuse_vfsop_vget;
|
||||||
|
|
||||||
struct vfsops fuse_vfsops = {
|
struct vfsops fuse_vfsops = {
|
||||||
|
.vfs_fhtovp = fuse_vfsop_fhtovp,
|
||||||
.vfs_mount = fuse_vfsop_mount,
|
.vfs_mount = fuse_vfsop_mount,
|
||||||
.vfs_unmount = fuse_vfsop_unmount,
|
.vfs_unmount = fuse_vfsop_unmount,
|
||||||
.vfs_root = fuse_vfsop_root,
|
.vfs_root = fuse_vfsop_root,
|
||||||
.vfs_statfs = fuse_vfsop_statfs,
|
.vfs_statfs = fuse_vfsop_statfs,
|
||||||
|
.vfs_vget = fuse_vfsop_vget,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int fuse_enforce_dev_perms = 0;
|
static int fuse_enforce_dev_perms = 0;
|
||||||
@ -256,6 +260,34 @@ fuse_vfs_remount(struct mount *mp, struct thread *td, uint64_t mntopts,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
fuse_vfsop_fhtovp(struct mount *mp, struct fid *fhp, int flags,
|
||||||
|
struct vnode **vpp)
|
||||||
|
{
|
||||||
|
struct fuse_fid *ffhp = (struct fuse_fid *)fhp;
|
||||||
|
struct fuse_vnode_data *fvdat;
|
||||||
|
struct vnode *nvp;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (!(fuse_get_mpdata(mp)->dataflags & FSESS_EXPORT_SUPPORT))
|
||||||
|
return EOPNOTSUPP;
|
||||||
|
|
||||||
|
error = VFS_VGET(mp, ffhp->nid, LK_EXCLUSIVE, &nvp);
|
||||||
|
if (error) {
|
||||||
|
*vpp = NULLVP;
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
fvdat = VTOFUD(nvp);
|
||||||
|
if (fvdat->generation != ffhp->gen ) {
|
||||||
|
vput(nvp);
|
||||||
|
*vpp = NULLVP;
|
||||||
|
return (ESTALE);
|
||||||
|
}
|
||||||
|
*vpp = nvp;
|
||||||
|
vnode_create_vobject(*vpp, 0, curthread);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fuse_vfsop_mount(struct mount *mp)
|
fuse_vfsop_mount(struct mount *mp)
|
||||||
{
|
{
|
||||||
@ -322,7 +354,6 @@ fuse_vfsop_mount(struct mount *mp)
|
|||||||
SDT_PROBE1(fusefs, , vfsops, mntopts, mntopts);
|
SDT_PROBE1(fusefs, , vfsops, mntopts, mntopts);
|
||||||
|
|
||||||
if (mp->mnt_flag & MNT_UPDATE) {
|
if (mp->mnt_flag & MNT_UPDATE) {
|
||||||
/*dev_rel(fdev);*/
|
|
||||||
return fuse_vfs_remount(mp, td, mntopts, max_read,
|
return fuse_vfs_remount(mp, td, mntopts, max_read,
|
||||||
daemon_timeout);
|
daemon_timeout);
|
||||||
}
|
}
|
||||||
@ -352,7 +383,8 @@ fuse_vfsop_mount(struct mount *mp)
|
|||||||
td->td_fpop = fptmp;
|
td->td_fpop = fptmp;
|
||||||
fdrop(fp, td);
|
fdrop(fp, td);
|
||||||
FUSE_LOCK();
|
FUSE_LOCK();
|
||||||
if (err != 0 || data == NULL || data->mp != NULL) {
|
|
||||||
|
if (err != 0 || data == NULL) {
|
||||||
err = ENXIO;
|
err = ENXIO;
|
||||||
SDT_PROBE4(fusefs, , vfsops, mount_err,
|
SDT_PROBE4(fusefs, , vfsops, mount_err,
|
||||||
"invalid or not opened device", data, mp, err);
|
"invalid or not opened device", data, mp, err);
|
||||||
@ -480,7 +512,6 @@ fuse_vfsop_unmount(struct mount *mp, int mntflags)
|
|||||||
|
|
||||||
MNT_ILOCK(mp);
|
MNT_ILOCK(mp);
|
||||||
mp->mnt_data = NULL;
|
mp->mnt_data = NULL;
|
||||||
mp->mnt_flag &= ~MNT_LOCAL;
|
|
||||||
MNT_IUNLOCK(mp);
|
MNT_IUNLOCK(mp);
|
||||||
|
|
||||||
dev_rel(fdev);
|
dev_rel(fdev);
|
||||||
@ -488,6 +519,77 @@ fuse_vfsop_unmount(struct mount *mp, int mntflags)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
fuse_vfsop_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
|
||||||
|
{
|
||||||
|
uint64_t nodeid = ino;
|
||||||
|
struct thread *td = curthread;
|
||||||
|
struct fuse_dispatcher fdi;
|
||||||
|
struct fuse_entry_out *feo;
|
||||||
|
struct fuse_vnode_data *fvdat;
|
||||||
|
const char dot[] = ".";
|
||||||
|
off_t filesize;
|
||||||
|
enum vtype vtyp;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO Check the vnode cache, verifying entry cache timeout. Normally
|
||||||
|
* done during VOP_LOOKUP
|
||||||
|
*/
|
||||||
|
/*error = vfs_hash_get(mp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, vpp,*/
|
||||||
|
/*fuse_vnode_cmp, &nodeid);*/
|
||||||
|
/*if (error || *vpp != NULL)*/
|
||||||
|
/*return error;*/
|
||||||
|
|
||||||
|
/* Do a LOOKUP, using nodeid as the parent and "." as filename */
|
||||||
|
fdisp_init(&fdi, sizeof(dot));
|
||||||
|
fdisp_make(&fdi, FUSE_LOOKUP, mp, nodeid, td, td->td_ucred);
|
||||||
|
memcpy(fdi.indata, dot, sizeof(dot));
|
||||||
|
error = fdisp_wait_answ(&fdi);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
feo = (struct fuse_entry_out *)fdi.answ;
|
||||||
|
if (feo->nodeid == 0) {
|
||||||
|
/* zero nodeid means ENOENT and cache it */
|
||||||
|
error = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
vtyp = IFTOVT(feo->attr.mode);
|
||||||
|
error = fuse_vnode_get(mp, feo, nodeid, NULL, vpp, NULL, vtyp);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
filesize = feo->attr.size;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the case where we are looking up a FUSE node represented by an
|
||||||
|
* existing cached vnode, and the true size reported by FUSE_LOOKUP
|
||||||
|
* doesn't match the vnode's cached size, then any cached writes beyond
|
||||||
|
* the file's current size are lost.
|
||||||
|
*
|
||||||
|
* We can get here:
|
||||||
|
* * following attribute cache expiration, or
|
||||||
|
* * due a bug in the daemon, or
|
||||||
|
*/
|
||||||
|
fvdat = VTOFUD(*vpp);
|
||||||
|
if (vnode_isreg(*vpp) &&
|
||||||
|
filesize != fvdat->cached_attrs.va_size &&
|
||||||
|
fvdat->flag & FN_SIZECHANGE) {
|
||||||
|
printf("%s: WB cache incoherent on %s!\n", __func__,
|
||||||
|
vnode_mount(*vpp)->mnt_stat.f_mntonname);
|
||||||
|
|
||||||
|
fvdat->flag &= ~FN_SIZECHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fuse_internal_cache_attrs(*vpp, td->td_ucred, &feo->attr,
|
||||||
|
feo->attr_valid, feo->attr_valid_nsec, NULL);
|
||||||
|
out:
|
||||||
|
fdisp_destroy(&fdi);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fuse_vfsop_root(struct mount *mp, int lkflags, struct vnode **vpp)
|
fuse_vfsop_root(struct mount *mp, int lkflags, struct vnode **vpp)
|
||||||
{
|
{
|
||||||
|
@ -151,6 +151,7 @@ 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_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 = {
|
struct vop_vector fuse_fifoops = {
|
||||||
.vop_default = &fifo_specops,
|
.vop_default = &fifo_specops,
|
||||||
@ -165,6 +166,7 @@ struct vop_vector fuse_fifoops = {
|
|||||||
.vop_reclaim = fuse_vnop_reclaim,
|
.vop_reclaim = fuse_vnop_reclaim,
|
||||||
.vop_setattr = fuse_vnop_setattr,
|
.vop_setattr = fuse_vnop_setattr,
|
||||||
.vop_write = VOP_PANIC,
|
.vop_write = VOP_PANIC,
|
||||||
|
.vop_vptofh = fuse_vnop_vptofh,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vop_vector fuse_vnops = {
|
struct vop_vector fuse_vnops = {
|
||||||
@ -202,6 +204,7 @@ struct vop_vector fuse_vnops = {
|
|||||||
.vop_getpages = fuse_vnop_getpages,
|
.vop_getpages = fuse_vnop_getpages,
|
||||||
.vop_putpages = fuse_vnop_putpages,
|
.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;
|
static u_long fuse_lookup_cache_hits = 0;
|
||||||
@ -908,6 +911,8 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
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;
|
||||||
@ -970,8 +975,8 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
|
|
||||||
if (!lookup_err) {
|
if (!lookup_err) {
|
||||||
/* lookup call succeeded */
|
/* lookup call succeeded */
|
||||||
nid = ((struct fuse_entry_out *)fdi.answ)->nodeid;
|
|
||||||
feo = (struct fuse_entry_out *)fdi.answ;
|
feo = (struct fuse_entry_out *)fdi.answ;
|
||||||
|
nid = feo->nodeid;
|
||||||
if (nid == 0) {
|
if (nid == 0) {
|
||||||
/* zero nodeid means ENOENT and cache it */
|
/* zero nodeid means ENOENT and cache it */
|
||||||
struct timespec timeout;
|
struct timespec timeout;
|
||||||
@ -2446,3 +2451,48 @@ fuse_vnop_print(struct vop_print_args *ap)
|
|||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ GTESTS+= lookup
|
|||||||
GTESTS+= mkdir
|
GTESTS+= mkdir
|
||||||
GTESTS+= mknod
|
GTESTS+= mknod
|
||||||
GTESTS+= mount
|
GTESTS+= mount
|
||||||
|
GTESTS+= nfs
|
||||||
GTESTS+= open
|
GTESTS+= open
|
||||||
GTESTS+= opendir
|
GTESTS+= opendir
|
||||||
GTESTS+= read
|
GTESTS+= read
|
||||||
@ -52,6 +53,7 @@ SRCS.$p+= utils.cc
|
|||||||
TEST_METADATA.default_permissions+= required_user="unprivileged"
|
TEST_METADATA.default_permissions+= required_user="unprivileged"
|
||||||
TEST_METADATA.default_permissions_privileged+= required_user="root"
|
TEST_METADATA.default_permissions_privileged+= required_user="root"
|
||||||
TEST_METADATA.mknod+= required_user="root"
|
TEST_METADATA.mknod+= required_user="root"
|
||||||
|
TEST_METADATA.nfs+= required_user="root"
|
||||||
|
|
||||||
# TODO: drastically increase timeout after test development is mostly complete
|
# TODO: drastically increase timeout after test development is mostly complete
|
||||||
TEST_METADATA+= timeout=10
|
TEST_METADATA+= timeout=10
|
||||||
|
307
tests/sys/fs/fusefs/nfs.cc
Normal file
307
tests/sys/fs/fusefs/nfs.cc
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
/*-
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 The FreeBSD Foundation
|
||||||
|
*
|
||||||
|
* This software was developed by BFF Storage Systems, LLC under sponsorship
|
||||||
|
* from the FreeBSD Foundation.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This file tests functionality needed by NFS servers */
|
||||||
|
extern "C" {
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "mockfs.hh"
|
||||||
|
#include "utils.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace testing;
|
||||||
|
|
||||||
|
|
||||||
|
class Nfs: public FuseTest {
|
||||||
|
public:
|
||||||
|
virtual void SetUp() {
|
||||||
|
if (geteuid() != 0)
|
||||||
|
GTEST_SKIP() << "This test requires a privileged user";
|
||||||
|
FuseTest::SetUp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Exportable: public Nfs {
|
||||||
|
public:
|
||||||
|
virtual void SetUp() {
|
||||||
|
m_init_flags = FUSE_EXPORT_SUPPORT;
|
||||||
|
Nfs::SetUp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Fhstat: public Exportable {};
|
||||||
|
class FhstatNotExportable: public Nfs {};
|
||||||
|
class Getfh: public Exportable {};
|
||||||
|
class Readdir: public Exportable {};
|
||||||
|
|
||||||
|
/* If the server returns a different generation number, then file is stale */
|
||||||
|
TEST_F(Fhstat, estale)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
struct stat sb;
|
||||||
|
const uint64_t ino = 42;
|
||||||
|
const mode_t mode = S_IFDIR | 0755;
|
||||||
|
Sequence seq;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.InSequence(seq)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(ino, ".")
|
||||||
|
.InSequence(seq)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 2;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
|
||||||
|
ASSERT_EQ(-1, fhstat(&fhp, &sb));
|
||||||
|
EXPECT_EQ(ESTALE, errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we must lookup an entry from the server, send a LOOKUP request for "." */
|
||||||
|
TEST_F(Fhstat, lookup_dot)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
struct stat sb;
|
||||||
|
const uint64_t ino = 42;
|
||||||
|
const mode_t mode = S_IFDIR | 0755;
|
||||||
|
const uid_t uid = 12345;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr.uid = uid;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(ino, ".")
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr.uid = uid;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
|
||||||
|
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
|
||||||
|
EXPECT_EQ(uid, sb.st_uid);
|
||||||
|
EXPECT_EQ(mode, sb.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use a file handle whose entry is still cached */
|
||||||
|
/*
|
||||||
|
* Disabled because fuse_vfsop_vget doesn't yet check the entry cache. No PR
|
||||||
|
* because that's a feature request, not a bug
|
||||||
|
*/
|
||||||
|
TEST_F(Fhstat, DISABLED_cached)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
struct stat sb;
|
||||||
|
const uint64_t ino = 42;
|
||||||
|
const mode_t mode = S_IFDIR | 0755;
|
||||||
|
const uid_t uid = 12345;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr.uid = uid;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = UINT64_MAX;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
|
||||||
|
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
|
||||||
|
EXPECT_EQ(uid, sb.st_uid);
|
||||||
|
EXPECT_EQ(mode, sb.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
|
||||||
|
* lookups
|
||||||
|
*/
|
||||||
|
TEST_F(FhstatNotExportable, lookup_dot)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
const uint64_t ino = 42;
|
||||||
|
const mode_t mode = S_IFDIR | 0755;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(-1, getfh(FULLPATH, &fhp));
|
||||||
|
ASSERT_EQ(EOPNOTSUPP, errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FreeBSD's fid struct doesn't have enough space for 64-bit generations */
|
||||||
|
TEST_F(Getfh, eoverflow)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
uint64_t ino = 42;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = S_IFDIR | 0755;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = (uint64_t)UINT32_MAX + 1;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = UINT64_MAX;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_NE(0, getfh(FULLPATH, &fhp));
|
||||||
|
EXPECT_EQ(EOVERFLOW, errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get an NFS file handle */
|
||||||
|
TEST_F(Getfh, ok)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir/.";
|
||||||
|
const char RELDIRPATH[] = "some_dir";
|
||||||
|
fhandle_t fhp;
|
||||||
|
uint64_t ino = 42;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELDIRPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = S_IFDIR | 0755;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = UINT64_MAX;
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call readdir via a file handle.
|
||||||
|
*
|
||||||
|
* This is how a userspace nfs server like nfs-ganesha or unfs3 would call
|
||||||
|
* readdir. The in-kernel NFS server never does any equivalent of open. I
|
||||||
|
* haven't discovered a way to mimic nfsd's behavior short of actually running
|
||||||
|
* nfsd.
|
||||||
|
*/
|
||||||
|
TEST_F(Readdir, getdirentries)
|
||||||
|
{
|
||||||
|
const char FULLPATH[] = "mountpoint/some_dir";
|
||||||
|
const char RELPATH[] = "some_dir";
|
||||||
|
uint64_t ino = 42;
|
||||||
|
mode_t mode = S_IFDIR | 0755;
|
||||||
|
fhandle_t fhp;
|
||||||
|
int fd;
|
||||||
|
char buf[8192];
|
||||||
|
ssize_t r;
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(1, RELPATH)
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
EXPECT_LOOKUP(ino, ".")
|
||||||
|
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
SET_OUT_HEADER_LEN(out, entry);
|
||||||
|
out->body.entry.attr.mode = mode;
|
||||||
|
out->body.entry.nodeid = ino;
|
||||||
|
out->body.entry.generation = 1;
|
||||||
|
out->body.entry.attr_valid = UINT64_MAX;
|
||||||
|
out->body.entry.entry_valid = 0;
|
||||||
|
})));
|
||||||
|
|
||||||
|
expect_opendir(ino);
|
||||||
|
|
||||||
|
EXPECT_CALL(*m_mock, process(
|
||||||
|
ResultOf([=](auto in) {
|
||||||
|
return (in->header.opcode == FUSE_READDIR &&
|
||||||
|
in->header.nodeid == ino &&
|
||||||
|
in->body.readdir.size == sizeof(buf));
|
||||||
|
}, Eq(true)),
|
||||||
|
_)
|
||||||
|
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
|
||||||
|
out->header.error = 0;
|
||||||
|
out->header.len = sizeof(out->header);
|
||||||
|
})));
|
||||||
|
|
||||||
|
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
|
||||||
|
fd = fhopen(&fhp, O_DIRECTORY);
|
||||||
|
ASSERT_LE(0, fd) << strerror(errno);
|
||||||
|
r = getdirentries(fd, buf, sizeof(buf), 0);
|
||||||
|
ASSERT_EQ(0, r) << strerror(errno);
|
||||||
|
|
||||||
|
/* Deliberately leak fd. RELEASEDIR will be tested separately */
|
||||||
|
}
|
@ -45,56 +45,6 @@ void expect_lookup(const char *relpath, uint64_t ino)
|
|||||||
{
|
{
|
||||||
FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
|
FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
|
|
||||||
{
|
|
||||||
EXPECT_CALL(*m_mock, process(
|
|
||||||
ResultOf([=](auto in) {
|
|
||||||
return (in->header.opcode == FUSE_READDIR &&
|
|
||||||
in->header.nodeid == ino &&
|
|
||||||
in->body.readdir.fh == FH &&
|
|
||||||
in->body.readdir.offset == off);
|
|
||||||
}, Eq(true)),
|
|
||||||
_)
|
|
||||||
).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
|
|
||||||
struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
out->header.error = 0;
|
|
||||||
out->header.len = 0;
|
|
||||||
|
|
||||||
for (const auto& it: ents) {
|
|
||||||
size_t entlen, entsize;
|
|
||||||
|
|
||||||
fde->ino = it.d_fileno;
|
|
||||||
fde->off = it.d_off;
|
|
||||||
fde->type = it.d_type;
|
|
||||||
fde->namelen = it.d_namlen;
|
|
||||||
strncpy(fde->name, it.d_name, it.d_namlen);
|
|
||||||
entlen = FUSE_NAME_OFFSET + fde->namelen;
|
|
||||||
entsize = FUSE_DIRENT_SIZE(fde);
|
|
||||||
/*
|
|
||||||
* The FUSE protocol does not require zeroing out the
|
|
||||||
* unused portion of the name. But it's a good
|
|
||||||
* practice to prevent information disclosure to the
|
|
||||||
* FUSE client, even though the client is usually the
|
|
||||||
* kernel
|
|
||||||
*/
|
|
||||||
memset(fde->name + fde->namelen, 0, entsize - entlen);
|
|
||||||
if (out->header.len + entsize > in->body.read.size) {
|
|
||||||
printf("Overflow in readdir expectation: i=%d\n"
|
|
||||||
, i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
out->header.len += entsize;
|
|
||||||
fde = (struct fuse_dirent*)
|
|
||||||
((long*)fde + entsize / sizeof(long));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
out->header.len += sizeof(out->header);
|
|
||||||
})));
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Readdir_7_8: public Readdir {
|
class Readdir_7_8: public Readdir {
|
||||||
|
@ -35,6 +35,7 @@ extern "C" {
|
|||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <semaphore.h>
|
#include <semaphore.h>
|
||||||
@ -285,6 +286,56 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
|
|||||||
}))).RetiresOnSaturation();
|
}))).RetiresOnSaturation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FuseTest::expect_readdir(uint64_t ino, uint64_t off,
|
||||||
|
std::vector<struct dirent> &ents)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(*m_mock, process(
|
||||||
|
ResultOf([=](auto in) {
|
||||||
|
return (in->header.opcode == FUSE_READDIR &&
|
||||||
|
in->header.nodeid == ino &&
|
||||||
|
in->body.readdir.fh == FH &&
|
||||||
|
in->body.readdir.offset == off);
|
||||||
|
}, Eq(true)),
|
||||||
|
_)
|
||||||
|
).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
|
||||||
|
struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
out->header.error = 0;
|
||||||
|
out->header.len = 0;
|
||||||
|
|
||||||
|
for (const auto& it: ents) {
|
||||||
|
size_t entlen, entsize;
|
||||||
|
|
||||||
|
fde->ino = it.d_fileno;
|
||||||
|
fde->off = it.d_off;
|
||||||
|
fde->type = it.d_type;
|
||||||
|
fde->namelen = it.d_namlen;
|
||||||
|
strncpy(fde->name, it.d_name, it.d_namlen);
|
||||||
|
entlen = FUSE_NAME_OFFSET + fde->namelen;
|
||||||
|
entsize = FUSE_DIRENT_SIZE(fde);
|
||||||
|
/*
|
||||||
|
* The FUSE protocol does not require zeroing out the
|
||||||
|
* unused portion of the name. But it's a good
|
||||||
|
* practice to prevent information disclosure to the
|
||||||
|
* FUSE client, even though the client is usually the
|
||||||
|
* kernel
|
||||||
|
*/
|
||||||
|
memset(fde->name + fde->namelen, 0, entsize - entlen);
|
||||||
|
if (out->header.len + entsize > in->body.read.size) {
|
||||||
|
printf("Overflow in readdir expectation: i=%d\n"
|
||||||
|
, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out->header.len += entsize;
|
||||||
|
fde = (struct fuse_dirent*)
|
||||||
|
((long*)fde + entsize / sizeof(long));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
out->header.len += sizeof(out->header);
|
||||||
|
})));
|
||||||
|
|
||||||
|
}
|
||||||
void FuseTest::expect_release(uint64_t ino, uint64_t fh)
|
void FuseTest::expect_release(uint64_t ino, uint64_t fh)
|
||||||
{
|
{
|
||||||
EXPECT_CALL(*m_mock, process(
|
EXPECT_CALL(*m_mock, process(
|
||||||
|
@ -150,6 +150,14 @@ class FuseTest : public ::testing::Test {
|
|||||||
void expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
|
void expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
|
||||||
uint64_t osize, const void *contents);
|
uint64_t osize, const void *contents);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an expectation that FUSE_READIR will be called any number of
|
||||||
|
* times on the given ino with the given offset, returning (by copy)
|
||||||
|
* the provided entries
|
||||||
|
*/
|
||||||
|
void expect_readdir(uint64_t ino, uint64_t off,
|
||||||
|
std::vector<struct dirent> &ents);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an expectation that FUSE_RELEASE will be called exactly once
|
* Create an expectation that FUSE_RELEASE will be called exactly once
|
||||||
* for the given inode and filehandle, returning success
|
* for the given inode and filehandle, returning success
|
||||||
|
Loading…
Reference in New Issue
Block a user