diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/uts/common/io/sysmsg.c | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/uts/common/io/sysmsg.c')
-rw-r--r-- | usr/src/uts/common/io/sysmsg.c | 712 |
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); +} |