X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0b4e3aa066abc0728aacb4bbeb86f53f9737156e..bb59bff194111743b33cc36712410b5656329d3c:/bsd/kern/subr_log.c diff --git a/bsd/kern/subr_log.c b/bsd/kern/subr_log.c index 6f78c5d10..0f8c00b36 100644 --- a/bsd/kern/subr_log.c +++ b/bsd/kern/subr_log.c @@ -1,23 +1,29 @@ /* - * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2010 Apple, Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * - * @APPLE_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ /* @@ -61,18 +67,24 @@ #include #include -#include +#include #include #include #include -#include +#include #include #include +#include #include -#if 0 /* [ */ -#include -#include -#endif /* 0 ] */ +#include +#include +#include +#include +#include +#include + +/* XXX should be in a common header somewhere */ +extern void logwakeup(void); #define LOG_RDPRI (PZERO + 1) @@ -80,6 +92,10 @@ #define LOG_ASYNC 0x04 #define LOG_RDWAIT 0x08 +#define MAX_UNREAD_CHARS (CONFIG_MSG_BSIZE/2) +/* All globals should be accessed under LOG_LOCK() */ + +/* logsoftc only valid while log_open=1 */ struct logsoftc { int sc_state; /* see above for possibilities */ struct selinfo sc_selp; /* thread waiting for select */ @@ -87,198 +103,191 @@ struct logsoftc { } logsoftc; int log_open; /* also used in log() */ -struct msgbuf temp_msgbuf; -struct msgbuf *msgbufp; -static int _logentrypend = 0; +char smsg_bufc[CONFIG_MSG_BSIZE]; /* static buffer */ +struct msgbuf msgbuf = {MSG_MAGIC,sizeof(smsg_bufc),0,0,smsg_bufc}; +struct msgbuf *msgbufp __attribute__((used)) = &msgbuf; + +/* the following are implemented in osfmk/kern/printf.c */ +extern void bsd_log_lock(void); +extern void bsd_log_unlock(void); +extern void bsd_log_init(void); + +/* XXX wants a linker set so these can be static */ +extern d_open_t logopen; +extern d_close_t logclose; +extern d_read_t logread; +extern d_ioctl_t logioctl; +extern d_select_t logselect; /* * Serialize log access. Note that the log can be written at interrupt level, * so any log manipulations that can be done from, or affect, another processor * at interrupt level must be guarded with a spin lock. */ -decl_simple_lock_data(,log_lock); /* stop races dead in their tracks */ -#define LOG_LOCK() simple_lock(&log_lock) -#define LOG_UNLOCK() simple_unlock(&log_lock) -#define LOG_LOCK_INIT() simple_lock_init(&log_lock) + +#define LOG_LOCK() bsd_log_lock() +#define LOG_UNLOCK() bsd_log_unlock() + +#if DEBUG +#define LOG_SETSIZE_DEBUG(x...) kprintf(x) +#else +#define LOG_SETSIZE_DEBUG(x...) do { } while(0) +#endif + +static int sysctl_kern_msgbuf(struct sysctl_oid *oidp, + void *arg1, + int arg2, + struct sysctl_req *req); /*ARGSUSED*/ -logopen(dev, flags, mode, p) - dev_t dev; - int flags, mode; - struct proc *p; +int +logopen(__unused dev_t dev, __unused int flags, __unused int mode, struct proc *p) { - unix_master(); /* for pg_id, sigh */ LOG_LOCK(); if (log_open) { LOG_UNLOCK(); - unix_release(); return (EBUSY); } - log_open = 1; logsoftc.sc_pgid = p->p_pid; /* signal process only */ - /* - * Potential race here with putchar() but since putchar should be - * called by autoconf, msg_magic should be initialized by the time - * we get here. - */ - if (msgbufp->msg_magic != MSG_MAGIC) { - register int i; + log_open = 1; - msgbufp->msg_magic = MSG_MAGIC; - msgbufp->msg_bufx = msgbufp->msg_bufr = 0; - for (i=0; i < MSG_BSIZE; i++) - msgbufp->msg_bufc[i] = 0; - } LOG_UNLOCK(); - unix_release(); + return (0); } /*ARGSUSED*/ int -logclose(dev, flag) - dev_t dev; +logclose(__unused dev_t dev, __unused int flag, __unused int devtype, __unused struct proc *p) { - int oldpri; LOG_LOCK(); - log_open = 0; selwakeup(&logsoftc.sc_selp); - oldpri = splhigh(); selthreadclear(&logsoftc.sc_selp); - splx(oldpri); + log_open = 0; LOG_UNLOCK(); return (0); } /*ARGSUSED*/ int -logread(dev, uio, flag) - dev_t dev; - struct uio *uio; - int flag; +logread(__unused dev_t dev, struct uio *uio, int flag) { - register long l; - register int s; + int l; int error = 0; - s = splhigh(); + LOG_LOCK(); while (msgbufp->msg_bufr == msgbufp->msg_bufx) { if (flag & IO_NDELAY) { - splx(s); - return (EWOULDBLOCK); + error = EWOULDBLOCK; + goto out; } if (logsoftc.sc_state & LOG_NBIO) { - splx(s); - return (EWOULDBLOCK); + error = EWOULDBLOCK; + goto out; } logsoftc.sc_state |= LOG_RDWAIT; - if (error = tsleep((caddr_t)msgbufp, LOG_RDPRI | PCATCH, - "klog", 0)) { - splx(s); - return (error); + LOG_UNLOCK(); + /* + * If the wakeup is missed + * then wait for 5 sec and reevaluate + */ + if ((error = tsleep((caddr_t)msgbufp, LOG_RDPRI | PCATCH, + "klog", 5 * hz)) != 0) { + /* if it times out; ignore */ + if (error != EWOULDBLOCK) + return (error); } + LOG_LOCK(); } - splx(s); logsoftc.sc_state &= ~LOG_RDWAIT; - while (uio->uio_resid > 0) { + while (uio_resid(uio) > 0) { + int readpos; + l = msgbufp->msg_bufx - msgbufp->msg_bufr; if (l < 0) - l = MSG_BSIZE - msgbufp->msg_bufr; - l = min(l, uio->uio_resid); + l = msgbufp->msg_size - msgbufp->msg_bufr; + l = min(l, uio_resid(uio)); if (l == 0) break; - error = uiomove((caddr_t)&msgbufp->msg_bufc[msgbufp->msg_bufr], - (int)l, uio); + + readpos = msgbufp->msg_bufr; + LOG_UNLOCK(); + error = uiomove((caddr_t)&msgbufp->msg_bufc[readpos], + l, uio); + LOG_LOCK(); if (error) break; - msgbufp->msg_bufr += l; - if (msgbufp->msg_bufr < 0 || msgbufp->msg_bufr >= MSG_BSIZE) + msgbufp->msg_bufr = readpos + l; + if (msgbufp->msg_bufr >= msgbufp->msg_size) msgbufp->msg_bufr = 0; } +out: + LOG_UNLOCK(); return (error); } /*ARGSUSED*/ int -logselect(dev, rw, wql, p) - dev_t dev; - int rw; - void * wql; - struct proc *p; +logselect(__unused dev_t dev, int rw, void * wql, struct proc *p) { - int s = splhigh(); - switch (rw) { case FREAD: + LOG_LOCK(); if (msgbufp->msg_bufr != msgbufp->msg_bufx) { - splx(s); + LOG_UNLOCK(); return (1); } selrecord(p, &logsoftc.sc_selp, wql); + LOG_UNLOCK(); break; } - splx(s); return (0); } void -logwakeup() +logwakeup(void) { - struct proc *p; int pgid; - boolean_t funnel_state; - if (!log_open) + LOG_LOCK(); + if (!log_open) { + LOG_UNLOCK(); return; - funnel_state = thread_funnel_set(kernel_flock, TRUE); + } selwakeup(&logsoftc.sc_selp); if (logsoftc.sc_state & LOG_ASYNC) { - unix_master(); - LOG_LOCK(); pgid = logsoftc.sc_pgid; LOG_UNLOCK(); if (pgid < 0) gsignal(-pgid, SIGIO); - else if (p = pfind(pgid)) - psignal(p, SIGIO); - unix_release(); + else + proc_signal(pgid, SIGIO); + LOG_LOCK(); } if (logsoftc.sc_state & LOG_RDWAIT) { wakeup((caddr_t)msgbufp); logsoftc.sc_state &= ~LOG_RDWAIT; } - (void) thread_funnel_set(kernel_flock, funnel_state); + LOG_UNLOCK(); } -void -klogwakeup() -{ - - if (_logentrypend) { - _logentrypend = 0; - logwakeup(); - } -} /*ARGSUSED*/ int -logioctl(com, data, flag) - caddr_t data; +logioctl(__unused dev_t dev, u_long com, caddr_t data, __unused int flag, __unused struct proc *p) { - long l; - int s; + int l; + LOG_LOCK(); switch (com) { /* return number of characters immediately available */ case FIONREAD: - s = splhigh(); l = msgbufp->msg_bufx - msgbufp->msg_bufr; - splx(s); if (l < 0) - l += MSG_BSIZE; + l += msgbufp->msg_size; *(off_t *)data = l; break; @@ -297,49 +306,279 @@ logioctl(com, data, flag) break; case TIOCSPGRP: - LOG_LOCK(); logsoftc.sc_pgid = *(int *)data; - LOG_UNLOCK(); break; case TIOCGPGRP: - LOG_LOCK(); *(int *)data = logsoftc.sc_pgid; - LOG_UNLOCK(); break; default: + LOG_UNLOCK(); return (-1); } + LOG_UNLOCK(); return (0); } void -log_init() +bsd_log_init(void) +{ + /* After this point, we must be ready to accept characters */ +} + + +/* + * log_putc_locked + * + * Decription: Output a character to the log; assumes the LOG_LOCK() is held + * by the caller. + * + * Parameters: c Character to output + * + * Returns: (void) + * + * Notes: This functions is used for multibyte output to the log; it + * should be used preferrentially where possible to ensure that + * log entries do not end up interspersed due to preemption or + * SMP reentrancy. + */ +void +log_putc_locked(char c) { - msgbufp = &temp_msgbuf; - LOG_LOCK_INIT(); + struct msgbuf *mbp; + + mbp = msgbufp; + mbp->msg_bufc[mbp->msg_bufx++] = c; + if (mbp->msg_bufx >= msgbufp->msg_size) + mbp->msg_bufx = 0; } + +/* + * log_putc + * + * Decription: Output a character to the log; assumes the LOG_LOCK() is NOT + * held by the caller. + * + * Parameters: c Character to output + * + * Returns: (void) + * + * Notes: This function is used for syingle byte output to the log. It + * primarily exists to maintain binary backward compatibility. + */ void log_putc(char c) { - register struct msgbuf *mbp; + int unread_count = 0; + LOG_LOCK(); + log_putc_locked(c); + unread_count = msgbufp->msg_bufx - msgbufp->msg_bufr; + LOG_UNLOCK(); + + if (unread_count < 0) + unread_count = 0 - unread_count; + if (c == '\n' || unread_count >= MAX_UNREAD_CHARS) + logwakeup(); +} - if (msgbufp == NULL) - msgbufp =&temp_msgbuf; - mbp = msgbufp; - if (mbp-> msg_magic != MSG_MAGIC) { - register int i; +/* + * it is possible to increase the kernel log buffer size by adding + * msgbuf=n + * to the kernel command line, and to read the current size using + * sysctl kern.msgbuf + * If there is no parameter on the kernel command line, the buffer is + * allocated statically and is CONFIG_MSG_BSIZE characters in size, otherwise + * memory is dynamically allocated. Memory management must already be up. + */ +int +log_setsize(int size) { + char *new_logdata; + int new_logsize, new_bufr, new_bufx; + char *old_logdata; + int old_logsize, old_bufr, old_bufx; + int i, count; + char *p, ch; + + if (size > MAX_MSG_BSIZE) + return (EINVAL); + + if (size <= 0) + return (EINVAL); + + new_logsize = size; + if (!(new_logdata = (char*)kalloc(size))) { + printf("log_setsize: unable to allocate memory\n"); + return (ENOMEM); + } + bzero(new_logdata, new_logsize); + + LOG_LOCK(); - mbp->msg_magic = MSG_MAGIC; - mbp->msg_bufx = mbp->msg_bufr = 0; - for (i=0; i < MSG_BSIZE; i++) - mbp->msg_bufc[i] = 0; + old_logsize = msgbufp->msg_size; + old_logdata = msgbufp->msg_bufc; + old_bufr = msgbufp->msg_bufr; + old_bufx = msgbufp->msg_bufx; + + LOG_SETSIZE_DEBUG("log_setsize(%d): old_logdata %p old_logsize %d old_bufr %d old_bufx %d\n", + size, old_logdata, old_logsize, old_bufr, old_bufx); + + /* start "new_logsize" bytes before the write pointer */ + if (new_logsize <= old_bufx) { + count = new_logsize; + p = old_logdata + old_bufx - count; + } else { + /* + * if new buffer is bigger, copy what we have and let the + * bzero above handle the difference + */ + count = MIN(new_logsize, old_logsize); + p = old_logdata + old_logsize - (count - old_bufx); } - mbp->msg_bufc[mbp->msg_bufx++] = c; - _logentrypend = 1; - if (mbp->msg_bufx < 0 || mbp->msg_bufx >= MSG_BSIZE) - mbp->msg_bufx = 0; + for (i = 0; i < count; i++) { + if (p >= old_logdata + old_logsize) + p = old_logdata; + + ch = *p++; + new_logdata[i] = ch; + } + + new_bufx = i; + if (new_bufx >= new_logsize) + new_bufx = 0; + msgbufp->msg_bufx = new_bufx; + + new_bufr = old_bufx - old_bufr; /* how much were we trailing bufx by? */ + if (new_bufr < 0) + new_bufr += old_logsize; + new_bufr = new_bufx - new_bufr; /* now relative to oldest data in new buffer */ + if (new_bufr < 0) + new_bufr += new_logsize; + msgbufp->msg_bufr = new_bufr; + + msgbufp->msg_size = new_logsize; + msgbufp->msg_bufc = new_logdata; + + LOG_SETSIZE_DEBUG("log_setsize(%d): new_logdata %p new_logsize %d new_bufr %d new_bufx %d\n", + size, new_logdata, new_logsize, new_bufr, new_bufx); + + LOG_UNLOCK(); + + /* this memory is now dead - clear it so that it compresses better + in case of suspend to disk etc. */ + bzero(old_logdata, old_logsize); + if (old_logdata != smsg_bufc) { + /* dynamic memory that must be freed */ + kfree(old_logdata, old_logsize); + } + + printf("set system log size to %d bytes\n", new_logsize); + + return 0; +} + +SYSCTL_PROC(_kern, OID_AUTO, msgbuf, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_kern_msgbuf, "I", ""); + +static int sysctl_kern_msgbuf(struct sysctl_oid *oidp __unused, + void *arg1 __unused, + int arg2 __unused, + struct sysctl_req *req) +{ + int old_bufsize, bufsize; + int error; + + LOG_LOCK(); + old_bufsize = bufsize = msgbufp->msg_size; + LOG_UNLOCK(); + + error = sysctl_io_number(req, bufsize, sizeof(bufsize), &bufsize, NULL); + if (error) + return (error); + + if (bufsize != old_bufsize) { + error = log_setsize(bufsize); + } + + return (error); +} + + +/* + * This should be called by /sbin/dmesg only via libproc. + * It returns as much data still in the buffer as possible. + */ +int +log_dmesg(user_addr_t buffer, uint32_t buffersize, int32_t * retval) { + uint32_t i; + uint32_t localbuff_size; + int error = 0, newl, skip; + char *localbuff, *p, *copystart, ch; + size_t copysize; + + LOG_LOCK(); + localbuff_size = (msgbufp->msg_size + 2); /* + '\n' + '\0' */ + LOG_UNLOCK(); + + /* Allocate a temporary non-circular buffer for copyout */ + if (!(localbuff = (char *)kalloc(localbuff_size))) { + printf("log_dmesg: unable to allocate memory\n"); + return (ENOMEM); + } + + /* in between here, the log could become bigger, but that's fine */ + LOG_LOCK(); + + /* + * The message buffer is circular; start at the write pointer, and + * make one loop up to write pointer - 1. + */ + p = msgbufp->msg_bufc + msgbufp->msg_bufx; + for (i = newl = skip = 0; p != msgbufp->msg_bufc + msgbufp->msg_bufx - 1; ++p) { + if (p >= msgbufp->msg_bufc + msgbufp->msg_size) + p = msgbufp->msg_bufc; + ch = *p; + /* Skip "\n<.*>" syslog sequences. */ + if (skip) { + if (ch == '>') + newl = skip = 0; + continue; + } + if (newl && ch == '<') { + skip = 1; + continue; + } + if (ch == '\0') + continue; + newl = (ch == '\n'); + localbuff[i++] = ch; + /* The original version of this routine contained a buffer + * overflow. At the time, a "small" targeted fix was desired + * so the change below to check the buffer bounds was made. + * TODO: rewrite this needlessly convoluted routine. + */ + if (i == (localbuff_size - 2)) + break; + } + if (!newl) + localbuff[i++] = '\n'; + localbuff[i++] = 0; + + if (buffersize >= i) { + copystart = localbuff; + copysize = i; + } else { + copystart = localbuff + i - buffersize; + copysize = buffersize; + } + + LOG_UNLOCK(); + + error = copyout(copystart, buffer, copysize); + if (!error) + *retval = copysize; + + kfree(localbuff, localbuff_size); + return (error); }