]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - sys-utils/dmesg.c
misc: consolidate version printing and close_stdout()
[thirdparty/util-linux.git] / sys-utils / dmesg.c
index b351595fca6883ca6a15db9b58e9de4d4828b6cf..f96e5bdf7e52a1c2c26a086d87adb415f91f3ac0 100644 (file)
@@ -6,7 +6,6 @@
  *
  * This program comes with ABSOLUTELY NO WARRANTY.
  */
-#include <linux/unistd.h>
 #include <stdio.h>
 #include <getopt.h>
 #include <stdlib.h>
@@ -119,7 +118,7 @@ static const struct dmesg_name level_names[] =
 
 /*
  * sys/syslog.h uses (f << 3) for all facility codes.
- * We want to use the codes as array idexes, so shift back...
+ * 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 :-)
@@ -196,7 +195,9 @@ struct dmesg_control {
                        fltr_fac:1,     /* filter out by facilities[] */
                        decode:1,       /* use "facility: level: " prefix */
                        pager:1,        /* pipe output into a pager */
-                       color:1;        /* colorize messages */
+                       color:1,        /* colorize messages */
+                       force_prefix:1; /* force timestamp and decode prefix
+                                          on each line */
        int             indent;         /* due to timestamps if newline */
 };
 
@@ -244,7 +245,7 @@ static int set_level_color(int log_level, const char *mesg, size_t mesgsz)
                break;
        }
 
-       /* well, sometimes the messges contains important keywords, but in
+       /* well, sometimes the messages contains important keywords, but in
         * non-warning/error messages
         */
        if (id < 0 && memmem(mesg, mesgsz, "segfault at", 11))
@@ -256,8 +257,9 @@ static int set_level_color(int log_level, const char *mesg, size_t mesgsz)
        return id >= 0 ? 0 : -1;
 }
 
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
        size_t i;
 
        fputs(USAGE_HEADER, out);
@@ -281,6 +283,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        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);
@@ -295,8 +298,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
                "                               [delta|reltime|ctime|notime|iso]\n"
                "Suspending/resume will make ctime and iso timestamps inaccurate.\n"), out);
        fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(USAGE_VERSION, 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",
@@ -308,9 +310,9 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
                fprintf(out, " %7s - %s\n",
                        level_names[i].name,
                        _(level_names[i].help));
-       fputs(USAGE_SEPARATOR, out);
-       fprintf(out, USAGE_MAN_TAIL("dmesg(1)"));
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+
+       printf(USAGE_MAN_TAIL("dmesg(1)"));
+       exit(EXIT_SUCCESS);
 }
 
 /*
@@ -320,7 +322,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
  *  <name>   ::= case-insensitive text
  *
  *  Note that @len argument is not set when parsing "-n <level>" command line
- *  option. The console_level is intepreted as "log level less than the value".
+ *  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
@@ -591,6 +593,8 @@ static ssize_t read_buffer(struct dmesg_control *ctl, char **buf)
                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;
@@ -601,7 +605,7 @@ 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%02x", buf[i]);
+               int rc = fprintf(out, "\\x%02hhx", buf[i]);
                if (rc < 0)
                        return rc;
        }
@@ -639,8 +643,8 @@ static void safe_fwrite(const char *buf, size_t size, int indent, FILE *out)
                i += len - 1;
 #else
                len = 1;
-               if (!isprint((unsigned int) *p) &&
-                       !isspace((unsigned int) *p))        /* non-printable */
+               if (!isprint((unsigned char) *p) &&
+                   !isspace((unsigned char) *p))        /* non-printable */
                        hex = 1;
 #endif
                if (hex)
@@ -742,6 +746,10 @@ static int get_next_syslog_record(struct dmesg_control *ctl,
                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)
@@ -832,9 +840,7 @@ static char *iso_8601_time(struct dmesg_control *ctl, struct dmesg_record *rec,
                .tv_usec = rec->tv.tv_usec
        };
 
-       if (strtimeval_iso(&tv, ISO_8601_DATE|ISO_8601_TIME|ISO_8601_COMMAUSEC|
-                               ISO_8601_TIMEZONE,
-                               buf, bufsz) != 0)
+       if (strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA_T, buf, bufsz) != 0)
                return NULL;
 
        return buf;
@@ -861,7 +867,7 @@ static const char *get_subsys_delimiter(const char *mesg, size_t mesg_size)
                const char *d = strnchr(p, sz, ':');
                if (!d)
                        return NULL;
