Collect and report kernel warnings.

A flex-based daemon collects kernel warnings by parsing /var/log/messages
as lines are added to it.  For every warning the daemon sends a sample
to a sparse UMA histogram using a hash of the warning as the bucket.
Then, if the warning hasn't been seen before, the daemon invokes the
crash collector to upload the warning stack trace.

BUG=chromium:217382
TEST=manually tested (for now), automated test on its way
CQ-DEPEND=Ic8d5773d05d717a275c4a4b5616e0e4c307337b8
CQ-DEPEND=I6a4010acad0ffe20c702bb0fc455e3da7cdf3ac1

Change-Id: I89090e5c2b61ec46b4e740f0895c591728d70e77
Reviewed-on: https://gerrit.chromium.org/gerrit/48277
Commit-Queue: Luigi Semenzato <semenzato@chromium.org>
Reviewed-by: Luigi Semenzato <semenzato@chromium.org>
Tested-by: Luigi Semenzato <semenzato@chromium.org>
diff --git a/crash_reporter/Makefile b/crash_reporter/Makefile
index 648308f..4a0f488 100644
--- a/crash_reporter/Makefile
+++ b/crash_reporter/Makefile
@@ -12,6 +12,7 @@
 CRASH_OBJS = \
 	crash_collector.o \
 	kernel_collector.o \
+	kernel_warning_collector.o \
 	udev_collector.o \
 	unclean_shutdown_collector.o \
 	user_collector.o
@@ -44,6 +45,18 @@
 all: CXX_BINARY(list_proxies)
 
 
+# -- Executable warn_collector --
+
+CC_BINARY(warn_collector): LDLIBS += -lfl -lmetrics
+CC_BINARY(warn_collector): lex.yy.o
+clean: CLEAN(warn_collector)
+all: CC_BINARY(warn_collector)
+lex.yy.c: $(SRC)/warn_collector.l
+	flex $<
+clean: CLEAN(lex.yy.o)
+$(eval $(call add_object_rules,lex.yy.o,CC,c))
+
+
 # -- Unit Tests --
 
 # Uncomment these to just run specific test(s).
@@ -65,8 +78,10 @@
 tests: TEST(CXX_BINARY($(1)))
 endef
 
