root/sys/miscfs/kernfs/kernfs_vnops.c
/*      $NetBSD: kernfs_vnops.c,v 1.174 2022/03/27 17:10:56 christos Exp $      */

/*
 * Copyright (c) 1992, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software donated to Berkeley by
 * Jan-Simon Pendry.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 *      @(#)kernfs_vnops.c      8.15 (Berkeley) 5/21/95
 */

/*
 * Kernel parameter filesystem (/kern)
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kernfs_vnops.c,v 1.174 2022/03/27 17:10:56 christos Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/vmmeter.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/dirent.h>
#include <sys/msgbuf.h>

#include <miscfs/genfs/genfs.h>
#include <miscfs/kernfs/kernfs.h>
#include <miscfs/specfs/specdev.h>

#include <uvm/uvm_extern.h>

#define KSTRING 256             /* Largest I/O available via this filesystem */
#define UIO_MX 32

#define READ_MODE       (S_IRUSR|S_IRGRP|S_IROTH)
#define WRITE_MODE      (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
#define UREAD_MODE      (S_IRUSR)
#define DIR_MODE        (S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#define UDIR_MODE       (S_IRUSR|S_IXUSR)

#define N(s) sizeof(s)-1, s
const struct kern_target kern_targets[] = {
/* NOTE: The name must be less than UIO_MX-16 chars in length */
     /*        name            data          tag           type  ro/rw */
     { DT_DIR, N("."),         0,            KFSkern,        VDIR, DIR_MODE   },
     { DT_DIR, N(".."),        0,            KFSroot,        VDIR, DIR_MODE   },
     { DT_REG, N("boottime"),  0,            KFSboottime,    VREG, READ_MODE  },
                        /* XXXUNCONST */
     { DT_REG, N("copyright"), __UNCONST(copyright),
                                             KFSstring,      VREG, READ_MODE  },
     { DT_REG, N("hostname"),  0,            KFShostname,    VREG, WRITE_MODE },
     { DT_REG, N("hz"),        &hz,          KFSint,         VREG, READ_MODE  },
     { DT_REG, N("loadavg"),   0,            KFSavenrun,     VREG, READ_MODE  },
     { DT_REG, N("msgbuf"),    0,            KFSmsgbuf,      VREG, READ_MODE  },
     { DT_REG, N("pagesize"),  &uvmexp.pagesize, KFSint,     VREG, READ_MODE  },
     { DT_REG, N("physmem"),   &physmem,     KFSint,         VREG, READ_MODE  },
#if 0
     { DT_DIR, N("root"),      0,            KFSnull,        VDIR, DIR_MODE   },
#endif
     { DT_BLK, N("rootdev"),   &rootdev,     KFSdevice,      VBLK, UREAD_MODE  },
     { DT_CHR, N("rrootdev"),  &rrootdev,    KFSdevice,      VCHR, UREAD_MODE  },
     { DT_REG, N("time"),      0,            KFStime,        VREG, READ_MODE  },
                        /* XXXUNCONST */
     { DT_REG, N("version"),   __UNCONST(version),
                                             KFSstring,      VREG, READ_MODE  },
};
const struct kern_target subdir_targets[] = {
/* NOTE: The name must be less than UIO_MX-16 chars in length */
     /*        name            data          tag           type  ro/rw */
     { DT_DIR, N("."),         0,            KFSsubdir,      VDIR, DIR_MODE   },
     { DT_DIR, N(".."),        0,            KFSkern,        VDIR, DIR_MODE   },
};
#undef N
SIMPLEQ_HEAD(,dyn_kern_target) dyn_kern_targets =
        SIMPLEQ_HEAD_INITIALIZER(dyn_kern_targets);
int nkern_targets = sizeof(kern_targets) / sizeof(kern_targets[0]);
const int static_nkern_targets = sizeof(kern_targets) / sizeof(kern_targets[0]);
int nkern_dirs = 2;

int kernfs_try_fileop(kfstype, kfsfileop, void *, int);
int kernfs_try_xread(kfstype, const struct kernfs_node *, char **,
    size_t, int);
int kernfs_try_xwrite(kfstype, const struct kernfs_node *, char *,
    size_t, int);

static int kernfs_default_xread(void *v);
static int kernfs_default_xwrite(void *v);
static int kernfs_default_fileop_getattr(void *);