-               sz -= d - p;
+               sz -= d - p + 1;
                if (sz) {
                        if (isblank(*(d + 1)))
                                return d;
@@ -874,11 +880,13 @@ static const char *get_subsys_delimiter(const char *mesg, size_t mesg_size)
 static void print_record(struct dmesg_control *ctl,
                         struct dmesg_record *rec)
 {
-       char buf[256];
-       int has_color = 0;
-       const char *mesg;
-       size_t mesg_size;
-       int indent = 0;
+       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;
@@ -889,30 +897,28 @@ static void print_record(struct dmesg_control *ctl,
        }
 
        /*
-        * compose syslog(2) compatible raw output -- used for /dev/kmsg for
+        * Compose syslog(2) compatible raw output -- used for /dev/kmsg for
         * backward compatibility with syslog(2) buffers only
         */
        if (ctl->raw) {
-               ctl->indent = printf("<%d>[%5ld.%06ld] ",
-                                    LOG_MAKEPRI(rec->facility, rec->level),
-                                    (long) rec->tv.tv_sec,
-                                    (long) rec->tv.tv_usec);
-
-               goto mesg;
+               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;
        }
 
-       /*
-        * facility : priority :
-        */
+       /* Store decode information (facility & priority level) in a buffer */
        if (ctl->decode &&
-           -1 < rec->level    && rec->level     < (int) ARRAY_SIZE(level_names) &&
-           -1 < rec->facility && rec->facility  < (int) ARRAY_SIZE(facility_names))
-               indent = printf("%-6s:%-6s: ", facility_names[rec->facility].name,
-                                              level_names[rec->level].name);
-
-       if (ctl->color)
-               dmesg_enable_color(DMESG_COLOR_TIME);
-
+           (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;
@@ -920,15 +926,17 @@ static void print_record(struct dmesg_control *ctl,
                ctl->indent = 0;
                break;
        case DMESG_TIMEFTM_CTIME:
-               ctl->indent = printf("[%s] ", record_ctime(ctl, rec, buf, sizeof(buf)));
+               ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ",
+                                     record_ctime(ctl, rec, buf, sizeof(buf)));
                break;
        case DMESG_TIMEFTM_CTIME_DELTA:
-               ctl->indent = printf("[%s <%12.06f>] ",
-                                    record_ctime(ctl, rec, buf, sizeof(buf)),
-                                    record_count_delta(ctl, rec));
+               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 = printf("[<%12.06f>] ", record_count_delta(ctl, rec));
+               ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[<%12.06f>] ",
+                                     record_count_delta(ctl, rec));
                break;
        case DMESG_TIMEFTM_RELTIME:
                record_localtime(ctl, rec, &cur);
@@ -936,62 +944,120 @@ static void print_record(struct dmesg_control *ctl,
                if (cur.tm_min != ctl->lasttm.tm_min ||
                    cur.tm_hour != ctl->lasttm.tm_hour ||
                    cur.tm_yday != ctl->lasttm.tm_yday) {
-                       dmesg_enable_color(DMESG_COLOR_TIMEBREAK);
-                       ctl->indent = printf("[%s] ", short_ctime(&cur, buf, sizeof(buf)));
+                       timebreak = 1;
+                       ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ",
+                                             short_ctime(&cur, buf,
+                                                         sizeof(buf)));
                } else {
                        if (delta < 10)
-                               ctl->indent = printf("[  %+8.06f] ", delta);
+                               ctl->indent = snprintf(tsbuf, sizeof(tsbuf),
+                                               "[  %+8.06f] ",  delta);
                        else
-                               ctl->indent = printf("[ %+9.06f] ", delta);
+                               ctl->indent = snprintf(tsbuf, sizeof(tsbuf),
+                                               "[ %+9.06f] ", delta);
                }
                ctl->lasttm = cur;
                break;
        case DMESG_TIMEFTM_TIME:
-               ctl->indent = printf("[%5ld.%06ld] ",
-                              (long)rec->tv.tv_sec, (long)rec->tv.tv_usec);
+               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 = printf("[%5ld.%06ld <%12.06f>] ", (long)rec->tv.tv_sec,
-                              (long)rec->tv.tv_usec, record_count_delta(ctl, rec));
+               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 = printf("%s ", iso_8601_time(ctl, rec, buf, sizeof(buf)));
+               ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "%s ",
+                                     iso_8601_time(ctl, rec, buf,
+                                                   sizeof(buf)));
                break;
        default:
                abort();
        }
 
-       ctl->indent += indent;
-
-       if (ctl->color)
-               color_disable();
+       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();
+       }
 
