diff options
Diffstat (limited to '')
-rw-r--r-- | sys-utils/dmesg.c | 1547 |
1 files changed, 1547 insertions, 0 deletions
diff --git a/sys-utils/dmesg.c b/sys-utils/dmesg.c new file mode 100644 index 0000000..ba4e225 --- /dev/null +++ b/sys-utils/dmesg.c @@ -0,0 +1,1547 @@ +/* + * dmesg.c -- Print out the contents of the kernel ring buffer + * + * Copyright (C) 1993 Theodore Ts'o <tytso@athena.mit.edu> + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * + * This program comes with ABSOLUTELY NO WARRANTY. + */ +#include <stdio.h> +#include <getopt.h> +#include <stdlib.h> +#include <sys/klog.h> +#include <sys/syslog.h> +#include <sys/time.h> +#include <sys/sysinfo.h> +#include <ctype.h> +#include <time.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#include "c.h" +#include "colors.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include "widechar.h" +#include "all-io.h" +#include "bitops.h" +#include "closestream.h" +#include "optutils.h" +#include "timeutils.h" +#include "monotonic.h" +#include "mangle.h" +#include "pager.h" + +/* Close the log. Currently a NOP. */ +#define SYSLOG_ACTION_CLOSE 0 +/* Open the log. Currently a NOP. */ +#define SYSLOG_ACTION_OPEN 1 +/* Read from the log. */ +#define SYSLOG_ACTION_READ 2 +/* Read all messages remaining in the ring buffer. (allowed for non-root) */ +#define SYSLOG_ACTION_READ_ALL 3 +/* Read and clear all messages remaining in the ring buffer */ +#define SYSLOG_ACTION_READ_CLEAR 4 +/* Clear ring buffer. */ +#define SYSLOG_ACTION_CLEAR 5 +/* Disable printk's to console */ +#define SYSLOG_ACTION_CONSOLE_OFF 6 +/* Enable printk's to console */ +#define SYSLOG_ACTION_CONSOLE_ON 7 +/* Set level of messages printed to console */ +#define SYSLOG_ACTION_CONSOLE_LEVEL 8 +/* Return number of unread characters in the log buffer */ +#define SYSLOG_ACTION_SIZE_UNREAD 9 +/* Return size of the log buffer */ +#define SYSLOG_ACTION_SIZE_BUFFER 10 + +/* + * Color scheme + */ +struct dmesg_color { + const char *scheme; /* name used in termina-colors.d/dmesg.scheme */ + const char *dflt; /* default color ESC sequence */ +}; + +enum { + DMESG_COLOR_SUBSYS, + DMESG_COLOR_TIME, + DMESG_COLOR_TIMEBREAK, + DMESG_COLOR_ALERT, + DMESG_COLOR_CRIT, + DMESG_COLOR_ERR, + DMESG_COLOR_WARN, + DMESG_COLOR_SEGFAULT +}; + +static const struct dmesg_color colors[] = +{ + [DMESG_COLOR_SUBSYS] = { "subsys", UL_COLOR_BROWN }, + [DMESG_COLOR_TIME] = { "time", UL_COLOR_GREEN }, + [DMESG_COLOR_TIMEBREAK] = { "timebreak",UL_COLOR_GREEN UL_COLOR_BOLD }, + [DMESG_COLOR_ALERT] = { "alert", UL_COLOR_REVERSE UL_COLOR_RED }, + [DMESG_COLOR_CRIT] = { "crit", UL_COLOR_BOLD UL_COLOR_RED }, + [DMESG_COLOR_ERR] = { "err", UL_COLOR_RED }, + [DMESG_COLOR_WARN] = { "warn", UL_COLOR_BOLD }, + [DMESG_COLOR_SEGFAULT] = { "segfault", UL_COLOR_HALFBRIGHT UL_COLOR_RED } +}; + +#define dmesg_enable_color(_id) \ + color_scheme_enable(colors[_id].scheme, colors[_id].dflt); + +/* + * Priority and facility names + */ +struct dmesg_name { + const char *name; + const char *help; +}; + +/* + * Priority names -- based on sys/syslog.h + */ +static const struct dmesg_name level_names[] = +{ + [LOG_EMERG] = { "emerg", N_("system is unusable") }, + [LOG_ALERT] = { "alert", N_("action must be taken immediately") }, + [LOG_CRIT] = { "crit", N_("critical conditions") }, + [LOG_ERR] = { "err", N_("error conditions") }, + [LOG_WARNING] = { "warn", N_("warning conditions") }, + [LOG_NOTICE] = { "notice",N_("normal but significant condition") }, + [LOG_INFO] = { "info", N_("informational") }, + [LOG_DEBUG] = { "debug", N_("debug-level messages") } +}; + +/* + * sys/syslog.h uses (f << 3) for all facility codes. + * We want to use the codes as array indexes, so shift back... + * + * Note that libc LOG_FAC() macro returns the base codes, not the + * shifted code :-) + */ +#define FAC_BASE(f) ((f) >> 3) + +static const struct dmesg_name facility_names[] = +{ + [FAC_BASE(LOG_KERN)] = { "kern", N_("kernel messages") }, + [FAC_BASE(LOG_USER)] = { "user", N_("random user-level messages") }, + [FAC_BASE(LOG_MAIL)] = { "mail", N_("mail system") }, + [FAC_BASE(LOG_DAEMON)] = { "daemon", N_("system daemons") }, + [FAC_BASE(LOG_AUTH)] = { "auth", N_("security/authorization messages") }, + [FAC_BASE(LOG_SYSLOG)] = { "syslog", N_("messages generated internally by syslogd") }, + [FAC_BASE(LOG_LPR)] = { "lpr", N_("line printer subsystem") }, + [FAC_BASE(LOG_NEWS)] = { "news", N_("network news subsystem") }, + [FAC_BASE(LOG_UUCP)] = { "uucp", N_("UUCP subsystem") }, + [FAC_BASE(LOG_CRON)] = { "cron", N_("clock daemon") }, + [FAC_BASE(LOG_AUTHPRIV)] = { "authpriv", N_("security/authorization messages (private)") }, + [FAC_BASE(LOG_FTP)] = { "ftp", N_("FTP daemon") }, +}; + +/* supported methods to read message buffer + */ +enum { + DMESG_METHOD_KMSG, /* read messages from /dev/kmsg (default) */ + DMESG_METHOD_SYSLOG, /* klogctl() buffer */ + DMESG_METHOD_MMAP /* mmap file with records (see --file) */ +}; + +enum { + DMESG_TIMEFTM_NONE = 0, + DMESG_TIMEFTM_CTIME, /* [ctime] */ + DMESG_TIMEFTM_CTIME_DELTA, /* [ctime <delta>] */ + DMESG_TIMEFTM_DELTA, /* [<delta>] */ + DMESG_TIMEFTM_RELTIME, /* [relative] */ + DMESG_TIMEFTM_TIME, /* [time] */ + DMESG_TIMEFTM_TIME_DELTA, /* [time <delta>] */ + DMESG_TIMEFTM_ISO8601 /* 2013-06-13T22:11:00,123456+0100 */ +}; +#define is_timefmt(c, f) ((c)->time_fmt == (DMESG_TIMEFTM_ ##f)) + +struct dmesg_control { + /* bit arrays -- see include/bitops.h */ + char levels[ARRAY_SIZE(level_names) / NBBY + 1]; + char facilities[ARRAY_SIZE(facility_names) / NBBY + 1]; + + struct timeval lasttime; /* last printed timestamp */ + struct tm lasttm; /* last localtime */ + struct timeval boot_time; /* system boot time */ + + int action; /* SYSLOG_ACTION_* */ + int method; /* DMESG_METHOD_* */ + + size_t bufsize; /* size of syslog buffer */ + + int kmsg; /* /dev/kmsg file descriptor */ + ssize_t kmsg_first_read;/* initial read() return code */ + char kmsg_buf[BUFSIZ];/* buffer to read kmsg data */ + + /* + * For the --file option we mmap whole file. The unnecessary (already + * printed) pages are always unmapped. The result is that we have in + * memory only the currently used page(s). + */ + char *filename; + char *mmap_buff; + size_t pagesize; + unsigned int time_fmt; /* time format */ + + unsigned int follow:1, /* wait for new messages */ + raw:1, /* raw mode */ + fltr_lev:1, /* filter out by levels[] */ + fltr_fac:1, /* filter out by facilities[] */ + decode:1, /* use "facility: level: " prefix */ + pager:1, /* pipe output into a pager */ + color:1, /* colorize messages */ + force_prefix:1; /* force timestamp and decode prefix + on each line */ + int indent; /* due to timestamps if newline */ +}; + +struct dmesg_record { + const char *mesg; + size_t mesg_size; + + int level; + int facility; + struct timeval tv; + + const char *next; /* buffer with next unparsed record */ + size_t next_size; /* size of the next buffer */ +}; + +#define INIT_DMESG_RECORD(_r) do { \ + (_r)->mesg = NULL; \ + (_r)->mesg_size = 0; \ + (_r)->facility = -1; \ + (_r)->level = -1; \ + (_r)->tv.tv_sec = 0; \ + (_r)->tv.tv_usec = 0; \ + } while (0) + +static int read_kmsg(struct dmesg_control *ctl); + +static int set_level_color(int log_level, const char *mesg, size_t mesgsz) +{ + int id = -1; + + switch (log_level) { + case LOG_ALERT: + id = DMESG_COLOR_ALERT; + break; + case LOG_CRIT: + id = DMESG_COLOR_CRIT; + break; + case LOG_ERR: + id = DMESG_COLOR_ERR; + break; + case LOG_WARNING: + id = DMESG_COLOR_WARN; + break; + default: + break; + } + + /* well, sometimes the messages contains important keywords, but in + * non-warning/error messages + */ + if (id < 0 && memmem(mesg, mesgsz, "segfault at", 11)) + id = DMESG_COLOR_SEGFAULT; + + if (id >= 0) + dmesg_enable_color(id); + + return id >= 0 ? 0 : -1; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display or control the kernel ring buffer.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -C, --clear clear the kernel ring buffer\n"), out); + fputs(_(" -c, --read-clear read and clear all messages\n"), out); + fputs(_(" -D, --console-off disable printing messages to console\n"), out); + fputs(_(" -E, --console-on enable printing messages to console\n"), out); + fputs(_(" -F, --file <file> use the file instead of the kernel log buffer\n"), out); + fputs(_(" -f, --facility <list> restrict output to defined facilities\n"), out); + fputs(_(" -H, --human human readable output\n"), out); + fputs(_(" -k, --kernel display kernel messages\n"), out); + fputs(_(" -L, --color[=<when>] colorize messages (auto, always or never)\n"), out); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -l, --level <list> restrict output to defined levels\n"), out); + fputs(_(" -n, --console-level <level> set level of messages printed to console\n"), out); + fputs(_(" -P, --nopager do not pipe output into a pager\n"), out); + fputs(_(" -p, --force-prefix force timestamp output on each line of multi-line messages\n"), out); + fputs(_(" -r, --raw print the raw message buffer\n"), out); + fputs(_(" -S, --syslog force to use syslog(2) rather than /dev/kmsg\n"), out); + fputs(_(" -s, --buffer-size <size> buffer size to query the kernel ring buffer\n"), out); + fputs(_(" -u, --userspace display userspace messages\n"), out); + fputs(_(" -w, --follow wait for new messages\n"), out); + fputs(_(" -x, --decode decode facility and level to readable string\n"), out); + fputs(_(" -d, --show-delta show time delta between printed messages\n"), out); + fputs(_(" -e, --reltime show local time and time delta in readable format\n"), out); + fputs(_(" -T, --ctime show human-readable timestamp (may be inaccurate!)\n"), out); + fputs(_(" -t, --notime don't show any timestamp with messages\n"), out); + fputs(_(" --time-format <format> show timestamp using the given format:\n" + " [delta|reltime|ctime|notime|iso]\n" + "Suspending/resume will make ctime and iso timestamps inaccurate.\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(29)); + fputs(_("\nSupported log facilities:\n"), out); + for (i = 0; i < ARRAY_SIZE(level_names); i++) + fprintf(out, " %7s - %s\n", + facility_names[i].name, + _(facility_names[i].help)); + + fputs(_("\nSupported log levels (priorities):\n"), out); + for (i = 0; i < ARRAY_SIZE(level_names); i++) + fprintf(out, " %7s - %s\n", + level_names[i].name, + _(level_names[i].help)); + + printf(USAGE_MAN_TAIL("dmesg(1)")); + exit(EXIT_SUCCESS); +} + +/* + * LEVEL ::= <number> | <name> + * <number> ::= @len is set: number in range <0..N>, where N < ARRAY_SIZE(level_names) + * ::= @len not set: number in range <1..N>, where N <= ARRAY_SIZE(level_names) + * <name> ::= case-insensitive text + * + * Note that @len argument is not set when parsing "-n <level>" command line + * option. The console_level is interpreted as "log level less than the value". + * + * For example "dmesg -n 8" or "dmesg -n debug" enables debug console log + * level by klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, 8). The @str argument + * has to be parsed to number in range <1..8>. + */ +static int parse_level(const char *str, size_t len) +{ + int offset = 0; + + if (!str) + return -1; + if (!len) { + len = strlen(str); + offset = 1; + } + errno = 0; + + if (isdigit(*str)) { + char *end = NULL; + long x = strtol(str, &end, 10) - offset; + + if (!errno && end && end > str && (size_t) (end - str) == len && + x >= 0 && (size_t) x < ARRAY_SIZE(level_names)) + return x + offset; + } else { + size_t i; + + for (i = 0; i < ARRAY_SIZE(level_names); i++) { + const char *n = level_names[i].name; + + if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0') + return i + offset; + } + } + + if (errno) + err(EXIT_FAILURE, _("failed to parse level '%s'"), str); + + errx(EXIT_FAILURE, _("unknown level '%s'"), str); + return -1; +} + +/* + * FACILITY ::= <number> | <name> + * <number> ::= number in range <0..N>, where N < ARRAY_SIZE(facility_names) + * <name> ::= case-insensitive text + */ +static int parse_facility(const char *str, size_t len) +{ + if (!str) + return -1; + if (!len) + len = strlen(str); + errno = 0; + + if (isdigit(*str)) { + char *end = NULL; + long x = strtol(str, &end, 10); + + if (!errno && end && end > str && (size_t) (end - str) == len && + x >= 0 && (size_t) x < ARRAY_SIZE(facility_names)) + return x; + } else { + size_t i; + + for (i = 0; i < ARRAY_SIZE(facility_names); i++) { + const char *n = facility_names[i].name; + + if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0') + return i; + } + } + + if (errno) + err(EXIT_FAILURE, _("failed to parse facility '%s'"), str); + + errx(EXIT_FAILURE, _("unknown facility '%s'"), str); + return -1; +} + +/* + * Parses numerical prefix used for all messages in kernel ring buffer. + * + * Priorities/facilities are encoded into a single 32-bit quantity, where the + * bottom 3 bits are the priority (0-7) and the top 28 bits are the facility + * (0-big number). + * + * Note that the number has to end with '>' or ',' char. + */ +static const char *parse_faclev(const char *str, int *fac, int *lev) +{ + long num; + char *end = NULL; + + if (!str) + return str; + + errno = 0; + num = strtol(str, &end, 10); + + if (!errno && end && end > str) { + *fac = LOG_FAC(num); + *lev = LOG_PRI(num); + + if (*lev < 0 || (size_t) *lev > ARRAY_SIZE(level_names)) + *lev = -1; + if (*fac < 0 || (size_t) *fac > ARRAY_SIZE(facility_names)) + *fac = -1; + return end + 1; /* skip '<' or ',' */ + } + + return str; +} + +/* + * Parses timestamp from syslog message prefix, expected format: + * + * seconds.microseconds] + * + * the ']' is the timestamp field terminator. + */ +static const char *parse_syslog_timestamp(const char *str0, struct timeval *tv) +{ + const char *str = str0; + char *end = NULL; + + if (!str0) + return str0; + + errno = 0; + tv->tv_sec = strtol(str, &end, 10); + + if (!errno && end && *end == '.' && *(end + 1)) { + str = end + 1; + end = NULL; + tv->tv_usec = strtol(str, &end, 10); + } + if (errno || !end || end == str || *end != ']') + return str0; + + return end + 1; /* skip ']' */ +} + +/* + * Parses timestamp from /dev/kmsg, expected formats: + * + * microseconds, + * microseconds; + * + * the ',' is fields separators and ';' items terminator (for the last item) + */ +static const char *parse_kmsg_timestamp(const char *str0, struct timeval *tv) +{ + const char *str = str0; + char *end = NULL; + uint64_t usec; + + if (!str0) + return str0; + + errno = 0; + usec = strtoumax(str, &end, 10); + + if (!errno && end && (*end == ';' || *end == ',')) { + tv->tv_usec = usec % 1000000; + tv->tv_sec = usec / 1000000; + } else + return str0; + + return end + 1; /* skip separator */ +} + + +static double time_diff(struct timeval *a, struct timeval *b) +{ + return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6; +} + +static int get_syslog_buffer_size(void) +{ + int n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); + + return n > 0 ? n : 0; +} + +/* + * Reads messages from regular file by mmap + */ +static ssize_t mmap_file_buffer(struct dmesg_control *ctl, char **buf) +{ + struct stat st; + int fd; + + if (!ctl->filename) + return -1; + + fd = open(ctl->filename, O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), ctl->filename); + if (fstat(fd, &st)) + err(EXIT_FAILURE, _("stat of %s failed"), ctl->filename); + + *buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (*buf == MAP_FAILED) + err(EXIT_FAILURE, _("cannot mmap: %s"), ctl->filename); + ctl->mmap_buff = *buf; + ctl->pagesize = getpagesize(); + close(fd); + + return st.st_size; +} + +/* + * Reads messages from kernel ring buffer by klogctl() + */ +static ssize_t read_syslog_buffer(struct dmesg_control *ctl, char **buf) +{ + size_t sz; + int rc = -1; + + if (ctl->bufsize) { + sz = ctl->bufsize + 8; + *buf = xmalloc(sz * sizeof(char)); + rc = klogctl(ctl->action, *buf, sz); + } else { + sz = 16392; + while (1) { + *buf = xmalloc(sz * sizeof(char)); + rc = klogctl(SYSLOG_ACTION_READ_ALL, *buf, sz); + if (rc < 0) + break; + if ((size_t) rc != sz || sz > (1 << 28)) + break; + free(*buf); + *buf = NULL; + sz *= 4; + } + + if (rc > 0 && ctl->action == SYSLOG_ACTION_READ_CLEAR) + rc = klogctl(SYSLOG_ACTION_READ_CLEAR, *buf, sz); + } + + return rc; +} + +/* + * Top level function to read messages + */ +static ssize_t read_buffer(struct dmesg_control *ctl, char **buf) +{ + ssize_t n = -1; + + switch (ctl->method) { + case DMESG_METHOD_MMAP: + n = mmap_file_buffer(ctl, buf); + break; + case DMESG_METHOD_SYSLOG: + if (!ctl->bufsize) + ctl->bufsize = get_syslog_buffer_size(); + + n = read_syslog_buffer(ctl, buf); + break; + case DMESG_METHOD_KMSG: + /* + * Since kernel 3.5.0 + */ + n = read_kmsg(ctl); + if (n == 0 && ctl->action == SYSLOG_ACTION_READ_CLEAR) + n = klogctl(SYSLOG_ACTION_CLEAR, NULL, 0); + break; + default: + abort(); /* impossible method -> drop core */ + } + + return n; +} + +static int fwrite_hex(const char *buf, size_t size, FILE *out) +{ + size_t i; + + for (i = 0; i < size; i++) { + int rc = fprintf(out, "\\x%02hhx", buf[i]); + if (rc < 0) + return rc; + } + return 0; +} + +/* + * Prints to 'out' and non-printable chars are replaced with \x<hex> sequences. + */ +static void safe_fwrite(const char *buf, size_t size, int indent, FILE *out) +{ + size_t i; +#ifdef HAVE_WIDECHAR + mbstate_t s; + memset(&s, 0, sizeof (s)); +#endif + for (i = 0; i < size; i++) { + const char *p = buf + i; + int rc, hex = 0; + size_t len; + +#ifdef HAVE_WIDECHAR + wchar_t wc; + len = mbrtowc(&wc, p, size - i, &s); + + if (len == 0) /* L'\0' */ + return; + + if (len == (size_t)-1 || len == (size_t)-2) { /* invalid sequence */ + memset(&s, 0, sizeof (s)); + len = hex = 1; + } else if (len > 1 && !iswprint(wc)) { /* non-printable multibyte */ + hex = 1; + } + i += len - 1; +#else + len = 1; + if (!isprint((unsigned char) *p) && + !isspace((unsigned char) *p)) /* non-printable */ + hex = 1; +#endif + if (hex) + rc = fwrite_hex(p, len, out); + else if (*p == '\n' && *(p + 1) && indent) { + rc = fwrite(p, 1, len, out) != len; + if (fprintf(out, "%*s", indent, "") != indent) + rc |= 1; + } + else + rc = fwrite(p, 1, len, out) != len; + if (rc != 0) { + if (errno != EPIPE) + err(EXIT_FAILURE, _("write failed")); + exit(EXIT_SUCCESS); + } + } +} + +static const char *skip_item(const char *begin, const char *end, const char *sep) +{ + while (begin < end) { + int c = *begin++; + + if (c == '\0' || strchr(sep, c)) + break; + } + + return begin; +} + +/* + * Parses one record from syslog(2) buffer + */ +static int get_next_syslog_record(struct dmesg_control *ctl, + struct dmesg_record *rec) +{ + size_t i; + const char *begin = NULL; + + if (ctl->method != DMESG_METHOD_MMAP && + ctl->method != DMESG_METHOD_SYSLOG) + return -1; + + if (!rec->next || !rec->next_size) + return 1; + + INIT_DMESG_RECORD(rec); + + /* + * Unmap already printed file data from memory + */ + if (ctl->mmap_buff && (size_t) (rec->next - ctl->mmap_buff) > ctl->pagesize) { + void *x = ctl->mmap_buff; + + ctl->mmap_buff += ctl->pagesize; + munmap(x, ctl->pagesize); + } + + for (i = 0; i < rec->next_size; i++) { + const char *p = rec->next + i; + const char *end = NULL; + + if (!begin) + begin = p; + if (i + 1 == rec->next_size) { + end = p + 1; + i++; + } else if (*p == '\n' && *(p + 1) == '<') + end = p; + + if (begin && !*begin) + begin = NULL; /* zero(s) at the end of the buffer? */ + if (!begin || !end) + continue; + if (end <= begin) + continue; /* error or empty line? */ + + if (*begin == '<') { + if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode || ctl->color) + begin = parse_faclev(begin + 1, &rec->facility, + &rec->level); + else + begin = skip_item(begin, end, ">"); + } + + if (*begin == '[' && (*(begin + 1) == ' ' || + isdigit(*(begin + 1)))) { + + if (!is_timefmt(ctl, NONE)) + begin = parse_syslog_timestamp(begin + 1, &rec->tv); + else + begin = skip_item(begin, end, "]"); + + if (begin < end && *begin == ' ') + begin++; + } + + rec->mesg = begin; + rec->mesg_size = end - begin; + + /* Don't count \n from the last message to the message size */ + if (*end != '\n' && *(end - 1) == '\n') + rec->mesg_size--; + + rec->next_size -= end - rec->next; + rec->next = rec->next_size > 0 ? end + 1 : NULL; + if (rec->next_size > 0) + rec->next_size--; + + return 0; + } + + return 1; +} + +static int accept_record(struct dmesg_control *ctl, struct dmesg_record *rec) +{ + if (ctl->fltr_lev && (rec->facility < 0 || + !isset(ctl->levels, rec->level))) + return 0; + + if (ctl->fltr_fac && (rec->facility < 0 || + !isset(ctl->facilities, rec->facility))) + return 0; + + return 1; +} + +static void raw_print(struct dmesg_control *ctl, const char *buf, size_t size) +{ + int lastc = '\n'; + + if (!ctl->mmap_buff) { + /* + * Print whole ring buffer + */ + safe_fwrite(buf, size, 0, stdout); + lastc = buf[size - 1]; + } else { + /* + * Print file in small chunks to save memory + */ + while (size) { + size_t sz = size > ctl->pagesize ? ctl->pagesize : size; + char *x = ctl->mmap_buff; + + safe_fwrite(x, sz, 0, stdout); + lastc = x[sz - 1]; + size -= sz; + ctl->mmap_buff += sz; + munmap(x, sz); + } + } + + if (lastc != '\n') + putchar('\n'); +} + +static struct tm *record_localtime(struct dmesg_control *ctl, + struct dmesg_record *rec, + struct tm *tm) +{ + time_t t = ctl->boot_time.tv_sec + rec->tv.tv_sec; + return localtime_r(&t, tm); +} + +static char *record_ctime(struct dmesg_control *ctl, + struct dmesg_record *rec, + char *buf, size_t bufsiz) +{ + struct tm tm; + + record_localtime(ctl, rec, &tm); + + if (strftime(buf, bufsiz, "%a %b %e %H:%M:%S %Y", &tm) == 0) + *buf = '\0'; + return buf; +} + +static char *short_ctime(struct tm *tm, char *buf, size_t bufsiz) +{ + if (strftime(buf, bufsiz, "%b%e %H:%M", tm) == 0) + *buf = '\0'; + return buf; +} + +static char *iso_8601_time(struct dmesg_control *ctl, struct dmesg_record *rec, + char *buf, size_t bufsz) +{ + struct timeval tv = { + .tv_sec = ctl->boot_time.tv_sec + rec->tv.tv_sec, + .tv_usec = rec->tv.tv_usec + }; + + if (strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA_T, buf, bufsz) != 0) + return NULL; + + return buf; +} + +static double record_count_delta(struct dmesg_control *ctl, + struct dmesg_record *rec) +{ + double delta = 0; + + if (timerisset(&ctl->lasttime)) + delta = time_diff(&rec->tv, &ctl->lasttime); + + ctl->lasttime = rec->tv; + return delta; +} + +static const char *get_subsys_delimiter(const char *mesg, size_t mesg_size) +{ + const char *p = mesg; + size_t sz = mesg_size; + + while (sz > 0) { + const char *d = strnchr(p, sz, ':'); + if (!d) + return NULL; + sz -= d - p + 1; + if (sz) { + if (isblank(*(d + 1))) + return d; + p = d + 1; + } + } + return NULL; +} + +static void print_record(struct dmesg_control *ctl, + struct dmesg_record *rec) +{ + char buf[128]; + char fpbuf[32] = "\0"; + char tsbuf[64] = "\0"; + size_t mesg_size = rec->mesg_size; + int timebreak = 0; + char *mesg_copy = NULL; + const char *line = NULL; + + if (!accept_record(ctl, rec)) + return; + + if (!rec->mesg_size) { + putchar('\n'); + return; + } + + /* + * Compose syslog(2) compatible raw output -- used for /dev/kmsg for + * backward compatibility with syslog(2) buffers only + */ + if (ctl->raw) { + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), + "<%d>[%5ld.%06ld] ", + LOG_MAKEPRI(rec->facility, rec->level), + (long) rec->tv.tv_sec, + (long) rec->tv.tv_usec); + goto full_output; + } + + /* Store decode information (facility & priority level) in a buffer */ + if (ctl->decode && + (rec->level > -1) && (rec->level < (int) ARRAY_SIZE(level_names)) && + (rec->facility > -1) && + (rec->facility < (int) ARRAY_SIZE(facility_names))) + snprintf(fpbuf, sizeof(fpbuf), "%-6s:%-6s: ", + facility_names[rec->facility].name, + level_names[rec->level].name); + + /* Store the timestamp in a buffer */ + switch (ctl->time_fmt) { + double delta; + struct tm cur; + case DMESG_TIMEFTM_NONE: + ctl->indent = 0; + break; + case DMESG_TIMEFTM_CTIME: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ", + record_ctime(ctl, rec, buf, sizeof(buf))); + break; + case DMESG_TIMEFTM_CTIME_DELTA: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s <%12.06f>] ", + record_ctime(ctl, rec, buf, sizeof(buf)), + record_count_delta(ctl, rec)); + break; + case DMESG_TIMEFTM_DELTA: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[<%12.06f>] ", + record_count_delta(ctl, rec)); + break; + case DMESG_TIMEFTM_RELTIME: + record_localtime(ctl, rec, &cur); + delta = record_count_delta(ctl, rec); + if (cur.tm_min != ctl->lasttm.tm_min || + cur.tm_hour != ctl->lasttm.tm_hour || + cur.tm_yday != ctl->lasttm.tm_yday) { + timebreak = 1; + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ", + short_ctime(&cur, buf, + sizeof(buf))); + } else { + if (delta < 10) + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), + "[ %+8.06f] ", delta); + else + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), + "[ %+9.06f] ", delta); + } + ctl->lasttm = cur; + break; + case DMESG_TIMEFTM_TIME: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld] ", + (long)rec->tv.tv_sec, + (long)rec->tv.tv_usec); + break; + case DMESG_TIMEFTM_TIME_DELTA: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld <%12.06f>] ", + (long)rec->tv.tv_sec, + (long)rec->tv.tv_usec, + record_count_delta(ctl, rec)); + break; + case DMESG_TIMEFTM_ISO8601: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "%s ", + iso_8601_time(ctl, rec, buf, + sizeof(buf))); + break; + default: + abort(); + } + + ctl->indent += strlen(fpbuf); + +full_output: + /* Output the decode information */ + if (*fpbuf) + fputs(fpbuf, stdout); + + /* Output the timestamp buffer */ + if (*tsbuf) { + /* Colorize the timestamp */ + if (ctl->color) + dmesg_enable_color(timebreak ? DMESG_COLOR_TIMEBREAK : + DMESG_COLOR_TIME); + if (ctl->time_fmt != DMESG_TIMEFTM_RELTIME) { + fputs(tsbuf, stdout); + } else { + /* + * For relative timestamping, the first line's + * timestamp is the offset and all other lines will + * report an offset of 0.000000. + */ + if (!line) + fputs(tsbuf, stdout); + else + printf("[ +0.000000] "); + } + if (ctl->color) + color_disable(); + } + + /* + * A kernel message may contain several lines of output, separated + * by '\n'. If the timestamp and decode outputs are forced then each + * line of the message must be displayed with that information. + */ + if (ctl->force_prefix) { + if (!line) { + mesg_copy = xstrdup(rec->mesg); + line = strtok(mesg_copy, "\n"); + mesg_size = strlen(line); + } + } else { + line = rec->mesg; + mesg_size = rec->mesg_size; + } + + /* Colorize kernel message output */ + if (ctl->color) { + /* Subsystem prefix */ + const char *subsys = get_subsys_delimiter(line, mesg_size); + int has_color = 0; + + if (subsys) { + dmesg_enable_color(DMESG_COLOR_SUBSYS); + safe_fwrite(line, subsys - line, ctl->indent, stdout); + color_disable(); + + mesg_size -= subsys - line; + line = subsys; + } + /* Error, alert .. etc. colors */ + has_color = set_level_color(rec->level, line, mesg_size) == 0; + safe_fwrite(line, mesg_size, ctl->indent, stdout); + if (has_color) + color_disable(); + } else + safe_fwrite(line, mesg_size, ctl->indent, stdout); + + /* Get the next line */ + if (ctl->force_prefix) { + line = strtok(NULL, "\n"); + if (line && *line) { + putchar('\n'); + mesg_size = strlen(line); + goto full_output; + } + free(mesg_copy); + } + + putchar('\n'); +} + +/* + * Prints the 'buf' kernel ring buffer; the messages are filtered out according + * to 'levels' and 'facilities' bitarrays. + */ +static void print_buffer(struct dmesg_control *ctl, + const char *buf, size_t size) +{ + struct dmesg_record rec = { .next = buf, .next_size = size }; + + if (ctl->raw) { + raw_print(ctl, buf, size); + return; + } + + while (get_next_syslog_record(ctl, &rec) == 0) + print_record(ctl, &rec); +} + +static ssize_t read_kmsg_one(struct dmesg_control *ctl) +{ + ssize_t size; + + /* kmsg returns EPIPE if record was modified while reading */ + do { + size = read(ctl->kmsg, ctl->kmsg_buf, + sizeof(ctl->kmsg_buf) - 1); + } while (size < 0 && errno == EPIPE); + + return size; +} + +static int init_kmsg(struct dmesg_control *ctl) +{ + int mode = O_RDONLY; + + if (!ctl->follow) + mode |= O_NONBLOCK; + else + setlinebuf(stdout); + + ctl->kmsg = open("/dev/kmsg", mode); + if (ctl->kmsg < 0) + return -1; + + /* + * Seek after the last record available at the time + * the last SYSLOG_ACTION_CLEAR was issued. + * + * ... otherwise SYSLOG_ACTION_CLEAR will have no effect for kmsg. + */ + lseek(ctl->kmsg, 0, SEEK_DATA); + + /* + * Old kernels (<3.5) allow to successfully open /dev/kmsg for + * read-only, but read() returns -EINVAL :-((( + * + * Let's try to read the first record. The record is later processed in + * read_kmsg(). + */ + ctl->kmsg_first_read = read_kmsg_one(ctl); + if (ctl->kmsg_first_read < 0) { + close(ctl->kmsg); + ctl->kmsg = -1; + return -1; + } + + return 0; +} + +/* + * /dev/kmsg record format: + * + * faclev,seqnum,timestamp[optional, ...];message\n + * TAGNAME=value + * ... + * + * - fields are separated by ',' + * - last field is terminated by ';' + * + */ +#define LAST_KMSG_FIELD(s) (!s || !*s || *(s - 1) == ';') + +static int parse_kmsg_record(struct dmesg_control *ctl, + struct dmesg_record *rec, + char *buf, + size_t sz) +{ + const char *p = buf, *end; + + if (sz == 0 || !buf || !*buf) + return -1; + + end = buf + (sz - 1); + INIT_DMESG_RECORD(rec); + + while (p < end && isspace(*p)) + p++; + + /* A) priority and facility */ + if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode || + ctl->raw || ctl->color) + p = parse_faclev(p, &rec->facility, &rec->level); + else + p = skip_item(p, end, ","); + if (LAST_KMSG_FIELD(p)) + goto mesg; + + /* B) sequence number */ + p = skip_item(p, end, ",;"); + if (LAST_KMSG_FIELD(p)) + goto mesg; + + /* C) timestamp */ + if (is_timefmt(ctl, NONE)) + p = skip_item(p, end, ",;"); + else + p = parse_kmsg_timestamp(p, &rec->tv); + if (LAST_KMSG_FIELD(p)) + goto mesg; + + /* D) optional fields (ignore) */ + p = skip_item(p, end, ";"); + +mesg: + /* E) message text */ + rec->mesg = p; + p = skip_item(p, end, "\n"); + if (!p) + return -1; + + /* The message text is terminated by \n, but it's possible that the + * message contains another stuff behind this linebreak; in this case + * the previous skip_item() returns pointer to the stuff behind \n. + * Let's normalize all these situations and make sure we always point to + * the \n. + * + * Note that the next unhexmangle_to_buffer() will replace \n by \0. + */ + if (*p && *p != '\n') + p--; + + /* + * Kernel escapes non-printable characters, unfortunately kernel + * definition of "non-printable" is too strict. On UTF8 console we can + * print many chars, so let's decode from kernel. + */ + rec->mesg_size = unhexmangle_to_buffer(rec->mesg, + (char *) rec->mesg, p - rec->mesg + 1); + + rec->mesg_size--; /* don't count \0 */ + + /* F) message tags (ignore) */ + + return 0; +} + +/* + * Note that each read() call for /dev/kmsg returns always one record. It means + * that we don't have to read whole message buffer before the records parsing. + * + * So this function does not compose one huge buffer (like read_syslog_buffer()) + * and print_buffer() is unnecessary. All is done in this function. + * + * Returns 0 on success, -1 on error. + */ +static int read_kmsg(struct dmesg_control *ctl) +{ + struct dmesg_record rec; + ssize_t sz; + + if (ctl->method != DMESG_METHOD_KMSG || ctl->kmsg < 0) + return -1; + + /* + * The very first read() call is done in kmsg_init() where we test + * /dev/kmsg usability. The return code from the initial read() is + * stored in ctl->kmsg_first_read; + */ + sz = ctl->kmsg_first_read; + + while (sz > 0) { + *(ctl->kmsg_buf + sz) = '\0'; /* for debug messages */ + + if (parse_kmsg_record(ctl, &rec, + ctl->kmsg_buf, (size_t) sz) == 0) + print_record(ctl, &rec); + + sz = read_kmsg_one(ctl); + } + + return 0; +} + +static int which_time_format(const char *s) +{ + if (!strcmp(s, "notime")) + return DMESG_TIMEFTM_NONE; + if (!strcmp(s, "ctime")) + return DMESG_TIMEFTM_CTIME; + if (!strcmp(s, "delta")) + return DMESG_TIMEFTM_DELTA; + if (!strcmp(s, "reltime")) + return DMESG_TIMEFTM_RELTIME; + if (!strcmp(s, "iso")) + return DMESG_TIMEFTM_ISO8601; + errx(EXIT_FAILURE, _("unknown time format: %s"), s); +} + +#ifdef TEST_DMESG +static inline int dmesg_get_boot_time(struct timeval *tv) +{ + char *str = getenv("DMESG_TEST_BOOTIME"); + uintmax_t sec, usec; + + if (str && sscanf(str, "%ju.%ju", &sec, &usec) == 2) { + tv->tv_sec = sec; + tv->tv_usec = usec; + return tv->tv_sec >= 0 && tv->tv_usec >= 0 ? 0 : -EINVAL; + } + + return get_boot_time(tv); +} +#else +# define dmesg_get_boot_time get_boot_time +#endif + +int main(int argc, char *argv[]) +{ + char *buf = NULL; + int c, nopager = 0; + int console_level = 0; + int klog_rc = 0; + int delta = 0; + ssize_t n; + static struct dmesg_control ctl = { + .filename = NULL, + .action = SYSLOG_ACTION_READ_ALL, + .method = DMESG_METHOD_KMSG, + .kmsg = -1, + .time_fmt = DMESG_TIMEFTM_TIME, + .indent = 0, + }; + int colormode = UL_COLORMODE_UNDEF; + enum { + OPT_TIME_FORMAT = CHAR_MAX + 1, + }; + + static const struct option longopts[] = { + { "buffer-size", required_argument, NULL, 's' }, + { "clear", no_argument, NULL, 'C' }, + { "color", optional_argument, NULL, 'L' }, + { "console-level", required_argument, NULL, 'n' }, + { "console-off", no_argument, NULL, 'D' }, + { "console-on", no_argument, NULL, 'E' }, + { "decode", no_argument, NULL, 'x' }, + { "file", required_argument, NULL, 'F' }, + { "facility", required_argument, NULL, 'f' }, + { "follow", no_argument, NULL, 'w' }, + { "human", no_argument, NULL, 'H' }, + { "help", no_argument, NULL, 'h' }, + { "kernel", no_argument, NULL, 'k' }, + { "level", required_argument, NULL, 'l' }, + { "syslog", no_argument, NULL, 'S' }, + { "raw", no_argument, NULL, 'r' }, + { "read-clear", no_argument, NULL, 'c' }, + { "reltime", no_argument, NULL, 'e' }, + { "show-delta", no_argument, NULL, 'd' }, + { "ctime", no_argument, NULL, 'T' }, + { "notime", no_argument, NULL, 't' }, + { "nopager", no_argument, NULL, 'P' }, + { "userspace", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "time-format", required_argument, NULL, OPT_TIME_FORMAT }, + { "force-prefix", no_argument, NULL, 'p' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'C','D','E','c','n','r' }, /* clear,off,on,read-clear,level,raw*/ + { 'H','r' }, /* human, raw */ + { 'L','r' }, /* color, raw */ + { 'S','w' }, /* syslog,follow */ + { 'T','r' }, /* ctime, raw */ + { 'd','r' }, /* delta, raw */ + { 'e','r' }, /* reltime, raw */ + { 'r','x' }, /* raw, decode */ + { 'r','t' }, /* notime, raw */ + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhkL::l:n:iPprSs:TtuVwx", + longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'C': + ctl.action = SYSLOG_ACTION_CLEAR; + break; + case 'c': + ctl.action = SYSLOG_ACTION_READ_CLEAR; + break; + case 'D': + ctl.action = SYSLOG_ACTION_CONSOLE_OFF; + break; + case 'd': + delta = 1; + break; + case 'E': + ctl.action = SYSLOG_ACTION_CONSOLE_ON; + break; + case 'e': + ctl.time_fmt = DMESG_TIMEFTM_RELTIME; + break; + case 'F': + ctl.filename = optarg; + ctl.method = DMESG_METHOD_MMAP; + break; + case 'f': + ctl.fltr_fac = 1; + if (string_to_bitarray(optarg, + ctl.facilities, parse_facility) < 0) + return EXIT_FAILURE; + break; + case 'H': + ctl.time_fmt = DMESG_TIMEFTM_RELTIME; + colormode = UL_COLORMODE_AUTO; + ctl.pager = 1; + break; + case 'h': + usage(); + break; + case 'k': + ctl.fltr_fac = 1; + setbit(ctl.facilities, FAC_BASE(LOG_KERN)); + break; + case 'L': + colormode = UL_COLORMODE_AUTO; + if (optarg) + colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case 'l': + ctl.fltr_lev= 1; + if (string_to_bitarray(optarg, + ctl.levels, parse_level) < 0) + return EXIT_FAILURE; + break; + case 'n': + ctl.action = SYSLOG_ACTION_CONSOLE_LEVEL; + console_level = parse_level(optarg, 0); + break; + case 'P': + nopager = 1; + break; + case 'p': + ctl.force_prefix = 1; + break; + case 'r': + ctl.raw = 1; + break; + case 'S': + ctl.method = DMESG_METHOD_SYSLOG; + break; + case 's': + ctl.bufsize = strtou32_or_err(optarg, + _("invalid buffer size argument")); + if (ctl.bufsize < 4096) + ctl.bufsize = 4096; + break; + case 'T': + ctl.time_fmt = DMESG_TIMEFTM_CTIME; + break; + case 't': + ctl.time_fmt = DMESG_TIMEFTM_NONE; + break; + case 'u': + ctl.fltr_fac = 1; + for (n = 1; (size_t) n < ARRAY_SIZE(facility_names); n++) + setbit(ctl.facilities, n); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'w': + ctl.follow = 1; + break; + case 'x': + ctl.decode = 1; + break; + case OPT_TIME_FORMAT: + ctl.time_fmt = which_time_format(optarg); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (argc != optind) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if ((is_timefmt(&ctl, RELTIME) || + is_timefmt(&ctl, CTIME) || + is_timefmt(&ctl, ISO8601)) + && dmesg_get_boot_time(&ctl.boot_time) != 0) + ctl.time_fmt = DMESG_TIMEFTM_NONE; + + if (delta) + switch (ctl.time_fmt) { + case DMESG_TIMEFTM_CTIME: + ctl.time_fmt = DMESG_TIMEFTM_CTIME_DELTA; + break; + case DMESG_TIMEFTM_TIME: + ctl.time_fmt = DMESG_TIMEFTM_TIME_DELTA; + break; + case DMESG_TIMEFTM_ISO8601: + warnx(_("--show-delta is ignored when used together with iso8601 time format")); + break; + default: + ctl.time_fmt = DMESG_TIMEFTM_DELTA; + } + + + ctl.color = colors_init(colormode, "dmesg") ? 1 : 0; + if (ctl.follow) + nopager = 1; + ctl.pager = nopager ? 0 : ctl.pager; + if (ctl.pager) + pager_redirect(); + + switch (ctl.action) { + case SYSLOG_ACTION_READ_ALL: + case SYSLOG_ACTION_READ_CLEAR: + if (ctl.method == DMESG_METHOD_KMSG && init_kmsg(&ctl) != 0) + ctl.method = DMESG_METHOD_SYSLOG; + + if (ctl.raw + && ctl.method != DMESG_METHOD_KMSG + && (ctl.fltr_lev || ctl.fltr_fac)) + errx(EXIT_FAILURE, _("--raw can be used together with --level or " + "--facility only when reading messages from /dev/kmsg")); + + /* only kmsg supports multi-line messages */ + if (ctl.force_prefix && ctl.method != DMESG_METHOD_KMSG) + ctl.force_prefix = 0; + + if (ctl.pager) + pager_redirect(); + n = read_buffer(&ctl, &buf); + if (n > 0) + print_buffer(&ctl, buf, n); + if (!ctl.mmap_buff) + free(buf); + if (n < 0) + err(EXIT_FAILURE, _("read kernel buffer failed")); + if (ctl.kmsg >= 0) + close(ctl.kmsg); + break; + case SYSLOG_ACTION_CLEAR: + case SYSLOG_ACTION_CONSOLE_OFF: + case SYSLOG_ACTION_CONSOLE_ON: + klog_rc = klogctl(ctl.action, NULL, 0); + break; + case SYSLOG_ACTION_CONSOLE_LEVEL: + klog_rc = klogctl(ctl.action, NULL, console_level); + break; + default: + errx(EXIT_FAILURE, _("unsupported command")); + break; + } + + + if (klog_rc) + err(EXIT_FAILURE, _("klogctl failed")); + + return EXIT_SUCCESS; +} |