root/sys/fs/efs/efs_vnops.c
/*      $NetBSD: efs_vnops.c,v 1.44 2022/08/06 18:26:41 andvar Exp $    */

/*
 * Copyright (c) 2006 Stephen M. Rumble <rumble@ephemeral.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: efs_vnops.c,v 1.44 2022/08/06 18:26:41 andvar Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/mount.h>
#include <sys/malloc.h>
#include <sys/namei.h>
#include <sys/dirent.h>
#include <sys/lockf.h>
#include <sys/unistd.h>
#include <sys/buf.h>

#include <miscfs/genfs/genfs.h>
#include <miscfs/genfs/genfs_node.h>
#include <miscfs/fifofs/fifo.h>
#include <miscfs/specfs/specdev.h>

#include <fs/efs/efs.h>
#include <fs/efs/efs_sb.h>
#include <fs/efs/efs_dir.h>
#include <fs/efs/efs_genfs.h>
#include <fs/efs/efs_mount.h>
#include <fs/efs/efs_extent.h>
#include <fs/efs/efs_dinode.h>
#include <fs/efs/efs_inode.h>
#include <fs/efs/efs_subr.h>

MALLOC_DECLARE(M_EFSTMP);

/*
 * Lookup a pathname component in the given directory.
 *
 * Returns 0 on success.
 */
static int
efs_lookup(void *v)
{
        struct vop_lookup_v2_args /* {
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
        } */ *ap = v;
        struct componentname *cnp = ap->a_cnp;
        struct vnode *vp;
        ino_t ino;
        int err, nameiop = cnp->cn_nameiop;

        /* ensure that the directory can be accessed first */
        err = VOP_ACCESS(ap->a_dvp, VEXEC, cnp->cn_cred);
        if (err)
                return (err);

        if (cache_lookup(ap->a_dvp, cnp->cn_nameptr, cnp->cn_namelen,
                         cnp->cn_nameiop, cnp->cn_flags, NULL, ap->a_vpp)) {
                return *ap->a_vpp == NULLVP ? ENOENT : 0;
        }

        /*
         * Handle the lookup types: '.' or everything else.
         */
        if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
                vref(ap->a_dvp);
                *ap->a_vpp = ap->a_dvp;
        } else {
                err = efs_inode_lookup(VFSTOEFS(ap->a_dvp->v_mount),
                    EFS_VTOI(ap->a_dvp), ap->a_cnp, &ino);
                if (err) {
                        if (cnp->cn_flags & ISDOTDOT)
                                return (err);
                        if (err == ENOENT && nameiop != CREATE)
                                cache_enter(ap->a_dvp, NULL, cnp->cn_nameptr,
                                            cnp->cn_namelen, cnp->cn_flags);
                        if (err == ENOENT && (nameiop == CREATE ||
                            nameiop == RENAME)) {
                                err = VOP_ACCESS(ap->a_dvp, VWRITE,
                                    cnp->cn_cred);
                                if (err)
                                        return (err);
                                return (EJUSTRETURN);
                        }
                        return (err);
                }
                err = vcache_get(ap->a_dvp->v_mount, &ino, sizeof(ino), &vp);
                if (err)
                        return (err);
                *ap->a_vpp = vp;
        }

        cache_enter(ap->a_dvp, *ap->a_vpp, cnp->cn_nameptr, cnp->cn_namelen,
                    cnp->cn_flags);

        return 0;
}

static int
efs_check_possible(struct vnode *vp, struct efs_inode *eip, accmode_t accmode)
{

        if ((accmode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY))
                return (EROFS);

        return 0;
}

/*
 * Determine the accessibility of a file based on the permissions allowed by the
 * specified credentials.
 *
 * Returns 0 on success.
 */
static int
efs_check_permitted(struct vnode *vp, struct efs_inode *eip, accmode_t accmode,
    kauth_cred_t cred)
{

        return kauth_authorize_vnode(cred, KAUTH_ACCESS_ACTION(accmode,
            vp->v_type, eip->ei_mode), vp, NULL, genfs_can_access(vp,
            cred, eip->ei_uid, eip->ei_gid, eip->ei_mode, NULL, accmode));
}

