summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/sysmsg.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/sysmsg.c')
-rw-r--r--usr/src/uts/common/io/sysmsg.c712
1 files changed, 712 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/sysmsg.c b/usr/src/uts/common/io/sysmsg.c
new file mode 100644
index 0000000000..95aad728b9
--- /dev/null
+++ b/usr/src/uts/common/io/sysmsg.c
@@ -0,0 +1,712 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * System message redirection driver for Sun.
+ *
+ * Redirects system message output to the device designated as the underlying
+ * "hardware" console, as given by the value of sysmvp. The implementation
+ * assumes that sysmvp denotes a STREAMS device; the assumption is justified
+ * since consoles must be capable of effecting tty semantics.
+ */
+
+#include <sys/types.h>
+#include <sys/kmem.h>
+#include <sys/open.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/signal.h>
+#include <sys/cred.h>
+#include <sys/user.h>
+#include <sys/proc.h>
+#include <sys/vnode.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/stream.h>
+#include <sys/strsubr.h>
+#include <sys/poll.h>
+#include <sys/debug.h>
+#include <sys/sysmsg_impl.h>
+#include <sys/conf.h>
+#include <sys/termios.h>
+#include <sys/errno.h>
+#include <sys/modctl.h>
+#include <sys/pathname.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/consdev.h>
+#include <sys/policy.h>
+
+/*
+ * internal functions
+ */
+static int sysmopen(dev_t *, int, int, cred_t *);
+static int sysmclose(dev_t, int, int, cred_t *);
+static int sysmread(dev_t, struct uio *, cred_t *);
+static int sysmwrite(dev_t, struct uio *, cred_t *);
+static int sysmioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+static int sysmpoll(dev_t, short, int, short *, struct pollhead **);
+static int sysm_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int sysm_attach(dev_info_t *, ddi_attach_cmd_t);
+static int sysm_detach(dev_info_t *, ddi_detach_cmd_t);
+static void bind_consadm_conf(char *);
+static int checkarg(dev_t);
+
+static dev_info_t *sysm_dip; /* private copy of devinfo pointer */
+
+static struct cb_ops sysm_cb_ops = {
+
+ sysmopen, /* open */
+ sysmclose, /* close */
+ nodev, /* strategy */
+ nodev, /* print */
+ nodev, /* dump */
+ sysmread, /* read */
+ sysmwrite, /* write */
+ sysmioctl, /* ioctl */
+ nodev, /* devmap */
+ nodev, /* mmap */
+ nodev, /* segmap */
+ sysmpoll, /* poll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* streamtab */
+ D_NEW | D_MP, /* Driver compatibility flag */
+ CB_REV, /* cb_rev */
+ nodev, /* aread */
+ nodev /* awrite */
+};
+
+static struct dev_ops sysm_ops = {
+
+ DEVO_REV, /* devo_rev, */
+ 0, /* refcnt */
+ sysm_info, /* info */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ sysm_attach, /* attach */
+ sysm_detach, /* detach */
+ nodev, /* reset */
+ &sysm_cb_ops, /* driver operations */
+ (struct bus_ops *)0, /* bus operations */
+ nulldev /* power */
+
+};
+
+/*
+ * Global variables associated with the console device:
+ */
+
+#define SYS_SYSMIN 0 /* sysmsg minor number */
+#define SYS_MSGMIN 1 /* msglog minor number */
+#define SYSPATHLEN 255 /* length of device path */
+
+/*
+ * Private driver state:
+ */
+
+#define MAXDEVS 5
+
+typedef struct {
+ dev_t dca_devt;
+ int dca_flags;
+ vnode_t *dca_vp;
+ krwlock_t dca_lock;
+ char dca_name[SYSPATHLEN];
+} devicecache_t;
+
+/* list of dyn. + persist. config'ed dev's */
+static devicecache_t sysmcache[MAXDEVS];
+static kmutex_t dcvp_mutex;
+static vnode_t *dcvp = NULL;
+
+/* flags for device cache */
+#define SYSM_DISABLED 0x0
+#define SYSM_ENABLED 0x1
+
+/*
+ * Module linkage information for the kernel.
+ */
+
+static struct modldrv modldrv = {
+ &mod_driverops, /* Type of module. This one is a pseudo driver */
+ "System message redirection (fanout) driver %I%",
+ &sysm_ops, /* driver ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ &modldrv,
+ NULL
+};
+
+int
+_init(void)
+{
+ return (mod_install(&modlinkage));
+}
+
+int
+_fini(void)
+{
+ return (mod_remove(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+/*
+ * DDI glue routines
+ */
+static int
+sysm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
+{
+ int i;
+
+ switch (cmd) {
+ case DDI_ATTACH:
+ ASSERT(sysm_dip == NULL);
+
+ if (ddi_create_minor_node(devi, "sysmsg", S_IFCHR,
+ SYS_SYSMIN, DDI_PSEUDO, NULL) == DDI_FAILURE ||
+ ddi_create_minor_node(devi, "msglog", S_IFCHR,
+ SYS_MSGMIN, DDI_PSEUDO, NULL) == DDI_FAILURE) {
+ ddi_remove_minor_node(devi, NULL);
+ return (DDI_FAILURE);
+ }
+
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_init(&sysmcache[i].dca_lock, NULL, RW_DRIVER, NULL);
+ }
+
+ /* set everything up .. */
+ bind_consadm_conf("/etc/consadm.conf");
+ sysm_dip = devi;
+ return (DDI_SUCCESS);
+ case DDI_SUSPEND:
+ case DDI_PM_SUSPEND:
+ return (DDI_SUCCESS);
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+static int
+sysm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
+{
+ int i;
+
+ switch (cmd) {
+ case DDI_DETACH:
+ ASSERT(sysm_dip == devi);
+
+ if (dcvp) {
+ (void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred);
+ VN_RELE(dcvp);
+ dcvp = NULL;
+ }
+ for (i = 0; i < MAXDEVS; i++) {
+ if (sysmcache[i].dca_vp != NULL) {
+ (void) VOP_CLOSE(sysmcache[i].dca_vp, 0,
+ 1, (offset_t)0, 0);
+ VN_RELE(sysmcache[i].dca_vp);
+ }
+ sysmcache[i].dca_vp = NULL;
+ rw_destroy(&sysmcache[i].dca_lock);
+ }
+
+ ddi_remove_minor_node(devi, NULL);
+ sysm_dip = NULL;
+ return (DDI_SUCCESS);
+
+ case DDI_SUSPEND:
+ case DDI_PM_SUSPEND:
+ return (DDI_SUCCESS);
+ default:
+ return (DDI_FAILURE);
+ }
+
+}
+
+/* ARGSUSED */
+static int
+sysm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+ int rval = DDI_FAILURE;
+ minor_t instance;
+
+ instance = getminor((dev_t)arg);
+
+ switch (infocmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ if (sysm_dip != NULL &&
+ (instance == SYS_SYSMIN || instance == SYS_MSGMIN)) {
+ *result = sysm_dip;
+ rval = DDI_SUCCESS;
+ }
+ break;
+
+ case DDI_INFO_DEVT2INSTANCE:
+ if (instance == SYS_SYSMIN || instance == SYS_MSGMIN) {
+ *result = NULL;
+ rval = DDI_SUCCESS;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return (rval);
+}
+
+/*
+ * Parse the contents of the buffer, and bind the named
+ * devices as auxiliary consoles using our own ioctl routine.
+ *
+ * Comments begin with '#' and are terminated only by a newline
+ * Device names begin with a '/', and are terminated by a newline,
+ * space, '#' or tab.
+ */
+static void
+parse_buffer(char *buf, ssize_t fsize)
+{
+ char *ebuf = buf + fsize;
+ char *devname = NULL;
+ int eatcomments = 0;
+
+ while (buf < ebuf) {
+ if (eatcomments) {
+ if (*buf++ == '\n')
+ eatcomments = 0;
+ continue;
+ }
+ switch (*buf) {
+ case '/':
+ if (devname == NULL)
+ devname = buf;
+ break;
+ case '#':
+ eatcomments = 1;
+ /*FALLTHROUGH*/
+ case ' ':
+ case '\t':
+ case '\n':
+ *buf = '\0';
+ if (devname == NULL)
+ break;
+ (void) sysmioctl(NODEV, CIOCSETCONSOLE,
+ (intptr_t)devname, FNATIVE|FKIOCTL|FREAD|FWRITE,
+ kcred, NULL);
+ devname = NULL;
+ break;
+ default:
+ break;
+ }
+ buf++;
+ }
+}
+
+#define CNSADM_BYTES_MAX 2000 /* XXX nasty fixed size */
+
+static void
+bind_consadm_conf(char *path)
+{
+ struct vattr vattr;
+ vnode_t *vp;
+ void *buf;
+ size_t size;
+ ssize_t resid;
+ int err = 0;
+
+ if (vn_open(path, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0) != 0)
+ return;
+ vattr.va_mask = AT_SIZE;
+ if ((err = VOP_GETATTR(vp, &vattr, 0, kcred)) != 0) {
+ cmn_err(CE_WARN, "sysmsg: getattr: '%s': error %d",
+ path, err);
+ goto closevp;
+ }
+
+ size = vattr.va_size > CNSADM_BYTES_MAX ?
+ CNSADM_BYTES_MAX : (ssize_t)vattr.va_size;
+ buf = kmem_alloc(size, KM_SLEEP);
+
+ if ((err = vn_rdwr(UIO_READ, vp, buf, size, (offset_t)0,
+ UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid)) != 0)
+ cmn_err(CE_WARN, "sysmsg: vn_rdwr: '%s': error %d",
+ path, err);
+ else
+ parse_buffer(buf, size - resid);
+
+ kmem_free(buf, size);
+closevp:
+ (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, kcred);
+ VN_RELE(vp);
+}
+
+/* ARGSUSED */
+static int
+sysmopen(dev_t *dev, int flag, int state, cred_t *cred)
+{
+ int i;
+ vnode_t *vp;
+ minor_t instance;
+
+ instance = getminor(*dev);
+
+ if (state != OTYP_CHR || (instance != 0 && instance != 1))
+ return (ENXIO);
+
+ mutex_enter(&dcvp_mutex);
+ if ((dcvp == NULL) && (vn_open("/dev/console",
+ UIO_SYSSPACE, FWRITE, 0, &dcvp, 0, 0) != 0)) {
+ mutex_exit(&dcvp_mutex);
+ return (ENXIO);
+ }
+ mutex_exit(&dcvp_mutex);
+
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
+ if ((sysmcache[i].dca_flags & SYSM_ENABLED) &&
+ sysmcache[i].dca_vp == NULL) {
+ /*
+ * 4196476 - FTRUNC was causing E10K to return EINVAL
+ * on open
+ */
+ flag = flag & ~FTRUNC;
+ /*
+ * Open failures on the auxiliary consoles are
+ * not returned because we don't care if some
+ * subset get an error. We know the default console
+ * is okay, and preserve the semantics of the
+ * open for the default console.
+ * Set NONBLOCK|NDELAY in case there's no carrier.
+ */
+ if (vn_open(sysmcache[i].dca_name, UIO_SYSSPACE,
+ flag | FNONBLOCK | FNDELAY, 0, &vp, 0, 0) == 0)
+ sysmcache[i].dca_vp = vp;
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+sysmclose(dev_t dev, int flag, int state, cred_t *cred)
+{
+ int i;
+
+ if (state != OTYP_CHR)
+ return (ENXIO);
+
+ /*
+ * Close the auxiliary consoles, we're not concerned with
+ * passing up the errors.
+ */
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
+ if (sysmcache[i].dca_vp != NULL) {
+ (void) VOP_CLOSE(sysmcache[i].dca_vp, flag,
+ 1, (offset_t)0, cred);
+ VN_RELE(sysmcache[i].dca_vp);
+ sysmcache[i].dca_vp = NULL;
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+
+ return (0);
+}
+
+/* Reads occur only on the default console */
+
+/* ARGSUSED */
+static int
+sysmread(dev_t dev, struct uio *uio, cred_t *cred)
+{
+ ASSERT(dcvp != NULL);
+ return (VOP_READ(dcvp, uio, 0, cred, NULL));
+}
+
+/* ARGSUSED */
+static int
+sysmwrite(dev_t dev, struct uio *uio, cred_t *cred)
+{
+ int i = 0;
+ iovec_t uio_iov;
+ struct uio tuio;
+
+ ASSERT(dcvp != NULL);
+ ASSERT(uio != NULL);
+
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_READER);
+ if (sysmcache[i].dca_vp != NULL &&
+ (sysmcache[i].dca_flags & SYSM_ENABLED)) {
+ tuio = *uio;
+ uio_iov = *(uio->uio_iov);
+ tuio.uio_iov = &uio_iov;
+ (void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred,
+ NULL);
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+ return (VOP_WRITE(dcvp, uio, 0, cred, NULL));
+}
+
+/* ARGSUSED */
+static int
+sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp)
+{
+ int rval = 0;
+ int error = 0;
+ size_t size = 0;
+ int i;
+ char *infop;
+ char found = 0;
+ dev_t newdevt = (dev_t)NODEV; /* because 0 == /dev/console */
+ vnode_t *vp;
+
+ switch (cmd) {
+ case CIOCGETCONSOLE:
+ /* Sum over the number of enabled devices */
+ for (i = 0; i < MAXDEVS; i++) {
+ if (sysmcache[i].dca_flags & SYSM_ENABLED)
+ /* list is space separated, followed by NULL */
+ size += strlen(sysmcache[i].dca_name) + 1;
+ }
+ if (size == 0)
+ return (0);
+ break;
+ case CIOCSETCONSOLE:
+ case CIOCRMCONSOLE:
+ size = sizeof (sysmcache[0].dca_name);
+ break;
+ case CIOCTTYCONSOLE:
+ {
+ dev_t d;
+ dev32_t d32;
+ extern dev_t rwsconsdev, rconsdev, uconsdev;
+ proc_t *p;
+
+ if (drv_getparm(UPROCP, &p) != 0)
+ return (ENODEV);
+ else
+ d = cttydev(p);
+ /*
+ * If the controlling terminal is the real
+ * or workstation console device, map to what the
+ * user thinks is the console device.
+ */
+ if (d == rwsconsdev || d == rconsdev)
+ d = uconsdev;
+ if ((flag & FMODELS) != FNATIVE) {
+ if (!cmpldev(&d32, d))
+ return (EOVERFLOW);
+ if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32),
+ flag))
+ return (EFAULT);
+ } else {
+ if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag))
+ return (EFAULT);
+ }
+ return (0);
+ }
+ default:
+ /* everything else is sent to the console device */
+ return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp));
+ }
+
+ if ((rval = secpolicy_console(cred)) != 0)
+ return (EPERM);
+
+ infop = kmem_alloc(size, KM_SLEEP);
+ if (flag & FKIOCTL)
+ error = copystr((caddr_t)arg, infop, size, NULL);
+ else
+ error = copyinstr((caddr_t)arg, infop, size, NULL);
+
+ if (error) {
+ switch (cmd) {
+ case CIOCGETCONSOLE:
+ /*
+ * If the buffer is null, then return a byte count
+ * to user land.
+ */
+ *rvalp = size;
+ goto err_exit;
+ default:
+ rval = EFAULT;
+ goto err_exit;
+ }
+ }
+
+ if (infop[0] != NULL) {
+ if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW,
+ NULLVPP, &vp)) == 0) {
+ if (vp->v_type != VCHR) {
+ VN_RELE(vp);
+ rval = EINVAL;
+ goto err_exit;
+ }
+ newdevt = vp->v_rdev;
+ VN_RELE(vp);
+ } else
+ goto err_exit;
+ }
+
+ switch (cmd) {
+ case CIOCGETCONSOLE:
+ /*
+ * Return the list of device names that are enabled.
+ */
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_READER);
+ if (sysmcache[i].dca_flags & SYSM_ENABLED) {
+ if (infop[0] != NULL)
+ (void) strcat(infop, " ");
+ (void) strcat(infop, sysmcache[i].dca_name);
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+ if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL))
+ rval = EFAULT;
+ break;
+
+ case CIOCSETCONSOLE:
+ if ((rval = checkarg(newdevt)) != 0)
+ break;
+ /*
+ * The device does not have to be open or disabled to
+ * perform the set console.
+ */
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
+ if (sysmcache[i].dca_devt == newdevt &&
+ (sysmcache[i].dca_flags & SYSM_ENABLED)) {
+ (void) strcpy(sysmcache[i].dca_name, infop);
+ rval = EEXIST;
+ rw_exit(&sysmcache[i].dca_lock);
+ break;
+ } else if (sysmcache[i].dca_devt == newdevt &&
+ sysmcache[i].dca_flags == SYSM_DISABLED) {
+ sysmcache[i].dca_flags |= SYSM_ENABLED;
+ (void) strcpy(sysmcache[i].dca_name, infop);
+ rw_exit(&sysmcache[i].dca_lock);
+ found = 1;
+ break;
+ } else if (sysmcache[i].dca_devt == 0) {
+ ASSERT(sysmcache[i].dca_vp == NULL &&
+ sysmcache[i].dca_flags == SYSM_DISABLED);
+ (void) strcpy(sysmcache[i].dca_name, infop);
+ sysmcache[i].dca_flags = SYSM_ENABLED;
+ sysmcache[i].dca_devt = newdevt;
+ rw_exit(&sysmcache[i].dca_lock);
+ found = 1;
+ break;
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+ if (found == 0 && rval == 0)
+ rval = ENOENT;
+ break;
+
+ case CIOCRMCONSOLE:
+ for (i = 0; i < MAXDEVS; i++) {
+ rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
+ if (sysmcache[i].dca_devt == newdevt) {
+ sysmcache[i].dca_flags = SYSM_DISABLED;
+ sysmcache[i].dca_name[0] = '\0';
+ rw_exit(&sysmcache[i].dca_lock);
+ found = 1;
+ break;
+ }
+ rw_exit(&sysmcache[i].dca_lock);
+ }
+ if (found == 0)
+ rval = ENOENT;
+ break;
+
+ default:
+ break;
+ }
+
+err_exit:
+ kmem_free(infop, size);
+ return (rval);
+}
+
+/* As with the read, we poll only the default console */
+
+/* ARGSUSED */
+static int
+sysmpoll(dev_t dev, short events, int anyyet, short *reventsp,
+ struct pollhead **phpp)
+{
+ return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp));
+}
+
+/* Sanity check that the device is good */
+static int
+checkarg(dev_t devt)
+{
+ int rval = 0;
+ vnode_t *vp;
+ extern dev_t rwsconsdev, rconsdev, uconsdev;
+
+ if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) {
+ rval = EBUSY;
+ goto err_exit;
+ }
+ if ((rval = lookupname("/dev/sysmsg", UIO_SYSSPACE, FOLLOW,
+ NULLVPP, &vp)) == 0) {
+ if (devt == vp->v_rdev) {
+ VN_RELE(vp);
+ rval = EINVAL;
+ goto err_exit;
+ }
+ VN_RELE(vp);
+ }
+ if ((rval = lookupname("/dev/msglog", UIO_SYSSPACE, FOLLOW,
+ NULLVPP, &vp)) == 0) {
+ if (devt == vp->v_rdev) {
+ VN_RELE(vp);
+ rval = EINVAL;
+ goto err_exit;
+ }
+ VN_RELE(vp);
+ }
+
+err_exit:
+ return (rval);
+}