/* must include all fileop's */
const struct kernfs_fileop kernfs_default_fileops[] = {
  { .kf_fileop = KERNFS_XREAD },
  { .kf_fileop = KERNFS_XWRITE },
  { .kf_fileop = KERNFS_FILEOP_OPEN },
  { .kf_fileop = KERNFS_FILEOP_GETATTR,
    .kf_vop = kernfs_default_fileop_getattr },
  { .kf_fileop = KERNFS_FILEOP_IOCTL },
  { .kf_fileop = KERNFS_FILEOP_CLOSE },
  { .kf_fileop = KERNFS_FILEOP_READ, 
    .kf_vop = kernfs_default_xread },
  { .kf_fileop = KERNFS_FILEOP_WRITE, 
    .kf_vop = kernfs_default_xwrite },
};

int     kernfs_lookup(void *);
int     kernfs_open(void *);
int     kernfs_close(void *);
int     kernfs_access(void *);
int     kernfs_getattr(void *);
int     kernfs_setattr(void *);
int     kernfs_read(void *);
int     kernfs_write(void *);
int     kernfs_ioctl(void *);
int     kernfs_readdir(void *);
int     kernfs_inactive(void *);
int     kernfs_reclaim(void *);
int     kernfs_print(void *);
int     kernfs_pathconf(void *);
int     kernfs_getpages(void *);

static int      kernfs_xread(struct kernfs_node *, int, char **,
                                size_t, size_t *);
static int      kernfs_xwrite(const struct kernfs_node *, char *, size_t);

int (**kernfs_vnodeop_p)(void *);
const struct vnodeopv_entry_desc kernfs_vnodeop_entries[] = {
        { &vop_default_desc, vn_default_error },
        { &vop_parsepath_desc, genfs_parsepath },       /* parsepath */
        { &vop_lookup_desc, kernfs_lookup },            /* lookup */
        { &vop_create_desc, genfs_eopnotsupp },         /* create */
        { &vop_mknod_desc, genfs_eopnotsupp },          /* mknod */
        { &vop_open_desc, kernfs_open },                /* open */
        { &vop_close_desc, kernfs_close },              /* close */
        { &vop_access_desc, kernfs_access },            /* access */
        { &vop_accessx_desc, genfs_accessx },           /* accessx */
        { &vop_getattr_desc, kernfs_getattr },          /* getattr */
        { &vop_setattr_desc, kernfs_setattr },          /* setattr */
        { &vop_read_desc, kernfs_read },                /* read */
        { &vop_write_desc, kernfs_write },              /* write */
        { &vop_fallocate_desc, genfs_eopnotsupp },      /* fallocate */
        { &vop_fdiscard_desc, genfs_eopnotsupp },       /* fdiscard */
        { &vop_fcntl_desc, genfs_fcntl },               /* fcntl */
        { &vop_ioctl_desc, kernfs_ioctl },              /* ioctl */
        { &vop_poll_desc, genfs_poll },                 /* poll */
        { &vop_kqfilter_desc, genfs_kqfilter },         /* kqfilter */
        { &vop_revoke_desc, genfs_revoke },             /* revoke */
        { &vop_fsync_desc, genfs_nullop },              /* fsync */
        { &vop_seek_desc, genfs_nullop },               /* seek */
        { &vop_remove_desc, genfs_eopnotsupp },         /* remove */
        { &vop_link_desc, genfs_erofs_link },           /* link */
        { &vop_rename_desc, genfs_eopnotsupp },         /* rename */
        { &vop_mkdir_desc, genfs_eopnotsupp },          /* mkdir */
        { &vop_rmdir_desc, genfs_eopnotsupp },          /* rmdir */
        { &vop_symlink_desc, genfs_erofs_symlink },     /* symlink */
        { &vop_readdir_desc, kernfs_readdir },          /* readdir */
        { &vop_readlink_desc, genfs_eopnotsupp },       /* readlink */
        { &vop_abortop_desc, genfs_abortop },           /* abortop */
        { &vop_inactive_desc, kernfs_inactive },        /* inactive */
        { &vop_reclaim_desc, kernfs_reclaim },          /* reclaim */
        { &vop_lock_desc, genfs_lock },                 /* lock */
        { &vop_unlock_desc, genfs_unlock },             /* unlock */
        { &vop_bmap_desc, genfs_eopnotsupp },           /* bmap */
        { &vop_strategy_desc, genfs_eopnotsupp },       /* strategy */
        { &vop_print_desc, kernfs_print },              /* print */
        { &vop_islocked_desc, genfs_islocked },         /* islocked */
        { &vop_pathconf_desc, kernfs_pathconf },        /* pathconf */
        { &vop_advlock_desc, genfs_einval },            /* advlock */
        { &vop_bwrite_desc, genfs_eopnotsupp },         /* bwrite */
        { &vop_getpages_desc, kernfs_getpages },        /* getpages */
        { &vop_putpages_desc, genfs_putpages },         /* putpages */
        { NULL, NULL }
};
const struct vnodeopv_desc kernfs_vnodeop_opv_desc =
        { &kernfs_vnodeop_p, kernfs_vnodeop_entries };