static int
efs_access(void *v)
{
        struct vop_access_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                accmode_t a_accmode;
                struct ucred *a_cred;
        } */ *ap = v;
        struct vnode *vp = ap->a_vp;
        struct efs_inode *eip = EFS_VTOI(vp);
        int error;

        error = efs_check_possible(vp, eip, ap->a_accmode);
        if (error)
                return error;

        error = efs_check_permitted(vp, eip, ap->a_accmode, ap->a_cred);

        return error;
}

/*
 * Get specific vnode attributes on a file. See vattr(9).
 *
 * Returns 0 on success.
 */
static int
efs_getattr(void *v)
{
        struct vop_getattr_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct vattr *a_vap;
                struct ucred *a_cred;
        } */ *ap = v;

        struct vattr *vap = ap->a_vap;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);

        vattr_null(ap->a_vap);
        vap->va_type            = ap->a_vp->v_type;
        vap->va_mode            = eip->ei_mode;
        vap->va_nlink           = eip->ei_nlink;
        vap->va_uid             = eip->ei_uid;
        vap->va_gid             = eip->ei_gid;
        vap->va_fsid            = ap->a_vp->v_mount->mnt_stat.f_fsid;
        vap->va_fileid          = eip->ei_number;
        vap->va_size            = eip->ei_size;

        if (ap->a_vp->v_type == VBLK)
                vap->va_blocksize = BLKDEV_IOSIZE;
        else if (ap->a_vp->v_type == VCHR)
                vap->va_blocksize = MAXBSIZE;
        else
                vap->va_blocksize = EFS_BB_SIZE;

        vap->va_atime.tv_sec    = eip->ei_atime;
        vap->va_mtime.tv_sec    = eip->ei_mtime;
        vap->va_ctime.tv_sec    = eip->ei_ctime;
/*      vap->va_birthtime       = */
        vap->va_gen             = eip->ei_gen;
        vap->va_flags           = ap->a_vp->v_vflag |
            ap->a_vp->v_iflag | ap->a_vp->v_uflag;

        if (ap->a_vp->v_type == VBLK || ap->a_vp->v_type == VCHR) {
                uint32_t dmaj, dmin;

                if (be16toh(eip->ei_di.di_odev) != EFS_DINODE_ODEV_INVALID) {
                        dmaj = EFS_DINODE_ODEV_MAJ(be16toh(eip->ei_di.di_odev));
                        dmin = EFS_DINODE_ODEV_MIN(be16toh(eip->ei_di.di_odev));
                } else {
                        dmaj = EFS_DINODE_NDEV_MAJ(be32toh(eip->ei_di.di_ndev));
                        dmin = EFS_DINODE_NDEV_MIN(be32toh(eip->ei_di.di_ndev));
                }

                vap->va_rdev = makedev(dmaj, dmin);
        }

        vap->va_bytes           = eip->ei_size;
/*      vap->va_filerev         = */
/*      vap->va_vaflags         = */
        
        return (0);
}

/*
 * Read a file.
 *
 * Returns 0 on success.
 */
static int
efs_read(void *v)
{
        struct vop_read_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct uio *a_uio;
                int a_ioflag;
                struct ucred *a_cred;
        } */ *ap = v;
        struct efs_extent ex;
        struct efs_extent_iterator exi;
        struct uio *uio = ap->a_uio;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);
        off_t start;
        vsize_t len;
        int err, ret;
        const int advice = IO_ADV_DECODE(ap->a_ioflag);

        if (ap->a_vp->v_type == VDIR)
                return (EISDIR);

        if (ap->a_vp->v_type != VREG)
                return (EINVAL);

        efs_extent_iterator_init(&exi, eip, uio->uio_offset);
        ret = efs_extent_iterator_next(&exi, &ex);
        while (ret == 0) {
                if (uio->uio_offset < 0 || uio->uio_offset >= eip->ei_size ||
                    uio->uio_resid == 0)
                        break;

                start = ex.ex_offset * EFS_BB_SIZE;
                len   = ex.ex_length * EFS_BB_SIZE;

                if (!(uio->uio_offset >= start &&
                      uio->uio_offset < (start + len))) {
                        ret = efs_extent_iterator_next(&exi, &ex);
                        continue;
                }

                start = uio->uio_offset - start;

                len = MIN(len - start, uio->uio_resid);
                len = MIN(len, eip->ei_size - uio->uio_offset);

                err = ubc_uiomove(&ap->a_vp->v_uobj, uio, len, advice,
                    UBC_READ | UBC_PARTIALOK | UBC_VNODE_FLAGS(ap->a_vp));
                if (err) {
                        EFS_DPRINTF(("efs_read: uiomove error %d\n",
                            err));
                        return (err);
                }
        }

        return ((ret == -1) ? 0 : ret);
}

