diff options
Diffstat (limited to 'sys-utils/dmesg.c')
-rw-r--r-- | sys-utils/dmesg.c | 524 |
1 files changed, 398 insertions, 126 deletions
diff --git a/sys-utils/dmesg.c b/sys-utils/dmesg.c index 717cc58..25c674b 100644 --- a/sys-utils/dmesg.c +++ b/sys-utils/dmesg.c @@ -1,14 +1,21 @@ /* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * * 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. + * Copyright (C) 2011-2023 Karel Zak <kzak@redhat.com> */ #include <stdio.h> #include <getopt.h> +#include <stdbool.h> #include <stdlib.h> +#include <string.h> #include <sys/klog.h> #include <sys/syslog.h> #include <sys/time.h> @@ -36,6 +43,7 @@ #include "mangle.h" #include "pager.h" #include "jsonwrt.h" +#include "pathnames.h" /* Close the log. Currently a NOP. */ #define SYSLOG_ACTION_CLOSE 0 @@ -60,6 +68,12 @@ /* Return size of the log buffer */ #define SYSLOG_ACTION_SIZE_BUFFER 10 +#define PID_CHARS_MAX sizeof(stringify_value(LONG_MAX)) +#define PID_CHARS_DEFAULT sizeof(stringify_value(INT_MAX)) +#define SYSLOG_DEFAULT_CALLER_ID_CHARS sizeof(stringify_value(SHRT_MAX))-1 +#define DMESG_CALLER_PREFIX "caller=" +#define DMESG_CALLER_PREFIXSZ (sizeof(DMESG_CALLER_PREFIX)-1) + /* * Color scheme */ @@ -125,6 +139,7 @@ static const struct dmesg_name level_names[] = * shifted code :-) */ #define FAC_BASE(f) ((f) >> 3) +#define LOG_RAW_FAC_PRI(fac, pri) ((fac << 3) | pri) static const struct dmesg_name facility_names[] = { @@ -140,6 +155,18 @@ static const struct dmesg_name facility_names[] = [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") }, + [FAC_BASE(LOG_FTP) + 1] = { "res0", N_("reserved 0") }, + [FAC_BASE(LOG_FTP) + 2] = { "res1", N_("reserved 1") }, + [FAC_BASE(LOG_FTP) + 3] = { "res2", N_("reserved 2") }, + [FAC_BASE(LOG_FTP) + 4] = { "res3", N_("reserved 3") }, + [FAC_BASE(LOG_LOCAL0)] = { "local0", N_("local use 0") }, + [FAC_BASE(LOG_LOCAL1)] = { "local1", N_("local use 1") }, + [FAC_BASE(LOG_LOCAL2)] = { "local2", N_("local use 2") }, + [FAC_BASE(LOG_LOCAL3)] = { "local3", N_("local use 3") }, + [FAC_BASE(LOG_LOCAL4)] = { "local4", N_("local use 4") }, + [FAC_BASE(LOG_LOCAL5)] = { "local5", N_("local use 5") }, + [FAC_BASE(LOG_LOCAL6)] = { "local6", N_("local use 6") }, + [FAC_BASE(LOG_LOCAL7)] = { "local7", N_("local use 7") }, }; /* supported methods to read message buffer @@ -158,9 +185,12 @@ enum { DMESG_TIMEFTM_RELTIME, /* [relative] */ DMESG_TIMEFTM_TIME, /* [time] */ DMESG_TIMEFTM_TIME_DELTA, /* [time <delta>] */ - DMESG_TIMEFTM_ISO8601 /* 2013-06-13T22:11:00,123456+0100 */ + DMESG_TIMEFTM_ISO8601, /* 2013-06-13T22:11:00,123456+0100 */ + + __DMESG_TIMEFTM_COUNT }; -#define is_timefmt(c, f) ((c)->time_fmt == (DMESG_TIMEFTM_ ##f)) + +#define DMESG_TIMEFTM_DEFAULT DMESG_TIMEFTM_TIME struct dmesg_control { /* bit arrays -- see include/bitops.h */ @@ -198,7 +228,8 @@ struct dmesg_control { char *filename; char *mmap_buff; size_t pagesize; - unsigned int time_fmt; /* time format */ + size_t ntime_fmts; + unsigned int time_fmts[2 * __DMESG_TIMEFTM_COUNT]; /* time format */ struct ul_jsonwrt jfmt; /* -J formatting */ @@ -215,6 +246,7 @@ struct dmesg_control { force_prefix:1; /* force timestamp and decode prefix on each line */ int indent; /* due to timestamps if newline */ + size_t caller_id_size; /* PRINTK_CALLERID max field size */ }; struct dmesg_record { @@ -224,6 +256,7 @@ struct dmesg_record { int level; int facility; struct timeval tv; + char caller_id[PID_CHARS_MAX]; const char *next; /* buffer with next unparsed record */ size_t next_size; /* size of the next buffer */ @@ -236,9 +269,11 @@ struct dmesg_record { (_r)->level = -1; \ (_r)->tv.tv_sec = 0; \ (_r)->tv.tv_usec = 0; \ + (_r)->caller_id[0] = 0; \ } while (0) -static int read_kmsg(struct dmesg_control *ctl); +static int process_kmsg(struct dmesg_control *ctl); +static int process_kmsg_file(struct dmesg_control *ctl, char **buf); static int set_level_color(int log_level, const char *mesg, size_t mesgsz) { @@ -290,6 +325,7 @@ static void __attribute__((__noreturn__)) usage(void) 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(_(" -K, --kmsg-file <file> use the file in kmsg format\n"), out); fputs(_(" -f, --facility <list> restrict output to defined facilities\n"), out); fputs(_(" -H, --human human readable output\n"), out); fputs(_(" -J, --json use JSON output format\n"), out); @@ -315,15 +351,15 @@ static void __attribute__((__noreturn__)) usage(void) 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" + " [delta|reltime|ctime|notime|iso|raw]\n" "Suspending/resume will make ctime and iso timestamps inaccurate.\n"), out); fputs(_(" --since <time> display the lines since the specified time\n"), out); fputs(_(" --until <time> display the lines until the specified time\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(29)); + fprintf(out, USAGE_HELP_OPTIONS(29)); fputs(_("\nSupported log facilities:\n"), out); - for (i = 0; i < ARRAY_SIZE(level_names); i++) + for (i = 0; i < ARRAY_SIZE(facility_names); i++) fprintf(out, " %7s - %s\n", facility_names[i].name, _(facility_names[i].help)); @@ -334,10 +370,58 @@ static void __attribute__((__noreturn__)) usage(void) level_names[i].name, _(level_names[i].help)); - printf(USAGE_MAN_TAIL("dmesg(1)")); + fprintf(out, USAGE_MAN_TAIL("dmesg(1)")); exit(EXIT_SUCCESS); } +static void reset_time_fmts(struct dmesg_control *ctl) +{ + memset(ctl->time_fmts, 0, + __DMESG_TIMEFTM_COUNT * sizeof(*(ctl->time_fmts))); + ctl->time_fmts[0] = DMESG_TIMEFTM_DEFAULT; +} + +static int is_time_fmt_set(struct dmesg_control *ctl, unsigned int time_format) +{ + size_t i; + + if (ctl->ntime_fmts == 0) + return time_format == DMESG_TIMEFTM_DEFAULT; + + for (i = 0; i < ctl->ntime_fmts; i++) { + if (ctl->time_fmts[i] == time_format) + return 1; + } + return 0; +} + +static void include_time_fmt(struct dmesg_control *ctl, unsigned int time_format) +{ + if (ctl->ntime_fmts > 0 && is_time_fmt_set(ctl, time_format)) + return; + + if (ctl->ntime_fmts < __DMESG_TIMEFTM_COUNT) + ctl->time_fmts[ctl->ntime_fmts++] = time_format; +} + +static void exclude_time_fmt(struct dmesg_control *ctl, unsigned int time_format) +{ + size_t idx = 0; + + while (idx < ctl->ntime_fmts && ctl->time_fmts[idx] != time_format) + idx++; + + if (idx < ctl->ntime_fmts && ctl->time_fmts[idx] == time_format) { + while (idx + 1 < ctl->ntime_fmts) { + ctl->time_fmts[idx] = ctl->time_fmts[idx+1]; + idx++; + } + ctl->ntime_fmts--; + if (ctl->ntime_fmts == 0) + reset_time_fmts(ctl); + } +} + /* * LEVEL ::= <number> | <name> * <number> ::= @len is set: number in range <0..N>, where N < ARRAY_SIZE(level_names) @@ -510,18 +594,17 @@ static const char *parse_kmsg_timestamp(const char *str0, struct timeval *tv) usec = strtoumax(str, &end, 10); if (!errno && end && (*end == ';' || *end == ',')) { - tv->tv_usec = usec % 1000000; - tv->tv_sec = usec / 1000000; + tv->tv_usec = usec % USEC_PER_SEC; + tv->tv_sec = usec / USEC_PER_SEC; } 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; + return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / (double) USEC_PER_SEC; } static int get_syslog_buffer_size(void) @@ -532,6 +615,47 @@ static int get_syslog_buffer_size(void) } /* + * Get the number of characters needed to hold the maximum number + * of tasks this system supports. This size of string could hold + * a thread id large enough for the highest thread id. + * This is needed to determine the number of characters reserved for + * the PRINTK_CALLER field if it has been configured in the Linux Kernel. + * + * The number of digits sets the max value since the value can't exceed + * a value of that size. The /proc field defined by _PATH_PROC_PIDMAX + * holds the maximum number of PID values that may be ussed by the system, + * so 0 to that value minus one. + * + * For determining the size of the PRINTK_CALLER field, we make the safe + * assumption that the number of threads >= number of cpus. This because + * the PRINTK_CALLER field can hold either a thread id or a CPU id value. + * + * If we can't access the pid max kernel proc entry we assign a default + * field size of 5 characters as that is what the old syslog interface + * uses as the reserved field size. This is justified because 32-bit Linux + * systems are limited to PID values between (0-32767). + * + */ +static size_t max_threads_id_size(void) +{ + char taskmax[PID_CHARS_MAX] = {'\0'}; + ssize_t rdsize; + int fd; + + fd = open(_PATH_PROC_PIDMAX, O_RDONLY); + if (fd == -1) + return PID_CHARS_DEFAULT; + + rdsize = read(fd, taskmax, sizeof(taskmax)); + close(fd); + + if (rdsize == -1) + return PID_CHARS_DEFAULT; + + return strnlen(taskmax, sizeof(taskmax)); +} + +/* * Reads messages from regular file by mmap */ static ssize_t mmap_file_buffer(struct dmesg_control *ctl, char **buf) @@ -589,9 +713,9 @@ static ssize_t read_syslog_buffer(struct dmesg_control *ctl, char **buf) } /* - * Top level function to read messages + * Top level function to read (and print in case of kmesg) messages */ -static ssize_t read_buffer(struct dmesg_control *ctl, char **buf) +static ssize_t process_buffer(struct dmesg_control *ctl, char **buf) { ssize_t n = -1; @@ -604,12 +728,17 @@ static ssize_t read_buffer(struct dmesg_control *ctl, char **buf) ctl->bufsize = get_syslog_buffer_size(); n = read_syslog_buffer(ctl, buf); + /* Set number of PID characters for caller_id spacing */ + ctl->caller_id_size = SYSLOG_DEFAULT_CALLER_ID_CHARS; break; case DMESG_METHOD_KMSG: - /* - * Since kernel 3.5.0 - */ - n = read_kmsg(ctl); + if (ctl->filename) + n = process_kmsg_file(ctl, buf); + else + /* + * Since kernel 3.5.0 + */ + n = process_kmsg(ctl); break; default: abort(); /* impossible method -> drop core */ @@ -706,6 +835,39 @@ static const char *skip_item(const char *begin, const char *end, const char *sep } /* + * Checks to see if the caller (caller id) field is present in the kmsg record. + * This is true if the PRINTK_CALLER config option has been set in the kernel. + * + * If the caller_id is found in the kmsg buffer then return the id and id type + * to the caller in dmesg caller_id. Returns string pointer to next value. + * + */ +static const char *parse_callerid(const char *p_str, const char *end, + struct dmesg_record *p_drec) +{ + const char *p_after; + const char *p_next; + size_t cid_size; + char *p_scn; + char *p_cid; + + /* Check for PRINTK_CALLER prefix, must be before msg text */ + p_cid = strstr(p_str, DMESG_CALLER_PREFIX); + p_scn = strchr(p_str, ';'); + if (p_cid != NULL && p_drec != NULL && p_scn != NULL && p_cid < p_scn) { + p_next = p_cid + DMESG_CALLER_PREFIXSZ; + p_after = skip_item(p_next, end, ",;"); + cid_size = p_after - p_next; + if (cid_size < sizeof(p_drec->caller_id)) + xstrncpy(p_drec->caller_id, p_next, cid_size); + else + return p_str; + return p_after; + } + return p_str; +} + +/* * Parses one record from syslog(2) buffer */ static int get_next_syslog_record(struct dmesg_control *ctl, @@ -763,7 +925,7 @@ static int get_next_syslog_record(struct dmesg_control *ctl, if (*begin == '[' && (*(begin + 1) == ' ' || isdigit(*(begin + 1)))) { - if (!is_timefmt(ctl, NONE)) + if (!is_time_fmt_set(ctl, DMESG_TIMEFTM_NONE)) begin = parse_syslog_timestamp(begin + 1, &rec->tv); else begin = skip_item(begin, end, "]"); @@ -772,8 +934,26 @@ static int get_next_syslog_record(struct dmesg_control *ctl, begin++; } - rec->mesg = begin; - rec->mesg_size = end - begin; + if (*begin == '[' && (*(begin + 1) == ' ' || + (*(begin + 1) == 'T' || *(begin + 1) == 'C'))) { + const char *start = begin + 1; + size_t id_size; + + while (start < end && *start == ' ') + start++; + + begin = skip_item(begin, end, "]"); + id_size = begin - start; + + if (id_size < sizeof(rec->caller_id)) + xstrncpy(rec->caller_id, start, id_size); + + rec->mesg = begin < end ? begin + 1 : NULL; + rec->mesg_size = begin < end ? end - begin - 1 : 0; + } else { + 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') @@ -938,13 +1118,20 @@ static void print_record(struct dmesg_control *ctl, char buf[128]; char fpbuf[32] = "\0"; char tsbuf[64] = "\0"; + char full_tsbuf[64 * __DMESG_TIMEFTM_COUNT] = "\0"; size_t mesg_size = rec->mesg_size; int timebreak = 0; char *mesg_copy = NULL; const char *line = NULL; + double delta = 0; + size_t format_iter = 0; - if (!accept_record(ctl, rec)) + if (!accept_record(ctl, rec)) { + /* remember time of the rejected record to not affect delta for + * the following records */ + ctl->lasttime = rec->tv; return; + } if (!rec->mesg_size) { if (!ctl->json) @@ -952,6 +1139,8 @@ static void print_record(struct dmesg_control *ctl, return; } + delta = record_count_delta(ctl, rec); + if (ctl->json) { if (!ul_jsonwrt_is_ready(&ctl->jfmt)) { ul_jsonwrt_init(&ctl->jfmt, stdout, 0); @@ -966,9 +1155,9 @@ static void print_record(struct dmesg_control *ctl, * backward compatibility with syslog(2) buffers only */ if (ctl->raw) { - ctl->indent = snprintf(tsbuf, sizeof(tsbuf), + ctl->indent = snprintf(full_tsbuf, sizeof(full_tsbuf), "<%d>[%5ld.%06ld] ", - LOG_MAKEPRI(rec->facility, rec->level), + LOG_RAW_FAC_PRI(rec->facility, rec->level), (long) rec->tv.tv_sec, (long) rec->tv.tv_usec); goto full_output; @@ -981,64 +1170,72 @@ static void print_record(struct dmesg_control *ctl, 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; + for (format_iter = 0; + format_iter < (ctl->ntime_fmts > 0 ? ctl->ntime_fmts : 1); + format_iter++) { + switch (ctl->time_fmts[format_iter]) { + struct tm cur; + case DMESG_TIMEFTM_NONE: + ctl->indent = 0; + break; + case DMESG_TIMEFTM_CTIME: 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); + 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)), + delta); + break; + case DMESG_TIMEFTM_DELTA: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[<%12.06f>] ", + delta); + break; + case DMESG_TIMEFTM_RELTIME: + record_localtime(ctl, rec, &cur); + 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), + ctl->json ? "%5ld.%06ld" : "[%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, + delta); + break; + case DMESG_TIMEFTM_ISO8601: + ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "%s ", + iso_8601_time(ctl, rec, buf, + sizeof(buf))); + break; + default: + abort(); } - ctl->lasttm = cur; - break; - case DMESG_TIMEFTM_TIME: - ctl->indent = snprintf(tsbuf, sizeof(tsbuf), - ctl->json ? "%5ld.%06ld" : "[%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(); + + if (is_time_fmt_set(ctl, DMESG_TIMEFTM_NONE)) + break; + else if (*tsbuf) + strcat(full_tsbuf, tsbuf); + } ctl->indent += strlen(fpbuf); @@ -1052,32 +1249,45 @@ full_output: ul_jsonwrt_value_s(&ctl->jfmt, "fac", facility_names[rec->facility].name); ul_jsonwrt_value_s(&ctl->jfmt, "pri", level_names[rec->level].name); } else - ul_jsonwrt_value_u64(&ctl->jfmt, "pri", LOG_MAKEPRI(rec->facility, rec->level)); + ul_jsonwrt_value_u64(&ctl->jfmt, "pri", LOG_RAW_FAC_PRI(rec->facility, rec->level)); } /* Output the timestamp buffer */ - if (*tsbuf) { + if (*full_tsbuf) { /* Colorize the timestamp */ if (ctl->color) dmesg_enable_color(timebreak ? DMESG_COLOR_TIMEBREAK : DMESG_COLOR_TIME); - if (ctl->time_fmt != DMESG_TIMEFTM_RELTIME) { + if (!is_time_fmt_set(ctl, DMESG_TIMEFTM_RELTIME)) { if (ctl->json) - ul_jsonwrt_value_raw(&ctl->jfmt, "time", tsbuf); + ul_jsonwrt_value_raw(&ctl->jfmt, "time", full_tsbuf); else - fputs(tsbuf, stdout); + fputs(full_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. */ - fputs(!line ? tsbuf : "[ +0.000000] ", stdout); + fputs(!line ? full_tsbuf : "[ +0.000000] ", stdout); } if (ctl->color) color_disable(); } + if (*rec->caller_id) { + if (ctl->json) { + ul_jsonwrt_value_s(&ctl->jfmt, "caller", rec->caller_id); + } else { + char cidbuf[PID_CHARS_MAX+3] = {'\0'}; + + sprintf(cidbuf, "[%*s] ", + (char)ctl->caller_id_size, rec->caller_id); + ctl->indent += strnlen(cidbuf, sizeof(cidbuf)); + fputs(cidbuf, stdout); + } + } + /* * A kernel message may contain several lines of output, separated * by '\n'. If the timestamp and decode outputs are forced then each @@ -1117,7 +1327,7 @@ full_output: color_disable(); } else { if (ctl->json) - ul_jsonwrt_value_s(&ctl->jfmt, "msg", line); + ul_jsonwrt_value_s_sized(&ctl->jfmt, "msg", line, mesg_size); else safe_fwrite(ctl, line, mesg_size, ctl->indent, stdout); } @@ -1197,7 +1407,7 @@ static int init_kmsg(struct dmesg_control *ctl) * but read() returns -EINVAL :-((( * * Let's try to read the first record. The record is later processed in - * read_kmsg(). + * process_kmsg(). */ ctl->kmsg_first_read = read_kmsg_one(ctl); if (ctl->kmsg_first_read < 0) { @@ -1253,7 +1463,7 @@ static int parse_kmsg_record(struct dmesg_control *ctl, goto mesg; /* C) timestamp */ - if (is_timefmt(ctl, NONE)) + if (is_time_fmt_set(ctl, DMESG_TIMEFTM_NONE)) p = skip_item(p, end, ",;"); else p = parse_kmsg_timestamp(p, &rec->tv); @@ -1261,7 +1471,10 @@ static int parse_kmsg_record(struct dmesg_control *ctl, goto mesg; /* D) optional fields (ignore) */ - p = skip_item(p, end, ";"); + p = skip_item(p, end, ",;"); + + /* Include optional PRINTK_CALLER field if it is present */ + p = parse_callerid(p, end, rec); mesg: /* E) message text */ @@ -1305,7 +1518,7 @@ mesg: * * Returns 0 on success, -1 on error. */ -static int read_kmsg(struct dmesg_control *ctl) +static int process_kmsg(struct dmesg_control *ctl) { struct dmesg_record rec; ssize_t sz; @@ -1313,6 +1526,9 @@ static int read_kmsg(struct dmesg_control *ctl) if (ctl->method != DMESG_METHOD_KMSG || ctl->kmsg < 0) return -1; + /* Determine number of PID characters for caller_id spacing */ + ctl->caller_id_size = max_threads_id_size(); + /* * The very first read() call is done in kmsg_init() where we test * /dev/kmsg usability. The return code from the initial read() is @@ -1333,6 +1549,40 @@ static int read_kmsg(struct dmesg_control *ctl) return 0; } +static int process_kmsg_file(struct dmesg_control *ctl, char **buf) +{ + char str[sizeof(ctl->kmsg_buf)]; + struct dmesg_record rec; + ssize_t sz; + size_t len; + + if (ctl->method != DMESG_METHOD_KMSG || !ctl->filename) + return -1; + + sz = mmap_file_buffer(ctl, buf); + if (sz == -1) + return -1; + + while (sz > 0) { + len = strnlen(ctl->mmap_buff, sz); + if (len > sizeof(str)) + errx(EXIT_FAILURE, _("record too large")); + + memcpy(str, ctl->mmap_buff, len); + + if (parse_kmsg_record(ctl, &rec, str, len) == 0) + print_record(ctl, &rec); + + if (len < (size_t)sz) + len++; + + sz -= len; + ctl->mmap_buff += len; + } + + return 0; +} + static int which_time_format(const char *s) { if (!strcmp(s, "notime")) @@ -1345,6 +1595,8 @@ static int which_time_format(const char *s) return DMESG_TIMEFTM_RELTIME; if (!strcmp(s, "iso")) return DMESG_TIMEFTM_ISO8601; + if (!strcmp(s, "raw")) + return DMESG_TIMEFTM_TIME; errx(EXIT_FAILURE, _("unknown time format: %s"), s); } @@ -1387,8 +1639,9 @@ int main(int argc, char *argv[]) .action = SYSLOG_ACTION_READ_ALL, .method = DMESG_METHOD_KMSG, .kmsg = -1, - .time_fmt = DMESG_TIMEFTM_TIME, + .ntime_fmts = 0, .indent = 0, + .caller_id_size = 0, }; int colormode = UL_COLORMODE_UNDEF; enum { @@ -1414,6 +1667,7 @@ int main(int argc, char *argv[]) { "help", no_argument, NULL, 'h' }, { "json", no_argument, NULL, 'J' }, { "kernel", no_argument, NULL, 'k' }, + { "kmsg-file", required_argument, NULL, 'K' }, { "level", required_argument, NULL, 'l' }, { "since", required_argument, NULL, OPT_SINCE }, { "syslog", no_argument, NULL, 'S' }, @@ -1435,6 +1689,7 @@ int main(int argc, char *argv[]) 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*/ + { 'F','K' }, /* file, kmsg-file */ { 'H','r' }, /* human, raw */ { 'L','r' }, /* color, raw */ { 'S','w' }, /* syslog,follow */ @@ -1452,7 +1707,9 @@ int main(int argc, char *argv[]) textdomain(PACKAGE); close_stdout_atexit(); - while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhJkL::l:n:iPprSs:TtuVWwx", + ctl.time_fmts[0] = DMESG_TIMEFTM_DEFAULT; + + while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhJK:kL::l:n:iPprSs:TtuVWwx", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -1474,11 +1731,17 @@ int main(int argc, char *argv[]) ctl.action = SYSLOG_ACTION_CONSOLE_ON; break; case 'e': - ctl.time_fmt = DMESG_TIMEFTM_RELTIME; + include_time_fmt(&ctl, DMESG_TIMEFTM_RELTIME); break; case 'F': ctl.filename = optarg; ctl.method = DMESG_METHOD_MMAP; + ctl.caller_id_size = SYSLOG_DEFAULT_CALLER_ID_CHARS; + break; + case 'K': + ctl.filename = optarg; + ctl.method = DMESG_METHOD_KMSG; + ctl.caller_id_size = max_threads_id_size(); break; case 'f': ctl.fltr_fac = 1; @@ -1487,7 +1750,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; break; case 'H': - ctl.time_fmt = DMESG_TIMEFTM_RELTIME; + include_time_fmt(&ctl, DMESG_TIMEFTM_RELTIME); colormode = UL_COLORMODE_AUTO; ctl.pager = 1; break; @@ -1534,10 +1797,11 @@ int main(int argc, char *argv[]) ctl.bufsize = 4096; break; case 'T': - ctl.time_fmt = DMESG_TIMEFTM_CTIME; + include_time_fmt(&ctl, DMESG_TIMEFTM_CTIME); break; case 't': - ctl.time_fmt = DMESG_TIMEFTM_NONE; + reset_time_fmts(&ctl); + include_time_fmt(&ctl, DMESG_TIMEFTM_NONE); break; case 'u': ctl.fltr_fac = 1; @@ -1555,7 +1819,7 @@ int main(int argc, char *argv[]) ctl.decode = 1; break; case OPT_TIME_FORMAT: - ctl.time_fmt = which_time_format(optarg); + include_time_fmt(&ctl, which_time_format(optarg)); break; case OPT_NOESC: ctl.noesc = 1; @@ -1587,7 +1851,8 @@ int main(int argc, char *argv[]) } if (ctl.json) { - ctl.time_fmt = DMESG_TIMEFTM_TIME; + reset_time_fmts(&ctl); + ctl.ntime_fmts = 0; delta = 0; ctl.force_prefix = 0; ctl.raw = 0; @@ -1595,45 +1860,53 @@ int main(int argc, char *argv[]) nopager = 1; } - if ((is_timefmt(&ctl, RELTIME) || - is_timefmt(&ctl, CTIME) || - is_timefmt(&ctl, ISO8601)) || + if ((is_time_fmt_set(&ctl, DMESG_TIMEFTM_RELTIME) || + is_time_fmt_set(&ctl, DMESG_TIMEFTM_CTIME) || + is_time_fmt_set(&ctl, DMESG_TIMEFTM_ISO8601)) || ctl.since || ctl.until) { if (dmesg_get_boot_time(&ctl.boot_time) != 0) - ctl.time_fmt = DMESG_TIMEFTM_NONE; + include_time_fmt(&ctl, DMESG_TIMEFTM_NONE); else ctl.suspended_time = dmesg_get_suspended_time(); } - 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; + if (delta || is_time_fmt_set(&ctl, DMESG_TIMEFTM_DELTA)) { + if (is_time_fmt_set(&ctl, DMESG_TIMEFTM_TIME)) { + if (ctl.ntime_fmts == 0) { + ctl.time_fmts[ctl.ntime_fmts++] = DMESG_TIMEFTM_TIME_DELTA; + } else { + exclude_time_fmt(&ctl, DMESG_TIMEFTM_DELTA); + for (n = 0; (size_t) n < ctl.ntime_fmts; n++) { + if (ctl.time_fmts[n] == DMESG_TIMEFTM_TIME) { + ctl.time_fmts[n] = DMESG_TIMEFTM_TIME_DELTA; + break; + } + } + } + } else if (is_time_fmt_set(&ctl, DMESG_TIMEFTM_CTIME)) { + exclude_time_fmt(&ctl, DMESG_TIMEFTM_DELTA); + for (n = 0; (size_t) n < ctl.ntime_fmts; n++) { + if (ctl.time_fmts[n] == DMESG_TIMEFTM_CTIME) { + ctl.time_fmts[n] = DMESG_TIMEFTM_CTIME_DELTA; + break; + } + } + } else { + include_time_fmt(&ctl, DMESG_TIMEFTM_DELTA); } - + } if (!ctl.json) 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) + if (ctl.method == DMESG_METHOD_KMSG && !ctl.filename && init_kmsg(&ctl) != 0) ctl.method = DMESG_METHOD_SYSLOG; if (ctl.raw @@ -1642,12 +1915,11 @@ int main(int argc, char *argv[]) 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; + errx(EXIT_FAILURE, _("only kmsg supports multi-line messages")); if (ctl.pager) pager_redirect(); - n = read_buffer(&ctl, &buf); + n = process_buffer(&ctl, &buf); if (n > 0) print_buffer(&ctl, buf, n); if (!ctl.mmap_buff) |