+CC_BINARY(warn_collector_test): warn_collector_test.o
+
 # If $(TEST_BINS) is not already set, assume any filename matching *_test.cc
 # is a test.
 TEST_BINS ?= $(patsubst $(SRC)/%.cc,%,$(wildcard $(SRC)/*_test.cc))
 $(foreach prog,$(TEST_BINS),$(eval $(call TEST_RULES_template,$(prog))))
-
+tests: TEST(CC_BINARY(warn_collector_test))
diff --git a/crash_reporter/TEST_WARNING b/crash_reporter/TEST_WARNING
new file mode 100644
index 0000000..64ad2e9
--- /dev/null
+++ b/crash_reporter/TEST_WARNING
@@ -0,0 +1,31 @@
+Apr 31 25:25:25 localhost kernel: [117959.226729]  [<ffffffff810e16bf>] do_vfs_ioctl+0x469/0x4b3
+Apr 31 25:25:25 localhost kernel: [117959.226738]  [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
+Apr 31 25:25:25 localhost kernel: [117959.226747]  [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
+Apr 31 25:25:25 localhost kernel: [117959.226756]  [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
+Apr 31 25:25:25 localhost kernel: [117959.226765]  [<ffffffff810d37fe>] ? sys_read+0x43/0x73
+Apr 31 25:25:25 localhost kernel: [117959.226774]  [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
+Apr 31 25:25:25 localhost kernel: [117959.226782] ---[ end trace f16822cad7406cec ]---
+Apr 31 25:25:25 localhost kernel: [117959.231085] ------------[ cut here ]------------
+Apr 31 25:25:25 localhost kernel: [117959.231100] WARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
+Apr 31 25:25:25 localhost kernel: [117959.231113] Hardware name: Link
+Apr 31 25:25:25 localhost kernel: [117959.231117] eDP powered off while attempting aux channel communication.
+Apr 31 25:25:25 localhost kernel: [117959.231240] Pid: 10508, comm: X Tainted: G        WC   3.4.0 #1
+Apr 31 25:25:25 localhost kernel: [117959.231247] Call Trace:
+Apr 31 25:25:25 localhost kernel: [117959.231393]  [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
+Apr 31 25:25:25 localhost kernel: [117959.231402]  [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
+Apr 31 25:25:25 localhost kernel: [117959.231411]  [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
+Apr 31 25:25:25 localhost kernel: [117959.231420]  [<ffffffff810d37fe>] ? sys_read+0x43/0x73
+Apr 31 25:25:25 localhost kernel: [117959.231431]  [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
+Apr 31 25:25:25 localhost kernel: [117959.231439] ---[ end trace f16822cad7406ced ]---
+Apr 31 25:25:25 localhost kernel: [117959.231450] ------------[ cut here ]------------
+Apr 31 25:25:25 localhost kernel: [117959.231458] BARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
+Apr 31 25:25:25 localhost kernel: [117959.231458] ("BARNING" above is intentional)
+Apr 31 25:25:25 localhost kernel: [117959.231471] Hardware name: Link
+Apr 31 25:25:25 localhost kernel: [117959.231475] eDP powered off while attempting aux channel communication.
+Apr 31 25:25:25 localhost kernel: [117959.231482] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat rfcomm i2c_dev ath9k_btcoex snd_hda_codec_hdmi snd_hda_codec_ca0132 mac80211 snd_hda_intel ath9k_common_btcoex snd_hda_codec ath9k_hw_btcoex aesni_intel cryptd snd_hwdep ath snd_pcm aes_x86_64 isl29018(C) memconsole snd_timer snd_page_alloc industrialio(C) cfg80211 rtc_cmos nm10_gpio zram(C) zsmalloc(C) lzo_decompress lzo_compress fuse nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables xt_mark option usb_wwan cdc_ether usbnet ath3k btusb bluetooth uvcvideo videobuf2_core videodev videobuf2_vmalloc videobuf2_memops joydev
+Apr 31 25:25:25 localhost kernel: [117959.231588] Pid: 10508, comm: X Tainted: G        WC   3.4.0 #1
+Apr 31 25:25:25 localhost kernel: [117959.231595] Call Trace:
+Apr 31 25:25:25 localhost kernel: [117959.231601]  [<ffffffff8102a931>] warn_slowpath_common+0x83/0x9c
+Apr 31 25:25:25 localhost kernel: [117959.231610]  [<ffffffff8102a9ed>] warn_slowpath_fmt+0x46/0x48
+Apr 31 25:25:25 localhost kernel: [117959.231620]  [<ffffffff812af495>] intel_dp_check_edp+0x6b/0xb9
+Apr 31 25:25:25 localhost kernel: [117959.231629]  [<ffffffff8102a9ed>] ? warn_slowpath_fmt+
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
index e76c5c8..d298108 100644
--- a/crash_reporter/crash_reporter.cc
+++ b/crash_reporter/crash_reporter.cc
@@ -15,6 +15,7 @@
 #include "base/stringprintf.h"
 #include "chromeos/syslog_logging.h"
 #include "crash-reporter/kernel_collector.h"
+#include "crash-reporter/kernel_warning_collector.h"
 #include "crash-reporter/udev_collector.h"
 #include "crash-reporter/unclean_shutdown_collector.h"
 #include "crash-reporter/user_collector.h"
@@ -30,6 +31,7 @@
 DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
 DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
 DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
+DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
 #pragma GCC diagnostic error "-Wstrict-aliasing"
 
 static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
@@ -47,6 +49,7 @@
   kCrashKindUser = 2,
   kCrashKindKernel = 3,
   kCrashKindUdev = 4,
+  kCrashKindKernelWarning = 5,
   kCrashKindMax
 };
 
@@ -176,6 +179,17 @@
   return 0;
 }
 
+static int HandleKernelWarning(KernelWarningCollector
+                               *kernel_warning_collector) {
+  // Accumulate logs to help in diagnosing failures during collection.
+  chromeos::LogToString(true);
+  bool handled = kernel_warning_collector->Collect();
+  chromeos::LogToString(false);
+  if (!handled)
+    return 1;
+  return 0;
+}
+
 // Interactive/diagnostics mode for generating kernel crash signatures.
 static int GenerateKernelSignature(KernelCollector *kernel_collector) {
   std::string kcrash_contents;
@@ -238,6 +252,9 @@
   UdevCollector udev_collector;
   udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
 
+  KernelWarningCollector kernel_warning_collector;
+  udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+
   if (FLAGS_init) {
     return Initialize(&kernel_collector,
                       &user_collector,
@@ -258,5 +275,9 @@
     return HandleUdevCrash(&udev_collector);
   }
 
+  if (FLAGS_kernel_warning) {
+    return HandleKernelWarning(&kernel_warning_collector);
+  }
+
   return HandleUserCrash(&user_collector);
 }
diff --git a/crash_reporter/kernel_warning_collector.cc b/crash_reporter/kernel_warning_collector.cc
new file mode 100644
index 0000000..b504f49
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/kernel_warning_collector.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+
+namespace {
+const char kExecName[] = "kernel-warning";
+const char kKernelWarningSignatureKey[] = "sig";
+const char kKernelWarningPath[] = "/var/run/kwarn/warning";
+const pid_t kKernelPid = 0;
+const uid_t kRootUid = 0;
+}  // namespace
+
+KernelWarningCollector::KernelWarningCollector() {
+}
+
+KernelWarningCollector::~KernelWarningCollector() {
+}
+
+bool KernelWarningCollector::LoadKernelWarning(std::string *content,
+                                               std::string *hash_string) {
+  FilePath kernel_warning_path(kKernelWarningPath);
+  if (!file_util::ReadFileToString(kernel_warning_path, content)) {
+    LOG(ERROR) << "Could not open " << kKernelWarningPath;
+    return false;
+  }
+  /* Verify that the first line contains an 8-digit hex hash. */
+  *hash_string = content->substr(0, 8);
+  std::vector<uint8> output;
+  if (!base::HexStringToBytes(*hash_string, &output)) {
+    LOG(ERROR) << "Bad hash " << *hash_string << " in " << kKernelWarningPath;
+    return false;
+  }
+  return true;
+}
+
+bool KernelWarningCollector::Collect() {
+  std::string reason = "normal collection";
+  bool feedback = true;
+  if (IsDeveloperImage()) {
+    reason = "always collect from developer builds";
+    feedback = true;
+  } else if (!is_feedback_allowed_function_()) {
+    reason = "no user consent";
+    feedback = false;
+  }
+
+  LOG(INFO) << "Processing kernel warning: " << reason;
+
+  if (!feedback) {
+    return true;
+  }
+
+  std::string kernel_warning;
+  std::string warning_hash;
+  if (!LoadKernelWarning(&kernel_warning, &warning_hash)) {
+    return true;
+  }
+
+  FilePath root_crash_directory;
+  if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory, NULL)) {
+    return true;
+  }
+
+  std::string dump_basename =
+      FormatDumpBasename(kExecName, time(NULL), kKernelPid);
+  FilePath kernel_crash_path = root_crash_directory.Append(
+      StringPrintf("%s.kcrash", dump_basename.c_str()));
+
+  // We must use WriteNewFile instead of file_util::WriteFile as we
+  // do not want to write with root access to a symlink that an attacker
+  // might have created.
+  if (WriteNewFile(kernel_crash_path,
+                   kernel_warning.data(),
+                   kernel_warning.length()) !=
+      static_cast<int>(kernel_warning.length())) {
+    LOG(INFO) << "Failed to write kernel warning to "
+              << kernel_crash_path.value().c_str();
+    return true;
+  }
+
+  AddCrashMetaData(kKernelWarningSignatureKey, warning_hash);
+  WriteCrashMetaData(
+      root_crash_directory.Append(
+          StringPrintf("%s.meta", dump_basename.c_str())),
+    kExecName, kernel_crash_path.value());
+
+  LOG(INFO) << "Stored kernel warning into " << kernel_crash_path.value();
+  return true;
+}
diff --git a/crash_reporter/kernel_warning_collector.h b/crash_reporter/kernel_warning_collector.h
new file mode 100644
index 0000000..2f8c793
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+#define _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <string>
+
+#include "crash-reporter/crash_collector.h"
+
+// Kernel warning collector.
+class KernelWarningCollector : public CrashCollector {
+ public:
+  KernelWarningCollector();
+
+  virtual ~KernelWarningCollector();
+
+  // Collects warning.
+  bool Collect();
+
+ private:
+  friend class KernelWarningCollectorTest;
+  FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
+
+  // Reads the full content of the kernel warn dump and the warning hash.
+  bool LoadKernelWarning(std::string *hash, std::string *content);
+};
+
+#endif  // _CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
diff --git a/crash_reporter/warn_collector.l b/crash_reporter/warn_collector.l
new file mode 100644
index 0000000..d0977dc
--- /dev/null
+++ b/crash_reporter/warn_collector.l
@@ -0,0 +1,300 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This flex program reads /var/log/messages as it grows and saves kernel
+ * warnings to files.  It keeps track of warnings it has seen (based on
+ * file/line only, ignoring differences in the stack trace), and reports only
+ * the first warning of each kind, but maintains a count of all warnings by
+ * using their hashes as buckets in a UMA sparse histogram.  It also invokes
+ * the crash collector, which collects the warnings and prepares them for later
+ * shipment to the crash server.
+ */
+
+%{
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "metrics/c_metrics_library.h"
+
+int WarnStart(void);
+void WarnEnd(void);
+void WarnInput(char *buf, int *result, size_t max_size);
+
+#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
+
+%}
+
+/* Define a few useful regular expressions. */
+
+D               [0-9]
+PREFIX          .*" kernel: [ "*{D}+"."{D}+"]"
+CUT_HERE        {PREFIX}" ------------[ cut here".*
+WARNING         {PREFIX}" WARNING: at "
+END_TRACE       {PREFIX}" ---[ end trace".*
+
+/* Use exclusive start conditions. */
+%x PRE_WARN WARN
+
+%%
+ /* The scanner itself. */
+
+^{CUT_HERE}\n{WARNING}          BEGIN(PRE_WARN);
+.|\n                            /* ignore all other input in state 0 */
+<PRE_WARN>.*\n                  if (WarnStart()) {
+                                  BEGIN(WARN); ECHO;
+                                } else {
+                                  BEGIN(0);
+                                }
+
+ /* Assume the warning ends at the "end trace" line */
+<WARN>^{END_TRACE}\n            ECHO; BEGIN(0); WarnEnd();
+<WARN>^.*\n                     ECHO;
+
+%%
+
+#define HASH_BITMAP_SIZE        (1 << 15)  /* size in bits */
+#define HASH_BITMAP_MASK        (HASH_BITMAP_SIZE - 1)
+
+const char warn_hist_name[] = "Platform.KernelWarningHashes";
+uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
+CMetricsLibrary metrics_library;
+
+const char *prog_name;          /* the name of this program */
+int yyin_fd;                    /* instead of FILE *yyin to avoid buffering */
+int i_fd;                       /* for inotify, to detect file changes */
+int testing;                    /* 1 if running test */
+int filter;                     /* 1 when using as filter (for development) */
+int fifo;                       /* 1 when reading from fifo (for devel) */
+int draining;                   /* 1 when draining renamed log file */
+
+const char *msg_path = "/var/log/messages";
+const char warn_dump_dir[]  = "/var/run/kwarn";
+const char *warn_dump_path = "/var/run/kwarn/warning";
+const char *crash_reporter_command;
+
+static void Die(const char *format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  fprintf(stderr, "%s: ", prog_name);
+  vfprintf(stderr, format, ap);
+  exit(1);
+}
+
+static void RunCrashReporter(void) {
+  int status = system(crash_reporter_command);
+  if (status != 0)
+    Die("%s exited with status %d\n", crash_reporter_command, status);
+}
+
+static uint32_t StringHash(const char *string) {
+  uint32_t hash = 0;
+  while (*string != '\0') {
+    hash = (hash << 5) + hash + *string++;
+  }
+  return hash;
+}
+
+/* We expect only a handful of different warnings per boot session, so the
+ * probability of a collision is very low, and statistically it won't matter
+ * (unless warnings with the same hash also happens in tandem, which is even
+ * rarer).
+ */
+static int HashSeen(uint32_t hash) {
+  int word_index = (hash & HASH_BITMAP_MASK) / 32;
+  int bit_index = (hash & HASH_BITMAP_MASK) % 32;
+  return hash_bitmap[word_index] & 1 << bit_index;
+}
+
+static void SetHashSeen(uint32_t hash) {
+  int word_index = (hash & HASH_BITMAP_MASK) / 32;
+  int bit_index = (hash & HASH_BITMAP_MASK) % 32;
+  hash_bitmap[word_index] |= 1 << bit_index;
+}
+
+int WarnStart(void) {
+  uint32_t hash;
+
+  if (filter)
+    return 1;
+
+  hash = StringHash(yytext);
+  if (!(testing || fifo || filter)) {
+    CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
+  }
+  if (HashSeen(hash))
+    return 0;
+  SetHashSeen(hash);
+
+  yyout = fopen(warn_dump_path, "w");
+  if (yyout == NULL)
+    Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
+  fprintf(yyout, "%08x\n", hash);
+  return 1;
+}
+
+void WarnEnd(void) {
+  if (filter)
+    return;
+  fclose(yyout);
+  yyout = stdout;               /* for debugging */
+  RunCrashReporter();
+}
+
+static void WarnOpenInput(const char *path) {
+  yyin_fd = open(path, O_RDONLY);
+  if (yyin_fd < 0)
+    Die("could not open %s: %s\n", path, strerror(errno));
+  if (!fifo) {
+    /* Set up notification of file growth and rename. */
+    i_fd = inotify_init();
+    if (i_fd < 0)
+      Die("inotify_init: %s\n", strerror(errno));
+    if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
+      Die("inotify_add_watch: %s\n", strerror(errno));
+  }
+}
+
+/* We replace the default YY_INPUT() for the following reasons:
+ *
+ * 1.  We want to read data as soon as it becomes available, but the default
+ * YY_INPUT() uses buffered I/O.
+ *
+ * 2.  We want to block on end of input and wait for the file to grow.
+ *
+ * 3.  We want to detect log rotation, and reopen the input file as needed.
+ */
+void WarnInput(char *buf, int *result, size_t max_size) {
+  while (1) {
+    *result = read(yyin_fd, buf, max_size);
+    if (*result < 0)
+      Die("read: %s", strerror(errno));
+    if (*result > 0 || fifo || filter)
+      return;
+    if (draining) {
+      /* Assume we're done with this log, and move to next
+       * log.  Rsyslogd may keep writing to the old log file
+       * for a while, but we don't care since we don't have
+       * to be exact.
+       */
+      close(yyin_fd);
+      if (YYSTATE == WARN) {
+        /* Be conservative in case we lose the warn
+         * terminator during the switch---or we may
+         * collect personally identifiable information.
+         */
+        WarnEnd();
+      }
+      BEGIN(0);        /* see above comment */
+      sleep(1);        /* avoid race with log rotator */
+      WarnOpenInput(msg_path);
+      draining = 0;
+      continue;
+    }
+    /* Nothing left to read, so we must wait. */
+    struct inotify_event event;
+    int n = read(i_fd, &event, sizeof(event));
+    if (n <= 0)
+      Die("inotify: %s\n", strerror(errno));
+    if (event.mask & IN_MOVE_SELF) {
+      /* The file has been renamed.  Before switching
+       * to the new one, we process any remaining
+       * content of this file.
+       */
+      draining = 1;
+    }
+  }
+}
+
+int main(int argc, char **argv) {
+  int result;
+  struct passwd *user;
+  prog_name = argv[0];
+
+  if (argc == 2 && strcmp(argv[1], "--test") == 0)
+    testing = 1;
+  else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
+    filter = 1;
+  else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
+    fifo = 1;
+  } else if (argc != 1) {
+    fprintf(stderr,
+            "usage: %s [single-flag]\n"
+            "flags (for testing only):\n"
+            "--fifo\tinput is fifo \"fifo\", output is stdout\n"
+            "--filter\tinput is stdin, output is stdout\n"
+            "--test\trun self-test\n",
+            prog_name);
+    exit(1);
+  }
+
+  metrics_library = CMetricsLibraryNew();
+  CMetricsLibraryInit(metrics_library);
+
+  crash_reporter_command = testing ?
+    "./warn_collector_test_reporter.sh" :
+    "/sbin/crash_reporter --kernel_warning";
+
+  /* When filtering with --filter (for development) use stdin for input.
+   * Otherwise read input from a file or a fifo.
+   */
+  yyin_fd = fileno(stdin);
+  if (testing) {
+    msg_path = "messages";
+    warn_dump_path = "warning";
+  }
+  if (fifo) {
+    msg_path = "fifo";
+  }
+  if (!filter) {
+    WarnOpenInput(msg_path);
+  }
+
+  /* Create directory for dump file.  Still need to be root here. */
+  unlink(warn_dump_path);
+  if (!testing && !fifo && !filter) {
+    rmdir(warn_dump_dir);
+    result = mkdir(warn_dump_dir, 0755);
+    if (result < 0)
+      Die("could not create %s: %s\n",
+          warn_dump_dir, strerror(errno));
+  }
+
+  if (0) {
+    /* TODO(semenzato): put this back in once we decide it's safe
+     * to make /var/spool/crash rwxrwxrwx root, or use a different
+     * owner and setuid for the crash reporter as well.
+     */
+
+    /* Get low privilege uid, gid. */
+    user = getpwnam("chronos");
+    if (user == NULL)
+      Die("getpwnam failed\n");
+
+    /* Change dump directory ownership. */
+    if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
+      Die("chown: %s\n", strerror(errno));
+
+    /* Drop privileges. */
+    if (setuid(user->pw_uid) < 0) {
+      Die("setuid: %s\n", strerror(errno));
+    }
+  }
+
+  /* Go! */
+  return yylex();
+}
+
+/* Flex should really know not to generate these functions.
+ */
+void UnusedFunctionWarningSuppressor(void) {
+  yyunput(0, 0);
+  (void) input();
+}
diff --git a/crash_reporter/warn_collector_test.c b/crash_reporter/warn_collector_test.c
new file mode 100644
index 0000000..67f3455
--- /dev/null
+++ b/crash_reporter/warn_collector_test.c
@@ -0,0 +1,14 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Test driver for the warn_collector daemon.
+ */
+#include <stdlib.h>
+
+int main(int ac, char **av)
+{
+  return system("$SRC/warn_collector_test.sh");
+}
diff --git a/crash_reporter/warn_collector_test.sh b/crash_reporter/warn_collector_test.sh
new file mode 100755
index 0000000..e1f8a29
--- /dev/null
+++ b/crash_reporter/warn_collector_test.sh
@@ -0,0 +1,73 @@
+#! /bin/bash
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Test for warn_collector.  Run the warn collector in the background, emulate
+# the kernel by appending lines to the log file "messages", and observe the log
+# of the (fake) crash reporter each time is run by the warn collector daemon.
+
+set -e
+
+SYSROOT=/build/link
+LD_LIBRARY_PATH=$SYSROOT/lib64:$SYSROOT/usr/lib64
+PATH=.:$SYSROOT/bin:$SYSROOT/usr/bin:$PATH
+TESTLOG=warn-test-log
+
+trap cleanup EXIT
+
+cleanup() {
+  # Kill daemon (if started) on exit
+  kill %
+}
+
+check_log() {
+  n_expected=$1
+  if [ ! -f $TESTLOG ]; then
+    echo $TESTLOG was not created
+    exit 1
+  fi
+  if [ "$(wc -l < $TESTLOG)" -ne $n_expected ]; then
+    echo expected $n_expected lines in $TESTLOG, found this:
+    cat $TESTLOG
+    exit 1
+  fi
+  if egrep -qv '^[0-9a-f]{8}$' $TESTLOG; then
+    echo found bad lines in $TESTLOG:
+    cat $TESTLOG
+    exit 1
+  fi
+}
+
+rm -f warn-test-log
+cp $SRC/warn_collector_test_reporter.sh .
+cp $SRC/TEST_WARNING .
+cp TEST_WARNING messages
+
+# Start the collector daemon.  With the --test option, the daemon reads input
+# from ./messages, writes the warning into ./warning, and invokes
+# ./warn_collector_test_reporter.sh to report the warning.
+warn_collector --test &
+
+# After a while, check that the first warning has been collected.
+sleep 1
+check_log 1
+
+# Add the same warning to messages, verify that it is NOT collected
+cat TEST_WARNING >> messages
+sleep 1
+check_log 1
+
+# Add a slightly different warning to messages, check that it is collected.
+sed s/intel_dp.c/intel_xx.c/ < TEST_WARNING >> messages
+sleep 1
+check_log 2
+
+# Emulate log rotation, add a warning, and check.
+mv messages messages.1
+sed s/intel_dp.c/intel_xy.c/ < TEST_WARNING > messages
+sleep 2
+check_log 3
+
+# Success!
+exit 0
diff --git a/crash_reporter/warn_collector_test_reporter.sh b/crash_reporter/warn_collector_test_reporter.sh
new file mode 100755
index 0000000..d8f3fad
--- /dev/null
+++ b/crash_reporter/warn_collector_test_reporter.sh
@@ -0,0 +1,16 @@
+#! /bin/sh
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Replacement for the crash reporter, for testing.  Log the first line of the
+# "warning" file, which by convention contains the warning hash, and remove the
+# file.
+
+set -e
+
+exec 1>> warn-test-log
+exec 2>> warn-test-log
+
+head -1 warning
+rm warning