root/usr/src/uts/common/fs/dev/sdev_vtops.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * vnode ops for the /dev/vt directory
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/sunndi.h>
#include <fs/fs_subr.h>
#include <sys/fs/dv_node.h>
#include <sys/fs/sdev_impl.h>
#include <sys/policy.h>
#include <sys/stat.h>
#include <sys/vfs_opreg.h>
#include <sys/tty.h>
#include <sys/vt_impl.h>
#include <sys/note.h>

/* warlock in this file only cares about variables shared by vt and devfs */
_NOTE(SCHEME_PROTECTS_DATA("Do not care", sdev_node vattr vnode))

#define DEVVT_UID_DEFAULT       SDEV_UID_DEFAULT
#define DEVVT_GID_DEFAULT       (0)
#define DEVVT_DEVMODE_DEFAULT   (0600)
#define DEVVT_ACTIVE_NAME       "active"
#define DEVVT_CONSUSER_NAME     "console_user"

#define isdigit(ch)     ((ch) >= '0' && (ch) <= '9')

/* attributes for VT nodes */
static vattr_t devvt_vattr = {
        AT_TYPE|AT_MODE|AT_UID|AT_GID,          /* va_mask */
        VCHR,                                   /* va_type */
        S_IFCHR | DEVVT_DEVMODE_DEFAULT,        /* va_mode */
        DEVVT_UID_DEFAULT,                      /* va_uid */
        DEVVT_GID_DEFAULT,                      /* va_gid */
        0                                       /* 0 hereafter */
};

struct vnodeops         *devvt_vnodeops;

struct vnodeops *
devvt_getvnodeops(void)
{
        return (devvt_vnodeops);
}

static int
devvt_str2minor(const char *nm, minor_t *mp)
{
        long uminor = 0;
        char *endptr = NULL;

        if (nm == NULL || !isdigit(*nm))
                return (EINVAL);

        *mp = 0;
        if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
            *endptr != '\0' || uminor < 0) {
                return (EINVAL);
        }

        *mp = (minor_t)uminor;
        return (0);
}

/*
 * Validate that a node is up-to-date and correct.
 * A validator may not update the node state or
 * contents as a read lock permits entry by
 * multiple threads.
 */
int
devvt_validate(struct sdev_node *dv)
{
        minor_t min;
        char *nm = dv->sdev_name;
        int rval;

        ASSERT(dv->sdev_state == SDEV_READY);
        ASSERT(RW_LOCK_HELD(&(dv->sdev_dotdot)->sdev_contents));

        /* validate only READY nodes */
        if (dv->sdev_state != SDEV_READY) {
                sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
                    nm, (void *)dv));
                return (SDEV_VTOR_SKIP);
        }

        if (vt_wc_attached() == (major_t)-1)
                return (SDEV_VTOR_INVALID);

        if (strcmp(nm, DEVVT_ACTIVE_NAME) == 0) {
                char *link = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
                (void) vt_getactive(link, MAXPATHLEN);
                rval = (strcmp(link, dv->sdev_symlink) == 0) ?
                    SDEV_VTOR_VALID : SDEV_VTOR_STALE;
                kmem_free(link, MAXPATHLEN);
                return (rval);
        }

        if (strcmp(nm, DEVVT_CONSUSER_NAME) == 0) {
                char *link = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
                (void) vt_getconsuser(link, MAXPATHLEN);
                rval = (strcmp(link, dv->sdev_symlink) == 0) ?
                    SDEV_VTOR_VALID : SDEV_VTOR_STALE;
                kmem_free(link, MAXPATHLEN);
                return (rval);
        }

        if (devvt_str2minor(nm, &min) != 0) {
                return (SDEV_VTOR_INVALID);
        }

        if (vt_minor_valid(min) == B_FALSE)
                return (SDEV_VTOR_INVALID);

        return (SDEV_VTOR_VALID);
}

/*
 * This callback is invoked from devname_lookup_func() to create
 * a entry when the node is not found in the cache.
 */
/*ARGSUSED*/
static int
devvt_create_rvp(struct sdev_node *ddv, char *nm,
    void **arg, cred_t *cred, void *whatever, char *whichever)
{
        minor_t min;
        major_t maj;
        struct vattr *vap = (struct vattr *)arg;

        if ((maj = vt_wc_attached()) == (major_t)-1)
                return (SDEV_VTOR_INVALID);

        if (strcmp(nm, DEVVT_ACTIVE_NAME) == 0) {
                (void) vt_getactive((char *)*arg, MAXPATHLEN);
                return (0);
        }

        if (strcmp(nm, DEVVT_CONSUSER_NAME) == 0) {
                (void) vt_getconsuser((char *)*arg, MAXPATHLEN);
                return (0);
        }
        if (devvt_str2minor(nm, &min) != 0)
                return (-1);

        if (vt_minor_valid(min) == B_FALSE)
                return (-1);

        *vap = devvt_vattr;
        vap->va_rdev = makedevice(maj, min);

        return (0);
}

