[syslog-ng] [PATCH (3.4) 3/5] linux-kmsg-format: New format plugin to parse /dev/kmsg messages

Gergely Nagy algernon at balabit.hu
Sat Oct 13 17:20:19 CEST 2012


With linux 3.5, /dev/kmsg was changed substantially, and it now emits
structured kernel messages towards userspace. For its format, see
Documentation/ABI/testing/dev-kmsg in a recent kernel source.

This format plugin implements the parser for that format, including
special parsing for the DEVICE keys, and resolving of the message
timestamps on Linux.

To use it on a 3.5+ kernel, something along these lines should replace
the /proc/kmsg source:

  file("/dev/kmsg"
       flags(kernel, indented-multiline)
       program-override("kernel")
       format("linux-kmsg"));

Signed-off-by: Gergely Nagy <algernon at balabit.hu>
---
 configure.in                                       |    2 +
 modules/Makefile.am                                |    2 +-
 modules/linux-kmsg-format/Makefile.am              |   15 +
 .../linux-kmsg-format/linux-kmsg-format-plugin.c   |   57 +++
 modules/linux-kmsg-format/linux-kmsg-format.c      |  470 ++++++++++++++++++++
 modules/linux-kmsg-format/linux-kmsg-format.h      |   30 ++
 modules/linux-kmsg-format/tests/Makefile.am        |    7 +
 .../tests/test_linux_format_kmsg.c                 |  179 ++++++++
 8 files changed, 761 insertions(+), 1 deletion(-)
 create mode 100644 modules/linux-kmsg-format/Makefile.am
 create mode 100644 modules/linux-kmsg-format/linux-kmsg-format-plugin.c
 create mode 100644 modules/linux-kmsg-format/linux-kmsg-format.c
 create mode 100644 modules/linux-kmsg-format/linux-kmsg-format.h
 create mode 100644 modules/linux-kmsg-format/tests/Makefile.am
 create mode 100644 modules/linux-kmsg-format/tests/test_linux_format_kmsg.c

