diff options
Diffstat (limited to 'usr/src/uts/common/fs/dev/sdev_ptsops.c')
-rw-r--r-- | usr/src/uts/common/fs/dev/sdev_ptsops.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/dev/sdev_ptsops.c b/usr/src/uts/common/fs/dev/sdev_ptsops.c new file mode 100644 index 0000000000..7ec53cf417 --- /dev/null +++ b/usr/src/uts/common/fs/dev/sdev_ptsops.c @@ -0,0 +1,398 @@ +/* + * 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 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * vnode ops for the /dev/pts directory + * The lookup is based on the internal pty table. We also + * override readdir in order to delete pts nodes no longer + * in use. + */ + +#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/ptms.h> +#include <sys/stat.h> + +#define DEVPTS_UID_DEFAULT 0 +#define DEVPTS_GID_DEFAULT 3 +#define DEVPTS_DEVMODE_DEFAULT (0620) + +#define isdigit(ch) ((ch) >= '0' && (ch) <= '9') + +static vattr_t devpts_vattr = { + AT_TYPE|AT_MODE|AT_UID|AT_GID, /* va_mask */ + VCHR, /* va_type */ + S_IFCHR | DEVPTS_DEVMODE_DEFAULT, /* va_mode */ + DEVPTS_UID_DEFAULT, /* va_uid */ + DEVPTS_GID_DEFAULT, /* va_gid */ + 0 /* 0 hereafter */ +}; + +struct vnodeops *devpts_vnodeops; + +struct vnodeops * +devpts_getvnodeops(void) +{ + return (devpts_vnodeops); +} + +/* + * Convert string to minor number. Some care must be taken + * as we are processing user input. Catch cases like + * /dev/pts/4foo and /dev/pts/-1 + */ +static int +devpts_strtol(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 = uminor; + return (0); +} + +/* + * Check if a pts sdev_node is still valid - i.e. it represents a current pty. + * This serves two purposes + * - only valid pts nodes are returned during lookup() and readdir(). + * - since pts sdev_nodes are not actively destroyed when a pty goes + * away, we use the validator to do deferred cleanup i.e. when such + * nodes are encountered during subsequent lookup() and readdir(). + */ +/*ARGSUSED*/ +int +devpts_validate(struct sdev_node *dv) +{ + minor_t min; + uid_t uid; + gid_t gid; + timestruc_t now; + char *nm = dv->sdev_name; + + ASSERT(!(dv->sdev_flags & SDEV_STALE)); + ASSERT(dv->sdev_state == SDEV_READY); + + /* 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 (devpts_strtol(nm, &min) != 0) { + sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm)); + return (SDEV_VTOR_INVALID); + } + + /* + * Check if pts driver is attached + */ + if (ptms_slave_attached() == (major_t)-1) { + sdcmn_err7(("devpts_validate: slave not attached\n")); + return (SDEV_VTOR_INVALID); + } + + if (ptms_minor_valid(min, &uid, &gid) == 0) { + if (ptms_minor_exists(min)) { + sdcmn_err7(("devpts_validate: valid in different zone " + "%s\n", nm)); + return (SDEV_VTOR_SKIP); + } else { + sdcmn_err7(("devpts_validate: %s not valid pty\n", + nm)); + return (SDEV_VTOR_INVALID); + } + } + + ASSERT(dv->sdev_attr); + if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) { + ASSERT(uid >= 0); + ASSERT(gid >= 0); + dv->sdev_attr->va_uid = uid; + dv->sdev_attr->va_gid = gid; + gethrestime(&now); + dv->sdev_attr->va_atime = now; + dv->sdev_attr->va_mtime = now; + dv->sdev_attr->va_ctime = now; + sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm)); + } + + return (SDEV_VTOR_VALID); +} + +/* + * This callback is invoked from devname_lookup_func() to create + * a pts entry when the node is not found in the cache. + */ +/*ARGSUSED*/ +static int +devpts_create_rvp(struct sdev_node *ddv, char *nm, + void **arg, cred_t *cred, void *whatever, char *whichever) +{ + minor_t min; + major_t maj; + uid_t uid; + gid_t gid; + timestruc_t now; + struct vattr *vap = (struct vattr *)arg; + + if (devpts_strtol(nm, &min) != 0) { + sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm)); + return (-1); + } + + /* + * Check if pts driver is attached and if it is + * get the major number. + */ + maj = ptms_slave_attached(); + if (maj == (major_t)-1) { + sdcmn_err7(("devpts_create_rvp: slave not attached\n")); + return (-1); + } + + /* + * Only allow creation of ptys allocated to our zone + */ + if (!ptms_minor_valid(min, &uid, &gid)) { + sdcmn_err7(("devpts_create_rvp: %s not valid pty" + "or not valid in this zone\n", nm)); + return (-1); + } + + + /* + * This is a valid pty (at least at this point in time). + * Create the node by setting the attribute. The rest + * is taken care of by devname_lookup_func(). + */ + *vap = devpts_vattr; + vap->va_rdev = makedevice(maj, min); + ASSERT(uid >= 0); + ASSERT(gid >= 0); + vap->va_uid = uid; + vap->va_gid = gid; + gethrestime(&now); + vap->va_atime = now; + vap->va_mtime = now; + vap->va_ctime = now; + + return (0); +} + +/* + * Clean pts sdev_nodes that are no longer valid. + */ +static void +devpts_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); + + if (rw_tryupgrade(&ddv->sdev_contents) == NULL) { + rw_exit(&ddv->sdev_contents); + rw_enter(&ddv->sdev_contents, RW_WRITER); + } + + for (dv = ddv->sdev_dot; dv; dv = next) { + next = dv->sdev_next; + + /* skip stale nodes */ + if (dv->sdev_flags & SDEV_STALE) + continue; + + /* validate and prune only ready nodes */ + if (dv->sdev_state != SDEV_READY) + continue; + + switch (vtor(dv)) { + case SDEV_VTOR_VALID: + case SDEV_VTOR_SKIP: + continue; + case SDEV_VTOR_INVALID: + sdcmn_err7(("prunedir: destroy invalid " + "node: %s(%p)\n", dv->sdev_name, (void *)dv)); + break; + } + vp = SDEVTOV(dv); + if (vp->v_count > 0) + continue; + SDEV_HOLD(dv); + /* remove the cache node */ + (void) sdev_cache_update(ddv, &dv, dv->sdev_name, + SDEV_CACHE_DELETE); + } + rw_downgrade(&ddv->sdev_contents); +} + +/* + * Lookup for /dev/pts directory + * If the entry does not exist, the devpts_create_rvp() callback + * is invoked to create it. Nodes do not persist across reboot. + */ +/*ARGSUSED3*/ +static int +devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp, + struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred) +{ + struct sdev_node *sdvp = VTOSDEV(dvp); + struct sdev_node *dv; + int error; + + error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp, + SDEV_VATTR); + + if (error == 0) { + switch ((*vpp)->v_type) { + case VCHR: + dv = VTOSDEV(VTOS(*vpp)->s_realvp); + break; + case VDIR: + dv = VTOSDEV(*vpp); + break; + default: + cmn_err(CE_PANIC, "devpts_lookup: Unsupported node " + "type: %p: %d", (void *)(*vpp), (*vpp)->v_type); + break; + } + ASSERT(SDEV_HELD(dv)); + } + + return (error); +} + +/* + * 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 +devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl, + int mode, struct vnode **vpp, struct cred *cred, int flag) +{ + int error; + struct vnode *vp; + + *vpp = NULL; + + error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred); + if (error == 0) { + if (excl == EXCL) + error = EEXIST; + else if (vp->v_type == VDIR && (mode & VWRITE)) + error = EISDIR; + else + error = VOP_ACCESS(vp, mode, 0, cred); + + if (error) { + VN_RELE(vp); + } else + *vpp = vp; + } else if (error == ENOENT) { + error = EROFS; + } + + return (error); +} + +/* + * Display all instantiated pts (slave) device nodes. + * A /dev/pts entry will be created only after the first lookup of the slave + * device succeeds. + */ +static int +devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred, + int *eofp) +{ + struct sdev_node *sdvp = VTOSDEV(dvp); + if (uiop->uio_offset == 0) { + devpts_prunedir(sdvp); + } + + return (devname_readdir_func(dvp, uiop, cred, eofp, 0)); +} + + +static int +devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol) +{ + ASSERT((protocol & AT_UID) || (protocol & AT_GID)); + ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev), + vap->va_uid, vap->va_gid); + return (0); + +} + +static int +devpts_setattr(struct vnode *vp, struct vattr *vap, int flags, + struct cred *cred) +{ + ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR)); + return (devname_setattr_func(vp, vap, flags, cred, + devpts_set_id, AT_UID|AT_GID)); +} + +/* + * We override lookup and readdir to build entries based on the + * in kernel pty table. Also override setattr/setsecattr to + * avoid persisting permissions. + */ +const fs_operation_def_t devpts_vnodeops_tbl[] = { + VOPNAME_READDIR, devpts_readdir, + VOPNAME_LOOKUP, devpts_lookup, + VOPNAME_CREATE, devpts_create, + VOPNAME_SETATTR, devpts_setattr, + VOPNAME_REMOVE, fs_nosys, + VOPNAME_MKDIR, fs_nosys, + VOPNAME_RMDIR, fs_nosys, + VOPNAME_SYMLINK, fs_nosys, + VOPNAME_SETSECATTR, fs_nosys, + NULL, NULL +}; |