summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/dev/sdev_ptsops.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/dev/sdev_ptsops.c')
-rw-r--r--usr/src/uts/common/fs/dev/sdev_ptsops.c398
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
+};