You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
4.4 KiB

/* dmesg.c - display/control kernel ring buffer.
*
* Copyright 2006, 2007 Rob Landley <rob@landley.net>
*
* See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/dmesg.html
*
* Don't ask me why the horrible new dmesg API is still in "testing":
* http://kernel.org/doc/Documentation/ABI/testing/dev-kmsg
// We care that FLAG_c is 1, so keep c at the end.
USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN))
config DMESG
bool "dmesg"
default y
help
usage: dmesg [-Cc] [-r|-t|-T] [-n LEVEL] [-s SIZE] [-w]
Print or control the kernel ring buffer.
-C Clear ring buffer without printing
-c Clear ring buffer after printing
-n Set kernel logging LEVEL (1-9)
-r Raw output (with <level markers>)
-S Use syslog(2) rather than /dev/kmsg
-s Show the last SIZE many bytes
-T Human readable timestamps
-t Don't print timestamps
-w Keep waiting for more output (aka --follow)
*/
#define FOR_dmesg
#include "toys.h"
#include <sys/klog.h>
GLOBALS(
long n, s;
int use_color;
time_t tea;
)
static void color(int c)
{
if (TT.use_color) printf("\033[%dm", c);
}
static void format_message(char *msg, int new)
{
unsigned long long time_s, time_us;
int facpri, subsystem, pos;
char *p, *text;
// The new /dev/kmsg and the old syslog(2) formats differ slightly.
if (new) {
if (sscanf(msg, "%u,%*u,%llu,%*[^;]; %n", &facpri, &time_us, &pos) != 2)
return;
time_s = time_us/1000000;
time_us %= 1000000;
} else if (sscanf(msg, "<%u>[%llu.%llu] %n",
&facpri, &time_s, &time_us, &pos) != 3) return;
// Drop extras after end of message text.
if ((p = strchr(text = msg+pos, '\n'))) *p = 0;
// Is there a subsystem? (The ": " is just a convention.)
p = strstr(text, ": ");
subsystem = p ? (p-text) : 0;
// To get "raw" output for /dev/kmsg we need to add priority to each line
if (FLAG(r)) {
color(0);
printf("<%d>", facpri);
}
// Format the time.
if (!FLAG(t)) {
color(32);
if (FLAG(T)) {
time_t t = TT.tea+time_s;
char *ts = ctime(&t);
printf("[%.*s] ", (int)(strlen(ts)-1), ts);
} else printf("[%5lld.%06lld] ", time_s, time_us);
}
// Errors (or worse) are shown in red, subsystems are shown in yellow.
if (subsystem) {
color(33);
printf("%.*s", subsystem, text);
text += subsystem;
}
color(31*((facpri&7)<=3));
xputs(text);
}
static int xklogctl(int type, char *buf, int len)
{
int rc = klogctl(type, buf, len);
if (rc<0) perror_exit("klogctl");
return rc;
}
static void dmesg_cleanup(void)
{
color(0);
}
void dmesg_main(void)
{
TT.use_color = isatty(1);
if (TT.use_color) sigatexit(dmesg_cleanup);
// If we're displaying output, is it klogctl or /dev/kmsg?
if (FLAG(C)||FLAG(n)) goto no_output;
if (FLAG(T)) {
struct sysinfo info;
sysinfo(&info);
TT.tea = time(0)-info.uptime;
}
if (!FLAG(S)) {
char msg[8193]; // CONSOLE_EXT_LOG_MAX+1
ssize_t len;
int fd;
// Each read returns one message. By default, we block when there are no
// more messages (--follow); O_NONBLOCK is needed for for usual behavior.
fd = open("/dev/kmsg", O_RDONLY|(O_NONBLOCK*!FLAG(w)));
if (fd == -1) goto klogctl_mode;
// SYSLOG_ACTION_CLEAR(5) doesn't actually remove anything from /dev/kmsg,
// you need to seek to the last clear point.
lseek(fd, 0, SEEK_DATA);
for (;;) {
// why does /dev/kmesg return EPIPE instead of EAGAIN if oldest message
// expires as we read it?
if (-1==(len = read(fd, msg, sizeof(msg))) && errno==EPIPE) continue;
// read() from kmsg always fails on a pre-3.5 kernel.
if (len==-1 && errno==EINVAL) goto klogctl_mode;
if (len<1) break;
msg[len] = 0;
format_message(msg, 1);
}
close(fd);
} else {
char *data, *to, *from, *end;
int size;
klogctl_mode:
// Figure out how much data we need, and fetch it.
if (!(size = TT.s)) size = xklogctl(10, 0, 0);
data = from = xmalloc(size+1);
data[size = xklogctl(3+FLAG(c), data, size)] = 0;
// Send each line to format_message.
to = data + size;
while (from < to) {
if (!(end = memchr(from, '\n', to-from))) break;
*end = 0;
format_message(from, 0);
from = end + 1;
}
if (CFG_TOYBOX_FREE) free(data);
}
no_output:
// Set the log level?
if (FLAG(n)) xklogctl(8, 0, TT.n);
// Clear the buffer?
if (FLAG(C)||FLAG(c)) xklogctl(5, 0, 0);
}