root/sys/fs/p9fs/p9fs_vfsops.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", "msize", 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);

        p9_init_zones();

        return (0);
}

/* Destroy all the allocated memory */
static int
p9fs_uninit(struct vfsconf *vfsp)
{

        p9_destroy_zones();

        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);
                        P9FS_NODE_SETF(node, P9FS_NODE_DELETED);

                        vput(vp);
                        *vpp = NULL;
                        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 = NULL;
                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);
        if (vp->v_type != VFIFO)
                VN_LOCK_ASHARE(vp);
        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);
                P9FS_NODE_SETF(np, P9FS_NODE_IN_SESSION);
                P9FS_UNLOCK(vses);
                vn_set_state(vp, VSTATE_CONSTRUCTED);
                *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 = NULL;
        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_NODE_SETF(p9fs_root, P9FS_NODE_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 = NULL;
                        return (error);
                }
        }

        error = p9fs_vget_common(mp, np, lkflags, np, vfid, vpp, NULL);
        if (error != 0) {
                *vpp = NULL;
                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);