int (**kernfs_specop_p)(void *);
const struct vnodeopv_entry_desc kernfs_specop_entries[] = {
        { &vop_default_desc, vn_default_error },
        GENFS_SPECOP_ENTRIES,
        { &vop_close_desc, spec_close },                /* close */
        { &vop_access_desc, kernfs_access },            /* access */
        { &vop_accessx_desc, genfs_accessx },           /* accessx */
        { &vop_getattr_desc, kernfs_getattr },          /* getattr */
        { &vop_setattr_desc, kernfs_setattr },          /* 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, kernfs_inactive },        /* inactive */
        { &vop_reclaim_desc, kernfs_reclaim },          /* reclaim */
        { &vop_lock_desc, genfs_lock },                 /* lock */
        { &vop_unlock_desc, genfs_unlock },             /* unlock */
        { &vop_print_desc, kernfs_print },              /* print */
        { &vop_islocked_desc, genfs_islocked },         /* islocked */
        { &vop_bwrite_desc, vn_bwrite },                /* bwrite */
        { NULL, NULL }
};
const struct vnodeopv_desc kernfs_specop_opv_desc =
        { &kernfs_specop_p, kernfs_specop_entries };

static inline int
kernfs_fileop_compare(struct kernfs_fileop *a, struct kernfs_fileop *b)
{
        if (a->kf_type < b->kf_type)
                return -1;
        if (a->kf_type > b->kf_type)
                return 1;
        if (a->kf_fileop < b->kf_fileop)
                return -1;
        if (a->kf_fileop > b->kf_fileop)
                return 1;
        return (0);
}

SPLAY_HEAD(kfsfileoptree, kernfs_fileop) kfsfileoptree =
        SPLAY_INITIALIZER(kfsfileoptree);
SPLAY_PROTOTYPE(kfsfileoptree, kernfs_fileop, kf_node, kernfs_fileop_compare);
SPLAY_GENERATE(kfsfileoptree, kernfs_fileop, kf_node, kernfs_fileop_compare);