/*ARGSUSED3*/
static int
devvt_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
    struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
    caller_context_t *ct, int *direntflags, pathname_t *realpnp)
{
        struct sdev_node *sdvp = VTOSDEV(dvp);
        struct sdev_node *dv;
        struct vnode *rvp = NULL;
        int type, error;

        if ((strcmp(nm, DEVVT_ACTIVE_NAME) == 0) ||
            (strcmp(nm, DEVVT_CONSUSER_NAME) == 0)) {
                type = SDEV_VLINK;
        } else {
                type = SDEV_VATTR;
        }

/* Give warlock a more clear call graph */
#ifndef __lock_lint
        error = devname_lookup_func(sdvp, nm, vpp, cred,
            devvt_create_rvp, type);
#else
        devvt_create_rvp(0, 0, 0, 0, 0, 0);
#endif

        if (error == 0) {
                switch ((*vpp)->v_type) {
                case VCHR:
                        dv = VTOSDEV(VTOS(*vpp)->s_realvp);
                        ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
                        break;
                case VDIR:
                case VLNK:
                        dv = VTOSDEV(*vpp);
                        break;
                default:
                        cmn_err(CE_PANIC, "devvt_lookup: Unsupported node "
                            "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
                        break;
                }
                ASSERT(SDEV_HELD(dv));
        }

        return (error);
}

static void
devvt_create_snode(struct sdev_node *ddv, char *nm, struct cred *cred, int type)
{
        int error;
        struct sdev_node *sdv = NULL;
        struct vattr vattr;
        struct vattr *vap = &vattr;
        major_t maj;
        minor_t min;

        ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));

        if ((maj = vt_wc_attached()) == (major_t)-1)
                return;

        if (strcmp(nm, DEVVT_ACTIVE_NAME) != 0 &&
            strcmp(nm, DEVVT_CONSUSER_NAME) != 0 &&
            devvt_str2minor(nm, &min) != 0)
                return;

        error = sdev_mknode(ddv, nm, &sdv, NULL, NULL, NULL, cred, SDEV_INIT);
        if (error || !sdv) {
                return;
        }

        mutex_enter(&sdv->sdev_lookup_lock);
        SDEV_BLOCK_OTHERS(sdv, SDEV_LOOKUP);
        mutex_exit(&sdv->sdev_lookup_lock);

        if (type & SDEV_VATTR) {
                *vap = devvt_vattr;
                vap->va_rdev = makedevice(maj, min);
                error = sdev_mknode(ddv, nm, &sdv, vap, NULL,
                    NULL, cred, SDEV_READY);
        } else if (type & SDEV_VLINK) {
                char *link = kmem_zalloc(MAXPATHLEN, KM_SLEEP);

                (void) vt_getactive(link, MAXPATHLEN);
                *vap = sdev_vattr_lnk;
                vap->va_size = strlen(link);
                error = sdev_mknode(ddv, nm, &sdv, vap, NULL,
                    (void *)link, cred, SDEV_READY);

                kmem_free(link, MAXPATHLEN);
        }

        if (error != 0) {
                SDEV_RELE(sdv);
                return;
        }

        mutex_enter(&sdv->sdev_lookup_lock);
        SDEV_UNBLOCK_OTHERS(sdv, SDEV_LOOKUP);
        mutex_exit(&sdv->sdev_lookup_lock);

}

static void
devvt_rebuild_stale_link(struct sdev_node *ddv, struct sdev_node *dv)
{
        char *link;

        ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));

        ASSERT((strcmp(dv->sdev_name, DEVVT_ACTIVE_NAME) == 0) ||
            (strcmp(dv->sdev_name, DEVVT_CONSUSER_NAME) == 0));

        link = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
        if (strcmp(dv->sdev_name, DEVVT_ACTIVE_NAME) == 0) {
                (void) vt_getactive(link, MAXPATHLEN);
        } else if (strcmp(dv->sdev_name, DEVVT_CONSUSER_NAME) == 0) {
                (void) vt_getconsuser(link, MAXPATHLEN);
        }

        if (strcmp(link, dv->sdev_symlink) != 0) {
                strfree(dv->sdev_symlink);
                dv->sdev_symlink = strdup(link);
                dv->sdev_attr->va_size = strlen(link);
        }
        kmem_free(link, MAXPATHLEN);
}

/*
 * First step in refreshing directory contents.
 * Remove each invalid entry and rebuild the link
 * reference for each stale entry.
 */
static void
devvt_prunedir(struct sdev_node *ddv)
{
        struct vnode *vp;
        struct sdev_node *dv, *next = NULL;
        int (*vtor)(struct sdev_node *) = NULL;

        ASSERT(ddv->sdev_flags & SDEV_VTOR);

        vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
        ASSERT(vtor);

        for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
                next = SDEV_NEXT_ENTRY(ddv, dv);

                switch (vtor(dv)) {
                case SDEV_VTOR_VALID:
                        break;
                case SDEV_VTOR_SKIP:
                        break;
                case SDEV_VTOR_INVALID:
                        vp = SDEVTOV(dv);
                        if (vp->v_count != 0)
                                break;
                        /* remove the cached node */
                        SDEV_HOLD(dv);
                        (void) sdev_cache_update(ddv, &dv,
                            dv->sdev_name, SDEV_CACHE_DELETE);
                        SDEV_RELE(dv);
                        break;
                case SDEV_VTOR_STALE:
                        devvt_rebuild_stale_link(ddv, dv);
                        break;
                }
        }
}