-mesg:
-       mesg = rec->mesg;
-       mesg_size = rec->mesg_size;
+       /*
+        * 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 output */
+       /* Colorize kernel message output */
        if (ctl->color) {
-               /* subsystem prefix */
-               const char *subsys = get_subsys_delimiter(mesg, mesg_size);
+               /* 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(mesg, subsys - mesg, ctl->indent, stdout);
+                       safe_fwrite(line, subsys - line, ctl->indent, stdout);
                        color_disable();
 
-                       mesg_size -= subsys - mesg;
-                       mesg = subsys;
+                       mesg_size -= subsys - line;
+                       line = subsys;
                }
-               /* error, alert .. etc. colors */
-               has_color = set_level_color(rec->level, mesg, mesg_size) == 0;
-               safe_fwrite(mesg, mesg_size, ctl->indent, stdout);
+               /* 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(mesg, mesg_size, ctl->indent, stdout);
+               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);
+       }
 
-       if (*(mesg + mesg_size - 1) != '\n')
-               putchar('\n');
+       putchar('\n');
 }
 
 /*
@@ -1066,7 +1132,7 @@ static int init_kmsg(struct dmesg_control *ctl)
 /*
  * /dev/kmsg record format:
  *
- *     faclev,seqnum,timestamp[optional, ...];messgage\n
+ *     faclev,seqnum,timestamp[optional, ...];message\n
  *      TAGNAME=value
  *      ...
  *
@@ -1116,25 +1182,34 @@ static int parse_kmsg_record(struct dmesg_control *ctl,
 
        /* D) optional fields (ignore) */
        p = skip_item(p, end, ";");
-       if (LAST_KMSG_FIELD(p))
-               goto mesg;
 
 mesg:
        /* E) message text */
        rec->mesg = p;
        p = skip_item(p, end, "\n");
-
        if (!p)
                return -1;
 
-       rec->mesg_size = p - rec->mesg;
+       /* 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, unfortuately kernel
+        * 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.
         */
-       unhexmangle_to_buffer(rec->mesg, (char *) rec->mesg, rec->mesg_size + 1);
+       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) */
 
@@ -1178,19 +1253,19 @@ static int read_kmsg(struct dmesg_control *ctl)
        return 0;
 }
 
-static int which_time_format(const char *optarg)
+static int which_time_format(const char *s)
 {
-       if (!strcmp(optarg, "notime"))
+       if (!strcmp(s, "notime"))
                return DMESG_TIMEFTM_NONE;
-       if (!strcmp(optarg, "ctime"))
+       if (!strcmp(s, "ctime"))
                return DMESG_TIMEFTM_CTIME;
-       if (!strcmp(optarg, "delta"))
+       if (!strcmp(s, "delta"))
                return DMESG_TIMEFTM_DELTA;
-       if (!strcmp(optarg, "reltime"))
+       if (!strcmp(s, "reltime"))
                return DMESG_TIMEFTM_RELTIME;
-       if (!strcmp(optarg, "iso"))
+       if (!strcmp(s, "iso"))
                return DMESG_TIMEFTM_ISO8601;
-       errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
+       errx(EXIT_FAILURE, _("unknown time format: %s"), s);
 }
 
 #ifdef TEST_DMESG
@@ -1258,10 +1333,11 @@ int main(int argc, char *argv[])
                { "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 in ASCII order */
+       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 */
@@ -1278,9 +1354,9 @@ int main(int argc, char *argv[])
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
-       while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhkL::l:n:iPrSs:TtuVwx",
+       while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhkL::l:n:iPprSs:TtuVwx",
                                longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
@@ -1319,9 +1395,6 @@ int main(int argc, char *argv[])
                        colormode = UL_COLORMODE_AUTO;
                        ctl.pager = 1;
                        break;
-               case 'h':
-                       usage(stdout);
-                       break;
                case 'k':
                        ctl.fltr_fac = 1;
                        setbit(ctl.facilities, FAC_BASE(LOG_KERN));
@@ -1345,6 +1418,9 @@ int main(int argc, char *argv[])
                case 'P':
                        nopager = 1;
                        break;
+               case 'p':
+                       ctl.force_prefix = 1;
+                       break;
                case 'r':
                        ctl.raw = 1;
                        break;
@@ -1368,9 +1444,6 @@ int main(int argc, char *argv[])
                        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;
@@ -1380,24 +1453,27 @@ int main(int argc, char *argv[])
                case OPT_TIME_FORMAT:
                        ctl.time_fmt = which_time_format(optarg);
                        break;
-               case '?':
+
+               case 'h':
+                       usage();
+               case 'V':
+                       print_version(EXIT_SUCCESS);
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }
-       argc -= optind;
-       argv += optind;
 
-       if (argc > 1)
-               usage(stderr);
-
-       if (is_timefmt(&ctl, RELTIME) ||
-           is_timefmt(&ctl, CTIME) ||
-           is_timefmt(&ctl, ISO8601)) {
-               if (dmesg_get_boot_time(&ctl.boot_time) != 0)
-                       ctl.time_fmt = DMESG_TIMEFTM_NONE;
+       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:
@@ -1419,7 +1495,7 @@ int main(int argc, char *argv[])
                nopager = 1;
        ctl.pager = nopager ? 0 : ctl.pager;
        if (ctl.pager)
-               setup_pager();
+               pager_redirect();
 
        switch (ctl.action) {
        case SYSLOG_ACTION_READ_ALL:
@@ -1432,8 +1508,13 @@ int main(int argc, char *argv[])
                    && (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)
-                       setup_pager();
+                       pager_redirect();
                n = read_buffer(&ctl, &buf);
                if (n > 0)
                        print_buffer(&ctl, buf, n);