sysmsg.c revision da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0
/*
* 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 2007 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, NULL)) != 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, NULL);
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, NULL);
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, NULL);
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, NULL));
}
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, NULL));
}
/* 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);
}