diff options
Diffstat (limited to 'usr/src/uts/common/os/logsubr.c')
-rw-r--r-- | usr/src/uts/common/os/logsubr.c | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/logsubr.c b/usr/src/uts/common/os/logsubr.c new file mode 100644 index 0000000000..00df4a09bb --- /dev/null +++ b/usr/src/uts/common/os/logsubr.c @@ -0,0 +1,714 @@ +/* + * 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" + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/varargs.h> +#include <sys/systm.h> +#include <sys/cmn_err.h> +#include <sys/stream.h> +#include <sys/strsubr.h> +#include <sys/strsun.h> +#include <sys/sysmacros.h> +#include <sys/kmem.h> +#include <sys/log.h> +#include <sys/spl.h> +#include <sys/syslog.h> +#include <sys/console.h> +#include <sys/debug.h> +#include <sys/utsname.h> +#include <sys/id_space.h> +#include <sys/zone.h> + +log_zone_t log_global; +queue_t *log_consq; +queue_t *log_backlogq; +queue_t *log_intrq; + +#define LOG_PRISIZE 8 /* max priority size: 7 characters + null */ +#define LOG_FACSIZE 9 /* max priority size: 8 characters + null */ + +static krwlock_t log_rwlock; +static int log_rwlock_depth; +static int log_seq_no[SL_CONSOLE + 1]; +static stdata_t log_fakestr; +static id_space_t *log_minorspace; +static log_t log_backlog; +static log_t log_conslog; + +static queue_t *log_recentq; +static queue_t *log_freeq; + +static zone_key_t log_zone_key; + +static char log_overflow_msg[] = "message overflow on /dev/log minor #%d%s\n"; + +static char log_pri[LOG_PRIMASK + 1][LOG_PRISIZE] = { + "emerg", "alert", "crit", "error", + "warning", "notice", "info", "debug" +}; + +static char log_fac[LOG_NFACILITIES + 1][LOG_FACSIZE] = { + "kern", "user", "mail", "daemon", + "auth", "syslog", "lpr", "news", + "uucp", "resv9", "resv10", "resv11", + "resv12", "audit", "resv14", "cron", + "local0", "local1", "local2", "local3", + "local4", "local5", "local6", "local7", + "unknown" +}; + +/* + * Get exclusive access to the logging system; this includes all minor + * devices. We use an rwlock rather than a mutex because hold times + * are potentially long, so we don't want to waste cycles in adaptive mutex + * spin (rwlocks always block when contended). Note that we explicitly + * support recursive calls (e.g. printf() calls foo() calls printf()). + * + * Clients may use log_enter() / log_exit() to guarantee that a group + * of messages is treated atomically (i.e. they appear in order and are + * not interspersed with any other messages), e.g. for multiline printf(). + * + * This could probably be changed to a per-zone lock if contention becomes + * an issue. + */ +void +log_enter(void) +{ + if (rw_owner(&log_rwlock) != curthread) + rw_enter(&log_rwlock, RW_WRITER); + log_rwlock_depth++; +} + +void +log_exit(void) +{ + if (--log_rwlock_depth == 0) + rw_exit(&log_rwlock); +} + +void +log_flushq(queue_t *q) +{ + mblk_t *mp; + log_t *lp = (log_t *)q->q_ptr; + + /* lp will be NULL if the queue was created via log_makeq */ + while ((mp = getq_noenab(q)) != NULL) + log_sendmsg(mp, lp == NULL ? GLOBAL_ZONEID : lp->log_zoneid); +} + +/* + * Create a minimal queue with just enough fields filled in to support + * canput(9F), putq(9F), and getq_noenab(9F). We set QNOENB to ensure + * that the queue will never be enabled. + */ +static queue_t * +log_makeq(size_t lowat, size_t hiwat, void *ibc) +{ + queue_t *q; + + q = kmem_zalloc(sizeof (queue_t), KM_SLEEP); + q->q_stream = &log_fakestr; + q->q_flag = QISDRV | QMTSAFE | QNOENB | QREADR | QUSE; + q->q_nfsrv = q; + q->q_lowat = lowat; + q->q_hiwat = hiwat; + mutex_init(QLOCK(q), NULL, MUTEX_DRIVER, ibc); + + return (q); +} + +/* + * Initialize the log structure for a new zone. + */ +static void * +log_zoneinit(zoneid_t zoneid) +{ + int i; + log_zone_t *lzp; + + if (zoneid == GLOBAL_ZONEID) + lzp = &log_global; /* use statically allocated struct */ + else + lzp = kmem_zalloc(sizeof (log_zone_t), KM_SLEEP); + + for (i = 0; i < LOG_NUMCLONES; i++) { + lzp->lz_clones[i].log_minor = + (minor_t)id_alloc(log_minorspace); + lzp->lz_clones[i].log_zoneid = zoneid; + } + return (lzp); +} + +/*ARGSUSED*/ +static void +log_zonefree(zoneid_t zoneid, void *arg) +{ + log_zone_t *lzp = arg; + int i; + + ASSERT(lzp != &log_global && zoneid != GLOBAL_ZONEID); + if (lzp == NULL) + return; + for (i = 0; i < LOG_NUMCLONES; i++) + id_free(log_minorspace, lzp->lz_clones[i].log_minor); + kmem_free(lzp, sizeof (log_zone_t)); +} + +void +log_init(void) +{ + int log_maxzones; + + /* + * Create a backlog queue to consume console messages during periods + * when there is no console reader (e.g. before syslogd(1M) starts). + */ + log_backlogq = log_consq = log_makeq(0, LOG_HIWAT, NULL); + + /* + * Create a queue to hold free message of size <= LOG_MSGSIZE. + * Calls from high-level interrupt handlers will do a getq_noenab() + * from this queue, so its q_lock must be a maximum SPL spin lock. + */ + log_freeq = log_makeq(LOG_MINFREE, LOG_MAXFREE, (void *)ipltospl(SPL8)); + + /* + * Create a queue for messages from high-level interrupt context. + * These messages are drained via softcall, or explicitly by panic(). + */ + log_intrq = log_makeq(0, LOG_HIWAT, (void *)ipltospl(SPL8)); + + /* + * Create a queue to hold the most recent 8K of console messages. + * Useful for debugging. Required by the "$<msgbuf" adb macro. + */ + log_recentq = log_makeq(0, LOG_RECENTSIZE, NULL); + + /* + * Create an id space for clone devices opened via /dev/log. + * Need to limit the number of zones to avoid exceeding the + * available minor number space. + */ + log_maxzones = (L_MAXMIN32 - LOG_LOGMIN) / LOG_NUMCLONES - 1; + if (log_maxzones < maxzones) + maxzones = log_maxzones; + log_minorspace = id_space_create("logminor_space", LOG_LOGMIN + 1, + L_MAXMIN32); + /* + * Put ourselves on the ZSD list. Note that zones have not been + * initialized yet, but our constructor will be called on the global + * zone when they are. + */ + zone_key_create(&log_zone_key, log_zoneinit, NULL, log_zonefree); + + /* + * Initialize backlog structure. + */ + log_backlog.log_zoneid = GLOBAL_ZONEID; + log_backlog.log_minor = LOG_BACKLOG; + + /* + * Initialize conslog structure. + */ + log_conslog.log_zoneid = GLOBAL_ZONEID; + log_conslog.log_minor = LOG_CONSMIN; + + /* + * Let the logging begin. + */ + log_update(&log_backlog, log_backlogq, SL_CONSOLE, log_console); + + /* + * Now that logging is enabled, emit the SunOS banner. + */ + printf("\rSunOS Release %s Version %s %u-bit\n", + utsname.release, utsname.version, NBBY * (uint_t)sizeof (void *)); + printf("Copyright 1983-2005 Sun Microsystems, Inc. " + "All rights reserved.\nUse is subject to license terms.\n"); +#ifdef DEBUG + printf("DEBUG enabled\n"); +#endif +} + +/* + * Allocate a log device corresponding to supplied device type. All + * processes within a given zone that open /dev/conslog share the same + * device; processes opening /dev/log get distinct devices (if + * available). + */ +log_t * +log_alloc(minor_t type) +{ + zone_t *zptr = curproc->p_zone; + log_zone_t *lzp; + log_t *lp; + int i; + + if (type == LOG_CONSMIN) { + /* return the dedicated /dev/conslog device */ + return (&log_conslog); + } + + ASSERT(type == LOG_LOGMIN); + + lzp = zone_getspecific(log_zone_key, zptr); + ASSERT(lzp != NULL); + + /* search for an available /dev/log device for the zone */ + for (i = LOG_LOGMINIDX; i <= LOG_LOGMAXIDX; i++) { + lp = &lzp->lz_clones[i]; + if (lp->log_inuse == 0) + break; + } + if (i > LOG_LOGMAXIDX) + lp = NULL; + return (lp); +} + +/* + * Move console messages from src to dst. The time of day isn't known + * early in boot, so fix up the message timestamps if necessary. + */ +static void +log_conswitch(log_t *src, log_t *dst) +{ + mblk_t *mp; + mblk_t *hmp = NULL; + mblk_t *tmp = NULL; + log_ctl_t *hlc; + + while ((mp = getq_noenab(src->log_q)) != NULL) { + log_ctl_t *lc = (log_ctl_t *)mp->b_rptr; + lc->flags |= SL_LOGONLY; + + /* + * In the early boot phase hrestime is invalid. + * hrestime becomes valid when clock() runs for the first time. + * At this time is lbolt == 1. log_sendmsg() saves the lbolt + * value in ltime. + */ + if (lc->ltime < 2) { + /* + * Look ahead to first early boot message with time. + */ + if (hmp) { + tmp->b_next = mp; + tmp = mp; + } else + hmp = tmp = mp; + continue; + } + + while (hmp) { + tmp = hmp->b_next; + hmp->b_next = NULL; + hlc = (log_ctl_t *)hmp->b_rptr; + /* + * Calculate hrestime for an early log message with + * an invalid time stamp. We know: + * - the lbolt of the invalid time stamp. + * - the hrestime and lbolt of the first valid + * time stamp. + */ + hlc->ttime = lc->ttime - (lc->ltime - hlc->ltime) / hz; + (void) putq(dst->log_q, hmp); + hmp = tmp; + } + (void) putq(dst->log_q, mp); + } + while (hmp) { + tmp = hmp->b_next; + hmp->b_next = NULL; + hlc = (log_ctl_t *)hmp->b_rptr; + hlc->ttime = gethrestime_sec() - (lbolt - hlc->ltime) / hz; + (void) putq(dst->log_q, hmp); + hmp = tmp; + } + dst->log_overflow = src->log_overflow; + src->log_flags = 0; + dst->log_flags = SL_CONSOLE; + log_consq = dst->log_q; +} + +/* + * Set the fields in the 'target' clone to the specified values. + * Then, look at all clones to determine which message types are + * currently active and which clone is the primary console queue. + * If the primary console queue changes to or from the backlog + * queue, copy all messages from backlog to primary or vice versa. + */ +void +log_update(log_t *target, queue_t *q, short flags, log_filter_t *filter) +{ + log_t *lp; + short active = SL_CONSOLE; + zone_t *zptr = NULL; + log_zone_t *lzp; + zoneid_t zoneid = target->log_zoneid; + int i; + + log_enter(); + + if (q != NULL) + target->log_q = q; + target->log_wanted = filter; + target->log_flags = flags; + target->log_overflow = 0; + + /* + * Need to special case the global zone here since this may be + * called before zone_init. + */ + if (zoneid == GLOBAL_ZONEID) { + lzp = &log_global; + } else if ((zptr = zone_find_by_id(zoneid)) == NULL) { + log_exit(); + return; /* zone is being destroyed, ignore update */ + } else { + lzp = zone_getspecific(log_zone_key, zptr); + } + ASSERT(lzp != NULL); + + for (i = LOG_LOGMAXIDX; i >= LOG_LOGMINIDX; i--) { + lp = &lzp->lz_clones[i]; + if (zoneid == GLOBAL_ZONEID && (lp->log_flags & SL_CONSOLE)) + log_consq = lp->log_q; + active |= lp->log_flags; + } + lzp->lz_active = active; + + if (zptr) + zone_rele(zptr); + + if (log_consq == target->log_q) { + if (flags & SL_CONSOLE) + log_conswitch(&log_backlog, target); + else + log_conswitch(target, &log_backlog); + } + target->log_q = q; + + log_exit(); +} + +/*ARGSUSED*/ +int +log_error(log_t *lp, log_ctl_t *lc) +{ + if ((lc->pri & LOG_FACMASK) == LOG_KERN) + lc->pri = LOG_KERN | LOG_ERR; + return (1); +} + +int +log_trace(log_t *lp, log_ctl_t *lc) +{ + trace_ids_t *tid = (trace_ids_t *)lp->log_data->b_rptr; + trace_ids_t *tidend = (trace_ids_t *)lp->log_data->b_wptr; + + /* + * We use `tid + 1 <= tidend' here rather than the more traditional + * `tid < tidend', since the former ensures that there's at least + * `sizeof (trace_ids_t)' bytes available before executing the + * loop, whereas the latter only ensures that there's a single byte. + */ + for (; tid + 1 <= tidend; tid++) { + if (tid->ti_level < lc->level && tid->ti_level >= 0) + continue; + if (tid->ti_mid != lc->mid && tid->ti_mid >= 0) + continue; + if (tid->ti_sid != lc->sid && tid->ti_sid >= 0) + continue; + if ((lc->pri & LOG_FACMASK) == LOG_KERN) + lc->pri = LOG_KERN | LOG_DEBUG; + return (1); + } + return (0); +} + +/*ARGSUSED*/ +int +log_console(log_t *lp, log_ctl_t *lc) +{ + if ((lc->pri & LOG_FACMASK) == LOG_KERN) { + if (lc->flags & SL_FATAL) + lc->pri = LOG_KERN | LOG_CRIT; + else if (lc->flags & SL_ERROR) + lc->pri = LOG_KERN | LOG_ERR; + else if (lc->flags & SL_WARN) + lc->pri = LOG_KERN | LOG_WARNING; + else if (lc->flags & SL_NOTE) + lc->pri = LOG_KERN | LOG_NOTICE; + else if (lc->flags & SL_TRACE) + lc->pri = LOG_KERN | LOG_DEBUG; + else + lc->pri = LOG_KERN | LOG_INFO; + } + return (1); +} + +mblk_t * +log_makemsg(int mid, int sid, int level, int sl, int pri, void *msg, + size_t size, int on_intr) +{ + mblk_t *mp = NULL; + mblk_t *mp2; + log_ctl_t *lc; + + if (size <= LOG_MSGSIZE && + (on_intr || log_freeq->q_count > log_freeq->q_lowat)) + mp = getq_noenab(log_freeq); + + if (mp == NULL) { + if (on_intr || + (mp = allocb(sizeof (log_ctl_t), BPRI_HI)) == NULL || + (mp2 = allocb(MAX(size, LOG_MSGSIZE), BPRI_HI)) == NULL) { + freemsg(mp); + return (NULL); + } + DB_TYPE(mp) = M_PROTO; + mp->b_wptr += sizeof (log_ctl_t); + mp->b_cont = mp2; + } else { + mp2 = mp->b_cont; + mp2->b_wptr = mp2->b_rptr; + } + + lc = (log_ctl_t *)mp->b_rptr; + lc->mid = mid; + lc->sid = sid; + lc->level = level; + lc->flags = sl; + lc->pri = pri; + + bcopy(msg, mp2->b_wptr, size - 1); + mp2->b_wptr[size - 1] = '\0'; + mp2->b_wptr += strlen((char *)mp2->b_wptr) + 1; + + return (mp); +} + +void +log_freemsg(mblk_t *mp) +{ + mblk_t *mp2 = mp->b_cont; + + ASSERT(MBLKL(mp) == sizeof (log_ctl_t)); + ASSERT(mp2->b_rptr == mp2->b_datap->db_base); + + if ((log_freeq->q_flag & QFULL) == 0 && + MBLKL(mp2) <= LOG_MSGSIZE && MBLKSIZE(mp2) >= LOG_MSGSIZE) + (void) putq(log_freeq, mp); + else + freemsg(mp); +} + +void +log_sendmsg(mblk_t *mp, zoneid_t zoneid) +{ + log_t *lp; + char *src, *dst; + mblk_t *mp2 = mp->b_cont; + log_ctl_t *lc = (log_ctl_t *)mp->b_rptr; + int flags, fac; + off_t facility = 0; + off_t body = 0; + zone_t *zptr = NULL; + log_zone_t *lzp; + int i; + int backlog; + + /* + * Need to special case the global zone here since this may be + * called before zone_init. + */ + if (zoneid == GLOBAL_ZONEID) { + lzp = &log_global; + } else if ((zptr = zone_find_by_id(zoneid)) == NULL) { + /* specified zone doesn't exist, free message and return */ + log_freemsg(mp); + return; + } else { + lzp = zone_getspecific(log_zone_key, zptr); + } + ASSERT(lzp != NULL); + + if ((lc->flags & lzp->lz_active) == 0) { + if (zptr) + zone_rele(zptr); + log_freemsg(mp); + return; + } + + if (panicstr) { + /* + * Raise the console queue's q_hiwat to ensure that we + * capture all panic messages. + */ + log_consq->q_hiwat = 2 * LOG_HIWAT; + log_consq->q_flag &= ~QFULL; + + /* Message was created while panicking. */ + lc->flags |= SL_PANICMSG; + } + + src = (char *)mp2->b_rptr; + dst = strstr(src, "FACILITY_AND_PRIORITY] "); + if (dst != NULL) { + facility = dst - src; + body = facility + 23; /* strlen("FACILITY_AND_PRIORITY] ") */ + } + + log_enter(); + + lc->ltime = lbolt; + lc->ttime = gethrestime_sec(); + + flags = lc->flags & lzp->lz_active; + log_seq_no[flags & SL_ERROR]++; + log_seq_no[flags & SL_TRACE]++; + log_seq_no[flags & SL_CONSOLE]++; + + /* + * If this is in the global zone, start with the backlog, then + * walk through the clone logs. If not, just do the clone logs. + */ + backlog = (zoneid == GLOBAL_ZONEID); + i = LOG_LOGMINIDX; + while (i <= LOG_LOGMAXIDX) { + if (backlog) { + /* + * Do the backlog this time, then start on the + * others. + */ + backlog = 0; + lp = &log_backlog; + } else { + lp = &lzp->lz_clones[i++]; + } + + if ((lp->log_flags & flags) && lp->log_wanted(lp, lc)) { + if (canput(lp->log_q)) { + lp->log_overflow = 0; + lc->seq_no = log_seq_no[lp->log_flags]; + if ((mp2 = copymsg(mp)) == NULL) + break; + if (facility != 0) { + src = (char *)mp2->b_cont->b_rptr; + dst = src + facility; + fac = (lc->pri & LOG_FACMASK) >> 3; + dst += snprintf(dst, + LOG_FACSIZE + LOG_PRISIZE, "%s.%s", + log_fac[MIN(fac, LOG_NFACILITIES)], + log_pri[lc->pri & LOG_PRIMASK]); + src += body - 2; /* copy "] " too */ + while (*src != '\0') + *dst++ = *src++; + *dst++ = '\0'; + mp2->b_cont->b_wptr = (uchar_t *)dst; + } + (void) putq(lp->log_q, mp2); + } else if (++lp->log_overflow == 1) { + if (lp->log_q == log_consq) { + console_printf(log_overflow_msg, + lp->log_minor, + " -- is syslogd(1M) running?"); + } else { + printf(log_overflow_msg, + lp->log_minor, ""); + } + } + } + } + + if (zptr) + zone_rele(zptr); + + if ((flags & SL_CONSOLE) && (lc->pri & LOG_FACMASK) == LOG_KERN) { + if ((mp2 == NULL || log_consq == log_backlogq || panicstr) && + (lc->flags & SL_LOGONLY) == 0) + console_printf("%s", (char *)mp->b_cont->b_rptr + body); + if ((lc->flags & SL_CONSONLY) == 0 && + (mp2 = copymsg(mp)) != NULL) { + mp2->b_cont->b_rptr += body; + if (log_recentq->q_flag & QFULL) + freemsg(getq_noenab(log_recentq)); + (void) putq(log_recentq, mp2); + } + } + + log_freemsg(mp); + + log_exit(); +} + +/* + * Print queued messages to console. + */ +void +log_printq(queue_t *qfirst) +{ + mblk_t *mp; + queue_t *q, *qlast; + char *cp, *msgp; + log_ctl_t *lc; + + /* + * Look ahead to first queued message in the stream. + */ + qlast = NULL; + do { + for (q = qfirst; q->q_next != qlast; q = q->q_next) + continue; + for (mp = q->q_first; mp != NULL; mp = mp->b_next) { + lc = (log_ctl_t *)mp->b_rptr; + /* + * Check if message is already displayed at + * /dev/console. + */ + if (lc->flags & SL_PANICMSG) + continue; + + cp = (char *)mp->b_cont->b_rptr; + + /* Strip off the message ID. */ + if ((msgp = strstr(cp, "[ID ")) != NULL && + (msgp = strstr(msgp, "] ")) != NULL) { + cp = msgp + 2; + } + + /* + * Using console_printf instead of printf to avoid + * queueing messages to log_consq. + */ + console_printf("%s", cp); + } + } while ((qlast = q) != qfirst); +} |