1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 
  27 /*
  28  * Streams log driver.  See log(7D).
  29  */
  30 
  31 #include <sys/types.h>
  32 #include <sys/param.h>
  33 #include <sys/errno.h>
  34 #include <sys/stropts.h>
  35 #include <sys/strsubr.h>
  36 #include <sys/stream.h>
  37 #include <sys/strsun.h>
  38 #include <sys/debug.h>
  39 #include <sys/cred.h>
  40 #include <sys/file.h>
  41 #include <sys/ddi.h>
  42 #include <sys/stat.h>
  43 #include <sys/syslog.h>
  44 #include <sys/log.h>
  45 #include <sys/systm.h>
  46 #include <sys/modctl.h>
  47 #include <sys/policy.h>
  48 #include <sys/zone.h>
  49 
  50 #include <sys/conf.h>
  51 #include <sys/sunddi.h>
  52 
  53 static dev_info_t *log_devi;    /* private copy of devinfo pointer */
  54 int log_msgid;                  /* log.conf tunable: enable msgid generation */
  55 
  56 /* ARGSUSED */
  57 static int
  58 log_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
  59 {
  60         switch (infocmd) {
  61         case DDI_INFO_DEVT2DEVINFO:
  62                 *result = log_devi;
  63                 return (DDI_SUCCESS);
  64         case DDI_INFO_DEVT2INSTANCE:
  65                 *result = 0;
  66                 return (DDI_SUCCESS);
  67         }
  68         return (DDI_FAILURE);
  69 }
  70 
  71 /* ARGSUSED */
  72 static int
  73 log_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
  74 {
  75         if (ddi_create_minor_node(devi, "conslog", S_IFCHR,
  76             LOG_CONSMIN, DDI_PSEUDO, NULL) == DDI_FAILURE ||
  77             ddi_create_minor_node(devi, "log", S_IFCHR,
  78             LOG_LOGMIN, DDI_PSEUDO, NULL) == DDI_FAILURE) {
  79                 ddi_remove_minor_node(devi, NULL);
  80                 return (DDI_FAILURE);
  81         }
  82         log_devi = devi;
  83         log_msgid = ddi_getprop(DDI_DEV_T_ANY, log_devi,
  84             DDI_PROP_CANSLEEP, "msgid", 1);
  85         return (DDI_SUCCESS);
  86 }
  87 
  88 /*
  89  * log_open can be called for either /dev/log or dev/conslog.
  90  *
  91  * In the /dev/conslog case log_alloc() allocates a new minor device from
  92  * its cache.
  93  *
  94  * In the case of /dev/log, LOG_NUMCLONES devices are pre-allocated at zone
  95  * creation. log_alloc() finds the zone's next available minor device.
  96  *
  97  * On entry devp's minor number indicates which device (log or conslog), on
  98  * successful return it is the device instance.
  99  */
 100 
 101 /* ARGSUSED */
 102 static int
 103 log_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *cr)
 104 {
 105         log_t *lp;
 106         minor_t minor;
 107 
 108         if (sflag & (MODOPEN | CLONEOPEN))
 109                 return (ENXIO);
 110 
 111         switch (minor = getminor(*devp)) {
 112         case LOG_CONSMIN:               /* clone open of /dev/conslog */
 113                 if (flag & FREAD)
 114                         return (EINVAL);        /* write-only device */
 115                 if (q->q_ptr)
 116                         return (0);
 117                 break;
 118 
 119         case LOG_LOGMIN:                /* clone open of /dev/log */
 120                 break;
 121 
 122         default:
 123                 return (ENXIO);
 124         }
 125 
 126         lp = log_alloc(minor);
 127         if (lp == NULL)
 128                 return (ENXIO);
 129         *devp = makedevice(getmajor(*devp), lp->log_minor);
 130         q->q_ptr = lp;
 131         WR(q)->q_ptr = lp;
 132         lp->log_inuse = 1;
 133         qprocson(q);
 134 
 135         return (0);
 136 }
 137 
 138 /* ARGSUSED */
 139 static int
 140 log_close(queue_t *q, int flag, cred_t *cr)
 141 {
 142         log_t *lp = (log_t *)q->q_ptr;
 143 
 144         qprocsoff(q);
 145 
 146         lp->log_inuse = 0;
 147         log_update(lp, NULL, 0, NULL);
 148         freemsg(lp->log_data);
 149         lp->log_data = NULL;
 150         if (lp->log_major == LOG_CONSMIN)
 151                 log_free(lp);
 152         q->q_ptr = NULL;
 153         WR(q)->q_ptr = NULL;
 154 
 155         return (0);
 156 }
 157 
 158 static int
 159 log_wput(queue_t *q, mblk_t *mp)
 160 {
 161         log_t *lp = (log_t *)q->q_ptr;
 162         struct iocblk *iocp;
 163         mblk_t *mp2;
 164         cred_t *cr = msg_getcred(mp, NULL);
 165         zoneid_t zoneid;
 166 
 167         /*
 168          * Default to global zone if dblk doesn't have a valid cred.
 169          * Calls to syslog() go through putmsg(), which does set up
 170          * the cred.
 171          */
 172         zoneid = (cr != NULL) ? crgetzoneid(cr) : GLOBAL_ZONEID;
 173 
 174         switch (DB_TYPE(mp)) {
 175         case M_FLUSH:
 176                 if (*mp->b_rptr & FLUSHW) {
 177                         flushq(q, FLUSHALL);
 178                         *mp->b_rptr &= ~FLUSHW;
 179                 }
 180                 if (*mp->b_rptr & FLUSHR) {
 181                         flushq(RD(q), FLUSHALL);
 182                         qreply(q, mp);
 183                         return (0);
 184                 }
 185                 break;
 186 
 187         case M_IOCTL:
 188                 iocp = (struct iocblk *)mp->b_rptr;
 189 
 190                 if (lp->log_major != LOG_LOGMIN) {
 191                         /* write-only device */
 192                         miocnak(q, mp, 0, EINVAL);
 193                         return (0);
 194                 }
 195 
 196                 if (iocp->ioc_count == TRANSPARENT) {
 197                         miocnak(q, mp, 0, EINVAL);
 198                         return (0);
 199                 }
 200 
 201                 if (lp->log_flags) {
 202                         miocnak(q, mp, 0, EBUSY);
 203                         return (0);
 204                 }
 205 
 206                 freemsg(lp->log_data);
 207                 lp->log_data = mp->b_cont;
 208                 mp->b_cont = NULL;
 209 
 210                 switch (iocp->ioc_cmd) {
 211 
 212                 case I_CONSLOG:
 213                         log_update(lp, RD(q), SL_CONSOLE, log_console);
 214                         break;
 215 
 216                 case I_TRCLOG:
 217                         if (lp->log_data == NULL) {
 218                                 miocnak(q, mp, 0, EINVAL);
 219                                 return (0);
 220                         }
 221                         log_update(lp, RD(q), SL_TRACE, log_trace);
 222                         break;
 223 
 224                 case I_ERRLOG:
 225                         log_update(lp, RD(q), SL_ERROR, log_error);
 226                         break;
 227 
 228                 default:
 229                         miocnak(q, mp, 0, EINVAL);
 230                         return (0);
 231                 }
 232                 miocack(q, mp, 0, 0);
 233                 return (0);
 234 
 235         case M_PROTO:
 236                 if (MBLKL(mp) == sizeof (log_ctl_t) && mp->b_cont != NULL) {
 237                         log_ctl_t *lc = (log_ctl_t *)mp->b_rptr;
 238                         /* This code is used by savecore to log dump msgs */
 239                         if (mp->b_band != 0 &&
 240                             secpolicy_sys_config(CRED(), B_FALSE) == 0) {
 241                                 (void) putq(log_consq, mp);
 242                                 return (0);
 243                         }
 244                         if ((lc->pri & LOG_FACMASK) == LOG_KERN)
 245                                 lc->pri |= LOG_USER;
 246                         mp2 = log_makemsg(LOG_MID, LOG_CONSMIN, lc->level,
 247                             lc->flags, lc->pri, mp->b_cont->b_rptr,
 248                             MBLKL(mp->b_cont) + 1, 0);
 249                         if (mp2 != NULL)
 250                                 log_sendmsg(mp2, zoneid);
 251                 }
 252                 break;
 253 
 254         case M_DATA:
 255                 mp2 = log_makemsg(LOG_MID, LOG_CONSMIN, 0, SL_CONSOLE,
 256                     LOG_USER | LOG_INFO, mp->b_rptr, MBLKL(mp) + 1, 0);
 257                 if (mp2 != NULL)
 258                         log_sendmsg(mp2, zoneid);
 259                 break;
 260         }
 261 
 262         freemsg(mp);
 263         return (0);
 264 }
 265 
 266 static int
 267 log_rsrv(queue_t *q)
 268 {
 269         mblk_t *mp;
 270         char *msg, *msgid_start, *msgid_end;
 271         size_t idlen;
 272 
 273         while (canputnext(q) && (mp = getq(q)) != NULL) {
 274                 if (log_msgid == 0) {
 275                         /*
 276                          * Strip out the message ID.  If it's a kernel
 277                          * SL_CONSOLE message, replace msgid with "unix: ".
 278                          */
 279                         msg = (char *)mp->b_cont->b_rptr;
 280                         if ((msgid_start = strstr(msg, "[ID ")) != NULL &&
 281                             (msgid_end = strstr(msgid_start, "] ")) != NULL) {
 282                                 log_ctl_t *lc = (log_ctl_t *)mp->b_rptr;
 283                                 if ((lc->flags & SL_CONSOLE) &&
 284                                     (lc->pri & LOG_FACMASK) == LOG_KERN)
 285                                         msgid_start = msg + snprintf(msg,
 286                                             7, "unix: ");
 287                                 idlen = msgid_end + 2 - msgid_start;
 288                                 ovbcopy(msg, msg + idlen, msgid_start - msg);
 289                                 mp->b_cont->b_rptr += idlen;
 290                         }
 291                 }
 292                 mp->b_band = 0;
 293                 putnext(q, mp);
 294         }
 295         return (0);
 296 }
 297 
 298 static struct module_info logm_info =
 299         { LOG_MID, "LOG", LOG_MINPS, LOG_MAXPS, LOG_HIWAT, LOG_LOWAT };
 300 
 301 static struct qinit logrinit =
 302         { NULL, log_rsrv, log_open, log_close, NULL, &logm_info, NULL };
 303 
 304 static struct qinit logwinit =
 305         { log_wput, NULL, NULL, NULL, NULL, &logm_info, NULL };
 306 
 307 static struct streamtab loginfo = { &logrinit, &logwinit, NULL, NULL };
 308 
 309 DDI_DEFINE_STREAM_OPS(log_ops, nulldev, nulldev, log_attach, nodev,
 310         nodev, log_info, D_NEW | D_MP | D_MTPERMOD, &loginfo,
 311         ddi_quiesce_not_needed);
 312 
 313 static struct modldrv modldrv =
 314         { &mod_driverops, "streams log driver", &log_ops };
 315 
 316 static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL };
 317 
 318 int
 319 _init()
 320 {
 321         return (mod_install(&modlinkage));
 322 }
 323 
 324 int
 325 _fini()
 326 {
 327         return (mod_remove(&modlinkage));
 328 }
 329 
 330 int
 331 _info(struct modinfo *modinfop)
 332 {
 333         return (mod_info(&modlinkage, modinfop));
 334 }