kfstype
kernfs_alloctype(int nkf, const struct kernfs_fileop *kf)
{
        static u_char nextfreetype = KFSlasttype;
        struct kernfs_fileop *dkf, *fkf, skf;
        int i;

        /* XXX need to keep track of dkf's memory if we support
           deallocating types */
        dkf = malloc(sizeof(kernfs_default_fileops), M_TEMP, M_WAITOK);
        memcpy(dkf, kernfs_default_fileops, sizeof(kernfs_default_fileops));

        for (i = 0; i < sizeof(kernfs_default_fileops) /
                     sizeof(kernfs_default_fileops[0]); i++) {
                dkf[i].kf_type = nextfreetype;
                SPLAY_INSERT(kfsfileoptree, &kfsfileoptree, &dkf[i]);
        }

        for (i = 0; i < nkf; i++) {
                skf.kf_type = nextfreetype;
                skf.kf_fileop = kf[i].kf_fileop;
                if ((fkf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
                        fkf->kf_vop = kf[i].kf_vop;
        }

        return nextfreetype++;
}

int
kernfs_try_fileop(kfstype type, kfsfileop fileop, void *v, int error)
{
        struct kernfs_fileop *kf, skf;

        skf.kf_type = type;
        skf.kf_fileop = fileop;
        if ((kf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
                if (kf->kf_vop)
                        return kf->kf_vop(v);
        return error;
}

int
kernfs_try_xread(kfstype type, const struct kernfs_node *kfs, char **bfp,
    size_t len, int error)
{
        struct kernfs_fileop *kf, skf;

        skf.kf_type = type;
        skf.kf_fileop = KERNFS_XREAD;
        if ((kf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
                if (kf->kf_xread)
                        return kf->kf_xread(kfs, bfp, len);
        return error;
}

int
kernfs_try_xwrite(kfstype type, const struct kernfs_node *kfs, char *bf,
    size_t len, int error)
{
        struct kernfs_fileop *kf, skf;

        skf.kf_type = type;
        skf.kf_fileop = KERNFS_XWRITE;
        if ((kf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
                if (kf->kf_xwrite)
                        return kf->kf_xwrite(kfs, bf, len);
        return error;
}

int
kernfs_addentry(kernfs_parentdir_t *pkt, kernfs_entry_t *dkt)
{
        struct kernfs_subdir *ks, *parent;

        if (pkt == NULL) {
                SIMPLEQ_INSERT_TAIL(&dyn_kern_targets, dkt, dkt_queue);
                nkern_targets++;
                if (dkt->dkt_kt.kt_vtype == VDIR)
                        nkern_dirs++;
        } else {
                parent = (struct kernfs_subdir *)pkt->kt_data;
                SIMPLEQ_INSERT_TAIL(&parent->ks_entries, dkt, dkt_queue);
                parent->ks_nentries++;
                if (dkt->dkt_kt.kt_vtype == VDIR)
                        parent->ks_dirs++;
        }
        if (dkt->dkt_kt.kt_vtype == VDIR && dkt->dkt_kt.kt_data == NULL) {
                ks = malloc(sizeof(struct kernfs_subdir),
                    M_TEMP, M_WAITOK);
                SIMPLEQ_INIT(&ks->ks_entries);
                ks->ks_nentries = 2; /* . and .. */
                ks->ks_dirs = 2;
                ks->ks_parent = pkt ? pkt : &kern_targets[0];
                dkt->dkt_kt.kt_data = ks;
        }
        return 0;
}

static int
kernfs_xread(struct kernfs_node *kfs, int off, char **bufp, size_t len, size_t *wrlen)
{
        const struct kern_target *kt;
        int err;

        kt = kfs->kfs_kt;

        switch (kfs->kfs_type) {
        case KFStime: {
                struct timeval tv;

                microtime(&tv);
                snprintf(*bufp, len, "%lld %ld\n", (long long)tv.tv_sec,
                    (long)tv.tv_usec);
                break;
        }

        case KFSboottime: {
                struct timeval tv;

                /*
                 * Historically, /kern/boottime only contained seconds.
                 */
                getmicroboottime(&tv);
                snprintf(*bufp, len, "%lld\n", (long long)tv.tv_sec);
                break;
        }

        case KFSint: {
                int *ip = kt->kt_data;

                snprintf(*bufp, len, "%d\n", *ip);
                break;
        }

        case KFSstring: {
                char *cp = kt->kt_data;

                *bufp = cp;
                break;
        }

        case KFSmsgbuf: {
                long n;

                /*
                 * deal with cases where the message buffer has
                 * become corrupted.
                 */
                if (!logenabled(msgbufp)) {
                        msgbufenabled = 0;
                        return (ENXIO);
                }

                /*
                 * Note that reads of /kern/msgbuf won't necessarily yield
                 * consistent results, if the message buffer is modified
                 * while the read is in progress.  The worst that can happen
                 * is that incorrect data will be read.  There's no way
                 * that this can crash the system unless the values in the
                 * message buffer header are corrupted, but that'll cause
                 * the system to die anyway.
                 */
                if (off >= msgbufp->msg_bufs) {
                        *wrlen = 0;
                        return (0);
                }
                n = msgbufp->msg_bufx + off;
                if (n >= msgbufp->msg_bufs)
                        n -= msgbufp->msg_bufs;
                len = uimin(msgbufp->msg_bufs - n, msgbufp->msg_bufs - off);
                *bufp = msgbufp->msg_bufc + n;
                *wrlen = len;
                return (0);
        }

        case KFShostname: {
                char *cp = hostname;
                size_t xlen = hostnamelen;

                if (xlen >= (len - 2))
                        return (EINVAL);

                memcpy(*bufp, cp, xlen);
                (*bufp)[xlen] = '\n';
                (*bufp)[xlen+1] = '\0';
                break;
        }

        case KFSavenrun:
                averunnable.fscale = FSCALE;
                snprintf(*bufp, len, "%d %d %d %ld\n",
                    averunnable.ldavg[0], averunnable.ldavg[1],
                    averunnable.ldavg[2], averunnable.fscale);
                break;

        default:
                err = kernfs_try_xread(kfs->kfs_type, kfs, bufp, len,
                    EOPNOTSUPP);
                if (err)
                        return err;
        }

        len = strlen(*bufp);
        if (len <= off)
                *wrlen = 0;
        else {
                *bufp += off;
                *wrlen = len - off;
        }
        return (0);
}

static int
kernfs_xwrite(const struct kernfs_node *kfs, char *bf, size_t len)
{

        switch (kfs->kfs_type) {
        case KFShostname:
                if (bf[len-1] == '\n')
                        --len;
                memcpy(hostname, bf, len);
                hostname[len] = '\0';
                hostnamelen = (size_t) len;
                return (0);

        default:
                return kernfs_try_xwrite(kfs->kfs_type, kfs, bf, len, EIO);
        }
}


/*
 * vp is the current namei directory
 * ndp is the name to locate in that directory...
 */
int
kernfs_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 **vpp = ap->a_vpp;
        struct vnode *dvp = ap->a_dvp;
        const char *pname = cnp->cn_nameptr;
        const struct kernfs_node *kfs;
        const struct kern_target *kt;
        const struct dyn_kern_target *dkt;
        const struct kernfs_subdir *ks;
        int error, i;

        *vpp = NULLVP;

        if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)
                return (EROFS);

        if (cnp->cn_namelen == 1 && *pname == '.') {
                *vpp = dvp;
                vref(dvp);
                return (0);
        }

        kfs = VTOKERN(dvp);
        switch (kfs->kfs_type) {
        case KFSkern:
                /*
                 * Shouldn't get here with .. in the root node.
                 */
                if (cnp->cn_flags & ISDOTDOT)
                        return (EIO);

                for (i = 0; i < static_nkern_targets; i++) {
                        kt = &kern_targets[i];
                        if (cnp->cn_namelen == kt->kt_namlen &&
                            memcmp(kt->kt_name, pname, cnp->cn_namelen) == 0)
                                goto found;
                }
                SIMPLEQ_FOREACH(dkt, &dyn_kern_targets, dkt_queue) {
                        if (cnp->cn_namelen == dkt->dkt_kt.kt_namlen &&
                            memcmp(dkt->dkt_kt.kt_name, pname, cnp->cn_namelen) == 0) {
                                kt = &dkt->dkt_kt;
                                goto found;
                        }
                }
                break;

        found:
                error = vcache_get(dvp->v_mount, &kt, sizeof(kt), vpp);
                return error;

        case KFSsubdir:
                ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
                if (cnp->cn_flags & ISDOTDOT) {
                        kt = ks->ks_parent;
                        goto found;
                }

                SIMPLEQ_FOREACH(dkt, &ks->ks_entries, dkt_queue) {
                        if (cnp->cn_namelen == dkt->dkt_kt.kt_namlen &&
                            memcmp(dkt->dkt_kt.kt_name, pname, cnp->cn_namelen) == 0) {
                                kt = &dkt->dkt_kt;
                                goto found;
                        }
                }
                break;

        default:
                return (ENOTDIR);
        }

        return (cnp->cn_nameiop == LOOKUP ? ENOENT : EROFS);
}

int
kernfs_open(void *v)
{
        struct vop_open_args /* {
                struct vnode *a_vp;
                int a_mode;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);

        return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_OPEN, v, 0);
}

int
kernfs_close(void *v)
{
        struct vop_close_args /* {
                struct vnode *a_vp;
                int a_fflag;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);

        return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_CLOSE, v, 0);
}

int
kernfs_access(void *v)
{
        struct vop_access_args /* {
                struct vnode *a_vp;
                accmode_t a_accmode;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct vattr va;
        int error;

        if ((error = VOP_GETATTR(ap->a_vp, &va, ap->a_cred)) != 0)
                return (error);

        return kauth_authorize_vnode(ap->a_cred,
            KAUTH_ACCESS_ACTION(ap->a_accmode, ap->a_vp->v_type, va.va_mode),
            ap->a_vp, NULL, genfs_can_access(ap->a_vp, ap->a_cred,
            va.va_uid, va.va_gid, va.va_mode, NULL, ap->a_accmode));
}

static int
kernfs_default_fileop_getattr(void *v)
{
        struct vop_getattr_args /* {
                struct vnode *a_vp;
                struct vattr *a_vap;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct vattr *vap = ap->a_vap;

        vap->va_nlink = 1;
        vap->va_bytes = vap->va_size = 0;

        return 0;
}

int
kernfs_getattr(void *v)
{
        struct vop_getattr_args /* {
                struct vnode *a_vp;
                struct vattr *a_vap;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);
        struct kernfs_subdir *ks;
        struct vattr *vap = ap->a_vap;
        int error = 0;
        char strbuf[KSTRING], *bf;
        size_t nread, total;

        vattr_null(vap);
        vap->va_type = ap->a_vp->v_type;
        vap->va_uid = 0;
        vap->va_gid = 0;
        vap->va_mode = kfs->kfs_mode;
        vap->va_fileid = kfs->kfs_fileno;
        vap->va_flags = 0;
        vap->va_size = 0;
        vap->va_blocksize = DEV_BSIZE;
        /* Make all times be current TOD, except for the "boottime" node. */
        if (kfs->kfs_kt->kt_namlen == 8 &&
            !memcmp(kfs->kfs_kt->kt_name, "boottime", 8)) {
                getnanoboottime(&vap->va_ctime);
        } else {
                getnanotime(&vap->va_ctime);
        }
        vap->va_atime = vap->va_mtime = vap->va_ctime;
        vap->va_gen = 0;
        vap->va_flags = 0;
        vap->va_rdev = 0;
        vap->va_bytes = 0;

        switch (kfs->kfs_type) {
        case KFSkern:
                vap->va_nlink = nkern_dirs;
                vap->va_bytes = vap->va_size = DEV_BSIZE;
                break;

        case KFSdevice:
                vap->va_nlink = 1;
                vap->va_rdev = ap->a_vp->v_rdev;
                break;

        case KFSroot:
                vap->va_nlink = 1;
                vap->va_bytes = vap->va_size = DEV_BSIZE;
                break;

        case KFSsubdir:
                ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
                vap->va_nlink = ks->ks_dirs;
                vap->va_bytes = vap->va_size = DEV_BSIZE;
                break;

        case KFSnull:
        case KFStime:
        case KFSboottime:
        case KFSint:
        case KFSstring:
        case KFShostname:
        case KFSavenrun:
        case KFSmsgbuf:
                vap->va_nlink = 1;
                total = 0;
                do {
                        bf = strbuf;
                        error = kernfs_xread(kfs, total, &bf,
                            sizeof(strbuf), &nread);
                        total += nread;
                } while (error == 0 && nread != 0);
                vap->va_bytes = vap->va_size = total;
                break;

        default:
                error = kernfs_try_fileop(kfs->kfs_type,
                    KERNFS_FILEOP_GETATTR, v, EINVAL);
                break;
        }

        return (error);
}

/*ARGSUSED*/
int
kernfs_setattr(void *v)
{

        /*
         * Silently ignore attribute changes.
         * This allows for open with truncate to have no
         * effect until some data is written.  I want to
         * do it this way because all writes are atomic.
         */
        return (0);
}

int
kernfs_default_xread(void *v)
{
        struct vop_read_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct uio *uio = ap->a_uio;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);
        char strbuf[KSTRING], *bf;
        int off;
        size_t len;
        int error;

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

        off = (int)uio->uio_offset;
        /* Don't allow negative offsets */
        if (off < 0)
                return EINVAL;

        bf = strbuf;
        if ((error = kernfs_xread(kfs, off, &bf, sizeof(strbuf), &len)) == 0)
                error = uiomove(bf, len, uio);
        return (error);
}

int
kernfs_read(void *v)
{
        struct vop_read_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                struct ucred *a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);

        if (kfs->kfs_type < KFSlasttype) {
                /* use default function */
                return kernfs_default_xread(v);
        }
        return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_READ, v,
           EOPNOTSUPP);
}

static int
kernfs_default_xwrite(void *v)
{
        struct vop_write_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);
        struct uio *uio = ap->a_uio;
        int error;
        size_t xlen;
        char strbuf[KSTRING];

        if (uio->uio_offset != 0)
                return (EINVAL);

        xlen = uimin(uio->uio_resid, KSTRING-1);
        if ((error = uiomove(strbuf, xlen, uio)) != 0)
                return (error);

        if (uio->uio_resid != 0)
                return (EIO);

        strbuf[xlen] = '\0';
        xlen = strlen(strbuf);
        return (kernfs_xwrite(kfs, strbuf, xlen));
}

int
kernfs_write(void *v)
{
        struct vop_write_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);

        if (kfs->kfs_type < KFSlasttype) {
                /* use default function */
                return kernfs_default_xwrite(v);
        }
        return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_WRITE, v,
            EOPNOTSUPP);
}

