]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
dmesg: read /dev/kmsg (since kernel 3.5.0)
authorKarel Zak <kzak@redhat.com>
Fri, 20 Jul 2012 10:09:09 +0000 (12:09 +0200)
committerKarel Zak <kzak@redhat.com>
Fri, 20 Jul 2012 10:09:09 +0000 (12:09 +0200)
kmsg advantages:
  - extendible format
  - tags for messages
  - one read() returns one complete record

See kernel Documentation/ABI/testing/dev-kmsg.

Signed-off-by: Karel Zak <kzak@redhat.com>
sys-utils/dmesg.c

index 0bf47a11d3d5cd6044c32a1a6054169a4ad43fc8..57ba7d26bee8e5e262483a3cf6f8a69a3e55a138 100644 (file)
@@ -31,6 +31,7 @@
 #include "bitops.h"
 #include "closestream.h"
 #include "optutils.h"
+#include "mangle.h"
 
 /* Close the log.  Currently a NOP. */
 #define SYSLOG_ACTION_CLOSE          0
@@ -156,6 +157,18 @@ struct dmesg_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 void __attribute__((__noreturn__)) usage(FILE *out)
 {
        size_t i;
@@ -288,7 +301,7 @@ static int parse_facility(const char *str, size_t len)
  * 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 '>' char.
+ * Note that the number has to end with '>' or ',' char.
  */
 static const char *parse_faclev(const char *str, int *fac, int *lev)
 {
@@ -309,13 +322,20 @@ static const char *parse_faclev(const char *str, int *fac, int *lev)
                        *lev = -1;
                if (*fac < 0 || (size_t) *fac > ARRAY_SIZE(facility_names))
                        *fac = -1;
-               return end + 1;         /* skip '<' */
+               return end + 1;         /* skip '<' or ',' */
        }
 
        return str;
 }
 
-static const char *parse_timestamp(const char *str0, struct timeval *tv)
+/*
+ * 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;
@@ -337,6 +357,35 @@ static const char *parse_timestamp(const char *str0, struct timeval *tv)
        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)
 {
@@ -441,6 +490,12 @@ static ssize_t read_buffer(struct dmesg_control *ctl, char **buf)
 
                n = read_syslog_buffer(ctl, buf);
                break;
+       case DMESG_METHOD_KMSG:
+               /*
+                * Since kernel 3.5.0
+                */
+               n = read_kmsg(ctl);
+               break;
        }
 
        return n;
@@ -471,7 +526,7 @@ static void safe_fwrite(const char *buf, size_t size, FILE *out)
        for (i = 0; i < size; i++) {
                const char *p = buf + i;
                int rc, hex = 0;
-        size_t len = 1;
+               size_t len = 1;
 
 #ifdef HAVE_WIDECHAR
                wchar_t wc;
@@ -501,6 +556,18 @@ static void safe_fwrite(const char *buf, size_t size, FILE *out)
        }
 }
 
+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;
+}
+
 static int get_next_syslog_record(struct dmesg_control *ctl,
                                  struct dmesg_record *rec)
 {
@@ -514,12 +581,7 @@ static int get_next_syslog_record(struct dmesg_control *ctl,
        if (!rec->next || !rec->next_size)
                return 1;
 
-       rec->mesg = NULL;
-       rec->mesg_size = 0;
-       rec->facility = -1;
-       rec->level = -1;
-       rec->tv.tv_sec = 0;
-       rec->tv.tv_usec = 0;
+       INIT_DMESG_RECORD(rec);
 
        /*
         * Unmap already printed file data from memory
@@ -551,30 +613,20 @@ static int get_next_syslog_record(struct dmesg_control *ctl,
                        continue;       /* error or empty line? */
 
                if (*begin == '<') {
-                       if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode) {
+                       if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode)
                                begin = parse_faclev(begin + 1, &rec->facility,
                                                     &rec->level);
-                       } else {
-                               /* ignore facility and level */
-                               while (begin < end) {
-                                       begin++;
-                                       if (*(begin - 1) == '>')
-                                               break;
-                               }
-                       }
+                       else
+                               begin = skip_item(begin, end, ">");
                }
 
                if (*begin == '[' && (*(begin + 1) == ' ' ||
                                      isdigit(*(begin + 1)))) {
-                       if (ctl->delta || ctl->ctime) {
-                               begin = parse_timestamp(begin + 1, &rec->tv);
-                       } else if (ctl->notime) {
-                               while (begin < end) {
-                                       begin++;
-                                       if (*(begin - 1) == ']')
-                                               break;
-                               }
-                       }
+                       if (ctl->delta || ctl->ctime)
+                               begin = parse_syslog_timestamp(begin + 1, &rec->tv);
+                       else if (ctl->notime)
+                               begin = skip_item(begin, end, "]");
+
                        if (begin < end && *begin == ' ')
                                begin++;
                }
@@ -678,6 +730,18 @@ static void print_record(struct dmesg_control *ctl, struct dmesg_record *rec)
                printf("[%s] ", tbuf);
        }
 
+       /*
+        * In syslog output the timestamp is part of the message and we don't
+        * parse the timestamp by default. We parse the timestamp only if
+        * --show-delta or --ctime is specified.
+        *
+        * In kmsg output we always parse the timesptamp, so we have to compose
+        * the [sec.usec] string.
+        */
+       if (ctl->method == DMESG_METHOD_KMSG &&
+           !ctl->notime && !ctl->delta && !ctl->ctime)
+               printf("[%5d.%06d] ", (int) rec->tv.tv_sec, (int) rec->tv.tv_usec);
+
        safe_fwrite(rec->mesg, rec->mesg_size, stdout);
 
        if (*(rec->mesg + rec->mesg_size - 1) != '\n')
@@ -708,6 +772,116 @@ static int init_kmsg(struct dmesg_control *ctl)
        return ctl->kmsg < 0 ? -1 : 0;
 }
 
+/*
+ * /dev/kmsg record format:
+ *
+ *     faclev,seqnum,timestamp[optional, ...];messgage\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)
+               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 (ctl->notime)
+               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, ";");
+       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;
+
+       /*
+        * Kernel escapes non-printable characters, unfortuately 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);
+
+       /* 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)
+{
+       char buf[BUFSIZ];
+       struct dmesg_record rec;
+
+       if (ctl->method != DMESG_METHOD_KMSG || ctl->kmsg < 0)
+               return -1;
+
+       do {
+               ssize_t sz = read(ctl->kmsg, buf, sizeof(buf) - 1);
+
+               if (sz <= 0)
+                       break;
+
+               *(buf + sz) = '\0';     /* for debug messages */
+
+               if (parse_kmsg_record(ctl, &rec, buf, (size_t) sz) != 0)
+                       continue;
+               print_record(ctl, &rec);
+       } while (1);
+
+       return 0;
+}
+
 int main(int argc, char *argv[])
 {
        char *buf = NULL;