static int
efs_readdir(void *v)
{
        struct vop_readdir_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
                int *a_eofflag;
                off_t **a_cookies;
                int *a_ncookies;
        } */ *ap = v;
        struct dirent *dp;
        struct efs_dinode edi;
        struct efs_extent ex;
        struct efs_extent_iterator exi;
        struct buf *bp;
        struct efs_dirent *de;
        struct efs_dirblk *db;
        struct uio *uio = ap->a_uio;
        struct efs_inode *ei = EFS_VTOI(ap->a_vp);
        off_t *cookies = NULL;
        off_t offset;
        int i, j, err, ret, s, slot, ncookies, maxcookies = 0;

        if (ap->a_vp->v_type != VDIR)
                return (ENOTDIR);

        if (ap->a_eofflag != NULL)
                *ap->a_eofflag = false;

        if (ap->a_ncookies != NULL) {
                ncookies = 0;
                maxcookies =
                    uio->uio_resid / _DIRENT_MINSIZE((struct dirent *)0);
                cookies = malloc(maxcookies * sizeof(off_t), M_TEMP, M_WAITOK);
        }

        dp = malloc(sizeof(struct dirent), M_EFSTMP, M_WAITOK | M_ZERO);

        offset = 0;
        efs_extent_iterator_init(&exi, ei, 0);
        while ((ret = efs_extent_iterator_next(&exi, &ex)) == 0) {
                for (i = 0; i < ex.ex_length; i++) {
                        err = efs_bread(VFSTOEFS(ap->a_vp->v_mount),
                            ex.ex_bn + i, NULL, &bp);
                        if (err) {
                                goto exit_err;
                        }

                        db = (struct efs_dirblk *)bp->b_data;

                        if (be16toh(db->db_magic) != EFS_DIRBLK_MAGIC) {
                                printf("efs_readdir: bad dirblk\n");
                                brelse(bp, 0);
                                continue;
                        }

                        for (j = 0; j < db->db_slots; j++) {
                                slot = EFS_DIRENT_OFF_EXPND(db->db_space[j]);
                                if (slot == EFS_DIRBLK_SLOT_FREE)
                                        continue;

                                if (!EFS_DIRENT_OFF_VALID(slot)) {
                                        printf("efs_readdir: bad dirent\n");
                                        continue;
                                }

                                de = EFS_DIRBLK_TO_DIRENT(db, slot);
                                s = _DIRENT_RECLEN(dp, de->de_namelen);

                                if (offset < uio->uio_offset) {
                                        offset += s;
                                        continue;
                                }

                                /* XXX - shouldn't happen, right? */
                                if (offset > uio->uio_offset ||
                                    s > uio->uio_resid) {
                                        brelse(bp, 0);
                                        goto exit_ok;
                                }

                                /* de_namelen is uint8_t, d.d_name is 512b */
                                KASSERT(sizeof(dp->d_name)-de->de_namelen > 0);
                                dp->d_fileno = be32toh(de->de_inumber);
                                dp->d_reclen = s;
                                dp->d_namlen = de->de_namelen;
                                memcpy(dp->d_name, de->de_name,
                                    de->de_namelen);
                                dp->d_name[de->de_namelen] = '\0';

                                /* look up inode to get type */
                                err = efs_read_inode(
                                    VFSTOEFS(ap->a_vp->v_mount),
                                    dp->d_fileno, NULL, &edi);
                                if (err) {
                                        brelse(bp, 0);
                                        goto exit_err;
                                }

                                switch (be16toh(edi.di_mode) & EFS_IFMT) {
                                case EFS_IFIFO:
                                        dp->d_type = DT_FIFO;
                                        break;
                                case EFS_IFCHR:
                                        dp->d_type = DT_CHR;
                                        break;
                                case EFS_IFDIR:
                                        dp->d_type = DT_DIR;
                                        break;
                                case EFS_IFBLK:
                                        dp->d_type = DT_BLK;
                                        break;
                                case EFS_IFREG:
                                        dp->d_type = DT_REG;
                                        break;
                                case EFS_IFLNK:
                                        dp->d_type = DT_LNK;
                                        break;
                                case EFS_IFSOCK:
                                        dp->d_type = DT_SOCK;
                                        break;
                                default:
                                        dp->d_type = DT_UNKNOWN;
                                        break;
                                }

                                err = uiomove(dp, s, uio);
                                if (err) {
                                        brelse(bp, 0);
                                        goto exit_err;
                                }

                                offset += s;

                                if (cookies != NULL && maxcookies != 0) {
                                        cookies[ncookies++] = offset;
                                        if (ncookies == maxcookies) {
                                                brelse(bp, 0);
                                                goto exit_ok;
                                        }
                                }
                        }

                        brelse(bp, 0);
                }
        }

        if (ret != -1) {
                err = ret;
                goto exit_err;
        }

        if (ap->a_eofflag != NULL)
                *ap->a_eofflag = true;

 exit_ok:
        if (cookies != NULL) {
                *ap->a_cookies = cookies;
                *ap->a_ncookies = ncookies;
        }

        uio->uio_offset = offset;

        free(dp, M_EFSTMP);

        return (0);

 exit_err:
        if (cookies != NULL)
                free(cookies, M_TEMP);

        free(dp, M_EFSTMP);
        
        return (err);
}