int
kernfs_ioctl(void *v)
{
        struct vop_ioctl_args /* {
                const struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                u_long a_command;
                void *a_data;
                int a_fflag;
                kauth_cred_t a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);

        return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_IOCTL, v,
            EPASSTHROUGH);
}

static int
kernfs_setdirentfileno_kt(struct dirent *d, const struct kern_target *kt,
    struct vop_readdir_args *ap)
{
        struct kernfs_node *kfs;
        struct vnode *vp;
        int error;

        if ((error = vcache_get(ap->a_vp->v_mount, &kt, sizeof(kt), &vp)) != 0)
                return error;
        kfs = VTOKERN(vp);
        d->d_fileno = kfs->kfs_fileno;
        vrele(vp);
        return 0;
}

static int
kernfs_setdirentfileno(struct dirent *d, off_t entry,
    struct kernfs_node *thisdir_kfs, const struct kern_target *parent_kt,
    const struct kern_target *kt, struct vop_readdir_args *ap)
{
        const struct kern_target *ikt;
        int error;

        switch (entry) {
        case 0:
                d->d_fileno = thisdir_kfs->kfs_fileno;
                return 0;
        case 1:
                ikt = parent_kt;
                break;
        default:
                ikt = kt;
                break;
        }
        if (ikt != thisdir_kfs->kfs_kt) {
                if ((error = kernfs_setdirentfileno_kt(d, ikt, ap)) != 0)
                        return error;
        } else
                d->d_fileno = thisdir_kfs->kfs_fileno;
        return 0;
}

int
kernfs_readdir(void *v)
{
        struct vop_readdir_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                kauth_cred_t a_cred;
                int *a_eofflag;
                off_t **a_cookies;
                int a_*ncookies;
        } */ *ap = v;
        struct uio *uio = ap->a_uio;
        struct dirent d;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);
        const struct kern_target *kt;
        const struct dyn_kern_target *dkt = NULL;
        const struct kernfs_subdir *ks;
        off_t i, j;
        int error;
        off_t *cookies = NULL;
        int ncookies = 0, n;

        if (uio->uio_resid < UIO_MX)
                return (EINVAL);
        if (uio->uio_offset < 0)
                return (EINVAL);

        error = 0;
        i = uio->uio_offset;
        memset(&d, 0, sizeof(d));
        d.d_reclen = UIO_MX;
        ncookies = uio->uio_resid / UIO_MX;

        switch (kfs->kfs_type) {
        case KFSkern:
                if (i >= nkern_targets)
                        return (0);

                if (ap->a_ncookies) {
                        ncookies = uimin(ncookies, (nkern_targets - i));
                        cookies = malloc(ncookies * sizeof(off_t), M_TEMP,
                            M_WAITOK);
                        *ap->a_cookies = cookies;
                }

                n = 0;
                for (; i < nkern_targets && uio->uio_resid >= UIO_MX; i++) {
                        if (i < static_nkern_targets)
                                kt = &kern_targets[i];
                        else {
                                if (dkt == NULL) {
                                        dkt = SIMPLEQ_FIRST(&dyn_kern_targets);
                                        for (j = static_nkern_targets; j < i &&
                                                     dkt != NULL; j++)
                                                dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
                                        if (j != i)
                                                break;
                                } else {
                                        dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
                                }
                                if (dkt == NULL)
                                        break;
                                kt = &dkt->dkt_kt;
                        }
                        if (kt->kt_tag == KFSmsgbuf) {
                                if (!logenabled(msgbufp)) {
                                        continue;
                                }
                        }
                        d.d_namlen = kt->kt_namlen;
                        if ((error = kernfs_setdirentfileno(&d, i, kfs,
                            &kern_targets[0], kt, ap)) != 0)
                                break;
                        memcpy(d.d_name, kt->kt_name, kt->kt_namlen + 1);
                        d.d_type = kt->kt_type;
                        if ((error = uiomove(&d, UIO_MX, uio)) != 0)
                                break;
                        if (cookies)
                                *cookies++ = i + 1;
                        n++;
                }
                ncookies = n;
                break;

        case KFSroot:
                if (i >= 2)
                        return 0;

                if (ap->a_ncookies) {
                        ncookies = uimin(ncookies, (2 - i));
                        cookies = malloc(ncookies * sizeof(off_t), M_TEMP,
                            M_WAITOK);
                        *ap->a_cookies = cookies;
                }

                n = 0;
                for (; i < 2 && uio->uio_resid >= UIO_MX; i++) {
                        kt = &kern_targets[i];
                        d.d_namlen = kt->kt_namlen;
                        d.d_fileno = KERNFS_FILENO(kt, kt->kt_tag, 0);
                        memcpy(d.d_name, kt->kt_name, kt->kt_namlen + 1);
                        d.d_type = kt->kt_type;
                        if ((error = uiomove(&d, UIO_MX, uio)) != 0)
                                break;
                        if (cookies)
                                *cookies++ = i + 1;
                        n++;
                }
                ncookies = n;
                break;

        case KFSsubdir:
                ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
                if (i >= ks->ks_nentries)
                        return (0);

                if (ap->a_ncookies) {
                        ncookies = uimin(ncookies, (ks->ks_nentries - i));
                        cookies = malloc(ncookies * sizeof(off_t), M_TEMP,
                            M_WAITOK);
                        *ap->a_cookies = cookies;
                }

                dkt = SIMPLEQ_FIRST(&ks->ks_entries);
                for (j = 0; j < i && dkt != NULL; j++)
                        dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
                n = 0;
                for (; i < ks->ks_nentries && uio->uio_resid >= UIO_MX; i++) {
                        if (i < 2)
                                kt = &subdir_targets[i];
                        else {
                                /* check if ks_nentries lied to us */
                                if (dkt == NULL)
                                        break;
                                kt = &dkt->dkt_kt;
                                dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
                        }
                        d.d_namlen = kt->kt_namlen;
                        if ((error = kernfs_setdirentfileno(&d, i, kfs,
                            ks->ks_parent, kt, ap)) != 0)
                                break;
                        memcpy(d.d_name, kt->kt_name, kt->kt_namlen + 1);
                        d.d_type = kt->kt_type;
                        if ((error = uiomove(&d, UIO_MX, uio)) != 0)
                                break;
                        if (cookies)
                                *cookies++ = i + 1;
                        n++;
                }
                ncookies = n;
                break;

        default:
                error = ENOTDIR;
                break;
        }

        if (ap->a_ncookies) {
                if (error) {
                        if (cookies)
                                free(*ap->a_cookies, M_TEMP);
                        *ap->a_ncookies = 0;
                        *ap->a_cookies = NULL;
                } else
                        *ap->a_ncookies = ncookies;
        }

        uio->uio_offset = i;
        return (error);
}

