mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-07 00:50:50 +00:00
daa2c99c89
Mostly copied from smbfs. This driver in its current state has the exact same issue that prevents the generic putpages implementation from working. Sponsored by: https://www.patreon.com/valpackett Reviewed by: dfr Differential Revision: https://reviews.freebsd.org/D45639 MFC after: 3 months
609 lines
15 KiB
C
609 lines
15 KiB
C
/*-
|
|
* Copyright (c) 2017-2020 Juniper Networks, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* 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 ``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 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 consists of all the VFS interactions of VFS ops which include
|
|
* mount, unmount, initilaize etc. for p9fs.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/fnv_hash.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/buf.h>
|
|
#include <vm/uma.h>
|
|
|
|
#include <fs/p9fs/p9fs_proto.h>
|
|
#include <fs/p9fs/p9_client.h>
|
|
#include <fs/p9fs/p9_debug.h>
|
|
#include <fs/p9fs/p9fs.h>
|
|
|
|
SYSCTL_NODE(_vfs, OID_AUTO, p9fs, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
|
|
"Plan 9 filesystem");
|
|
|
|
/* This count is static now. Can be made tunable later */
|
|
#define P9FS_FLUSH_RETRIES 10
|
|
|
|
static MALLOC_DEFINE(M_P9MNT, "p9fs_mount", "Mount structures for p9fs");
|
|
static uma_zone_t p9fs_node_zone;
|
|
uma_zone_t p9fs_io_buffer_zone;
|
|
uma_zone_t p9fs_getattr_zone;
|
|
uma_zone_t p9fs_setattr_zone;
|
|
uma_zone_t p9fs_pbuf_zone;
|
|
extern struct vop_vector p9fs_vnops;
|
|
|
|
/* option parsing */
|
|
static const char *p9fs_opts[] = {
|
|
"from", "trans", "access", NULL
|
|
};
|
|
|
|
/* Dispose p9fs node, freeing it to the UMA zone */
|
|
void
|
|
p9fs_dispose_node(struct p9fs_node **npp)
|
|
{
|
|
struct p9fs_node *node;
|
|
struct vnode *vp;
|
|
|
|
node = *npp;
|
|
|
|
if (node == NULL)
|
|
return;
|
|
|
|
if (node->parent && node->parent != node) {
|
|
vrele(P9FS_NTOV(node->parent));
|
|
}
|
|
|
|
P9_DEBUG(VOPS, "%s: node: %p\n", __func__, *npp);
|
|
|
|
vp = P9FS_NTOV(node);
|
|
vp->v_data = NULL;
|
|
|
|
/* Free our associated memory */
|
|
if (!(vp->v_vflag & VV_ROOT)) {
|
|
free(node->inode.i_name, M_TEMP);
|
|
uma_zfree(p9fs_node_zone, node);
|
|
}
|
|
|
|
*npp = NULL;
|
|
}
|
|
|
|
/* Initialize memory allocation */
|
|
static int
|
|
p9fs_init(struct vfsconf *vfsp)
|
|
{
|
|
|
|
p9fs_node_zone = uma_zcreate("p9fs node zone",
|
|
sizeof(struct p9fs_node), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
|
|
/* Create the getattr_dotl zone */
|
|
p9fs_getattr_zone = uma_zcreate("p9fs getattr zone",
|
|
sizeof(struct p9_stat_dotl), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
|
|
/* Create the setattr_dotl zone */
|
|
p9fs_setattr_zone = uma_zcreate("p9fs setattr zone",
|
|
sizeof(struct p9_iattr_dotl), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
|
|
/* Create the putpages zone */
|
|
p9fs_pbuf_zone = pbuf_zsecond_create("p9fs pbuf zone", nswbuf / 2);
|
|
|
|
/*
|
|
* Create the io_buffer zone pool to keep things simpler in case of
|
|
* multiple threads. Each thread works with its own so there is no
|
|
* contention.
|
|
*/
|
|
p9fs_io_buffer_zone = uma_zcreate("p9fs io_buffer zone",
|
|
P9FS_MTU, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Destroy all the allocated memory */
|
|
static int
|
|
p9fs_uninit(struct vfsconf *vfsp)
|
|
{
|
|
|
|
uma_zdestroy(p9fs_node_zone);
|
|
uma_zdestroy(p9fs_io_buffer_zone);
|
|
uma_zdestroy(p9fs_getattr_zone);
|
|
uma_zdestroy(p9fs_setattr_zone);
|
|
uma_zdestroy(p9fs_pbuf_zone);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Function to umount p9fs */
|
|
static int
|
|
p9fs_unmount(struct mount *mp, int mntflags)
|
|
{
|
|
struct p9fs_mount *vmp;
|
|
struct p9fs_session *vses;
|
|
int error, flags, i;
|
|
|
|
error = 0;
|
|
flags = 0;
|
|
vmp = VFSTOP9(mp);
|
|
if (vmp == NULL)
|
|
return (0);
|
|
|
|
vses = &vmp->p9fs_session;
|
|
if (mntflags & MNT_FORCE)
|
|
flags |= FORCECLOSE;
|
|
|
|
p9fs_prepare_to_close(mp);
|
|
for (i = 0; i < P9FS_FLUSH_RETRIES; i++) {
|
|
|
|
/* Flush everything on this mount point.*/
|
|
error = vflush(mp, 1, flags, curthread);
|
|
|
|
if (error == 0 || (mntflags & MNT_FORCE) == 0)
|
|
break;
|
|
/* Sleep until interrupted or 1 tick expires. */
|
|
error = tsleep(&error, PSOCK, "p9unmnt", 1);
|
|
if (error == EINTR)
|
|
break;
|
|
error = EBUSY;
|
|
}
|
|
|
|
if (error != 0)
|
|
goto out;
|
|
p9fs_close_session(mp);
|
|
/* Cleanup the mount structure. */
|
|
free(vmp, M_P9MNT);
|
|
mp->mnt_data = NULL;
|
|
return (error);
|
|
out:
|
|
/* Restore the flag in case of error */
|
|
vses->clnt->trans_status = P9FS_CONNECT;
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Compare qid stored in p9fs node
|
|
* Return 1 if does not match otherwise return 0
|
|
*/
|
|
int
|
|
p9fs_node_cmp(struct vnode *vp, void *arg)
|
|
{
|
|
struct p9fs_node *np;
|
|
struct p9_qid *qid;
|
|
|
|
np = vp->v_data;
|
|
qid = (struct p9_qid *)arg;
|
|
|
|
if (np == NULL)
|
|
return (1);
|
|
|
|
if (np->vqid.qid_path == qid->path) {
|
|
if (vp->v_vflag & VV_ROOT)
|
|
return (0);
|
|
else if (np->vqid.qid_mode == qid->type &&
|
|
np->vqid.qid_version == qid->version)
|
|
return (0);
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Cleanup p9fs node
|
|
* - Destroy the FID LIST locks
|
|
* - Dispose all node knowledge
|
|
*/
|
|
void
|
|
p9fs_destroy_node(struct p9fs_node **npp)
|
|
{
|
|
struct p9fs_node *np;
|
|
|
|
np = *npp;
|
|
|
|
if (np == NULL)
|
|
return;
|
|
|
|
/* Destroy the FID LIST locks */
|
|
P9FS_VFID_LOCK_DESTROY(np);
|
|
P9FS_VOFID_LOCK_DESTROY(np);
|
|
|
|
/* Dispose all node knowledge.*/
|
|
p9fs_dispose_node(&np);
|
|
}
|
|
|
|
/*
|
|
* Common code used across p9fs to return vnode for the file represented
|
|
* by the fid.
|
|
* Lookup for the vnode in hash_list. This lookup is based on the qid path
|
|
* which is unique to a file. p9fs_node_cmp is called in this lookup process.
|
|
* I. If the vnode we are looking for is found in the hash list
|
|
* 1. Check if the vnode is a valid vnode by reloading its stats
|
|
* a. if the reloading of the vnode stats returns error then remove the
|
|
* vnode from hash list and return
|
|
* b. If reloading of vnode stats returns without any error then, clunk the
|
|
* new fid which was created for the vnode as we know that the vnode
|
|
* already has a fid associated with it and return the vnode.
|
|
* This is to avoid fid leaks
|
|
* II. If vnode is not found in the hash list then, create new vnode, p9fs
|
|
* node and return the vnode
|
|
*/
|
|
int
|
|
p9fs_vget_common(struct mount *mp, struct p9fs_node *np, int flags,
|
|
struct p9fs_node *parent, struct p9_fid *fid, struct vnode **vpp,
|
|
char *name)
|
|
{
|
|
struct p9fs_mount *vmp;
|
|
struct p9fs_session *vses;
|
|
struct vnode *vp;
|
|
struct p9fs_node *node;
|
|
struct thread *td;
|
|
uint32_t hash;
|
|
int error, error_reload = 0;
|
|
struct p9fs_inode *inode;
|
|
|
|
td = curthread;
|
|
vmp = VFSTOP9(mp);
|
|
vses = &vmp->p9fs_session;
|
|
|
|
/* Look for vp in the hash_list */
|
|
hash = fnv_32_buf(&fid->qid.path, sizeof(uint64_t), FNV1_32_INIT);
|
|
error = vfs_hash_get(mp, hash, flags, td, &vp, p9fs_node_cmp,
|
|
&fid->qid);
|
|
if (error != 0)
|
|
return (error);
|
|
else if (vp != NULL) {
|
|
if (vp->v_vflag & VV_ROOT) {
|
|
if (np == NULL)
|
|
p9_client_clunk(fid);
|
|
*vpp = vp;
|
|
return (0);
|
|
}
|
|
error = p9fs_reload_stats_dotl(vp, curthread->td_ucred);
|
|
if (error != 0) {
|
|
node = vp->v_data;
|
|
/* Remove stale vnode from hash list */
|
|
vfs_hash_remove(vp);
|
|
node->flags |= P9FS_NODE_DELETED;
|
|
|
|
vput(vp);
|
|
*vpp = NULLVP;
|
|
vp = NULL;
|
|
} else {
|
|
*vpp = vp;
|
|
/* Clunk the new fid if not root */
|
|
p9_client_clunk(fid);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We must promote to an exclusive lock for vnode creation. This
|
|
* can happen if lookup is passed LOCKSHARED.
|
|
*/
|
|
if ((flags & LK_TYPE_MASK) == LK_SHARED) {
|
|
flags &= ~LK_TYPE_MASK;
|
|
flags |= LK_EXCLUSIVE;
|
|
}
|
|
|
|
/* Allocate a new vnode. */
|
|
if ((error = getnewvnode("p9fs", mp, &p9fs_vnops, &vp)) != 0) {
|
|
*vpp = NULLVP;
|
|
P9_DEBUG(ERROR, "%s: getnewvnode failed: %d\n", __func__, error);
|
|
return (error);
|
|
}
|
|
|
|
/* If we dont have it, create one. */
|
|
if (np == NULL) {
|
|
np = uma_zalloc(p9fs_node_zone, M_WAITOK | M_ZERO);
|
|
/* Initialize the VFID list */
|
|
P9FS_VFID_LOCK_INIT(np);
|
|
STAILQ_INIT(&np->vfid_list);
|
|
p9fs_fid_add(np, fid, VFID);
|
|
|
|
/* Initialize the VOFID list */
|
|
P9FS_VOFID_LOCK_INIT(np);
|
|
STAILQ_INIT(&np->vofid_list);
|
|
|
|
vref(P9FS_NTOV(parent));
|
|
np->parent = parent;
|
|
np->p9fs_ses = vses; /* Map the current session */
|
|
inode = &np->inode;
|
|
/*Fill the name of the file in inode */
|
|
inode->i_name = malloc(strlen(name)+1, M_TEMP, M_NOWAIT | M_ZERO);
|
|
strlcpy(inode->i_name, name, strlen(name)+1);
|
|
} else {
|
|
vp->v_type = VDIR; /* root vp is a directory */
|
|
vp->v_vflag |= VV_ROOT;
|
|
vref(vp); /* Increment a reference on root vnode during mount */
|
|
}
|
|
|
|
vp->v_data = np;
|
|
np->v_node = vp;
|
|
inode = &np->inode;
|
|
inode->i_qid_path = fid->qid.path;
|
|
P9FS_SET_LINKS(inode);
|
|
|
|
lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL);
|
|
error = insmntque(vp, mp);
|
|
if (error != 0) {
|
|
/*
|
|
* vput(vp) is already called from insmntque_stddtr().
|
|
* Just goto 'out' to dispose the node.
|
|
*/
|
|
goto out;
|
|
}
|
|
|
|
/* Init the vnode with the disk info*/
|
|
error = p9fs_reload_stats_dotl(vp, curthread->td_ucred);
|
|
if (error != 0) {
|
|
error_reload = 1;
|
|
goto out;
|
|
}
|
|
|
|
error = vfs_hash_insert(vp, hash, flags, td, vpp,
|
|
p9fs_node_cmp, &fid->qid);
|
|
if (error != 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (*vpp == NULL) {
|
|
P9FS_LOCK(vses);
|
|
STAILQ_INSERT_TAIL(&vses->virt_node_list, np, p9fs_node_next);
|
|
np->flags |= P9FS_NODE_IN_SESSION;
|
|
P9FS_UNLOCK(vses);
|
|
|
|
*vpp = vp;
|
|
} else {
|
|
/*
|
|
* Returning matching vp found in hashlist.
|
|
* So cleanup the np allocated above in this context.
|
|
*/
|
|
if (!IS_ROOT(np)) {
|
|
p9fs_destroy_node(&np);
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
out:
|
|
/* Something went wrong, dispose the node */
|
|
if (!IS_ROOT(np)) {
|
|
p9fs_destroy_node(&np);
|
|
}
|
|
|
|
if (error_reload) {
|
|
vput(vp);
|
|
}
|
|
|
|
*vpp = NULLVP;
|
|
return (error);
|
|
}
|
|
|
|
/* Main mount function for 9pfs */
|
|
static int
|
|
p9_mount(struct mount *mp)
|
|
{
|
|
struct p9_fid *fid;
|
|
struct p9fs_mount *vmp;
|
|
struct p9fs_session *vses;
|
|
struct p9fs_node *p9fs_root;
|
|
int error;
|
|
char *from;
|
|
int len;
|
|
|
|
/* Verify the validity of mount options */
|
|
if (vfs_filteropt(mp->mnt_optnew, p9fs_opts))
|
|
return (EINVAL);
|
|
|
|
/* Extract NULL terminated mount tag from mount options */
|
|
error = vfs_getopt(mp->mnt_optnew, "from", (void **)&from, &len);
|
|
if (error != 0 || from[len - 1] != '\0')
|
|
return (EINVAL);
|
|
|
|
/* Allocate and initialize the private mount structure. */
|
|
vmp = malloc(sizeof (struct p9fs_mount), M_P9MNT, M_WAITOK | M_ZERO);
|
|
mp->mnt_data = vmp;
|
|
vmp->p9fs_mountp = mp;
|
|
vmp->mount_tag = from;
|
|
vmp->mount_tag_len = len;
|
|
vses = &vmp->p9fs_session;
|
|
vses->p9fs_mount = mp;
|
|
p9fs_root = &vses->rnp;
|
|
/* Hardware iosize from the Qemu */
|
|
mp->mnt_iosize_max = PAGE_SIZE;
|
|
/*
|
|
* Init the session for the p9fs root. This creates a new root fid and
|
|
* attaches the client and server.
|
|
*/
|
|
fid = p9fs_init_session(mp, &error);
|
|
if (fid == NULL) {
|
|
goto out;
|
|
}
|
|
|
|
P9FS_VFID_LOCK_INIT(p9fs_root);
|
|
STAILQ_INIT(&p9fs_root->vfid_list);
|
|
p9fs_fid_add(p9fs_root, fid, VFID);
|
|
P9FS_VOFID_LOCK_INIT(p9fs_root);
|
|
STAILQ_INIT(&p9fs_root->vofid_list);
|
|
p9fs_root->parent = p9fs_root;
|
|
p9fs_root->flags |= P9FS_ROOT;
|
|
p9fs_root->p9fs_ses = vses;
|
|
vfs_getnewfsid(mp);
|
|
strlcpy(mp->mnt_stat.f_mntfromname, from,
|
|
sizeof(mp->mnt_stat.f_mntfromname));
|
|
MNT_ILOCK(mp);
|
|
mp->mnt_flag |= MNT_LOCAL;
|
|
mp->mnt_kern_flag |= MNTK_LOOKUP_SHARED | MNTK_EXTENDED_SHARED;
|
|
MNT_IUNLOCK(mp);
|
|
P9_DEBUG(VOPS, "%s: Mount successful\n", __func__);
|
|
/* Mount structures created. */
|
|
|
|
return (0);
|
|
out:
|
|
P9_DEBUG(ERROR, "%s: Mount Failed \n", __func__);
|
|
if (vmp != NULL) {
|
|
free(vmp, M_P9MNT);
|
|
mp->mnt_data = NULL;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/* Mount entry point */
|
|
static int
|
|
p9fs_mount(struct mount *mp)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* Minimal support for MNT_UPDATE - allow changing from
|
|
* readonly.
|
|
*/
|
|
if (mp->mnt_flag & MNT_UPDATE) {
|
|
if ((mp->mnt_flag & MNT_RDONLY) && !vfs_flagopt(mp->mnt_optnew, "ro", NULL, 0)) {
|
|
mp->mnt_flag &= ~MNT_RDONLY;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
error = p9_mount(mp);
|
|
if (error != 0)
|
|
(void) p9fs_unmount(mp, MNT_FORCE);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Retrieve the root vnode of this mount. After filesystem is mounted, the root
|
|
* vnode is created for the first time. Subsequent calls to p9fs root will
|
|
* return the same vnode created during mount.
|
|
*/
|
|
static int
|
|
p9fs_root(struct mount *mp, int lkflags, struct vnode **vpp)
|
|
{
|
|
struct p9fs_mount *vmp;
|
|
struct p9fs_node *np;
|
|
struct p9_client *clnt;
|
|
struct p9_fid *vfid;
|
|
int error;
|
|
|
|
vmp = VFSTOP9(mp);
|
|
np = &vmp->p9fs_session.rnp;
|
|
clnt = vmp->p9fs_session.clnt;
|
|
error = 0;
|
|
|
|
P9_DEBUG(VOPS, "%s: node=%p name=%s\n",__func__, np, np->inode.i_name);
|
|
|
|
vfid = p9fs_get_fid(clnt, np, curthread->td_ucred, VFID, -1, &error);
|
|
|
|
if (error != 0) {
|
|
/* for root use the nobody user's fid as vfid.
|
|
* This is used while unmounting as root when non-root
|
|
* user has mounted p9fs
|
|
*/
|
|
if (vfid == NULL && clnt->trans_status == P9FS_BEGIN_DISCONNECT)
|
|
vfid = vmp->p9fs_session.mnt_fid;
|
|
else {
|
|
*vpp = NULLVP;
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
error = p9fs_vget_common(mp, np, lkflags, np, vfid, vpp, NULL);
|
|
if (error != 0) {
|
|
*vpp = NULLVP;
|
|
return (error);
|
|
}
|
|
np->v_node = *vpp;
|
|
return (error);
|
|
}
|
|
|
|
/* Retrieve the file system statistics */
|
|
static int
|
|
p9fs_statfs(struct mount *mp __unused, struct statfs *buf)
|
|
{
|
|
struct p9fs_mount *vmp;
|
|
struct p9fs_node *np;
|
|
struct p9_client *clnt;
|
|
struct p9_fid *vfid;
|
|
struct p9_statfs statfs;
|
|
int res, error;
|
|
|
|
vmp = VFSTOP9(mp);
|
|
np = &vmp->p9fs_session.rnp;
|
|
clnt = vmp->p9fs_session.clnt;
|
|
error = 0;
|
|
|
|
vfid = p9fs_get_fid(clnt, np, curthread->td_ucred, VFID, -1, &error);
|
|
if (error != 0) {
|
|
return (error);
|
|
}
|
|
|
|
res = p9_client_statfs(vfid, &statfs);
|
|
|
|
if (res == 0) {
|
|
buf->f_type = statfs.type;
|
|
/*
|
|
* We have a limit of 4k irrespective of what the
|
|
* Qemu server can do.
|
|
*/
|
|
if (statfs.bsize > PAGE_SIZE)
|
|
buf->f_bsize = PAGE_SIZE;
|
|
else
|
|
buf->f_bsize = statfs.bsize;
|
|
|
|
buf->f_iosize = buf->f_bsize;
|
|
buf->f_blocks = statfs.blocks;
|
|
buf->f_bfree = statfs.bfree;
|
|
buf->f_bavail = statfs.bavail;
|
|
buf->f_files = statfs.files;
|
|
buf->f_ffree = statfs.ffree;
|
|
}
|
|
else {
|
|
/* Atleast set these if stat fail */
|
|
buf->f_bsize = PAGE_SIZE;
|
|
buf->f_iosize = buf->f_bsize; /* XXX */
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
p9fs_fhtovp(struct mount *mp, struct fid *fhp, int flags, struct vnode **vpp)
|
|
{
|
|
|
|
return (EINVAL);
|
|
}
|
|
|
|
struct vfsops p9fs_vfsops = {
|
|
.vfs_init = p9fs_init,
|
|
.vfs_uninit = p9fs_uninit,
|
|
.vfs_mount = p9fs_mount,
|
|
.vfs_unmount = p9fs_unmount,
|
|
.vfs_root = p9fs_root,
|
|
.vfs_statfs = p9fs_statfs,
|
|
.vfs_fhtovp = p9fs_fhtovp,
|
|
};
|
|
|
|
VFS_SET(p9fs_vfsops, p9fs, VFCF_JAIL);
|
|
MODULE_VERSION(p9fs, 1);
|