/*- * 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 #include #include #include #include #include #include #include #include #include #include #include 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);