int
kernfs_inactive(void *v)
{
        struct vop_inactive_v2_args /* {
                struct vnode *a_vp;
                bool *a_recycle;
        } */ *ap = v;

        *ap->a_recycle = false;

        return (0);
}

int
kernfs_reclaim(void *v)
{
        struct vop_reclaim_v2_args /* {
                struct vnode *a_vp;
        } */ *ap = v;
        struct vnode *vp = ap->a_vp;
        struct kernfs_node *kfs = VTOKERN(vp);

        VOP_UNLOCK(vp);

        vp->v_data = NULL;
        mutex_enter(&kfs_lock);
        TAILQ_REMOVE(&VFSTOKERNFS(vp->v_mount)->nodelist, kfs, kfs_list);
        mutex_exit(&kfs_lock);
        kmem_free(kfs, sizeof(struct kernfs_node));

        return 0;
}

/*
 * Return POSIX pathconf information applicable to special devices.
 */
int
kernfs_pathconf(void *v)
{
        struct vop_pathconf_args /* {
                struct vnode *a_vp;
                int a_name;
                register_t *a_retval;
        } */ *ap = v;

        switch (ap->a_name) {
        case _PC_LINK_MAX:
                *ap->a_retval = LINK_MAX;
                return (0);
        case _PC_MAX_CANON:
                *ap->a_retval = MAX_CANON;
                return (0);
        case _PC_MAX_INPUT:
                *ap->a_retval = MAX_INPUT;
                return (0);
        case _PC_PIPE_BUF:
                *ap->a_retval = PIPE_BUF;
                return (0);
        case _PC_CHOWN_RESTRICTED:
                *ap->a_retval = 1;
                return (0);
        case _PC_VDISABLE:
                *ap->a_retval = _POSIX_VDISABLE;
                return (0);
        case _PC_SYNC_IO:
                *ap->a_retval = 1;
                return (0);
        default:
                return genfs_pathconf(ap);
        }
        /* NOTREACHED */
}

/*
 * Print out the contents of a /dev/fd vnode.
 */
/* ARGSUSED */
int
kernfs_print(void *v)
{

        printf("tag VT_KERNFS, kernfs vnode\n");
        return (0);
}

int
kernfs_getpages(void *v)
{
        struct vop_getpages_args /* {
                struct vnode *a_vp;
                voff_t a_offset;
                struct vm_page **a_m;
                int *a_count;
                int a_centeridx;
                vm_prot_t a_access_type;
                int a_advice;
                int a_flags;
        } */ *ap = v;

        if ((ap->a_flags & PGO_LOCKED) == 0)
                rw_exit(ap->a_vp->v_uobj.vmobjlock);

        return (EFAULT);
}