/* * 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 2005 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/session.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; static boolean_t sysmsg_opened; static boolean_t msglog_opened; /* 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); } 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); for (i = 0; i < MAXDEVS; i++) 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; static boolean_t initialized; 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); } if (instance == SYS_SYSMIN) sysmsg_opened = B_TRUE; else msglog_opened = B_TRUE; if (!initialized) { bind_consadm_conf("/etc/consadm.conf"); initialized = B_TRUE; } 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; minor_t instance; ASSERT(dcvp != NULL); if (state != OTYP_CHR) return (ENXIO); instance = getminor(dev); mutex_enter(&dcvp_mutex); if (instance == SYS_SYSMIN) sysmsg_opened = B_FALSE; else msglog_opened = B_FALSE; if (sysmsg_opened || msglog_opened) { mutex_exit(&dcvp_mutex); return (0); } (void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred); VN_RELE(dcvp); dcvp = NULL; mutex_exit(&dcvp_mutex); /* * 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; dev_t sysmsg_dev, msglog_dev; extern dev_t rwsconsdev, rconsdev, uconsdev; if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) { rval = EBUSY; } else { sysmsg_dev = makedevice(ddi_driver_major(sysm_dip), SYS_SYSMIN); msglog_dev = makedevice(ddi_driver_major(sysm_dip), SYS_MSGMIN); if (devt == sysmsg_dev || devt == msglog_dev) rval = EINVAL; } return (rval); }