#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");
#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;
static const char *p9fs_opts[] = {
"from", "trans", "access", NULL
};
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;
if (!(vp->v_vflag & VV_ROOT)) {
free(node->inode.i_name, M_TEMP);
uma_zfree(p9fs_node_zone, node);
}
*npp = NULL;
}
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);
p9fs_getattr_zone = uma_zcreate("p9fs getattr zone",
sizeof(struct p9_stat_dotl), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
p9fs_setattr_zone = uma_zcreate("p9fs setattr zone",
sizeof(struct p9_iattr_dotl), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
p9fs_pbuf_zone = pbuf_zsecond_create("p9fs pbuf zone", nswbuf / 2);
p9fs_io_buffer_zone = uma_zcreate("p9fs io_buffer zone",
P9FS_MTU, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
return (0);
}
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);
}
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++) {
error = vflush(mp, 1, flags, curthread);
if (error == 0 || (mntflags & MNT_FORCE) == 0)
break;
error = tsleep(&error, PSOCK, "p9unmnt", 1);
if (error == EINTR)
break;
error = EBUSY;
}
if (error != 0)
goto out;
p9fs_close_session(mp);
free(vmp, M_P9MNT);
mp->mnt_data = NULL;
return (error);
out:
vses->clnt->trans_status = P9FS_CONNECT;
return (error);
}
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);
}
void
p9fs_destroy_node(struct p9fs_node **npp)
{
struct p9fs_node *np;
np = *npp;
if (np == NULL)
return;
P9FS_VFID_LOCK_DESTROY(np);
P9FS_VOFID_LOCK_DESTROY(np);
p9fs_dispose_node(&np);
}
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;
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;
vfs_hash_remove(vp);
node->flags |= P9FS_NODE_DELETED;
vput(vp);
*vpp = NULL;
vp = NULL;
} else {
*vpp = vp;
p9_client_clunk(fid);
return (0);
}
}
if ((flags & LK_TYPE_MASK) == LK_SHARED) {
flags &= ~LK_TYPE_MASK;
flags |= LK_EXCLUSIVE;
}
if ((error = getnewvnode("p9fs", mp, &p9fs_vnops, &vp)) != 0) {
*vpp = NULL;
P9_DEBUG(ERROR, "%s: getnewvnode failed: %d\n", __func__, error);
return (error);
}
if (np == NULL) {
np = uma_zalloc(p9fs_node_zone, M_WAITOK | M_ZERO);
P9FS_VFID_LOCK_INIT(np);
STAILQ_INIT(&np->vfid_list);
p9fs_fid_add(np, fid, VFID);
P9FS_VOFID_LOCK_INIT(np);
STAILQ_INIT(&np->vofid_list);
vref(P9FS_NTOV(parent));
np->parent = parent;
np->p9fs_ses = vses;
inode = &np->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;
vp->v_vflag |= VV_ROOT;
vref(vp);
}
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) {
goto out;
}
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);
vn_set_state(vp, VSTATE_CONSTRUCTED);
*vpp = vp;
} else {
if (!IS_ROOT(np)) {
p9fs_destroy_node(&np);
}
}
return (0);
out:
if (!IS_ROOT(np)) {
p9fs_destroy_node(&np);
}
if (error_reload) {
vput(vp);
}
*vpp = NULL;
return (error);
}
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;
if (vfs_filteropt(mp->mnt_optnew, p9fs_opts))
return (EINVAL);
error = vfs_getopt(mp->mnt_optnew, "from", (void **)&from, &len);
if (error != 0 || from[len - 1] != '\0')
return (EINVAL);
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;
mp->mnt_iosize_max = PAGE_SIZE;
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__);
return (0);
out:
P9_DEBUG(ERROR, "%s: Mount Failed \n", __func__);
if (vmp != NULL) {
free(vmp, M_P9MNT);
mp->mnt_data = NULL;
}
return (error);
}
static int
p9fs_mount(struct mount *mp)
{
int error;
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);
}
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) {
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);
}
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;
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 {
buf->f_bsize = PAGE_SIZE;
buf->f_iosize = buf->f_bsize;
}
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);