static int
efs_readlink(void *v)
{
        struct vop_readlink_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
        } */ *ap = v;
        struct uio *uio = ap->a_uio;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);
        char *buf;
        size_t len;
        int err, i;

        if ((eip->ei_mode & EFS_IFMT) != EFS_IFLNK)
                return (EINVAL);

        if (uio->uio_resid < 1)
                return (EINVAL);

        buf = malloc(eip->ei_size + 1, M_EFSTMP, M_ZERO | M_WAITOK);

        /* symlinks are either inlined in the inode, or in extents */
        if (eip->ei_numextents == 0) {
                if (eip->ei_size > sizeof(eip->ei_di.di_symlink)) {
                        EFS_DPRINTF(("efs_readlink: too big for inline\n"));
                        free(buf, M_EFSTMP);
                        return (EBADF);
                }

                memcpy(buf, eip->ei_di.di_symlink, eip->ei_size);
                len = MIN(uio->uio_resid, eip->ei_size + 1);
        } else {
                struct efs_extent_iterator exi;
                struct efs_extent ex;
                struct buf *bp;
                int resid, off, ret;

                off = 0;
                resid = eip->ei_size;

                efs_extent_iterator_init(&exi, eip, 0);
                while ((ret = efs_extent_iterator_next(&exi, &ex)) == 0) {
                        for (i = 0; i < ex.ex_length; i++) {
                                err = efs_bread(VFSTOEFS(ap->a_vp->v_mount),
                                    ex.ex_bn + i, NULL, &bp);
                                if (err) {
                                        free(buf, M_EFSTMP);
                                        return (err);
                                }

                                len = MIN(resid, bp->b_bcount);
                                memcpy(buf + off, bp->b_data, len);
                                brelse(bp, 0);

                                off += len;
                                resid -= len;

                                if (resid == 0)
                                        break;
                        }

                        if (resid == 0)
                                break;
                }

                if (ret != 0 && ret != -1) {
                        free(buf, M_EFSTMP);
                        return (ret);
                }

                len = off + 1;
        }

        KASSERT(len >= 1 && len <= (eip->ei_size + 1));
        buf[len - 1] = '\0';
        err = uiomove(buf, len, uio);
        free(buf, M_EFSTMP);

        return (err);
}

/*
 * Release an inactive vnode. The vnode _must_ be unlocked on return.
 * It is either nolonger being used by the kernel, or an unmount is being
 * forced.
 *
 * Returns 0 on success.
 */
static int
efs_inactive(void *v)
{
        struct vop_inactive_v2_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                bool *a_recycle
        } */ *ap = v;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);

        *ap->a_recycle = (eip->ei_mode == 0);

        return (0);
}