static void
devvt_cleandir(struct vnode *dvp, struct cred *cred)
{
        struct sdev_node *sdvp = VTOSDEV(dvp);
        struct sdev_node *dv, *next = NULL;
        int min, cnt;
        char found = 0;

        mutex_enter(&vc_lock);
        cnt = VC_INSTANCES_COUNT;
        mutex_exit(&vc_lock);

        if (rw_tryupgrade(&sdvp->sdev_contents) == 0) {
                rw_exit(&sdvp->sdev_contents);
                rw_enter(&sdvp->sdev_contents, RW_WRITER);
        }

        /* 1.  prune invalid nodes and rebuild stale symlinks */
        devvt_prunedir(sdvp);

        /* 2. create missing nodes */
        for (min = 0; min < cnt; min++) {
                char nm[16];

                if (vt_minor_valid(min) == B_FALSE)
                        continue;

                (void) snprintf(nm, sizeof (nm), "%d", min);
                found = 0;
                for (dv = SDEV_FIRST_ENTRY(sdvp); dv; dv = next) {
                        next = SDEV_NEXT_ENTRY(sdvp, dv);

                        /* validate only ready nodes */
                        if (dv->sdev_state != SDEV_READY)
                                continue;
                        if (strcmp(nm, dv->sdev_name) == 0) {
                                found = 1;
                                break;
                        }
                }
                if (!found) {
                        devvt_create_snode(sdvp, nm, cred, SDEV_VATTR);
                }
        }

        /* 3. create active link node and console user link node */
        found = 0;
        for (dv = SDEV_FIRST_ENTRY(sdvp); dv; dv = next) {
                next = SDEV_NEXT_ENTRY(sdvp, dv);

                /* validate only ready nodes */
                if (dv->sdev_state != SDEV_READY)
                        continue;
                if ((strcmp(dv->sdev_name, DEVVT_ACTIVE_NAME) == 0))
                        found |= 0x01;
                if ((strcmp(dv->sdev_name, DEVVT_CONSUSER_NAME) == 0))
                        found |= 0x02;

                if ((found & 0x01) && (found & 0x02))
                        break;
        }
        if (!(found & 0x01))
                devvt_create_snode(sdvp, DEVVT_ACTIVE_NAME, cred, SDEV_VLINK);
        if (!(found & 0x02))
                devvt_create_snode(sdvp, DEVVT_CONSUSER_NAME, cred, SDEV_VLINK);

#ifndef __lock_lint
        rw_downgrade(&sdvp->sdev_contents);
#else
        rw_exit(&sdvp->sdev_contents);
#endif
}

/*ARGSUSED4*/
static int
devvt_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
    int *eofp, caller_context_t *ct, int flags)
{
        if (uiop->uio_offset == 0) {
                devvt_cleandir(dvp, cred);
        }

        return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
}

/*
 * We allow create to find existing nodes
 *      - if the node doesn't exist - EROFS
 *      - creating an existing dir read-only succeeds, otherwise EISDIR
 *      - exclusive creates fail - EEXIST
 */
/*ARGSUSED2*/
static int
devvt_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
    int mode, struct vnode **vpp, struct cred *cred, int flag,
    caller_context_t *ct, vsecattr_t *vsecp)
{
        int error;
        struct vnode *vp;

        *vpp = NULL;

        if ((error = devvt_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL,
            NULL)) != 0) {
                if (error == ENOENT)
                        error = EROFS;
                return (error);
        }

        if (excl == EXCL)
                error = EEXIST;
        else if (vp->v_type == VDIR && (mode & VWRITE))
                error = EISDIR;
        else
                error = VOP_ACCESS(vp, mode, 0, cred, ct);

        if (error) {
                VN_RELE(vp);
        } else
                *vpp = vp;

        return (error);
}

const fs_operation_def_t devvt_vnodeops_tbl[] = {
        VOPNAME_READDIR,        { .vop_readdir = devvt_readdir },
        VOPNAME_LOOKUP,         { .vop_lookup = devvt_lookup },
        VOPNAME_CREATE,         { .vop_create = devvt_create },
        VOPNAME_REMOVE,         { .error = fs_nosys },
        VOPNAME_MKDIR,          { .error = fs_nosys },
        VOPNAME_RMDIR,          { .error = fs_nosys },
        VOPNAME_SYMLINK,        { .error = fs_nosys },
        VOPNAME_SETSECATTR,     { .error = fs_nosys },
        NULL,                   NULL
};