diff --git a/configure.in b/configure.in
index 7757a87..91c61ca 100644
--- a/configure.in
+++ b/configure.in
@@ -1140,6 +1140,8 @@ AC_OUTPUT(dist.conf
           modules/confgen/Makefile
           modules/system-source/Makefile
           modules/syslogformat/Makefile
+          modules/linux-kmsg-format/Makefile
+          modules/linux-kmsg-format/tests/Makefile
           modules/pacctformat/Makefile
           modules/basicfuncs/Makefile
           modules/basicfuncs/tests/Makefile
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 8f9d63b..ddddb66 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -1 +1 @@
-SUBDIRS = afsocket afsql afstreams affile afprog afuser afmongodb afsmtp csvparser confgen system-source syslogformat pacctformat basicfuncs cryptofuncs dbparser json
+SUBDIRS = afsocket afsql afstreams affile afprog afuser afmongodb afsmtp csvparser confgen system-source syslogformat linux-kmsg-format pacctformat basicfuncs cryptofuncs dbparser json
diff --git a/modules/linux-kmsg-format/Makefile.am b/modules/linux-kmsg-format/Makefile.am
new file mode 100644
index 0000000..8f18801
--- /dev/null
+++ b/modules/linux-kmsg-format/Makefile.am
@@ -0,0 +1,15 @@
+SUBDIRS = . tests
+
+moduledir = @moduledir@
+AM_CPPFLAGS = -I$(top_srcdir)/lib -I../../lib
+export top_srcdir
+
+module_LTLIBRARIES := liblinux-kmsg-format.la
+liblinux_kmsg_format_la_SOURCES = \
+	linux-kmsg-format.c linux-kmsg-format.h linux-kmsg-format-plugin.c
+
+liblinux_kmsg_format_la_CPPFLAGS = $(AM_CPPFLAGS)
+liblinux_kmsg_format_la_LIBADD = $(MODULE_DEPS_LIBS)
+liblinux_kmsg_format_la_LDFLAGS = $(MODULE_LDFLAGS)
+
+include $(top_srcdir)/build/lex-rules.am
diff --git a/modules/linux-kmsg-format/linux-kmsg-format-plugin.c b/modules/linux-kmsg-format/linux-kmsg-format-plugin.c
new file mode 100644
index 0000000..bf91394
--- /dev/null
+++ b/modules/linux-kmsg-format/linux-kmsg-format-plugin.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012 BalaBit IT Ltd, Budapest, Hungary
+ * Copyright (c) 2012 Gergely Nagy <algernon at balabit.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "linux-kmsg-format.h"
+#include "messages.h"
+#include "plugin.h"
+
+static MsgFormatHandler linux_kmsg_handler =
+{
+  .parse = &linux_kmsg_format_handler
+};
+
+static MsgFormatHandler *
+linux_kmsg_format_construct(Plugin *self, GlobalConfig *cfg, gint plugin_type, const gchar *plugin_name)
+{
+  return &linux_kmsg_handler;
+}
+
+static Plugin linux_kmsg_format_plugin =
+{
+  .type = LL_CONTEXT_FORMAT,
+  .name = "linux-kmsg",
+  .construct = (gpointer (*)(Plugin *self, GlobalConfig *cfg, gint plugin_type, const gchar *plugin_name)) linux_kmsg_format_construct,
+};
+
+gboolean
+linux_kmsg_format_module_init(GlobalConfig *cfg, CfgArgs *args)
+{
+  linux_msg_format_init();
+  plugin_register(cfg, &linux_kmsg_format_plugin, 1);
+  return TRUE;
+}
+
+const ModuleInfo module_info =
+{
+  .canonical_name = "linux-kmsg-format",
+  .version = VERSION,
+  .description = "The linux-kmsg-format module provides support for parsing linux 3.5+ /dev/kmsg-format messages.",
+  .core_revision = SOURCE_REVISION,
+  .plugins = &linux_kmsg_format_plugin,
+  .plugins_len = 1,
+};
diff --git a/modules/linux-kmsg-format/linux-kmsg-format.c b/modules/linux-kmsg-format/linux-kmsg-format.c
new file mode 100644
index 0000000..db1658c
--- /dev/null
+++ b/modules/linux-kmsg-format/linux-kmsg-format.c
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) 2012 BalaBit IT Ltd, Budapest, Hungary
+ * Copyright (c) 2012 Gergely Nagy <algernon at balabit.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "linux-kmsg-format.h"
+#include "logmsg.h"
+#include "messages.h"
+#include "timeutils.h"
+#include "misc.h"
+#include "cfg.h"
+#include "str-format.h"
+#include "scratch-buffers.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <fcntl.h>
+
+static NVHandle KMSG_LM_V_DEV_TYPE;
+static NVHandle KMSG_LM_V_DEV_MINOR;
+static NVHandle KMSG_LM_V_DEV_MAJOR;
+static NVHandle KMSG_LM_V_DEV_NAME;
+static NVHandle KMSG_LM_V_NETDEV_INDEX;
+static struct timeval boot_time;
+
+/*
+ * The linux (3.5+) /dev/kmsg format looks like this:
+ *
+ * 6,802,65338577;ATL1E 0000:02:00.0: eth0: NIC Link is Up <100 Mbps Full Duplex>
+ *  SUBSYSTEM=pci
+ *  DEVICE=+pci:0000:02:00.0
+ *
+ * Where the first number is the syslog priority & facility, the
+ * second is a 64-bit sequence number, the third is a monotonic
+ * timestamp (where 0 is the boot time) in microseconds.
+ *
+ * Following the timestamp, there may be other items in the future,
+ * all comma-separated - these should be gracefully ignored.
+ *
+ * The message itself starts after the first ';', and lasts until the
+ * first newline.
+ *
+ * After the first newline, we have key-value pairs, each one line,
+ * starting with whitespace, the first '=' is the divisor.
+ *
+ * The "DEVICE" key can be further processed, its value is any of the
+ * following formats:
+ *
+ *  - b1:2 - a block dev_t, with major:minor
+ *  - c1:2 - a char dev_t, with major:minor
+ *  - n1 - netdev with device index
+ *  - +subsystem:device
+ *
+ * This implementation parses each of those, and also gracefully
+ * handles unknown formats.
+ */
+
+static guint64
+kmsg_timeval_diff(struct timeval *t1, struct timeval *t2)
+{
+  return (t1->tv_sec - t2->tv_sec) * G_USEC_PER_SEC + (t1->tv_usec - t2->tv_usec);
+}
+
+static void
+kmsg_to_absolute_time(guint64 timestamp, LogStamp *dest)
+{
+  guint64 t;
+
+  t = (boot_time.tv_sec + (timestamp / G_USEC_PER_SEC)) * G_USEC_PER_SEC +
+    boot_time.tv_usec + (timestamp % G_USEC_PER_SEC);
+
+  dest->tv_sec = t / G_USEC_PER_SEC;
+  dest->tv_usec = t % G_USEC_PER_SEC;
+}
+
+static gsize
+kmsg_parse_prio(const guchar *data, gsize pos, gsize length, LogMessage *msg)
+{
+  gint pri = 0;
+
+  while (pos < length && data[pos] != ',')
+    {
+      if (isdigit(data[pos]))
+        pri = pri * 10 + ((data[pos]) - '0');
+      else
+        return -1;
+      pos++;
+    }
+  if (data[pos] != ',' || pos == length)
+    return -1;
+
+  msg->pri = pri;
+  return pos;
+}
+
+static gsize
+kmsg_parse_seq(const guchar *data, gsize pos, gsize length, LogMessage *msg)
+{
+  gsize start = pos;
+
+  while (pos < length && data[pos] != ',')
+    {
+      if (!isdigit(data[pos]))
+        return -1;
+      pos++;
+    }
+  if (data[pos] != ',' || pos == length)
+    return -1;
+
+  log_msg_set_value(msg, LM_V_MSGID, (const gchar *)data + start, pos - start);
+  return pos;
+}
+
+static gsize
+kmsg_parse_timestamp(const guchar *data, gsize pos, gsize length, LogMessage *msg)
+{
+  guint64 timestamp = 0;
+
+  while (pos < length && data[pos] != ',' && data[pos] != ';')
+    {
+      if (isdigit(data[pos]))
+        timestamp = timestamp * 10 + ((data[pos]) - '0');
+      else
+        return -1;
+      pos++;
+    }
+  if ((data[pos] != ',' && data[pos] != ';') || pos == length)
+    return -1;
+
+  kmsg_to_absolute_time(timestamp, &msg->timestamps[LM_TS_STAMP]);
+  msg->timestamps[LM_TS_STAMP].zone_offset =
+    get_local_timezone_ofs(msg->timestamps[LM_TS_STAMP].tv_sec);
+
+  return pos;
+}
+
+static gsize
+kmsg_skip_to_message(const guchar *data, gsize pos, gsize length)
+{
+  while (pos < length && data[pos] != ';')
+    pos++;
+
+  if (data[pos] != ';' || pos == length)
+    return -1;
+  return pos;
+}
+
+static gsize
+kmsg_parse_message(const guchar *data, gsize pos, gsize length, LogMessage *msg)
+{
+  gsize start = pos;
+
+  while (pos < length && data[pos] != '\n')
+    pos++;
+  if (data[pos] != '\n')
+    return -1;
+
+  log_msg_set_value(msg, LM_V_MESSAGE, (const gchar *)data + start,
+                    pos - start);
+  return pos;
+}
+
+static gboolean
+kmsg_is_key_value_pair_device(const guchar *name, gsize length)
+{
+  if (strncmp((const char *)name, "DEVICE=", length + 1) == 0)
+    return TRUE;
+  return FALSE;
+}
+
+static void
+kmsg_parse_device_dev_t(const gchar *type,
+                        const guchar *value, gsize length,
+                        LogMessage *msg)
+{
+  gsize seppos = 0;
+
+  log_msg_set_value(msg, KMSG_LM_V_DEV_TYPE, type, -1);
+
+  while (seppos < length && value[seppos] != ':')
+    seppos++;
+
+  log_msg_set_value(msg, KMSG_LM_V_DEV_MAJOR,
+                    (const gchar *)value, seppos);
+  log_msg_set_value(msg, KMSG_LM_V_DEV_MINOR,
+                    (const gchar *)value + seppos + 1, length - seppos - 1);
+}
+
+static void
+kmsg_parse_device_netdev(const guchar *value, gsize length,
+                         LogMessage *msg)
+{
+  log_msg_set_value(msg, KMSG_LM_V_DEV_TYPE, "netdev", -1);
+  log_msg_set_value(msg, KMSG_LM_V_NETDEV_INDEX,
+                    (const gchar *)value, length);
+}
+
+static void
+kmsg_parse_device_subsys(const guchar *value, gsize length,
+                         LogMessage *msg)
+{
+  gsize seppos = 0;
+
+  while (seppos < length && value[seppos] != ':')
+    seppos++;
+
+  log_msg_set_value(msg, KMSG_LM_V_DEV_TYPE,
+                    (const gchar *)value, seppos);
+  log_msg_set_value(msg, KMSG_LM_V_DEV_NAME,
+                    (const gchar *)value + seppos + 1, length - seppos - 1);
+}
+
+static void
+kmsg_parse_device_unknown(const guchar *value, gsize length,
+                          LogMessage *msg)
+{
+  log_msg_set_value(msg, KMSG_LM_V_DEV_TYPE, "<unknown>", -1);
+  log_msg_set_value(msg, KMSG_LM_V_DEV_NAME,
+                    (const gchar *)value, length);
+
+}
+
+static void
+kmsg_parse_device_key_value_pair(const guchar *value, gsize length,
+                                 LogMessage *msg)
+{
+  switch (value[0])
+    {
+    case 'b':
+      kmsg_parse_device_dev_t("block", value + 1, length - 1, msg);
+      break;
+    case 'c':
+      kmsg_parse_device_dev_t("char", value + 1, length - 1, msg);
+      break;
+    case 'n':
+      kmsg_parse_device_netdev(value + 1, length - 1, msg);
+      break;
+    case '+':
+      kmsg_parse_device_subsys(value + 1, length - 1, msg);
+      break;
+    default:
+      kmsg_parse_device_unknown(value, length, msg);
+      break;
+    }
+}
+
+static gsize
+kmsg_parse_key_value_pair(const guchar *data, gsize pos, gsize length,
+                          LogMessage *msg)
+{
+  gsize name_start, name_len, value_start, value_len;
+  ScratchBuffer *name;
+
+  while (pos < length && (data[pos] == ' ' || data[pos] == '\t'))
+    pos++;
+  if (pos == length)
+    return -1;
+  name_start = pos;
+
+  while (pos < length && data[pos] != '=')
+    pos++;
+  if (pos == length)
+    return -1;
+  name_len = pos - name_start;
+  value_start = ++pos;
+
+  while (pos < length && data[pos] != '\n')
+    pos++;
+  if (data[pos] != '\n')
+    return -1;
+  value_len = pos - value_start;
+
+  if (kmsg_is_key_value_pair_device(data + name_start, name_len))
+    {
+      kmsg_parse_device_key_value_pair(data + value_start, value_len,
+                                       msg);
+      return pos;
+    }
+
+  name = scratch_buffer_acquire();
+
+  g_string_assign(sb_string(name), ".kernel.");
+  g_string_append_len(sb_string(name), (const gchar *)data + name_start, name_len);
+
+  log_msg_set_value(msg,
+                    log_msg_get_value_handle(sb_string(name)->str),
+                    (const gchar *)data + value_start, value_len);
+  scratch_buffer_release(name);
+
+  return pos;
+}
+
+static gboolean
+log_msg_parse_kmsg(LogMessage *msg, const guchar *data, gsize length)
+{
+  gsize pos = 0;
+
+  if ((pos = kmsg_parse_prio(data, pos, length, msg)) == -1)
+    return FALSE;
+
+  if ((pos = kmsg_parse_seq(data, pos + 1, length, msg)) == -1)
+    return FALSE;
+
+  if ((pos = kmsg_parse_timestamp(data, pos + 1, length, msg)) == -1)
+    return FALSE;
+
+  if ((pos = kmsg_skip_to_message(data, pos, length)) == -1)
+    return FALSE;
+
+  if ((pos = kmsg_parse_message(data, pos + 1, length, msg)) == -1)
+    return FALSE;
+
+  if (pos + 1 >= length)
+    return TRUE;
+
+  do
+    {
+      if ((pos = kmsg_parse_key_value_pair(data, pos + 1, length, msg)) == -1)
+        return FALSE;
+    }
+  while (pos < length);
+
+  return TRUE;
+}
+
+void
+linux_kmsg_format_handler(MsgFormatOptions *parse_options,
+                          const guchar *data, gsize length,
+                          LogMessage *self)
+{
+  gboolean success;
+
+  while (length > 0 && (data[length - 1] == '\n' || data[length - 1] == '\0'))
+    length--;
+
+  if (parse_options->flags & LP_NOPARSE)
+    {
+      log_msg_set_value(self, LM_V_MESSAGE, (gchar *) data, length);
+      self->pri = parse_options->default_pri;
+      return;
+    }
+
+  self->flags |= LF_UTF8;
+
+  if (parse_options->flags & LP_LOCAL)
+    self->flags |= LF_LOCAL;
+
+  self->initial_parse = TRUE;
+
+  success = log_msg_parse_kmsg(self, data, length);
+
+  self->initial_parse = FALSE;
+
+  if (G_UNLIKELY(!success))
+    {
+      gchar buf[2048];
+
+      self->timestamps[LM_TS_STAMP] = self->timestamps[LM_TS_RECVD];
+      if ((self->flags & LF_STATE_OWN_PAYLOAD) && self->payload)
+        nv_table_unref(self->payload);
+      self->flags |= LF_STATE_OWN_PAYLOAD;
+      self->payload = nv_table_new(LM_V_MAX, 16, MAX(length * 2, 256));
+      log_msg_set_value(self, LM_V_HOST, "", 0);
+
+      g_snprintf(buf, sizeof(buf), "Error processing log message: %.*s", (gint) length, data);
+      log_msg_set_value(self, LM_V_MESSAGE, buf, -1);
+      log_msg_set_value(self, LM_V_PROGRAM, "syslog-ng", 9);
+      g_snprintf(buf, sizeof(buf), "%d", (int) getpid());
+      log_msg_set_value(self, LM_V_PID, buf, -1);
+
+      if (self->sdata)
+        {
+          g_free(self->sdata);
+          self->alloc_sdata = self->num_sdata = 0;
+          self->sdata = NULL;
+        }
+      self->pri = LOG_SYSLOG | LOG_ERR;
+      return;
+    }
+}
+
+#ifdef __linux__
+static void
+kmsg_init_boot_time(void)
+{
+  int fd, pos = 0;
+  gchar buf[1024];
+  ssize_t rc;
+  struct timeval curr_time;
+  guint64 tdiff;
+
+  if ((fd = open ("/proc/uptime", O_RDONLY)) == -1)
+    return;
+
+  if ((rc = read (fd, buf, sizeof(buf))) <= 0)
+    {
+      close(fd);
+      return;
+    }
+  close(fd);
+
+  gettimeofday(&curr_time, NULL);
+
+  /* Read the seconds part */
+  while (pos < rc && buf[pos] != '.')
+    {
+      if (isdigit(buf[pos]))
+        boot_time.tv_sec = boot_time.tv_sec * 10 + ((buf[pos]) - '0');
+      else
+        {
+          boot_time.tv_sec = 0;
+          return;
+        }
+      pos++;
+    }
+  pos++;
+
+  /* Then the microsecond part */
+  while (pos < rc && buf[pos] != ' ')
+    {
+      if (isdigit(buf[pos]))
+        boot_time.tv_usec = boot_time.tv_usec * 10 + ((buf[pos]) - '0');
+      else
+        {
+          boot_time.tv_sec = 0;
+          boot_time.tv_usec = 0;
+          return;
+        }
+      pos++;
+    }
+
+  tdiff = kmsg_timeval_diff(&curr_time, &boot_time);
+  boot_time.tv_sec = tdiff / G_USEC_PER_SEC;
+  boot_time.tv_usec = tdiff % G_USEC_PER_SEC;
+}
+#endif
+
+void
+linux_msg_format_init(void)
+{
+  static gboolean initialized = FALSE;
+
+  if (!initialized)
+    {
+      KMSG_LM_V_DEV_TYPE = log_msg_get_value_handle(".kernel.DEVICE.type");
+      KMSG_LM_V_DEV_MINOR = log_msg_get_value_handle(".kernel.DEVICE.minor");
+      KMSG_LM_V_DEV_MAJOR = log_msg_get_value_handle(".kernel.DEVICE.major");
+      KMSG_LM_V_DEV_NAME = log_msg_get_value_handle(".kernel.DEVICE.name");
+      KMSG_LM_V_NETDEV_INDEX = log_msg_get_value_handle(".kernel.DEVICE.index");
+
+#ifdef __linux__
+      kmsg_init_boot_time();
+#endif
+
+      initialized = TRUE;
+    }
+}
diff --git a/modules/linux-kmsg-format/linux-kmsg-format.h b/modules/linux-kmsg-format/linux-kmsg-format.h
new file mode 100644
index 0000000..04ca939
--- /dev/null
+++ b/modules/linux-kmsg-format/linux-kmsg-format.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012 BalaBit IT Ltd, Budapest, Hungary
+ * Copyright (c) 2012 Gergely Nagy <algernon at balabit.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef LINUX_KMSG_FORMAT_H_INCLUDED
+#define LINUX_KMSG_FORMAT_H_INCLUDED
+
+#include "msg-format.h"
+
+void linux_kmsg_format_handler(MsgFormatOptions *parse_options,
+                               const guchar *data, gsize length,
+                               LogMessage *self);
+
+void linux_msg_format_init (void);
+
+#endif
diff --git a/modules/linux-kmsg-format/tests/Makefile.am b/modules/linux-kmsg-format/tests/Makefile.am
new file mode 100644
index 0000000..d045a3e
--- /dev/null
+++ b/modules/linux-kmsg-format/tests/Makefile.am
@@ -0,0 +1,7 @@
+AM_CFLAGS = -I$(top_srcdir)/lib -I../../../lib -I$(top_srcdir)/libtest -I../../../libtest -I$(top_srcdir)/modules/linux-kmsg-format -I..
+
+AM_LDFLAGS = -dlpreopen ../../syslogformat/libsyslogformat.la -dlpreopen ../liblinux-kmsg-format.la
+LDADD = $(top_builddir)/lib/libsyslog-ng.la $(top_builddir)/libtest/libsyslog-ng-test.a @TOOL_DEPS_LIBS@
+
+check_PROGRAMS = test_linux_format_kmsg
+TESTS = $(check_PROGRAMS)
diff --git a/modules/linux-kmsg-format/tests/test_linux_format_kmsg.c b/modules/linux-kmsg-format/tests/test_linux_format_kmsg.c
new file mode 100644
index 0000000..dd282d1
--- /dev/null
+++ b/modules/linux-kmsg-format/tests/test_linux_format_kmsg.c
@@ -0,0 +1,179 @@
+#include "testutils.h"
+#include "msg_parse_lib.h"
+#include "apphook.h"
+#include "plugin.h"
+
+static LogMessage *
+kmsg_parse_message(const gchar *raw_message_str)
+{
+  LogMessage *message;
+  GSockAddr *addr = g_sockaddr_inet_new("10.10.10.10", 1010);
+
+  message = log_msg_new(raw_message_str, strlen(raw_message_str), addr, &parse_options);
+
+  g_sockaddr_unref(addr);
+  return message;
+}
+
+static void
+assert_log_kmsg_value(LogMessage *message, const gchar *key,
+                      const gchar *expected_value)
+{
+  const gchar *actual_value = log_msg_get_value(message,
+                                                log_msg_get_value_handle(key),
+                                                NULL);
+  assert_string(actual_value, expected_value, NULL);
+}
+
+void
+test_kmsg_single_line(void)
+{
+  gchar msg[] = "5,2,0;Linux version 3.5-trunk-amd64 (Debian 3.5.2-1~experimental.1) (debian-kernel at lists.debian.org) (gcc version 4.6.3 (Debian 4.6.3-1) ) #1 SMP Mon Aug 20 04:17:46 UTC 2012\n";
+  LogMessage *parsed_message;
+
+  testcase_begin("Testing single-line /dev/kmsg parsing; msg='%s'", msg);
+
+  parsed_message = kmsg_parse_message(msg);
+
+  assert_guint16(parsed_message->pri, 5, "Unexpected message priority");
+  assert_log_message_value(parsed_message, LM_V_MSGID, "2");
+  msg[sizeof(msg) - 2] = '\0';
+  assert_log_message_value(parsed_message, LM_V_MESSAGE, msg + 6);
+
+  log_msg_unref(parsed_message);
+
+  testcase_end();
+}
+
+void
+test_kmsg_multi_line(void)
+{
+  gchar msg[] = "6,202,98513;pci_root PNP0A08:00: host bridge window [io  0x0000-0x0cf7]\n" \
+    " SUBSYSTEM=acpi\n" \
+    " DEVICE=+acpi:PNP0A08:00\n";
+  LogMessage *parsed_message;
+
+  testcase_begin("Testing multi-line /dev/kmsg parsing; msg='%s'", msg);
+
+  parsed_message = kmsg_parse_message(msg);
+
+  assert_guint16(parsed_message->pri, 6, "Unexpected message priority");
+  assert_log_message_value(parsed_message, LM_V_MSGID, "202");
+  assert_log_message_value(parsed_message, LM_V_MESSAGE, "pci_root PNP0A08:00: host bridge window [io  0x0000-0x0cf7]");
+  assert_log_kmsg_value(parsed_message, ".kernel.SUBSYSTEM", "acpi");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "acpi");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.name", "PNP0A08:00");
+
+  log_msg_unref(parsed_message);
+
+  testcase_end();
+}
+
+void
+test_kmsg_with_extra_fields(void)
+{
+  gchar msg[] = "5,2,0,some extra field,3,4,5;And this is the real message\n";
+  LogMessage *parsed_message;
+
+  testcase_begin("Testing /dev/kmsg parsing, with extra fields; msg='%s'", msg);
+
+  parsed_message = kmsg_parse_message(msg);
+  assert_guint16(parsed_message->pri, 5, "Unexpected message priority");
+  assert_log_message_value(parsed_message, LM_V_MSGID, "2");
+  assert_log_message_value(parsed_message, LM_V_MESSAGE, "And this is the real message");
+
+  log_msg_unref(parsed_message);
+
+  testcase_end();
+}
+
+void
+test_kmsg_device_parsing(void)
+{
+  gchar msg_subsys[] = "6,202,98513;pci_root PNP0A08:00: host bridge window [io  0x0000-0x0cf7]\n" \
+    " SUBSYSTEM=acpi\n" \
+    " DEVICE=+acpi:PNP0A08:00\n";
+  gchar msg_block[] = "6,202,98513;Fake message\n" \
+    " DEVICE=b12:1\n";
+  gchar msg_char[] = "6,202,98513;Fake message\n" \
+    " DEVICE=c3:4\n";
+  gchar msg_netdev[] = "6,202,98513;Fake message\n" \
+    " DEVICE=n8\n";
+  gchar msg_unknown[] = "6,202,98513;Fake message\n" \
+    " DEVICE=w12345\n";
+  LogMessage *parsed_message;
+
+  testcase_begin("Testing /dev/kmsg DEVICE= parsing");
+
+  parsed_message = kmsg_parse_message(msg_subsys);
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "acpi");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.name", "PNP0A08:00");
+  log_msg_unref(parsed_message);
+
+  parsed_message = kmsg_parse_message(msg_block);
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "block");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.major", "12");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.minor", "1");
+  log_msg_unref(parsed_message);
+
+  parsed_message = kmsg_parse_message(msg_char);
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "char");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.major", "3");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.minor", "4");
+  log_msg_unref(parsed_message);
+
+  parsed_message = kmsg_parse_message(msg_netdev);
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "netdev");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.index", "8");
+  log_msg_unref(parsed_message);
+
+  parsed_message = kmsg_parse_message(msg_unknown);
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.type", "<unknown>");
+  assert_log_kmsg_value(parsed_message, ".kernel.DEVICE.name", "w12345");
+  log_msg_unref(parsed_message);
+
+  testcase_end();
+}
+
+void
+init_and_load_kmsgformat_module()
+{
+  Plugin *p;
+
+  configuration = cfg_new(VERSION_VALUE);
+  plugin_load_module("linux-kmsg-format", configuration, NULL);
+  msg_format_options_defaults(&parse_options);
+  msg_format_options_init(&parse_options, configuration);
+
+  parse_options.format = "linux-kmsg";
+  p = plugin_find(configuration, LL_CONTEXT_FORMAT, parse_options.format);
+  parse_options.format_handler = plugin_construct(p, configuration, LL_CONTEXT_FORMAT, parse_options.format);
+}
+
+void
+deinit_kmsgformat_module()
+{
+  if (configuration)
+    cfg_free(configuration);
+  configuration = NULL;
+  parse_options.format = NULL;
+  parse_options.format_handler = NULL;
+}
+
+int
+main(void)
+{
+  app_startup();
+  putenv("TZ=UTC");
+  tzset();
+
+  init_and_load_kmsgformat_module();
+
+  test_kmsg_single_line();
+  test_kmsg_multi_line();
+  test_kmsg_with_extra_fields();
+  test_kmsg_device_parsing();
+
+  deinit_kmsgformat_module();
+  app_shutdown();
+}
-- 
1.7.10.4




More information about the syslog-ng mailing list