static int
efs_reclaim(void *v)
{
        struct vop_reclaim_v2_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
        } */ *ap = v;
        struct vnode *vp = ap->a_vp;
        struct efs_inode *eip = EFS_VTOI(vp);

        VOP_UNLOCK(vp);

        genfs_node_destroy(vp);
        pool_put(&efs_inode_pool, eip);
        vp->v_data = NULL;

        return (0);
}

static int
efs_bmap(void *v)
{
        struct vop_bmap_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                daddr_t a_bn;
                struct vnode **a_vpp;
                daddr_t *a_bnp;
                int *a_runp;
        } */ *ap = v;
        struct efs_extent ex;
        struct efs_extent_iterator exi;
        struct vnode *vp = ap->a_vp;
        struct efs_inode *eip = EFS_VTOI(vp);
        bool found;
        int ret;

        if (ap->a_vpp != NULL)
                *ap->a_vpp = VFSTOEFS(vp->v_mount)->em_devvp;

        found = false;
        efs_extent_iterator_init(&exi, eip, ap->a_bn * EFS_BB_SIZE);
        while ((ret = efs_extent_iterator_next(&exi, &ex)) == 0) {
                if (ap->a_bn >= ex.ex_offset &&
                    ap->a_bn < (ex.ex_offset + ex.ex_length)) {
                        found = true;
                        break;
                }
        }

        KASSERT(!found || ret == 0);

        if (!found) {
                EFS_DPRINTF(("efs_bmap: ap->a_bn not in extents\n"));
                return ((ret == -1) ? EIO : ret);
        }

        if (ex.ex_magic != EFS_EXTENT_MAGIC) {
                EFS_DPRINTF(("efs_bmap: exn.ex_magic != EFS_EXTENT_MAGIC\n"));
                return (EIO);
        }

        if (ap->a_bn < ex.ex_offset) {
                EFS_DPRINTF(("efs_bmap: ap->a_bn < exn.ex_offset\n"));
                return (EIO);
        }

        KASSERT(ap->a_bn >= ex.ex_offset);
        KASSERT(ex.ex_length > ap->a_bn - ex.ex_offset);

        *ap->a_bnp = ex.ex_bn + (ap->a_bn - ex.ex_offset);
        if (ap->a_runp != NULL)
                *ap->a_runp = ex.ex_length - (ap->a_bn - ex.ex_offset) - 1;

        return (0);
}

static int
efs_strategy(void *v)
{
        struct vop_strategy_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct buf *a_bp;
        } */ *ap = v;
        struct vnode *vp = ap->a_vp;
        struct buf *bp = ap->a_bp;
        int error;

        if (vp == NULL) {
                bp->b_error = EIO;
                biodone(bp);
                return (EIO);
        }

        if (bp->b_blkno == bp->b_lblkno) {
                error = VOP_BMAP(vp, bp->b_lblkno, NULL, &bp->b_blkno, NULL);
                if (error) {
                        bp->b_error = error;
                        biodone(bp);
                        return (error);
                }
                if ((long)bp->b_blkno == -1)
                        clrbuf(bp);
        }

        if ((long)bp->b_blkno == -1) {
                biodone(bp);
                return (0);
        }

        return (VOP_STRATEGY(VFSTOEFS(vp->v_mount)->em_devvp, bp));
}

static int
efs_print(void *v)
{
        struct vop_print_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
        } */ *ap = v;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);

        printf( "efs_inode (ino %lu):\n"
                "    ei_mode:         %07o\n"
                "    ei_nlink:        %d\n"
                "    ei_uid:          %d\n"
                "    ei_gid:          %d\n"
                "    ei_size:         %d\n"
                "    ei_atime:        %d\n"
                "    ei_mtime:        %d\n"
                "    ei_ctime:        %d\n"
                "    ei_gen:          %d\n"
                "    ei_numextents:   %d\n"
                "    ei_version:      %d\n",
                (unsigned long)eip->ei_number,
                (unsigned int)eip->ei_mode,
                eip->ei_nlink,
                eip->ei_uid,
                eip->ei_gid,
                eip->ei_size,
                (int32_t)eip->ei_atime,
                (int32_t)eip->ei_mtime,
                (int32_t)eip->ei_ctime,
                eip->ei_gen,
                eip->ei_numextents,
                eip->ei_version);

        return (0);
}

static int
efs_pathconf(void *v)
{
        struct vop_pathconf_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                int a_name;
                register_t *a_retval;
        } */ *ap = v;

        /* IRIX 4 values */
        switch (ap->a_name) {
        case _PC_LINK_MAX:
                *ap->a_retval = 30000;
                break;
        case _PC_NAME_MAX:
                *ap->a_retval = 255;
                break;
        case _PC_PATH_MAX:
                *ap->a_retval = 1024;
                break;
        case _PC_NO_TRUNC:
                *ap->a_retval = 1;
                break;
        case _PC_CHOWN_RESTRICTED:
                *ap->a_retval = 1;
                break;
        case _PC_SYNC_IO:
                *ap->a_retval = 1;
                break;
        case _PC_FILESIZEBITS:
                *ap->a_retval = 32;
                break;
        default:
                return genfs_pathconf(ap);
        }

        return (0);
}

static int
efs_advlock(void *v)
{
        struct vop_advlock_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                void *a_id;
                int a_op;
                struct flock *a_fl;
                int a_flags;
        } */ *ap = v;
        struct efs_inode *eip = EFS_VTOI(ap->a_vp);

        return (lf_advlock(ap, &eip->ei_lockf, eip->ei_size));
}

/* Global vfs data structures for efs */
int (**efs_vnodeop_p)(void *);
const struct vnodeopv_entry_desc efs_vnodeop_entries[] = {
        { &vop_default_desc,    vn_default_error},      /* error handler */
        { &vop_parsepath_desc, genfs_parsepath },       /* parsepath */
        { &vop_lookup_desc,     efs_lookup      },      /* lookup */
        { &vop_create_desc,     genfs_eopnotsupp},      /* create */
        { &vop_mknod_desc,      genfs_eopnotsupp},      /* mknod */
        { &vop_open_desc,       genfs_nullop    },      /* open */
        { &vop_close_desc,      genfs_nullop    },      /* close */
        { &vop_access_desc,     efs_access      },      /* access */
        { &vop_accessx_desc,    genfs_accessx   },      /* accessx */
        { &vop_getattr_desc,    efs_getattr     },      /* getattr */
        { &vop_setattr_desc,    genfs_eopnotsupp},      /* setattr */
        { &vop_read_desc,       efs_read        },      /* read */
        { &vop_write_desc,      genfs_eopnotsupp},      /* write */
        { &vop_fallocate_desc,  genfs_eopnotsupp},      /* fallocate */
        { &vop_fdiscard_desc,   genfs_eopnotsupp},      /* fdiscard */
        { &vop_ioctl_desc,      genfs_enoioctl  },      /* ioctl */
        { &vop_fcntl_desc,      genfs_fcntl     },      /* fcntl */
        { &vop_poll_desc,       genfs_poll      },      /* poll */
        { &vop_kqfilter_desc,   genfs_kqfilter  },      /* kqfilter */
        { &vop_revoke_desc,     genfs_revoke    },      /* revoke */
        { &vop_mmap_desc,       genfs_mmap      },      /* mmap */
        { &vop_fsync_desc,      genfs_eopnotsupp},      /* fsync */
        { &vop_seek_desc,       genfs_seek      },      /* seek */
        { &vop_remove_desc,     genfs_eopnotsupp},      /* remove */
        { &vop_link_desc,       genfs_eopnotsupp},      /* link */
        { &vop_rename_desc,     genfs_eopnotsupp},      /* rename */
        { &vop_mkdir_desc,      genfs_eopnotsupp},      /* mkdir */
        { &vop_rmdir_desc,      genfs_eopnotsupp},      /* rmdir */
        { &vop_symlink_desc,    genfs_eopnotsupp},      /* symlink */
        { &vop_readdir_desc,    efs_readdir     },      /* readdir */
        { &vop_readlink_desc,   efs_readlink    },      /* readlink */
        { &vop_abortop_desc,    genfs_abortop   },      /* abortop */
        { &vop_inactive_desc,   efs_inactive    },      /* inactive */
        { &vop_reclaim_desc,    efs_reclaim     },      /* reclaim */
        { &vop_lock_desc,       genfs_lock,     },      /* lock */
        { &vop_unlock_desc,     genfs_unlock,   },      /* unlock */
        { &vop_islocked_desc,   genfs_islocked, },      /* islocked */
        { &vop_bmap_desc,       efs_bmap        },      /* bmap */
        { &vop_print_desc,      efs_print       },      /* print */
        { &vop_pathconf_desc,   efs_pathconf    },      /* pathconf */
        { &vop_advlock_desc,    efs_advlock     },      /* advlock */
                                                        /* blkatoff */
                                                        /* valloc */
                                                        /* balloc */
                                                        /* vfree */
                                                        /* truncate */
                                                        /* whiteout */
        { &vop_getpages_desc,   genfs_getpages  },      /* getpages */
        { &vop_putpages_desc,   genfs_putpages  },      /* putpages */
        { &vop_bwrite_desc,     vn_bwrite       },      /* bwrite */
        { &vop_strategy_desc,   efs_strategy    },      /* strategy */
        { NULL, NULL }
};
const struct vnodeopv_desc efs_vnodeop_opv_desc = {
        &efs_vnodeop_p,
        efs_vnodeop_entries
};

int (**efs_specop_p)(void *);
const struct vnodeopv_entry_desc efs_specop_entries[] = {
        { &vop_default_desc,    vn_default_error},      /* error handler */
        GENFS_SPECOP_ENTRIES,
        { &vop_close_desc,      spec_close      },      /* close */
        { &vop_access_desc,     efs_access      },      /* access */
        { &vop_accessx_desc,    genfs_accessx   },      /* accessx */
        { &vop_getattr_desc,    efs_getattr     },      /* getattr */
        { &vop_setattr_desc,    genfs_eopnotsupp},      /* setattr */
        { &vop_read_desc,       spec_read       },      /* read */
        { &vop_write_desc,      spec_write      },      /* write */
        { &vop_fcntl_desc,      genfs_fcntl     },      /* fcntl */
        { &vop_fsync_desc,      spec_fsync      },      /* fsync */
        { &vop_inactive_desc,   efs_inactive    },      /* inactive */
        { &vop_reclaim_desc,    efs_reclaim     },      /* reclaim */
        { &vop_lock_desc,       genfs_lock,     },      /* lock */
        { &vop_unlock_desc,     genfs_unlock,   },      /* unlock */
        { &vop_islocked_desc,   genfs_islocked, },      /* islocked */
        { &vop_print_desc,      efs_print       },      /* print */
        { &vop_bwrite_desc,     vn_bwrite       },      /* bwrite */
        { NULL, NULL }
};
const struct vnodeopv_desc efs_specop_opv_desc = {
        &efs_specop_p,
        efs_specop_entries
};

int (**efs_fifoop_p)(void *);
const struct vnodeopv_entry_desc efs_fifoop_entries[] = {
        { &vop_default_desc,    vn_default_error},      /* error handler */
        GENFS_FIFOOP_ENTRIES,
        { &vop_close_desc,      vn_fifo_bypass  },      /* close */
        { &vop_access_desc,     efs_access      },      /* access */
        { &vop_accessx_desc,    genfs_accessx   },      /* accessx */
        { &vop_getattr_desc,    efs_getattr     },      /* getattr */
        { &vop_setattr_desc,    genfs_eopnotsupp},      /* setattr */
        { &vop_read_desc,       vn_fifo_bypass  },      /* read */
        { &vop_write_desc,      vn_fifo_bypass  },      /* write */
        { &vop_fcntl_desc,      genfs_fcntl     },      /* fcntl */
        { &vop_fsync_desc,      vn_fifo_bypass  },      /* fsync */
        { &vop_inactive_desc,   efs_inactive    },      /* inactive */
        { &vop_reclaim_desc,    efs_reclaim     },      /* reclaim */
        { &vop_lock_desc,       genfs_lock,     },      /* lock */
        { &vop_unlock_desc,     genfs_unlock,   },      /* unlock */
        { &vop_islocked_desc,   genfs_islocked, },      /* islocked */
        { &vop_strategy_desc,   vn_fifo_bypass  },      /* strategy */
        { &vop_print_desc,      efs_print       },      /* print */
        { &vop_bwrite_desc,     vn_bwrite       },      /* bwrite */
        { NULL, NULL }
};
const struct vnodeopv_desc efs_fifoop_opv_desc = {
        &efs_fifoop_p,
        efs_fifoop_entries
};