Merge branch 'rewrite-crash_reporter' into merge-crash_reporter

Initial import of crash_reporter from platform/system/core

BUG: 29548040
diff --git a/.project_alias b/.project_alias
new file mode 100644
index 0000000..0bc3798
--- /dev/null
+++ b/.project_alias
@@ -0,0 +1 @@
+crash
diff --git a/99-crash-reporter.rules b/99-crash-reporter.rules
new file mode 100644
index 0000000..aea5b1c
--- /dev/null
+++ b/99-crash-reporter.rules
@@ -0,0 +1,6 @@
+ACTION=="change", SUBSYSTEM=="drm", KERNEL=="card0", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=KERNEL=card0:SUBSYSTEM=drm:ACTION=change"
+# For detecting cypress trackpad issue. Passing into crash_reporter SUBSYSTEM=i2c-cyapa since crash_reporter does not handle DRIVER string.
+ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="cyapa", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-cyapa:ACTION=change"
+# For detecting Atmel trackpad/touchscreen issue. Passing into crash_reporter SUBSYSTEM=i2c-atmel_mxt_ts since crash_reporter does not handle DRIVER string.
+ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="atmel_mxt_ts", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-atmel_mxt_ts:ACTION=change"
+ACTION=="add", SUBSYSTEM=="devcoredump", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=devcoredump:ACTION=add:KERNEL_NUMBER=%n"
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..ce9dc73
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,147 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+crash_reporter_cpp_extension := .cc
+
+crash_reporter_src := crash_collector.cc \
+    kernel_collector.cc \
+    kernel_warning_collector.cc \
+    unclean_shutdown_collector.cc \
+    user_collector.cc
+
+crash_reporter_includes := external/gtest/include
+
+crash_reporter_test_src := crash_collector_test.cc \
+    crash_reporter_logs_test.cc \
+    kernel_collector_test.cc \
+    testrunner.cc \
+    unclean_shutdown_collector_test.cc \
+    user_collector_test.cc
+
+warn_collector_src := warn_collector.l
+
+# Crash reporter static library.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libcrash
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_C_INCLUDES := $(crash_reporter_includes)
+LOCAL_SHARED_LIBRARIES := libchrome \
+    libbinder \
+    libbrillo \
+    libcutils \
+    libmetrics \
+    libpcrecpp
+LOCAL_STATIC_LIBRARIES := libmetricscollectorservice
+LOCAL_SRC_FILES := $(crash_reporter_src)
+include $(BUILD_STATIC_LIBRARY)
+
+# Crash reporter client.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_C_INCLUDES := $(crash_reporter_includes)
+LOCAL_REQUIRED_MODULES := core2md \
+    crash_reporter_logs.conf \
+    crash_sender \
+    crash_server
+LOCAL_INIT_RC := crash_reporter.rc
+LOCAL_SHARED_LIBRARIES := libchrome \
+    libbinder \
+    libbrillo \
+    libcutils \
+    libmetrics \
+    libpcrecpp \
+    libutils
+LOCAL_SRC_FILES := crash_reporter.cc
+LOCAL_STATIC_LIBRARIES := libcrash \
+    libmetricscollectorservice
+include $(BUILD_EXECUTABLE)
+
+# Crash sender script.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_sender
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
+LOCAL_REQUIRED_MODULES := curl grep periodic_scheduler
+LOCAL_SRC_FILES := crash_sender
+include $(BUILD_PREBUILT)
+
+# Warn collector client.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := warn_collector
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_SHARED_LIBRARIES := libmetrics
+LOCAL_SRC_FILES := $(warn_collector_src)
+include $(BUILD_EXECUTABLE)
+
+# /etc/os-release.d/crash_server configuration file.
+# ========================================================
+ifdef OSRELEASED_DIRECTORY
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_server
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
+include $(BUILD_SYSTEM)/base_rules.mk
+
+# Optionally populate the BRILLO_CRASH_SERVER variable from a product
+# configuration file: brillo/crash_server.
+LOADED_BRILLO_CRASH_SERVER := $(call cfgtree-get-if-exists,brillo/crash_server)
+
+# If the crash server isn't set, use a blank value.  crash_sender
+# will log it as a configuration error.
+$(LOCAL_BUILT_MODULE): BRILLO_CRASH_SERVER ?= "$(LOADED_BRILLO_CRASH_SERVER)"
+$(LOCAL_BUILT_MODULE):
+	$(hide)mkdir -p $(dir $@)
+	echo $(BRILLO_CRASH_SERVER) > $@
+endif
+
+# Crash reporter logs conf file.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter_logs.conf
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/etc
+LOCAL_SRC_FILES := crash_reporter_logs.conf
+include $(BUILD_PREBUILT)
+
+# Periodic Scheduler.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := periodic_scheduler
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
+LOCAL_SRC_FILES := periodic_scheduler
+include $(BUILD_PREBUILT)
+
+# Crash reporter tests.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter_tests
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+ifdef BRILLO
+LOCAL_MODULE_TAGS := eng
+endif
+LOCAL_SHARED_LIBRARIES := libchrome \
+    libbrillo \
+    libcutils \
+    libpcrecpp
+LOCAL_SRC_FILES := $(crash_reporter_test_src)
+LOCAL_STATIC_LIBRARIES := libcrash libgmock
+include $(BUILD_NATIVE_TEST)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..96ea5b2
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+vapier@chromium.org
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9ac0a86
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# crash_reporter
+
+`crash_reporter` is a deamon running on the device that saves the call stack of
+crashing programs. It makes use of the
+[Breakpad](https://chromium.googlesource.com/breakpad/breakpad/) library.
+
+During a build, Breakpad symbol files are generated for all binaries.  They are
+packaged into a zip file when running `m dist`, so that a developer can upload
+them to the crash server.
+
+On a device, if the user has opted in to metrics and crash reporting, a
+Breakpad minidump is generated when an executable crashes, which is then
+uploaded to the crash server.
+
+On the crash server, it compares the minidump's signature to the symbol files
+that the developer has uploaded, and extracts and symbolizes the stack trace
+from the minidump.
+
+## SELinux policies
+
+In order to correctly generate a minidump, `crash_reporter` needs to be given
+the proper SELinux permissions for accessing the domain of the crashing
+executable.  By default, `crash_reporter` has only been given access to a select
+number of system domains, such as `metricsd`, `weave`, and `update_engine`.  If
+a developer wants their executable's crashes to be caught by `crash_reporter`,
+they will have to set their SELinux policies in their .te file to allow
+`crash_reporter` access to their domain.  This can be done through a simple
+[macro](https://android.googlesource.com/device/generic/brillo/+/master/sepolicy/te_macros):
+
+    allow_crash_reporter(domain_name)
+
+Replace *domain_name* with whatever domain is assigned to the executable in
+the `file_contexts` file.
+
+## Configuration
+
+`crash_reporter` has a few different configuration options that have to be set.
+
+- Crashes are only handled and uploaded if analytics reporting is enabled,
+  either via the weave call to set `_metrics.enableAnalyticsReporting` or by
+  manually creating the file `/data/misc/metrics/enabled` (for testing only).
+- The `BRILLO_CRASH_SERVER` make variable should be set in the `product.mk`
+  file to the URL of the crash server.  For Brillo builds, it is set
+  automatically through the product configuration.  Setting this variable will
+  populate the `/etc/os-release.d/crash_server` file on the device, which is
+  read by `crash_sender`.
+- The `BRILLO_PRODUCT_ID` make variable should be set in the `product.mk` file
+  to the product's ID.  For Brillo builds, it is set automatically through the
+  product configuration.  Setting this variable will populate the
+  `/etc/os-release.d/product_id`, which is read by `crash_sender`.
+
+## Uploading crash reports in *eng* builds
+
+By default, crash reports are only uploaded to the server for production
+*user* and *userdebug* images.  In *eng* builds, with crash reporting enabled
+the device will generate minidumps for any crashing executables but will not
+send them to the crash server.  If a developer does want to force an upload,
+they can do so by issuing the command `SECONDS_SEND_SPREAD=5 FORCE_OFFICIAL=1
+crash_sender` from an ADB shell.  This will send the report to the server, with
+the *image_type* field set to *force-official* so that these reports can be
+differentiated from normal reports.
diff --git a/TEST_WARNING b/TEST_WARNING
new file mode 100644
index 0000000..64ad2e9
--- /dev/null
+++ b/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_collector.cc b/crash_collector.cc
new file mode 100644
index 0000000..d993576
--- /dev/null
+++ b/crash_collector.cc
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "crash_collector.h"
+
+#include <dirent.h>
+#include <fcntl.h>  // For file creation modes.
+#include <inttypes.h>
+#include <linux/limits.h>  // PATH_MAX
+#include <pwd.h>  // For struct passwd.
+#include <sys/types.h>  // for mode_t.
+#include <sys/wait.h>  // For waitpid.
+#include <unistd.h>  // For execv and fork.
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/key_value_store.h>
+#include <brillo/osrelease_reader.h>
+#include <brillo/process.h>
+
+namespace {
+
+const char kCollectChromeFile[] =
+    "/mnt/stateful_partition/etc/collect_chrome_crashes";
+const char kCrashTestInProgressPath[] =
+    "/data/misc/crash_reporter/tmp/crash-test-in-progress";
+const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
+const char kDefaultUserName[] = "chronos";
+const char kLeaveCoreFile[] = "/data/misc/crash_reporter/.leave_core";
+const char kShellPath[] = "/system/bin/sh";
+const char kSystemCrashPath[] = "/data/misc/crash_reporter/crash";
+const char kUploadVarPrefix[] = "upload_var_";
+const char kUploadFilePrefix[] = "upload_file_";
+
+// Product information keys in the /etc/os-release.d folder.
+static const char kBdkVersionKey[] = "bdk_version";
+static const char kProductIDKey[] = "product_id";
+static const char kProductVersionKey[] = "product_version";
+
+// Normally this path is not used.  Unfortunately, there are a few edge cases
+// where we need this.  Any process that runs as kDefaultUserName that crashes
+// is consider a "user crash".  That includes the initial Chrome browser that
+// runs the login screen.  If that blows up, there is no logged in user yet,
+// so there is no per-user dir for us to stash things in.  Instead we fallback
+// to this path as it is at least encrypted on a per-system basis.
+//
+// This also comes up when running autotests.  The GUI is sitting at the login
+// screen while tests are sshing in, changing users, and triggering crashes as
+// the user (purposefully).
+const char kFallbackUserCrashPath[] = "/home/chronos/crash";
+
+// Directory mode of the user crash spool directory.
+const mode_t kUserCrashPathMode = 0755;
+
+// Directory mode of the system crash spool directory.
+const mode_t kSystemCrashPathMode = 01755;
+
+const uid_t kRootOwner = 0;
+const uid_t kRootGroup = 0;
+
+}  // namespace
+
+// Maximum crash reports per crash spool directory.  Note that this is
+// a separate maximum from the maximum rate at which we upload these
+// diagnostics.  The higher this rate is, the more space we allow for
+// core files, minidumps, and kcrash logs, and equivalently the more
+// processor and I/O bandwidth we dedicate to handling these crashes when
+// many occur at once.  Also note that if core files are configured to
+// be left on the file system, we stop adding crashes when either the
+// number of core files or minidumps reaches this number.
+const int CrashCollector::kMaxCrashDirectorySize = 32;
+
+using base::FilePath;
+using base::StringPrintf;
+
+CrashCollector::CrashCollector()
+    : log_config_path_(kDefaultLogConfig) {
+}
+
+CrashCollector::~CrashCollector() {
+}
+
+void CrashCollector::Initialize(
+    CrashCollector::CountCrashFunction count_crash_function,
+    CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
+  CHECK(count_crash_function);
+  CHECK(is_feedback_allowed_function);
+
+  count_crash_function_ = count_crash_function;
+  is_feedback_allowed_function_ = is_feedback_allowed_function;
+}
+
+int CrashCollector::WriteNewFile(const FilePath &filename,
+                                 const char *data,
+                                 int size) {
+  int fd = HANDLE_EINTR(open(filename.value().c_str(),
+                             O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
+  if (fd < 0) {
+    return -1;
+  }
+
+  int rv = base::WriteFileDescriptor(fd, data, size) ? size : -1;
+  IGNORE_EINTR(close(fd));
+  return rv;
+}
+
+std::string CrashCollector::Sanitize(const std::string &name) {
+  // Make sure the sanitized name does not include any periods.
+  // The logic in crash_sender relies on this.
+  std::string result = name;
+  for (size_t i = 0; i < name.size(); ++i) {
+    if (!isalnum(result[i]) && result[i] != '_')
+      result[i] = '_';
+  }
+  return result;
+}
+
+std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
+                                               time_t timestamp,
+                                               pid_t pid) {
+  struct tm tm;
+  localtime_r(&timestamp, &tm);
+  std::string sanitized_exec_name = Sanitize(exec_name);
+  return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
+                      sanitized_exec_name.c_str(),
+                      tm.tm_year + 1900,
+                      tm.tm_mon + 1,
+                      tm.tm_mday,
+                      tm.tm_hour,
+                      tm.tm_min,
+                      tm.tm_sec,
+                      pid);
+}
+
+FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
+                                      const std::string &basename,
+                                      const std::string &extension) {
+  return crash_directory.Append(StringPrintf("%s.%s",
+                                             basename.c_str(),
+                                             extension.c_str()));
+}
+
+FilePath CrashCollector::GetCrashDirectoryInfo(
+    mode_t *mode,
+    uid_t *directory_owner,
+    gid_t *directory_group) {
+  *mode = kSystemCrashPathMode;
+  *directory_owner = kRootOwner;
+  *directory_group = kRootGroup;
+  return FilePath(kSystemCrashPath);
+}
+
+bool CrashCollector::GetUserInfoFromName(const std::string &name,
+                                         uid_t *uid,
+                                         gid_t *gid) {
+  char storage[256];
+  struct passwd passwd_storage;
+  struct passwd *passwd_result = nullptr;
+
+  if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
+                 &passwd_result) != 0 || passwd_result == nullptr) {
+    LOG(ERROR) << "Cannot find user named " << name;
+    return false;
+  }
+
+  *uid = passwd_result->pw_uid;
+  *gid = passwd_result->pw_gid;
+  return true;
+}
+
+bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid __unused,
+                                                    FilePath *crash_directory,
+                                                    bool *out_of_capacity) {
+  if (out_of_capacity) *out_of_capacity = false;
+
+  // For testing.
+  if (!forced_crash_directory_.empty()) {
+    *crash_directory = forced_crash_directory_;
+    return true;
+  }
+
+  mode_t directory_mode;
+  uid_t directory_owner;
+  gid_t directory_group;
+  *crash_directory =
+      GetCrashDirectoryInfo(&directory_mode,
+                            &directory_owner,
+                            &directory_group);
+
+  if (!base::PathExists(*crash_directory)) {
+    // Create the spool directory with the appropriate mode (regardless of
+    // umask) and ownership.
+    mode_t old_mask = umask(0);
+    if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
+        chown(crash_directory->value().c_str(),
+              directory_owner,
+              directory_group) < 0) {
+      LOG(ERROR) << "Unable to create appropriate crash directory";
+      return false;
+    }
+    umask(old_mask);
+  }
+
+  if (!base::PathExists(*crash_directory)) {
+    LOG(ERROR) << "Unable to create crash directory "
+               << crash_directory->value().c_str();
+    return false;
+  }
+
+  if (!CheckHasCapacity(*crash_directory)) {
+    if (out_of_capacity) *out_of_capacity = true;
+    LOG(ERROR) << "Directory " << crash_directory->value()
+               << " is out of capacity.";
+    return false;
+  }
+
+  return true;
+}
+
+FilePath CrashCollector::GetProcessPath(pid_t pid) {
+  return FilePath(StringPrintf("/proc/%d", pid));
+}
+
+bool CrashCollector::GetSymlinkTarget(const FilePath &symlink,
+                                      FilePath *target) {
+  ssize_t max_size = 64;
+  std::vector<char> buffer;
+
+  while (true) {
+    buffer.resize(max_size + 1);
+    ssize_t size = readlink(symlink.value().c_str(), buffer.data(), max_size);
+    if (size < 0) {
+      int saved_errno = errno;
+      LOG(ERROR) << "Readlink failed on " << symlink.value() << " with "
+                 << saved_errno;
+      return false;
+    }
+
+    buffer[size] = 0;
+    if (size == max_size) {
+      max_size *= 2;
+      if (max_size > PATH_MAX) {
+        return false;
+      }
+      continue;
+    }
+    break;
+  }
+
+  *target = FilePath(buffer.data());
+  return true;
+}
+
+bool CrashCollector::GetExecutableBaseNameFromPid(pid_t pid,
+                                                 std::string *base_name) {
+  FilePath target;
+  FilePath process_path = GetProcessPath(pid);
+  FilePath exe_path = process_path.Append("exe");
+  if (!GetSymlinkTarget(exe_path, &target)) {
+    LOG(INFO) << "GetSymlinkTarget failed - Path " << process_path.value()
+              << " DirectoryExists: "
+              << base::DirectoryExists(process_path);
+    // Try to further diagnose exe readlink failure cause.
+    struct stat buf;
+    int stat_result = stat(exe_path.value().c_str(), &buf);
+    int saved_errno = errno;
+    if (stat_result < 0) {
+      LOG(INFO) << "stat " << exe_path.value() << " failed: " << stat_result
+                << " " << saved_errno;
+    } else {
+      LOG(INFO) << "stat " << exe_path.value() << " succeeded: st_mode="
+                << buf.st_mode;
+    }
+    return false;
+  }
+  *base_name = target.BaseName().value();
+  return true;
+}
+
+// Return true if the given crash directory has not already reached
+// maximum capacity.
+bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
+  DIR* dir = opendir(crash_directory.value().c_str());
+  if (!dir) {
+    LOG(WARNING) << "Unable to open crash directory "
+                 << crash_directory.value();
+    return false;
+  }
+  struct dirent ent_buf;
+  struct dirent* ent;
+  bool full = false;
+  std::set<std::string> basenames;
+  while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) {
+    if ((strcmp(ent->d_name, ".") == 0) ||
+        (strcmp(ent->d_name, "..") == 0))
+      continue;
+
+    std::string filename(ent->d_name);
+    size_t last_dot = filename.rfind(".");
+    std::string basename;
+    // If there is a valid looking extension, use the base part of the
+    // name.  If the only dot is the first byte (aka a dot file), treat
+    // it as unique to avoid allowing a directory full of dot files
+    // from accumulating.
+    if (last_dot != std::string::npos && last_dot != 0)
+      basename = filename.substr(0, last_dot);
+    else
+      basename = filename;
+    basenames.insert(basename);
+
+    if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
+      LOG(WARNING) << "Crash directory " << crash_directory.value()
+                   << " already full with " << kMaxCrashDirectorySize
+                   << " pending reports";
+      full = true;
+      break;
+    }
+  }
+  closedir(dir);
+  return !full;
+}
+
+bool CrashCollector::GetLogContents(const FilePath &config_path,
+                                    const std::string &exec_name,
+                                    const FilePath &output_file) {
+  brillo::KeyValueStore store;
+  if (!store.Load(config_path)) {
+    LOG(INFO) << "Unable to read log configuration file "
+              << config_path.value();
+    return false;
+  }
+
+  std::string command;
+  if (!store.GetString(exec_name, &command))
+    return false;
+
+  brillo::ProcessImpl diag_process;
+  diag_process.AddArg(kShellPath);
+  diag_process.AddStringOption("-c", command);
+  diag_process.RedirectOutput(output_file.value());
+
+  const int result = diag_process.Run();
+  if (result != 0) {
+    LOG(INFO) << "Log command \"" << command << "\" exited with " << result;
+    return false;
+  }
+  return true;
+}
+
+void CrashCollector::AddCrashMetaData(const std::string &key,
+                                      const std::string &value) {
+  extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
+}
+
+void CrashCollector::AddCrashMetaUploadFile(const std::string &key,
+                                            const std::string &path) {
+  if (!path.empty())
+    AddCrashMetaData(kUploadFilePrefix + key, path);
+}
+
+void CrashCollector::AddCrashMetaUploadData(const std::string &key,
+                                            const std::string &value) {
+  if (!value.empty())
+    AddCrashMetaData(kUploadVarPrefix + key, value);
+}
+
+void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
+                                        const std::string &exec_name,
+                                        const std::string &payload_path) {
+  int64_t payload_size = -1;
+  base::GetFileSize(FilePath(payload_path), &payload_size);
+
+  brillo::OsReleaseReader reader;
+  if (!forced_osreleased_directory_.empty()) {
+    reader.LoadTestingOnly(forced_osreleased_directory_);
+  } else {
+    reader.Load();
+  }
+  std::string bdk_version = "undefined";
+  std::string product_id = "undefined";
+  std::string product_version = "undefined";
+
+  if (!reader.GetString(kBdkVersionKey, &bdk_version)) {
+    LOG(ERROR) << "Could not read " << kBdkVersionKey
+               << " from /etc/os-release.d/";
+  }
+
+  if (!reader.GetString(kProductIDKey, &product_id)) {
+    LOG(ERROR) << "Could not read " << kProductIDKey
+               << " from /etc/os-release.d/";
+  }
+
+  if (!reader.GetString(kProductVersionKey, &product_version)) {
+    LOG(ERROR) << "Could not read " << kProductVersionKey
+               << " from /etc/os-release.d/";
+  }
+
+  std::string meta_data = StringPrintf("%sexec_name=%s\n"
+                                       "payload=%s\n"
+                                       "payload_size=%" PRId64 "\n"
+                                       "%s=%s\n"
+                                       "%s=%s\n"
+                                       "%s=%s\n"
+                                       "done=1\n",
+                                       extra_metadata_.c_str(),
+                                       exec_name.c_str(),
+                                       payload_path.c_str(),
+                                       payload_size,
+                                       kBdkVersionKey,
+                                       bdk_version.c_str(),
+                                       kProductIDKey,
+                                       product_id.c_str(),
+                                       kProductVersionKey,
+                                       product_version.c_str());
+  // We must use WriteNewFile instead of base::WriteFile as we
+  // do not want to write with root access to a symlink that an attacker
+  // might have created.
+  if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
+    LOG(ERROR) << "Unable to write " << meta_path.value();
+  }
+}
+
+bool CrashCollector::IsCrashTestInProgress() {
+  return base::PathExists(FilePath(kCrashTestInProgressPath));
+}
+
+bool CrashCollector::IsDeveloperImage() {
+  // If we're testing crash reporter itself, we don't want to special-case
+  // for developer images.
+  if (IsCrashTestInProgress())
+    return false;
+  return base::PathExists(FilePath(kLeaveCoreFile));
+}
diff --git a/crash_collector.h b/crash_collector.h
new file mode 100644
index 0000000..21b9198
--- /dev/null
+++ b/crash_collector.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_CRASH_COLLECTOR_H_
+#define CRASH_REPORTER_CRASH_COLLECTOR_H_
+
+#include <sys/stat.h>
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+// User crash collector.
+class CrashCollector {
+ public:
+  typedef void (*CountCrashFunction)();
+  typedef bool (*IsFeedbackAllowedFunction)();
+
+  CrashCollector();
+
+  virtual ~CrashCollector();
+
+  // Initialize the crash collector for detection of crashes, given a
+  // crash counting function, and metrics collection enabled oracle.
+  void Initialize(CountCrashFunction count_crash,
+                  IsFeedbackAllowedFunction is_metrics_allowed);
+
+ protected:
+  friend class CrashCollectorTest;
+  FRIEND_TEST(ChromeCollectorTest, HandleCrash);
+  FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
+  FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
+  FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
+  FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
+  FRIEND_TEST(CrashCollectorTest, GetCrashPath);
+  FRIEND_TEST(CrashCollectorTest, GetLogContents);
+  FRIEND_TEST(CrashCollectorTest, ForkExecAndPipe);
+  FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
+  FRIEND_TEST(CrashCollectorTest, Initialize);
+  FRIEND_TEST(CrashCollectorTest, MetaData);
+  FRIEND_TEST(CrashCollectorTest, Sanitize);
+  FRIEND_TEST(CrashCollectorTest, WriteNewFile);
+  FRIEND_TEST(ForkExecAndPipeTest, Basic);
+  FRIEND_TEST(ForkExecAndPipeTest, NonZeroReturnValue);
+  FRIEND_TEST(ForkExecAndPipeTest, BadOutputFile);
+  FRIEND_TEST(ForkExecAndPipeTest, ExistingOutputFile);
+  FRIEND_TEST(ForkExecAndPipeTest, BadExecutable);
+  FRIEND_TEST(ForkExecAndPipeTest, StderrCaptured);
+  FRIEND_TEST(ForkExecAndPipeTest, NULLParam);
+  FRIEND_TEST(ForkExecAndPipeTest, NoParams);
+  FRIEND_TEST(ForkExecAndPipeTest, SegFaultHandling);
+
+  // Set maximum enqueued crashes in a crash directory.
+  static const int kMaxCrashDirectorySize;
+
+  // Writes |data| of |size| to |filename|, which must be a new file.
+  // If the file already exists or writing fails, return a negative value.
+  // Otherwise returns the number of bytes written.
+  int WriteNewFile(const base::FilePath &filename, const char *data, int size);
+
+  // Return a filename that has only [a-z0-1_] characters by mapping
+  // all others into '_'.
+  std::string Sanitize(const std::string &name);
+
+  // For testing, set the directory always returned by
+  // GetCreatedCrashDirectoryByEuid.
+  void ForceCrashDirectory(const base::FilePath &forced_directory) {
+    forced_crash_directory_ = forced_directory;
+  }
+
+  // For testing, set the root directory to read etc/os-release.d properties
+  // from.
+  void ForceOsReleaseDDirectory(const base::FilePath &forced_directory) {
+    forced_osreleased_directory_ = forced_directory;
+  }
+
+  base::FilePath GetCrashDirectoryInfo(mode_t *mode,
+                                       uid_t *directory_owner,
+                                       gid_t *directory_group);
+  bool GetUserInfoFromName(const std::string &name,
+                           uid_t *uid,
+                           gid_t *gid);
+
+  // Determines the crash directory for given euid, and creates the
+  // directory if necessary with appropriate permissions.  If
+  // |out_of_capacity| is not nullptr, it is set to indicate if the call
+  // failed due to not having capacity in the crash directory. Returns
+  // true whether or not directory needed to be created, false on any
+  // failure.  If the crash directory is at capacity, returns false.
+  bool GetCreatedCrashDirectoryByEuid(uid_t euid,
+                                      base::FilePath *crash_file_path,
+                                      bool *out_of_capacity);
+
+  // Format crash name based on components.
+  std::string FormatDumpBasename(const std::string &exec_name,
+                                 time_t timestamp,
+                                 pid_t pid);
+
+  // Create a file path to a file in |crash_directory| with the given
+  // |basename| and |extension|.
+  base::FilePath GetCrashPath(const base::FilePath &crash_directory,
+                              const std::string &basename,
+                              const std::string &extension);
+
+  base::FilePath GetProcessPath(pid_t pid);
+  bool GetSymlinkTarget(const base::FilePath &symlink,
+                        base::FilePath *target);
+  bool GetExecutableBaseNameFromPid(pid_t pid,
+                                    std::string *base_name);
+
+  // Check given crash directory still has remaining capacity for another
+  // crash.
+  bool CheckHasCapacity(const base::FilePath &crash_directory);
+
+  // Write a log applicable to |exec_name| to |output_file| based on the
+  // log configuration file at |config_path|.
+  bool GetLogContents(const base::FilePath &config_path,
+                      const std::string &exec_name,
+                      const base::FilePath &output_file);
+
+  // Add non-standard meta data to the crash metadata file.  Call
+  // before calling WriteCrashMetaData.  Key must not contain "=" or
+  // "\n" characters.  Value must not contain "\n" characters.
+  void AddCrashMetaData(const std::string &key, const std::string &value);
+
+  // Add a file to be uploaded to the crash reporter server. The file must
+  // persist until the crash report is sent; ideally it should live in the same
+  // place as the .meta file, so it can be cleaned up automatically.
+  void AddCrashMetaUploadFile(const std::string &key, const std::string &path);
+
+  // Add non-standard meta data to the crash metadata file.
+  // Data added though this call will be uploaded to the crash reporter server,
+  // appearing as a form field.
+  void AddCrashMetaUploadData(const std::string &key, const std::string &value);
+
+  // Write a file of metadata about crash.
+  void WriteCrashMetaData(const base::FilePath &meta_path,
+                          const std::string &exec_name,
+                          const std::string &payload_path);
+
+  // Returns true if the a crash test is currently running.
+  bool IsCrashTestInProgress();
+  // Returns true if we should consider ourselves to be running on a
+  // developer image.
+  bool IsDeveloperImage();
+
+  CountCrashFunction count_crash_function_;
+  IsFeedbackAllowedFunction is_feedback_allowed_function_;
+  std::string extra_metadata_;
+  base::FilePath forced_crash_directory_;
+  base::FilePath forced_osreleased_directory_;
+  base::FilePath log_config_path_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CrashCollector);
+};
+
+#endif  // CRASH_REPORTER_CRASH_COLLECTOR_H_
diff --git a/crash_collector_test.cc b/crash_collector_test.cc
new file mode 100644
index 0000000..a386cd1
--- /dev/null
+++ b/crash_collector_test.cc
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "crash_collector_test.h"
+
+#include <unistd.h>
+#include <utility>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/syslog_logging.h>
+#include <gtest/gtest.h>
+
+#include "crash_collector.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using brillo::FindLog;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace {
+
+void CountCrash() {
+  ADD_FAILURE();
+}
+
+bool IsMetrics() {
+  ADD_FAILURE();
+  return false;
+}
+
+}  // namespace
+
+class CrashCollectorTest : public ::testing::Test {
+ public:
+  void SetUp() {
+    EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(Return());
+
+    collector_.Initialize(CountCrash, IsMetrics);
+    EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
+    brillo::ClearLog();
+  }
+
+  bool CheckHasCapacity();
+
+ protected:
+  CrashCollectorMock collector_;
+
+  // Temporary directory used for tests.
+  base::ScopedTempDir test_dir_;
+};
+
+TEST_F(CrashCollectorTest, Initialize) {
+  ASSERT_TRUE(CountCrash == collector_.count_crash_function_);
+  ASSERT_TRUE(IsMetrics == collector_.is_feedback_allowed_function_);
+}
+
+TEST_F(CrashCollectorTest, WriteNewFile) {
+  FilePath test_file = test_dir_.path().Append("test_new");
+  const char kBuffer[] = "buffer";
+  unsigned int numBytesWritten = collector_.WriteNewFile(
+      test_file,
+      kBuffer,
+      strlen(kBuffer));
+  EXPECT_EQ(strlen(kBuffer), numBytesWritten);
+  EXPECT_LT(collector_.WriteNewFile(test_file,
+                                    kBuffer,
+                                    strlen(kBuffer)), 0);
+}
+
+TEST_F(CrashCollectorTest, Sanitize) {
+  EXPECT_EQ("chrome", collector_.Sanitize("chrome"));
+  EXPECT_EQ("CHROME", collector_.Sanitize("CHROME"));
+  EXPECT_EQ("1chrome2", collector_.Sanitize("1chrome2"));
+  EXPECT_EQ("chrome__deleted_", collector_.Sanitize("chrome (deleted)"));
+  EXPECT_EQ("foo_bar", collector_.Sanitize("foo.bar"));
+  EXPECT_EQ("", collector_.Sanitize(""));
+  EXPECT_EQ("_", collector_.Sanitize(" "));
+}
+
+TEST_F(CrashCollectorTest, FormatDumpBasename) {
+  struct tm tm = {};
+  tm.tm_sec = 15;
+  tm.tm_min = 50;
+  tm.tm_hour = 13;
+  tm.tm_mday = 23;
+  tm.tm_mon = 4;
+  tm.tm_year = 110;
+  tm.tm_isdst = -1;
+  std::string basename =
+      collector_.FormatDumpBasename("foo", mktime(&tm), 100);
+  ASSERT_EQ("foo.20100523.135015.100", basename);
+}
+
+TEST_F(CrashCollectorTest, GetCrashPath) {
+  EXPECT_EQ("/var/spool/crash/myprog.20100101.1200.1234.core",
+            collector_.GetCrashPath(FilePath("/var/spool/crash"),
+                                    "myprog.20100101.1200.1234",
+                                    "core").value());
+  EXPECT_EQ("/home/chronos/user/crash/chrome.20100101.1200.1234.dmp",
+            collector_.GetCrashPath(FilePath("/home/chronos/user/crash"),
+                                    "chrome.20100101.1200.1234",
+                                    "dmp").value());
+}
+
+
+bool CrashCollectorTest::CheckHasCapacity() {
+  const char* kFullMessage =
+      StringPrintf("Crash directory %s already full",
+                   test_dir_.path().value().c_str()).c_str();
+  bool has_capacity = collector_.CheckHasCapacity(test_dir_.path());
+  bool has_message = FindLog(kFullMessage);
+  EXPECT_EQ(has_message, !has_capacity);
+  return has_capacity;
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityUsual) {
+  // Test kMaxCrashDirectorySize - 1 non-meta files can be added.
+  for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf("file%d.core", i)),
+                    "", 0);
+    EXPECT_TRUE(CheckHasCapacity());
+  }
+
+  // Test an additional kMaxCrashDirectorySize - 1 meta files fit.
+  for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf("file%d.meta", i)),
+                    "", 0);
+    EXPECT_TRUE(CheckHasCapacity());
+  }
+
+  // Test an additional kMaxCrashDirectorySize meta files don't fit.
+  for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf("overage%d.meta", i)),
+                    "", 0);
+    EXPECT_FALSE(CheckHasCapacity());
+  }
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityCorrectBasename) {
+  // Test kMaxCrashDirectorySize - 1 files can be added.
+  for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf("file.%d.core", i)),
+                    "", 0);
+    EXPECT_TRUE(CheckHasCapacity());
+  }
+  base::WriteFile(test_dir_.path().Append("file.last.core"), "", 0);
+  EXPECT_FALSE(CheckHasCapacity());
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityStrangeNames) {
+  // Test many files with different extensions and same base fit.
+  for (int i = 0; i < 5 * CrashCollector::kMaxCrashDirectorySize; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf("a.%d", i)), "", 0);
+    EXPECT_TRUE(CheckHasCapacity());
+  }
+  // Test dot files are treated as individual files.
+  for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 2; ++i) {
+    base::WriteFile(test_dir_.path().Append(StringPrintf(".file%d", i)), "", 0);
+    EXPECT_TRUE(CheckHasCapacity());
+  }
+  base::WriteFile(test_dir_.path().Append("normal.meta"), "", 0);
+  EXPECT_FALSE(CheckHasCapacity());
+}
+
+TEST_F(CrashCollectorTest, MetaData) {
+  const char kMetaFileBasename[] = "generated.meta";
+  FilePath meta_file = test_dir_.path().Append(kMetaFileBasename);
+  FilePath payload_file = test_dir_.path().Append("payload-file");
+  FilePath osreleased_directory =
+      test_dir_.path().Append("etc").Append("os-release.d");
+  ASSERT_TRUE(base::CreateDirectory(osreleased_directory));
+  collector_.ForceOsReleaseDDirectory(test_dir_.path());
+
+  std::string contents;
+  const char kPayload[] = "foo";
+  ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
+  const char kBdkVersion[] = "1";
+  ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("bdk_version"),
+                              kBdkVersion,
+                              strlen(kBdkVersion)));
+  const char kProductId[] = "baz";
+  ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("product_id"),
+                              kProductId,
+                              strlen(kProductId)));
+  const char kProductVersion[] = "1.2.3.4";
+  ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("product_version"),
+                              kProductVersion,
+                              strlen(kProductVersion)));
+  collector_.AddCrashMetaData("foo", "bar");
+  collector_.WriteCrashMetaData(meta_file, "kernel", payload_file.value());
+  EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
+  const std::string kExpectedMeta =
+      StringPrintf("foo=bar\n"
+          "exec_name=kernel\n"
+          "payload=%s\n"
+          "payload_size=3\n"
+          "bdk_version=1\n"
+          "product_id=baz\n"
+          "product_version=1.2.3.4\n"
+          "done=1\n",
+          test_dir_.path().Append("payload-file").value().c_str());
+  EXPECT_EQ(kExpectedMeta, contents);
+
+  // Test target of symlink is not overwritten.
+  payload_file = test_dir_.path().Append("payload2-file");
+  ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
+  FilePath meta_symlink_path = test_dir_.path().Append("symlink.meta");
+  ASSERT_EQ(0,
+            symlink(kMetaFileBasename,
+                    meta_symlink_path.value().c_str()));
+  ASSERT_TRUE(base::PathExists(meta_symlink_path));
+  brillo::ClearLog();
+  collector_.WriteCrashMetaData(meta_symlink_path,
+                                "kernel",
+                                payload_file.value());
+  // Target metadata contents should have stayed the same.
+  contents.clear();
+  EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
+  EXPECT_EQ(kExpectedMeta, contents);
+  EXPECT_TRUE(FindLog("Unable to write"));
+
+  // Test target of dangling symlink is not created.
+  base::DeleteFile(meta_file, false);
+  ASSERT_FALSE(base::PathExists(meta_file));
+  brillo::ClearLog();
+  collector_.WriteCrashMetaData(meta_symlink_path, "kernel",
+                                payload_file.value());
+  EXPECT_FALSE(base::PathExists(meta_file));
+  EXPECT_TRUE(FindLog("Unable to write"));
+}
+
+TEST_F(CrashCollectorTest, GetLogContents) {
+  FilePath config_file = test_dir_.path().Append("crash_config");
+  FilePath output_file = test_dir_.path().Append("crash_log");
+  const char kConfigContents[] =
+      "foobar=echo hello there | \\\n  sed -e \"s/there/world/\"";
+  ASSERT_TRUE(
+      base::WriteFile(config_file, kConfigContents, strlen(kConfigContents)));
+  base::DeleteFile(FilePath(output_file), false);
+  EXPECT_FALSE(collector_.GetLogContents(config_file,
+                                         "barfoo",
+                                         output_file));
+  EXPECT_FALSE(base::PathExists(output_file));
+  base::DeleteFile(FilePath(output_file), false);
+  EXPECT_TRUE(collector_.GetLogContents(config_file,
+                                        "foobar",
+                                        output_file));
+  ASSERT_TRUE(base::PathExists(output_file));
+  std::string contents;
+  EXPECT_TRUE(base::ReadFileToString(output_file, &contents));
+  EXPECT_EQ("hello world\n", contents);
+}
diff --git a/crash_collector_test.h b/crash_collector_test.h
new file mode 100644
index 0000000..cfbb97b
--- /dev/null
+++ b/crash_collector_test.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+
+#include "crash_collector.h"
+
+#include <map>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+class CrashCollectorMock : public CrashCollector {
+ public:
+  MOCK_METHOD0(SetUpDBus, void());
+  MOCK_METHOD1(GetActiveUserSessions,
+               bool(std::map<std::string, std::string> *sessions));
+};
+
+#endif  // CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
diff --git a/crash_reporter.cc b/crash_reporter.cc
new file mode 100644
index 0000000..16e70d8
--- /dev/null
+++ b/crash_reporter.cc
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>  // for open
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/guid.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <binder/IServiceManager.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+#include <metrics/metrics_collector_service_client.h>
+#include <metrics/metrics_library.h>
+#include <utils/String16.h>
+
+
+#include "kernel_collector.h"
+#include "kernel_warning_collector.h"
+#include "unclean_shutdown_collector.h"
+#include "user_collector.h"
+
+#if !defined(__ANDROID__)
+#include "udev_collector.h"
+#endif
+
+static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
+static const char kKernelCrashDetected[] =
+    "/data/misc/crash_reporter/run/kernel-crash-detected";
+static const char kUncleanShutdownDetected[] =
+    "/var/run/unclean-shutdown-detected";
+static const char kGUIDFileName[] = "/data/misc/crash_reporter/guid";
+
+// Enumeration of kinds of crashes to be used in the CrashCounter histogram.
+enum CrashKinds {
+  kCrashKindUncleanShutdown = 1,
+  kCrashKindUser = 2,
+  kCrashKindKernel = 3,
+  kCrashKindUdev = 4,
+  kCrashKindKernelWarning = 5,
+  kCrashKindMax
+};
+
+static MetricsLibrary s_metrics_lib;
+
+using android::brillo::metrics::IMetricsCollectorService;
+using base::FilePath;
+using base::StringPrintf;
+
+static bool IsFeedbackAllowed() {
+  return s_metrics_lib.AreMetricsEnabled();
+}
+
+static bool TouchFile(const FilePath &file_path) {
+  return base::WriteFile(file_path, "", 0) == 0;
+}
+
+static void SendCrashMetrics(CrashKinds type, const char* name) {
+  // TODO(kmixter): We can remove this histogram as part of
+  // crosbug.com/11163.
+  s_metrics_lib.SendEnumToUMA(kCrashCounterHistogram, type, kCrashKindMax);
+  s_metrics_lib.SendCrashToUMA(name);
+}
+
+static void CountKernelCrash() {
+  SendCrashMetrics(kCrashKindKernel, "kernel");
+}
+
+static void CountUdevCrash() {
+  SendCrashMetrics(kCrashKindUdev, "udevcrash");
+}
+
+static void CountUncleanShutdown() {
+  SendCrashMetrics(kCrashKindUncleanShutdown, "uncleanshutdown");
+}
+
+static void CountUserCrash() {
+  SendCrashMetrics(kCrashKindUser, "user");
+  // Tell the metrics collector about the user crash, in order to log active
+  // use time between crashes.
+  MetricsCollectorServiceClient metrics_collector_service;
+
+  if (metrics_collector_service.Init())
+    metrics_collector_service.notifyUserCrash();
+  else
+    LOG(ERROR) << "Failed to send user crash notification to metrics_collector";
+}
+
+
+static int Initialize(KernelCollector *kernel_collector,
+                      UserCollector *user_collector,
+                      UncleanShutdownCollector *unclean_shutdown_collector,
+                      const bool unclean_check,
+                      const bool clean_shutdown) {
+  CHECK(!clean_shutdown) << "Incompatible options";
+
+  // Try to read the GUID from kGUIDFileName.  If the file doesn't exist, is
+  // blank, or the read fails, generate a new GUID and write it to the file.
+  std::string guid;
+  base::FilePath filepath(kGUIDFileName);
+  if (!base::ReadFileToString(filepath, &guid) || guid.empty()) {
+    guid = base::GenerateGUID();
+    // If we can't read or write the file, log an error.  However it is not
+    // a fatal error, as the crash server will assign a random GUID based
+    // on a hash of the IP address if one is not provided in the report.
+    if (base::WriteFile(filepath, guid.c_str(), guid.size()) <= 0) {
+      LOG(ERROR) << "Could not write guid " << guid << " to file "
+                 << filepath.value();
+    }
+  }
+
+  bool was_kernel_crash = false;
+  bool was_unclean_shutdown = false;
+  kernel_collector->Enable();
+  if (kernel_collector->is_enabled()) {
+    was_kernel_crash = kernel_collector->Collect();
+  }
+
+  if (unclean_check) {
+    was_unclean_shutdown = unclean_shutdown_collector->Collect();
+  }
+
+  // Touch a file to notify the metrics daemon that a kernel
+  // crash has been detected so that it can log the time since
+  // the last kernel crash.
+  if (IsFeedbackAllowed()) {
+    if (was_kernel_crash) {
+      TouchFile(FilePath(kKernelCrashDetected));
+    } else if (was_unclean_shutdown) {
+      // We only count an unclean shutdown if it did not come with
+      // an associated kernel crash.
+      TouchFile(FilePath(kUncleanShutdownDetected));
+    }
+  }
+
+  // Must enable the unclean shutdown collector *after* collecting.
+  unclean_shutdown_collector->Enable();
+  user_collector->Enable();
+
+  return 0;
+}
+
+static int HandleUserCrash(UserCollector *user_collector,
+                           const std::string& user, const bool crash_test) {
+  // Handle a specific user space crash.
+  CHECK(!user.empty()) << "--user= must be set";
+
+  // Make it possible to test what happens when we crash while
+  // handling a crash.
+  if (crash_test) {
+    *(volatile char *)0 = 0;
+    return 0;
+  }
+
+  // Accumulate logs to help in diagnosing failures during user collection.
+  brillo::LogToString(true);
+  // Handle the crash, get the name of the process from procfs.
+  bool handled = user_collector->HandleCrash(user, nullptr);
+  brillo::LogToString(false);
+  if (!handled)
+    return 1;
+  return 0;
+}
+
+#if !defined(__ANDROID__)
+static int HandleUdevCrash(UdevCollector *udev_collector,
+                           const std::string& udev_event) {
+  // Handle a crash indicated by a udev event.
+  CHECK(!udev_event.empty()) << "--udev= must be set";
+
+  // Accumulate logs to help in diagnosing failures during user collection.
+  brillo::LogToString(true);
+  bool handled = udev_collector->HandleCrash(udev_event);
+  brillo::LogToString(false);
+  if (!handled)
+    return 1;
+  return 0;
+}
+#endif
+
+static int HandleKernelWarning(KernelWarningCollector
+                               *kernel_warning_collector) {
+  // Accumulate logs to help in diagnosing failures during collection.
+  brillo::LogToString(true);
+  bool handled = kernel_warning_collector->Collect();
+  brillo::LogToString(false);
+  if (!handled)
+    return 1;
+  return 0;
+}
+
+// Interactive/diagnostics mode for generating kernel crash signatures.
+static int GenerateKernelSignature(KernelCollector *kernel_collector,
+                                   const std::string& kernel_signature_file) {
+  std::string kcrash_contents;
+  std::string signature;
+  if (!base::ReadFileToString(FilePath(kernel_signature_file),
+                              &kcrash_contents)) {
+    fprintf(stderr, "Could not read file.\n");
+    return 1;
+  }
+  if (!kernel_collector->ComputeKernelStackSignature(
+          kcrash_contents,
+          &signature,
+          true)) {
+    fprintf(stderr, "Signature could not be generated.\n");
+    return 1;
+  }
+  printf("Kernel crash signature is \"%s\".\n", signature.c_str());
+  return 0;
+}
+
+// Ensure stdout, stdin, and stderr are open file descriptors.  If
+// they are not, any code which writes to stderr/stdout may write out
+// to files opened during execution.  In particular, when
+// crash_reporter is run by the kernel coredump pipe handler (via
+// kthread_create/kernel_execve), it will not have file table entries
+// 1 and 2 (stdout and stderr) populated.  We populate them here.
+static void OpenStandardFileDescriptors() {
+  int new_fd = -1;
+  // We open /dev/null to fill in any of the standard [0, 2] file
+  // descriptors.  We leave these open for the duration of the
+  // process.  This works because open returns the lowest numbered
+  // invalid fd.
+  do {
+    new_fd = open("/dev/null", 0);
+    CHECK_GE(new_fd, 0) << "Unable to open /dev/null";
+  } while (new_fd >= 0 && new_fd <= 2);
+  close(new_fd);
+}
+
+int main(int argc, char *argv[]) {
+  DEFINE_bool(init, false, "Initialize crash logging");
+  DEFINE_bool(clean_shutdown, false, "Signal clean shutdown");
+  DEFINE_string(generate_kernel_signature, "",
+                "Generate signature from given kcrash file");
+  DEFINE_bool(crash_test, false, "Crash test");
+  DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
+  DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
+
+#if !defined(__ANDROID__)
+  DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
+#endif
+
+  DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
+  DEFINE_string(pid, "", "PID of crashing process");
+  DEFINE_string(uid, "", "UID of crashing process");
+  DEFINE_string(exe, "", "Executable name of crashing process");
+  DEFINE_bool(core2md_failure, false, "Core2md failure test");
+  DEFINE_bool(directory_failure, false, "Spool directory failure test");
+  DEFINE_string(filter_in, "",
+                "Ignore all crashes but this for testing");
+
+  OpenStandardFileDescriptors();
+  FilePath my_path = base::MakeAbsoluteFilePath(FilePath(argv[0]));
+  s_metrics_lib.Init();
+  brillo::FlagHelper::Init(argc, argv, "Chromium OS Crash Reporter");
+  brillo::OpenLog(my_path.BaseName().value().c_str(), true);
+  brillo::InitLog(brillo::kLogToSyslog);
+
+  KernelCollector kernel_collector;
+  kernel_collector.Initialize(CountKernelCrash, IsFeedbackAllowed);
+  UserCollector user_collector;
+  user_collector.Initialize(CountUserCrash,
+                            my_path.value(),
+                            IsFeedbackAllowed,
+                            true,  // generate_diagnostics
+                            FLAGS_core2md_failure,
+                            FLAGS_directory_failure,
+                            FLAGS_filter_in);
+  UncleanShutdownCollector unclean_shutdown_collector;
+  unclean_shutdown_collector.Initialize(CountUncleanShutdown,
+                                        IsFeedbackAllowed);
+
+#if !defined(__ANDROID__)
+  UdevCollector udev_collector;
+  udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+#endif
+
+  KernelWarningCollector kernel_warning_collector;
+  kernel_warning_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+
+  if (FLAGS_init) {
+    return Initialize(&kernel_collector,
+                      &user_collector,
+                      &unclean_shutdown_collector,
+                      FLAGS_unclean_check,
+                      FLAGS_clean_shutdown);
+  }
+
+  if (FLAGS_clean_shutdown) {
+    unclean_shutdown_collector.Disable();
+    user_collector.Disable();
+    return 0;
+  }
+
+  if (!FLAGS_generate_kernel_signature.empty()) {
+    return GenerateKernelSignature(&kernel_collector,
+                                   FLAGS_generate_kernel_signature);
+  }
+
+#if !defined(__ANDROID__)
+  if (!FLAGS_udev.empty()) {
+    return HandleUdevCrash(&udev_collector, FLAGS_udev);
+  }
+#endif
+
+  if (FLAGS_kernel_warning) {
+    return HandleKernelWarning(&kernel_warning_collector);
+  }
+
+  return HandleUserCrash(&user_collector, FLAGS_user, FLAGS_crash_test);
+}
diff --git a/crash_reporter.rc b/crash_reporter.rc
new file mode 100644
index 0000000..e6d1ec5
--- /dev/null
+++ b/crash_reporter.rc
@@ -0,0 +1,37 @@
+on property:crash_reporter.coredump.enabled=1
+    write /proc/sys/kernel/core_pattern \
+          "|/system/bin/crash_reporter --user=%P:%s:%u:%g:%e"
+
+on property:crash_reporter.coredump.enabled=0
+    write /proc/sys/kernel/core_pattern "core"
+
+on post-fs-data
+    # Allow catching multiple unrelated concurrent crashes, but use a finite
+    # number to prevent infinitely recursing on crash handling.
+    write /proc/sys/kernel/core_pipe_limit 4
+
+    # Remove any previous orphaned locks.
+    rmdir /data/misc/crash_reporter/lock/crash_sender
+
+    # Remove any previous run files.
+    rm /data/misc/crash_reporter/run/kernel-crash-detected
+    rmdir /data/misc/crash_reporter/run
+
+    # Create crash directories.
+    # These directories are group-writable by root so that crash_reporter can
+    # still access them when it switches users.
+    mkdir /data/misc/crash_reporter 0770 root root
+    mkdir /data/misc/crash_reporter/crash 0770 root root
+    mkdir /data/misc/crash_reporter/lock 0700 root root
+    mkdir /data/misc/crash_reporter/log 0700 root root
+    mkdir /data/misc/crash_reporter/run 0700 root root
+    mkdir /data/misc/crash_reporter/tmp 0770 root root
+
+service crash_reporter /system/bin/crash_reporter --init
+    class late_start
+    oneshot
+
+service crash_sender /system/bin/periodic_scheduler 3600 14400 crash_sender \
+    /system/bin/crash_sender
+    class late_start
+    group system
diff --git a/crash_reporter_logs.conf b/crash_reporter_logs.conf
new file mode 100644
index 0000000..7db308c
--- /dev/null
+++ b/crash_reporter_logs.conf
@@ -0,0 +1,122 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file is parsed by chromeos::KeyValueStore. It has the format:
+#
+# <basename>=<shell command>\n
+#
+# Commands may be split across multiple lines using trailing backslashes.
+#
+# When an executable named <basename> crashes, the corresponding command is
+# executed and its standard output and standard error are attached to the crash
+# report.
+#
+# Use caution in modifying this file. Only run common Unix commands here, as
+# these commands will be run when a crash has recently occurred and we should
+# avoid running anything that might cause another crash. Similarly, these
+# commands block notification of the crash to parent processes, so commands
+# should execute quickly.
+
+update_engine=cat $(ls -1tr /var/log/update_engine | tail -5 | \
+  sed s.^./var/log/update_engine/.) | tail -c 50000
+
+# The cros_installer output is logged into the update engine log file,
+# so it is handled in the same way as update_engine.
+cros_installer=cat $(ls -1tr /var/log/update_engine | tail -5 | \
+  sed s.^./var/log/update_engine/.) | tail -c 50000
+
+# Dump the last 20 lines of the last two files in Chrome's system and user log
+# directories, along with the last 20 messages from the session manager.
+chrome=\
+  for f in $(ls -1rt /var/log/chrome/chrome_[0-9]* | tail -2) \
+    $(ls -1rt /home/chronos/u-*/log/chrome_[0-9]* 2>/dev/null | tail -2); do \
+    echo "===$f (tail)==="; \
+    tail -20 $f; \
+    echo EOF; \
+    echo; \
+  done; \
+  echo "===session_manager (tail)==="; \
+  awk '$3 ~ "^session_manager\[" { print }' /var/log/messages | tail -20; \
+  echo EOF
+
+# The following rule is used for generating additional diagnostics when
+# collection of user crashes fails.  This output should not be too large
+# as it is stored in memory.  The output format specified for 'ps' is the
+# same as with the "u" ("user-oriented") option, except it doesn't show
+# the commands' arguments (i.e. "comm" instead of "command").
+crash_reporter-user-collection=\
+  echo "===ps output==="; \
+  ps axw -o user,pid,%cpu,%mem,vsz,rss,tname,stat,start_time,bsdtime,comm | \
+    tail -c 25000; \
+  echo "===meminfo==="; \
+  cat /proc/meminfo
+
+# This rule is similar to the crash_reporter-user-collection rule, except it is
+# run for kernel errors reported through udev events.
+crash_reporter-udev-collection-change-card0-drm=\
+  for dri in /sys/kernel/debug/dri/*; do \
+    echo "===$dri/i915_error_state==="; \
+    cat $dri/i915_error_state; \
+  done
+
+# When trackpad driver cyapa detects some abnormal behavior, we collect
+# additional logs from kernel messages.
+crash_reporter-udev-collection-change--i2c-cyapa=\
+  /usr/sbin/kernel_log_collector.sh cyapa 30
+# When trackpad/touchscreen driver atmel_mxt_ts detects some abnormal behavior,
+# we collect additional logs from kernel messages.
+crash_reporter-udev-collection-change--i2c-atmel_mxt_ts=\
+  /usr/sbin/kernel_log_collector.sh atmel 30
+# When touch device noise are detected, we collect relevant logs.
+# (crosbug.com/p/16788)
+crash_reporter-udev-collection---TouchNoise=cat /var/log/touch_noise.log
+# Periodically collect touch event log for debugging (crosbug.com/p/17244)
+crash_reporter-udev-collection---TouchEvent=cat /var/log/touch_event.log
+
+# Collect the last 50 lines of /var/log/messages and /var/log/net.log for
+# intel wifi driver (iwlwifi) for debugging purpose.
+crash_reporter-udev-collection-devcoredump-iwlwifi=\
+  echo "===/var/log/messages==="; \
+  tail -n 50 /var/log/messages; \
+  echo "===/var/log/net.log==="; \
+  tail -n 50 /var/log/net.log; \
+  echo EOF
+
+# Dump the last 50 lines of the last two powerd log files -- if the job has
+# already restarted, we want to see the end of the previous instance's logs.
+powerd=\
+  for f in $(ls -1tr /var/log/power_manager/powerd.[0-9]* | tail -2); do \
+    echo "===$(basename $f) (tail)==="; \
+    tail -50 $f; \
+    echo EOF; \
+  done
+# If power_supply_info aborts (due to e.g. a bad battery), its failure message
+# could end up in various places depending on which process was running it.
+# Attach the end of powerd's log since it might've also logged the underlying
+# problem.
+power_supply_info=\
+  echo "===powerd.LATEST (tail)==="; \
+  tail -50 /var/log/power_manager/powerd.LATEST; \
+  echo EOF
+# powerd_setuid_helper gets run by powerd, so its stdout/stderr will be mixed in
+# with powerd's stdout/stderr.
+powerd_setuid_helper=\
+  echo "===powerd.OUT (tail)==="; \
+  tail -50 /var/log/powerd.out; \
+  echo EOF
+
+# The following rules are only for testing purposes.
+crash_log_test=echo hello world
+crash_log_recursion_test=sleep 1 && \
+  /usr/local/autotest/tests/crash_log_recursion_test
diff --git a/crash_reporter_logs_test.cc b/crash_reporter_logs_test.cc
new file mode 100644
index 0000000..77f5a7f
--- /dev/null
+++ b/crash_reporter_logs_test.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <brillo/key_value_store.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+// Name of the checked-in configuration file containing log-collection commands.
+const char kConfigFile[] = "/system/etc/crash_reporter_logs.conf";
+
+// Signature name for crash_reporter user collection.
+// kConfigFile is expected to contain this entry.
+const char kUserCollectorSignature[] = "crash_reporter-user-collection";
+
+}  // namespace
+
+// Tests that the config file is parsable and that Chrome is listed.
+TEST(CrashReporterLogsTest, ReadConfig) {
+  brillo::KeyValueStore store;
+  ASSERT_TRUE(store.Load(base::FilePath(kConfigFile)));
+  std::string command;
+  EXPECT_TRUE(store.GetString(kUserCollectorSignature, &command));
+  EXPECT_FALSE(command.empty());
+}
diff --git a/crash_sender b/crash_sender
new file mode 100755
index 0000000..a430ab5
--- /dev/null
+++ b/crash_sender
@@ -0,0 +1,719 @@
+#!/system/bin/sh
+
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
+BRILLO_PRODUCT=Brillo
+
+# Base directory that contains any crash reporter state files.
+CRASH_STATE_DIR="/data/misc/crash_reporter"
+
+# File containing crash_reporter's anonymized guid.
+GUID_FILE="${CRASH_STATE_DIR}/guid"
+
+# Crash sender lock in case the sender is already running.
+CRASH_SENDER_LOCK="${CRASH_STATE_DIR}/lock/crash_sender"
+
+# Path to file that indicates a crash test is currently running.
+CRASH_TEST_IN_PROGRESS_FILE="${CRASH_STATE_DIR}/tmp/crash-test-in-progress"
+
+# Set this to 1 in the environment to allow uploading crash reports
+# for unofficial versions.
+FORCE_OFFICIAL=${FORCE_OFFICIAL:-0}
+
+# Path to hardware class description.
+HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID"
+
+# Path to file that indicates this is a developer image.
+LEAVE_CORE_FILE="${CRASH_STATE_DIR}/.leave_core"
+
+# Path to list_proxies.
+LIST_PROXIES="list_proxies"
+
+# Maximum crashes to send per day.
+MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
+
+# File whose existence mocks crash sending.  If empty we pretend the
+# crash sending was successful, otherwise unsuccessful.
+MOCK_CRASH_SENDING="${CRASH_STATE_DIR}/tmp/mock-crash-sending"
+
+# Set this to 1 in the environment to pretend to have booted in developer
+# mode.  This is used by autotests.
+MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0}
+
+# Ignore PAUSE_CRASH_SENDING file if set.
+OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0}
+
+# File whose existence causes crash sending to be delayed (for testing).
+# Must be stateful to enable testing kernel crashes.
+PAUSE_CRASH_SENDING="${CRASH_STATE_DIR}/lock/crash_sender_paused"
+
+# Path to a directory of restricted certificates which includes
+# a certificate for the crash server.
+RESTRICTED_CERTIFICATES_PATH="/system/etc/security/cacerts"
+RESTRICTED_CERTIFICATES_PATH_GOOGLE="/system/etc/security/cacerts_google"
+
+# File whose existence implies we're running and not to start again.
+RUN_FILE="${CRASH_STATE_DIR}/run/crash_sender.pid"
+
+# Maximum time to sleep between sends.
+SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600}
+
+# Set this to 1 to allow uploading of device coredumps.
+DEVCOREDUMP_UPLOAD_FLAG_FILE="${CRASH_STATE_DIR}/device_coredump_upload_allowed"
+
+# The weave configuration file.
+WEAVE_CONF_FILE="/etc/weaved/weaved.conf"
+
+# The os-release.d folder.
+OSRELEASED_FOLDER="/etc/os-release.d"
+
+# The syslog tag for all logging we emit.
+TAG="$(basename $0)[$$]"
+
+# Directory to store timestamp files indicating the uploads in the past 24
+# hours.
+TIMESTAMPS_DIR="${CRASH_STATE_DIR}/crash_sender"
+
+# Temp directory for this process.
+TMP_DIR=""
+
+# Crash report log file.
+CRASH_LOG="${CRASH_STATE_DIR}/log/uploads.log"
+
+lecho() {
+  log -t "${TAG}" "$@"
+}
+
+lwarn() {
+  lecho -psyslog.warn "$@"
+}
+
+# Returns true if mock is enabled.
+is_mock() {
+  [ -f "${MOCK_CRASH_SENDING}" ] && return 0
+  return 1
+}
+
+is_mock_successful() {
+  local mock_in=$(cat "${MOCK_CRASH_SENDING}")
+  [ "${mock_in}" = "" ] && return 0  # empty file means success
+  return 1
+}
+
+cleanup() {
+  if [ -n "${TMP_DIR}" ]; then
+    rm -rf "${TMP_DIR}"
+  fi
+  rm -f "${RUN_FILE}"
+  if [ -n "${CRASH_SENDER_LOCK}" ]; then
+    rm -rf "${CRASH_SENDER_LOCK}"
+  fi
+  crash_done
+}
+
+crash_done() {
+  if is_mock; then
+    # For testing purposes, emit a message to log so that we
+    # know when the test has received all the messages from this run.
+    lecho "crash_sender done."
+  fi
+}
+
+is_official_image() {
+  [ ${FORCE_OFFICIAL} -ne 0 ] && return 0
+  if [ "$(getprop ro.secure)" = "1" ]; then
+    return 0
+  else
+    return 1
+  fi
+}
+
+# Returns 0 if the a crash test is currently running.  NOTE: Mirrors
+# crash_collector.cc:CrashCollector::IsCrashTestInProgress().
+is_crash_test_in_progress() {
+  [ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0
+  return 1
+}
+
+# Returns 0 if we should consider ourselves to be running on a developer
+# image.  NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
+is_developer_image() {
+  # If we're testing crash reporter itself, we don't want to special-case
+  # for developer images.
+  is_crash_test_in_progress && return 1
+  [ -f "${LEAVE_CORE_FILE}" ] && return 0
+  return 1
+}
+
+# Returns 0 if we should consider ourselves to be running on a test image.
+is_test_image() {
+  # If we're testing crash reporter itself, we don't want to special-case
+  # for test images.
+  is_crash_test_in_progress && return 1
+  case $(get_channel) in
+  test*) return 0;;
+  esac
+  return 1
+}
+
+# Returns 0 if the machine booted up in developer mode.
+is_developer_mode() {
+  [ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0
+  # If we're testing crash reporter itself, we don't want to special-case
+  # for developer mode.
+  is_crash_test_in_progress && return 1
+  if [ "$(getprop ro.debuggable)" = "1" ]; then
+    return 0
+  else
+    return 1
+  fi
+}
+
+# Returns the path of the certificates directory to be used when sending
+# reports to the crash server.
+# If crash_reporter.full_certs=1, return the full certificates path.
+# Otherwise return the Google-specific certificates path.
+get_certificates_path() {
+  if [ "$(getprop crash_reporter.full_certs)" = "1" ]; then
+    echo "${RESTRICTED_CERTIFICATES_PATH}"
+  else
+    echo "${RESTRICTED_CERTIFICATES_PATH_GOOGLE}"
+  fi
+}
+
+# Return 0 if the uploading of device coredumps is allowed.
+is_device_coredump_upload_allowed() {
+  [ -f "${DEVCOREDUMP_UPLOAD_FLAG_FILE}" ] && return 0
+  return 1
+}
+
+# Generate a uniform random number in 0..max-1.
+# POSIX arithmetic expansion requires support of at least signed long integers.
+# On 32-bit systems, that may mean 32-bit signed integers, in which case the
+# 32-bit random number read from /dev/urandom may be interpreted as negative
+# when used inside an arithmetic expansion (since the high bit might be set).
+# mksh at least is known to behave this way.
+# For this case, simply take the absolute value, which will still give a
+# roughly uniform random distribution for the modulo (as we are merely ignoring
+# the high/sign bit).
+# See corresponding Arithmetic Expansion and Arithmetic Expression sections:
+# POSIX: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_04
+# mksh: http://linux.die.net/man/1/mksh
+generate_uniform_random() {
+  local max=$1
+  local random="$(od -An -N4 -tu /dev/urandom)"
+  echo $(((random < 0 ? -random : random) % max))
+}
+
+# Check if sending a crash now does not exceed the maximum 24hr rate and
+# commit to doing so, if not.
+check_rate() {
+  mkdir -p ${TIMESTAMPS_DIR}
+  # Only consider minidumps written in the past 24 hours by removing all older.
+  find "${TIMESTAMPS_DIR}" -mindepth 1 -mtime +1 \
+      -exec rm -- '{}' ';'
+  local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w)
+  lecho "Current send rate: ${sends_in_24hrs}sends/24hrs"
+  if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then
+    lecho "Cannot send more crashes:"
+    lecho "  current ${sends_in_24hrs}send/24hrs >= " \
+          "max ${MAX_CRASH_RATE}send/24hrs"
+    return 1
+  fi
+  mktemp "${TIMESTAMPS_DIR}"/XXXXXX > /dev/null
+  return 0
+}
+
+# Gets the base part of a crash report file, such as name.01234.5678.9012 from
+# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz.  We make sure
+# "name" is sanitized in CrashCollector::Sanitize to not include any periods.
+get_base() {
+  echo "$1" | cut -d. -f-4
+}
+
+get_extension() {
+  local extension="${1##*.}"
+  local filename="${1%.*}"
+  # For gzipped file, we ignore .gz and get the real extension
+  if [ "${extension}" = "gz" ]; then
+    echo "${filename##*.}"
+  else
+    echo "${extension}"
+  fi
+}
+
+# Return which kind of report the given metadata file relates to
+get_kind() {
+  local payload="$(get_key_value "$1" "payload")"
+  if [ ! -r "${payload}" ]; then
+    lecho "Missing payload: ${payload}"
+    echo "undefined"
+    return
+  fi
+  local kind="$(get_extension "${payload}")"
+  if [ "${kind}" = "dmp" ]; then
+    echo "minidump"
+    return
+  fi
+  echo "${kind}"
+}
+
+get_key_value() {
+  local file="$1" key="$2" value
+
+  if [ -f "${file}/${key}" ]; then
+    # Get the value from a folder where each key is its own file.  The key
+    # file's entire contents is the value.
+    value=$(cat "${file}/${key}")
+  elif [ -f "${file}" ]; then
+    # Get the value from a file that has multiple key=value combinations.
+    # Return the first entry.  There shouldn't be more than one anyways.
+    # Substr at length($1) + 2 skips past the key and following = sign (awk
+    # uses 1-based indexes), but preserves embedded = characters.
+    value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}")
+  fi
+
+  echo "${value:-undefined}"
+}
+
+get_keys() {
+  local file="$1" regex="$2"
+
+  cut -d '=' -f1 "${file}" | grep --color=never "${regex}"
+}
+
+# Return the channel name (sans "-channel" suffix).
+get_channel() {
+  getprop ro.product.channel | sed 's:-channel$::'
+}
+
+# Return the hardware class or "undefined".
+get_hardware_class() {
+  if [ -r "${HWCLASS_PATH}" ]; then
+    cat "${HWCLASS_PATH}"
+  else
+    echo "undefined"
+  fi
+}
+
+# Return the log string filtered with only JSON-safe white-listed characters.
+filter_log_string() {
+  echo "$1" | tr -cd '[:alnum:]_.\-:;'
+}
+
+send_crash() {
+  local meta_path="$1"
+  local report_payload="$(get_key_value "${meta_path}" "payload")"
+  local kind="$(get_kind "${meta_path}")"
+  local exec_name="$(get_key_value "${meta_path}" "exec_name")"
+  local url="$(get_key_value "${OSRELEASED_FOLDER}" "crash_server")"
+  local bdk_version="$(get_key_value "${meta_path}" "bdk_version")"
+  local hwclass="$(get_hardware_class)"
+  local write_payload_size="$(get_key_value "${meta_path}" "payload_size")"
+  local log="$(get_key_value "${meta_path}" "log")"
+  local sig="$(get_key_value "${meta_path}" "sig")"
+  local send_payload_size="$(stat -c "%s" "${report_payload}" 2>/dev/null)"
+  local product="$(get_key_value "${meta_path}" "product_id")"
+  local version="$(get_key_value "${meta_path}" "product_version")"
+  local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")"
+  local guid
+  local model_manifest_id="$(get_key_value "${WEAVE_CONF_FILE}" "model_id")"
+
+  # If crash_reporter.server is not set return with an error.
+  if [ -z "${url}" ]; then
+    lecho "Configuration error: crash_reporter.server not set."
+    return 1
+  fi
+
+  set -- \
+    -F "write_payload_size=${write_payload_size}" \
+    -F "send_payload_size=${send_payload_size}"
+  if [ "${sig}" != "undefined" ]; then
+    set -- "$@" \
+      -F "sig=${sig}" \
+      -F "sig2=${sig}"
+  fi
+  if [ -r "${report_payload}" ]; then
+    set -- "$@" \
+      -F "upload_file_${kind}=@${report_payload}"
+  fi
+  if [ "${log}" != "undefined" -a -r "${log}" ]; then
+    set -- "$@" \
+      -F "log=@${log}"
+  fi
+
+  if [ "${upload_prefix}" = "undefined" ]; then
+    upload_prefix=""
+  fi
+
+  # Grab any variable that begins with upload_.
+  local v
+  for k in $(get_keys "${meta_path}" "^upload_"); do
+    v="$(get_key_value "${meta_path}" "${k}")"
+    case ${k} in
+      # Product & version are handled separately.
+      upload_var_prod) ;;
+      upload_var_ver) ;;
+      upload_var_*)
+        set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}"
+        ;;
+      upload_file_*)
+        if [ -r "${v}" ]; then
+          set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}"
+        fi
+        ;;
+    esac
+  done
+
+  # If ID or VERSION_ID is undefined, we use the default product name
+  # and bdk_version from /etc/os-release.d.
+  if [ "${product}" = "undefined" ]; then
+    product="${BRILLO_PRODUCT}"
+  fi
+  if [ "${version}" = "undefined" ]; then
+    version="${bdk_version}"
+  fi
+
+  local image_type
+  if is_test_image; then
+    image_type="test"
+  elif is_developer_image; then
+    image_type="dev"
+  elif [ ${FORCE_OFFICIAL} -ne 0 ]; then
+    image_type="force-official"
+  elif is_mock && ! is_mock_successful; then
+    image_type="mock-fail"
+  fi
+
+  local boot_mode
+  if is_developer_mode; then
+    boot_mode="dev"
+  fi
+
+  # Need to strip dashes ourselves as Chrome preserves it in the file
+  # nowadays.  This is also what the Chrome breakpad client does.
+  guid=$(tr -d '-' < "${GUID_FILE}")
+
+  local error_type="$(get_key_value "${meta_path}" "error_type")"
+  [ "${error_type}" = "undefined" ] && error_type=
+
+  lecho "Sending crash:"
+  if [ "${product}" != "${BRILLO_PRODUCT}" ]; then
+    lecho "  Sending crash report on behalf of ${product}"
+  fi
+  lecho "  Metadata: ${meta_path} (${kind})"
+  lecho "  Payload: ${report_payload}"
+  lecho "  Version: ${version}"
+  lecho "  Bdk Version: ${bdk_version}"
+  [ -n "${image_type}" ] && lecho "  Image type: ${image_type}"
+  [ -n "${boot_mode}" ] && lecho "  Boot mode: ${boot_mode}"
+  if is_mock; then
+    lecho "  Product: ${product}"
+    lecho "  URL: ${url}"
+    lecho "  HWClass: ${hwclass}"
+    lecho "  write_payload_size: ${write_payload_size}"
+    lecho "  send_payload_size: ${send_payload_size}"
+    if [ "${log}" != "undefined" ]; then
+      lecho "  log: @${log}"
+    fi
+    if [ "${sig}" != "undefined" ]; then
+      lecho "  sig: ${sig}"
+    fi
+  fi
+  lecho "  Exec name: ${exec_name}"
+  [ -n "${error_type}" ] && lecho "  Error type: ${error_type}"
+  if is_mock; then
+    if ! is_mock_successful; then
+      lecho "Mocking unsuccessful send"
+      return 1
+    fi
+    lecho "Mocking successful send"
+    return 0
+  fi
+
+  # Read in the first proxy, if any, for a given URL.  NOTE: The
+  # double-quotes are necessary due to a bug in dash with the "local"
+  # builtin command and values that have spaces in them (see
+  # "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097").
+  if [ -f "${LIST_PROXIES}" ]; then
+    local proxy ret
+    proxy=$("${LIST_PROXIES}" --quiet "${url}")
+    ret=$?
+    if [ ${ret} -ne 0 ]; then
+      proxy=''
+      lwarn "Listing proxies failed with exit code ${ret}"
+    else
+      proxy=$(echo "${proxy}" | head -1)
+    fi
+  fi
+  # if a direct connection should be used, unset the proxy variable.
+  [ "${proxy}" = "direct://" ] && proxy=
+  local report_id="${TMP_DIR}/report_id"
+  local curl_stderr="${TMP_DIR}/curl_stderr"
+
+  set +e
+  curl "${url}" -f -v ${proxy:+--proxy "$proxy"} \
+    --capath "$(get_certificates_path)" --ciphers HIGH \
+    -F "prod=${product}" \
+    -F "ver=${version}" \
+    -F "bdk_version=${bdk_version}" \
+    -F "hwclass=${hwclass}" \
+    -F "exec_name=${exec_name}" \
+    -F "model_manifest_id=${model_manifest_id}" \
+    ${image_type:+-F "image_type=${image_type}"} \
+    ${boot_mode:+-F "boot_mode=${boot_mode}"} \
+    ${error_type:+-F "error_type=${error_type}"} \
+    -F "guid=${guid}" \
+    -o "${report_id}" \
+    "$@" \
+    2>"${curl_stderr}"
+  curl_result=$?
+  set -e
+
+  if [ ${curl_result} -eq 0 ]; then
+    local id="$(cat "${report_id}")"
+    local timestamp="$(date +%s)"
+    local filter_prod="$(filter_log_string "${product}")"
+    local filter_exec="$(filter_log_string "${exec_name}")"
+    if [ "${filter_prod}" != "${product}" ]; then
+      lwarn "Product name filtered to: ${filter_prod}."
+    fi
+    if [ "${filter_exec}" != "${exec_name}" ]; then
+      lwarn "Exec name filtered to: ${filter_exec}."
+    fi
+    printf "{'time':%s,'id':'%s','product':'%s','exec_name':'%s'}\n" \
+      "${timestamp}" "${id}" "${filter_prod}" "${filter_exec}" >> "${CRASH_LOG}"
+    lecho "Crash report receipt ID ${id}"
+  else
+    lecho "Crash sending failed with exit code ${curl_result}: " \
+      "$(cat "${curl_stderr}")"
+  fi
+
+  rm -f "${report_id}"
+
+  return ${curl_result}
+}
+
+# *.meta files always end with done=1 so we can tell if they are complete.
+is_complete_metadata() {
+  grep -q "done=1" "$1"
+}
+
+# Remove the given report path.
+remove_report() {
+  local base="${1%.*}"
+  rm -f -- "${base}".*
+}
+
+# Send all crashes from the given directory.  This applies even when we're on a
+# 3G connection (see crosbug.com/3304 for discussion).
+send_crashes() {
+  local dir="$1"
+  lecho "Sending crashes for ${dir}"
+
+  if [ ! -d "${dir}" ]; then
+    return
+  fi
+
+  # Consider any old files which still have no corresponding meta file
+  # as orphaned, and remove them.
+  for old_file in $(find "${dir}" -mindepth 1 \
+                    -mtime +1 -type f); do
+    if [ ! -e "$(get_base "${old_file}").meta" ]; then
+      lecho "Removing old orphaned file: ${old_file}."
+      rm -f -- "${old_file}"
+    fi
+  done
+
+  # Look through all metadata (*.meta) files, oldest first.  That way, the rate
+  # limit does not stall old crashes if there's a high amount of new crashes
+  # coming in.
+  # For each crash report, first evaluate conditions that might lead to its
+  # removal to honor user choice and to free disk space as soon as possible,
+  # then decide whether it should be sent right now or kept for later sending.
+  for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do
+    lecho "Considering metadata ${meta_path}."
+
+    local kind=$(get_kind "${meta_path}")
+    if [ "${kind}" != "minidump" ] && \
+       [ "${kind}" != "kcrash" ] && \
+       [ "${kind}" != "log" ] &&
+       [ "${kind}" != "devcore" ]; then
+      lecho "Unknown report kind ${kind}.  Removing report."
+      remove_report "${meta_path}"
+      continue
+    fi
+
+    if ! is_complete_metadata "${meta_path}"; then
+      # This report is incomplete, so if it's old, just remove it.
+      local old_meta=$(find "${dir}" -mindepth 1 -name \
+        $(basename "${meta_path}") -mtime +1 -type f)
+      if [ -n "${old_meta}" ]; then
+        lecho "Removing old incomplete metadata."
+        remove_report "${meta_path}"
+      else
+        lecho "Ignoring recent incomplete metadata."
+      fi
+      continue
+    fi
+
+    # Ignore device coredump if device coredump uploading is not allowed.
+    if [ "${kind}" = "devcore" ] && ! is_device_coredump_upload_allowed; then
+      lecho "Ignoring device coredump. Device coredump upload not allowed."
+      continue
+    fi
+
+    if ! is_mock && ! is_official_image; then
+      lecho "Not an official OS version.  Removing crash."
+      remove_report "${meta_path}"
+      continue
+    fi
+
+    # Remove existing crashes in case user consent has not (yet) been given or
+    # has been revoked.  This must come after the guest mode check because
+    # metrics_client always returns "not consented" in guest mode.
+    if ! metrics_client -c; then
+      lecho "Crash reporting is disabled.  Removing crash."
+      remove_report "${meta_path}"
+      continue
+    fi
+
+    # Skip report if the upload rate is exceeded.  (Don't exit right now because
+    # subsequent reports may be candidates for deletion.)
+    if ! check_rate; then
+      lecho "Sending ${meta_path} would exceed rate.  Leaving for later."
+      continue
+    fi
+
+    # The .meta file should be written *after* all to-be-uploaded files that it
+    # references.  Nevertheless, as a safeguard, a hold-off time of thirty
+    # seconds after writing the .meta file is ensured.  Also, sending of crash
+    # reports is spread out randomly by up to SECONDS_SEND_SPREAD.  Thus, for
+    # the sleep call the greater of the two delays is used.
+    local now=$(date +%s)
+    local holdoff_time=$(($(stat -c "%Y" "${meta_path}") + 30 - ${now}))
+    local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}")
+    local sleep_time
+    if [ ${spread_time} -gt ${holdoff_time} ]; then
+      sleep_time="${spread_time}"
+    else
+      sleep_time="${holdoff_time}"
+    fi
+    lecho "Scheduled to send in ${sleep_time}s."
+    if ! is_mock; then
+      if ! sleep "${sleep_time}"; then
+          lecho "Sleep failed"
+          return 1
+      fi
+    fi
+
+    # Try to upload.
+    if ! send_crash "${meta_path}"; then
+      lecho "Problem sending ${meta_path}, not removing."
+      continue
+    fi
+
+    # Send was successful, now remove.
+    lecho "Successfully sent crash ${meta_path} and removing."
+    remove_report "${meta_path}"
+  done
+}
+
+usage() {
+  cat <<EOF
+Usage: crash_sender [options]
+
+Options:
+ -e <var>=<val>     Set env |var| to |val| (only some vars)
+EOF
+  exit ${1:-1}
+}
+
+parseargs() {
+  # Parse the command line arguments.
+  while [ $# -gt 0 ]; do
+    case $1 in
+    -e)
+      shift
+      case $1 in
+      FORCE_OFFICIAL=*|\
+      MAX_CRASH_RATE=*|\
+      MOCK_DEVELOPER_MODE=*|\
+      OVERRIDE_PAUSE_SENDING=*|\
+      SECONDS_SEND_SPREAD=*)
+        export "$1"
+        ;;
+      *)
+        lecho "Unknown var passed to -e: $1"
+        exit 1
+        ;;
+      esac
+      ;;
+    -h)
+      usage 0
+      ;;
+    *)
+      lecho "Unknown options: $*"
+      exit 1
+      ;;
+    esac
+    shift
+  done
+}
+
+main() {
+  parseargs "$@"
+
+  if [ -e "${PAUSE_CRASH_SENDING}" ] && \
+     [ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then
+    lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
+    exit 1
+  fi
+
+  if is_test_image; then
+    lecho "Exiting early due to test image."
+    exit 1
+  fi
+
+  # We don't perform checks on this because we have a master lock with the
+  # CRASH_SENDER_LOCK file.  This pid file is for the system to keep track
+  # (like with autotests) that we're still running.
+  echo $$ > "${RUN_FILE}"
+
+  for dependency in "$(get_certificates_path)"; do
+    if [ ! -x "${dependency}" ]; then
+      lecho "Fatal: Crash sending disabled: ${dependency} not found."
+      exit 1
+    fi
+  done
+
+  TMP_DIR="$(mktemp -d "${CRASH_STATE_DIR}/tmp/crash_sender.XXXXXX")"
+
+  # Send system-wide crashes
+  send_crashes "${CRASH_STATE_DIR}/crash"
+}
+
+trap cleanup EXIT INT TERM
+
+#TODO(http://b/23937249): Change the locking logic back to using flock.
+if ! mkdir "${CRASH_SENDER_LOCK}" 2>/dev/null; then
+  lecho "Already running; quitting."
+  crash_done
+  exit 1
+fi
+main "$@"
diff --git a/dbus_bindings/org.chromium.LibCrosService.xml b/dbus_bindings/org.chromium.LibCrosService.xml
new file mode 100644
index 0000000..64b8b84
--- /dev/null
+++ b/dbus_bindings/org.chromium.LibCrosService.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/LibCrosService"
+      xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <interface name="org.chromium.LibCrosServiceInterface">
+    <method name="ResolveNetworkProxy">
+      <arg name="source_url" type="s" direction="in"/>
+      <arg name="signal_interface" type="s" direction="in"/>
+      <arg name="signal_name" type="s" direction="in"/>
+      <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+    </method>
+  </interface>
+  <interface name="org.chromium.CrashReporterLibcrosProxyResolvedInterface">
+    <signal name="ProxyResolved">
+      <arg name="source_url" type="s" direction="out"/>
+      <arg name="proxy_info" type="s" direction="out"/>
+      <arg name="error_message" type="s" direction="out"/>
+    </signal>
+  </interface>
+</node>
diff --git a/init/crash-reporter.conf b/init/crash-reporter.conf
new file mode 100644
index 0000000..19f2cdb
--- /dev/null
+++ b/init/crash-reporter.conf
@@ -0,0 +1,27 @@
+# Copyright (c) 2011 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.
+
+description     "Initialize crash reporting services"
+author          "chromium-os-dev@chromium.org"
+
+# This job merely initializes its service and then terminates; the
+# actual checking and reporting of crash dumps is triggered by an
+# hourly cron job.
+start on starting system-services
+
+pre-start script
+  mkdir -p /var/spool
+
+  # Only allow device coredumps on a "developer system".
+  if ! is_developer_end_user; then
+    # consumer end-user - disable device coredumps, if driver exists.
+    echo 1 > /sys/class/devcoredump/disabled || true
+  fi
+end script
+
+# crash_reporter uses argv[0] as part of the command line for
+# /proc/sys/kernel/core_pattern.  That command line is invoked by
+# the kernel, and can't rely on PATH, so argv[0] must be a full
+# path; we invoke it as such here.
+exec /sbin/crash_reporter --init
diff --git a/init/crash-sender.conf b/init/crash-sender.conf
new file mode 100644
index 0000000..892186f
--- /dev/null
+++ b/init/crash-sender.conf
@@ -0,0 +1,11 @@
+# Copyright (c) 2014 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.
+
+description     "Run the crash sender periodically"
+author          "chromium-os-dev@chromium.org"
+
+start on starting system-services
+stop on stopping system-services
+
+exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender
diff --git a/init/warn-collector.conf b/init/warn-collector.conf
new file mode 100644
index 0000000..3be80da
--- /dev/null
+++ b/init/warn-collector.conf
@@ -0,0 +1,12 @@
+# 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.
+
+description "Runs a daemon which collects and reports kernel warnings"
+author      "chromium-os-dev@chromium.org"
+
+start on started system-services
+stop on stopping system-services
+respawn
+
+exec warn_collector
diff --git a/kernel_collector.cc b/kernel_collector.cc
new file mode 100644
index 0000000..68f2d9e
--- /dev/null
+++ b/kernel_collector.cc
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kernel_collector.h"
+
+#include <map>
+#include <sys/stat.h>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+using base::FilePath;
+using base::StringPrintf;
+
+namespace {
+
+const char kDefaultKernelStackSignature[] = "kernel-UnspecifiedStackSignature";
+const char kDumpParentPath[] = "/sys/fs";
+const char kDumpPath[] = "/sys/fs/pstore";
+const char kDumpFormat[] = "dmesg-ramoops-%zu";
+const char kKernelExecName[] = "kernel";
+// Maximum number of records to examine in the kDumpPath.
+const size_t kMaxDumpRecords = 100;
+const pid_t kKernelPid = 0;
+const char kKernelSignatureKey[] = "sig";
+// Byte length of maximum human readable portion of a kernel crash signature.
+const int kMaxHumanStringLength = 40;
+const uid_t kRootUid = 0;
+// Time in seconds from the final kernel log message for a call stack
+// to count towards the signature of the kcrash.
+const int kSignatureTimestampWindow = 2;
+// Kernel log timestamp regular expression.
+const char kTimestampRegex[] = "^<.*>\\[\\s*(\\d+\\.\\d+)\\]";
+
+//
+// These regular expressions enable to us capture the PC in a backtrace.
+// The backtrace is obtained through dmesg or the kernel's preserved/kcrashmem
+// feature.
+//
+// For ARM we see:
+//   "<5>[   39.458982] PC is at write_breakme+0xd0/0x1b4"
+// For MIPS we see:
+//   "<5>[ 3378.552000] epc   : 804010f0 lkdtm_do_action+0x68/0x3f8"
+// For x86:
+//   "<0>[   37.474699] EIP: [<790ed488>] write_breakme+0x80/0x108
+//    SS:ESP 0068:e9dd3efc"
+//
+const char* const kPCRegex[] = {
+  0,
+  " PC is at ([^\\+ ]+).*",
+  " epc\\s+:\\s+\\S+\\s+([^\\+ ]+).*",  // MIPS has an exception program counter
+  " EIP: \\[<.*>\\] ([^\\+ ]+).*",  // X86 uses EIP for the program counter
+  " RIP  \\[<.*>\\] ([^\\+ ]+).*",  // X86_64 uses RIP for the program counter
+};
+
+static_assert(arraysize(kPCRegex) == KernelCollector::kArchCount,
+              "Missing Arch PC regexp");
+
+}  // namespace
+
+KernelCollector::KernelCollector()
+    : is_enabled_(false),
+      ramoops_dump_path_(kDumpPath),
+      records_(0),
+      // We expect crash dumps in the format of architecture we are built for.
+      arch_(GetCompilerArch()) {
+}
+
+KernelCollector::~KernelCollector() {
+}
+
+void KernelCollector::OverridePreservedDumpPath(const FilePath &file_path) {
+  ramoops_dump_path_ = file_path;
+}
+
+bool KernelCollector::ReadRecordToString(std::string *contents,
+                                         size_t current_record,
+                                         bool *record_found) {
+  // A record is a ramoops dump. It has an associated size of "record_size".
+  std::string record;
+  std::string captured;
+
+  // Ramoops appends a header to a crash which contains ==== followed by a
+  // timestamp. Ignore the header.
+  pcrecpp::RE record_re(
+      "====\\d+\\.\\d+\n(.*)",
+      pcrecpp::RE_Options().set_multiline(true).set_dotall(true));
+
+  pcrecpp::RE sanity_check_re("\n<\\d+>\\[\\s*(\\d+\\.\\d+)\\]");
+
+  FilePath ramoops_record;
+  GetRamoopsRecordPath(&ramoops_record, current_record);
+  if (!base::ReadFileToString(ramoops_record, &record)) {
+    LOG(ERROR) << "Unable to open " << ramoops_record.value();
+    return false;
+  }
+
+  *record_found = false;
+  if (record_re.FullMatch(record, &captured)) {
+    // Found a ramoops header, so strip the header and append the rest.
+    contents->append(captured);
+    *record_found = true;
+  } else if (sanity_check_re.PartialMatch(record.substr(0, 1024))) {
+    // pstore compression has been added since kernel 3.12. In order to
+    // decompress dmesg correctly, ramoops driver has to strip the header
+    // before handing over the record to the pstore driver, so we don't
+    // need to do it here anymore. However, the sanity check is needed because
+    // sometimes a pstore record is just a chunk of uninitialized memory which
+    // is not the result of a kernel crash. See crbug.com/443764
+    contents->append(record);
+    *record_found = true;
+  } else {
+    LOG(WARNING) << "Found invalid record at " << ramoops_record.value();
+  }
+
+  // Remove the record from pstore after it's found.
+  if (*record_found)
+    base::DeleteFile(ramoops_record, false);
+
+  return true;
+}
+
+void KernelCollector::GetRamoopsRecordPath(FilePath *path,
+                                           size_t record) {
+  // Disable error "format not a string literal, argument types not checked"
+  // because this is valid, but GNU apparently doesn't bother checking a const
+  // format string.
+  #pragma GCC diagnostic push
+  #pragma GCC diagnostic ignored "-Wformat-nonliteral"
+  *path = ramoops_dump_path_.Append(StringPrintf(kDumpFormat, record));
+  #pragma GCC diagnostic pop
+}
+
+bool KernelCollector::LoadParameters() {
+  // Discover how many ramoops records are being exported by the driver.
+  size_t count;
+
+  for (count = 0; count < kMaxDumpRecords; ++count) {
+    FilePath ramoops_record;
+    GetRamoopsRecordPath(&ramoops_record, count);
+
+    if (!base::PathExists(ramoops_record))
+      break;
+  }
+
+  records_ = count;
+  return (records_ > 0);
+}
+
+bool KernelCollector::LoadPreservedDump(std::string *contents) {
+  // Load dumps from the preserved memory and save them in contents.
+  // Since the system is set to restart on oops we won't actually ever have
+  // multiple records (only 0 or 1), but check in case we don't restart on
+  // oops in the future.
+  bool any_records_found = false;
+  bool record_found = false;
+  // clear contents since ReadFileToString actually appends to the string.
+  contents->clear();
+
+  for (size_t i = 0; i < records_; ++i) {
+    if (!ReadRecordToString(contents, i, &record_found)) {
+      break;
+    }
+    if (record_found) {
+      any_records_found = true;
+    }
+  }
+
+  if (!any_records_found) {
+    LOG(ERROR) << "No valid records found in " << ramoops_dump_path_.value();
+    return false;
+  }
+
+  return true;
+}
+
+void KernelCollector::StripSensitiveData(std::string *kernel_dump) {
+  // Strip any data that the user might not want sent up to the crash servers.
+  // We'll read in from kernel_dump and also place our output there.
+  //
+  // At the moment, the only sensitive data we strip is MAC addresses.
+
+  // Get rid of things that look like MAC addresses, since they could possibly
+  // give information about where someone has been.  This is strings that look
+  // like this: 11:22:33:44:55:66
+  // Complications:
+  // - Within a given kernel_dump, want to be able to tell when the same MAC
+  //   was used more than once.  Thus, we'll consistently replace the first
+  //   MAC found with 00:00:00:00:00:01, the second with ...:02, etc.
+  // - ACPI commands look like MAC addresses.  We'll specifically avoid getting
+  //   rid of those.
+  std::ostringstream result;
+  std::string pre_mac_str;
+  std::string mac_str;
+  std::map<std::string, std::string> mac_map;
+  pcrecpp::StringPiece input(*kernel_dump);
+
+  // This RE will find the next MAC address and can return us the data preceding
+  // the MAC and the MAC itself.
+  pcrecpp::RE mac_re("(.*?)("
+                     "[0-9a-fA-F][0-9a-fA-F]:"
+                     "[0-9a-fA-F][0-9a-fA-F]:"
+                     "[0-9a-fA-F][0-9a-fA-F]:"
+                     "[0-9a-fA-F][0-9a-fA-F]:"
+                     "[0-9a-fA-F][0-9a-fA-F]:"
+                     "[0-9a-fA-F][0-9a-fA-F])",
+                     pcrecpp::RE_Options()
+                       .set_multiline(true)
+                       .set_dotall(true));
+
+  // This RE will identify when the 'pre_mac_str' shows that the MAC address
+  // was really an ACPI cmd.  The full string looks like this:
+  //   ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES) filtered out
+  pcrecpp::RE acpi_re("ACPI cmd ef/$",
+                      pcrecpp::RE_Options()
+                        .set_multiline(true)
+                        .set_dotall(true));
+
+  // Keep consuming, building up a result string as we go.
+  while (mac_re.Consume(&input, &pre_mac_str, &mac_str)) {
+    if (acpi_re.PartialMatch(pre_mac_str)) {
+      // We really saw an ACPI command; add to result w/ no stripping.
+      result << pre_mac_str << mac_str;
+    } else {
+      // Found a MAC address; look up in our hash for the mapping.
+      std::string replacement_mac = mac_map[mac_str];
+      if (replacement_mac == "") {
+        // It wasn't present, so build up a replacement string.
+        int mac_id = mac_map.size();
+
+        // Handle up to 2^32 unique MAC address; overkill, but doesn't hurt.
+        replacement_mac = StringPrintf("00:00:%02x:%02x:%02x:%02x",
+                                       (mac_id & 0xff000000) >> 24,
+                                       (mac_id & 0x00ff0000) >> 16,
+                                       (mac_id & 0x0000ff00) >> 8,
+                                       (mac_id & 0x000000ff));
+        mac_map[mac_str] = replacement_mac;
+      }
+
+      // Dump the string before the MAC and the fake MAC address into result.
+      result << pre_mac_str << replacement_mac;
+    }
+  }
+
+  // One last bit of data might still be in the input.
+  result << input;
+
+  // We'll just assign right back to kernel_dump.
+  *kernel_dump = result.str();
+}
+
+bool KernelCollector::DumpDirMounted() {
+  struct stat st_parent;
+  if (stat(kDumpParentPath, &st_parent)) {
+    PLOG(WARNING) << "Could not stat " << kDumpParentPath;
+    return false;
+  }
+
+  struct stat st_dump;
+  if (stat(kDumpPath, &st_dump)) {
+    PLOG(WARNING) << "Could not stat " << kDumpPath;
+    return false;
+  }
+
+  if (st_parent.st_dev == st_dump.st_dev) {
+    LOG(WARNING) << "Dump dir " << kDumpPath << " not mounted";
+    return false;
+  }
+
+  return true;
+}
+
+bool KernelCollector::Enable() {
+  if (arch_ == kArchUnknown || arch_ >= kArchCount ||
+      kPCRegex[arch_] == nullptr) {
+    LOG(WARNING) << "KernelCollector does not understand this architecture";
+    return false;
+  }
+
+  if (!DumpDirMounted()) {
+    LOG(WARNING) << "Kernel does not support crash dumping";
+    return false;
+  }
+
+  // To enable crashes, we will eventually need to set
+  // the chnv bit in BIOS, but it does not yet work.
+  LOG(INFO) << "Enabling kernel crash handling";
+  is_enabled_ = true;
+  return true;
+}
+
+// Hash a string to a number.  We define our own hash function to not
+// be dependent on a C++ library that might change.  This function
+// uses basically the same approach as tr1/functional_hash.h but with
+// a larger prime number (16127 vs 131).
+static unsigned HashString(const std::string &input) {
+  unsigned hash = 0;
+  for (size_t i = 0; i < input.length(); ++i)
+    hash = hash * 16127 + input[i];
+  return hash;
+}
+
+void KernelCollector::ProcessStackTrace(
+    pcrecpp::StringPiece kernel_dump,
+    bool print_diagnostics,
+    unsigned *hash,
+    float *last_stack_timestamp,
+    bool *is_watchdog_crash) {
+  pcrecpp::RE line_re("(.+)", pcrecpp::MULTILINE());
+  pcrecpp::RE stack_trace_start_re(std::string(kTimestampRegex) +
+        " (Call Trace|Backtrace):$");
+
+  // Match lines such as the following and grab out "function_name".
+  // The ? may or may not be present.
+  //
+  // For ARM:
+  // <4>[ 3498.731164] [<c0057220>] ? (function_name+0x20/0x2c) from
+  // [<c018062c>] (foo_bar+0xdc/0x1bc)
+  //
+  // For MIPS:
+  // <5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8
+  //
+  // For X86:
+  // <4>[ 6066.849504]  [<7937bcee>] ? function_name+0x66/0x6c
+  //
+  pcrecpp::RE stack_entry_re(std::string(kTimestampRegex) +
+    "\\s+\\[<[[:xdigit:]]+>\\]"      // Matches "  [<7937bcee>]"
+    "([\\s\\?(]+)"                   // Matches " ? (" (ARM) or " ? " (X86)
+    "([^\\+ )]+)");                  // Matches until delimiter reached
+  std::string line;
+  std::string hashable;
+  std::string previous_hashable;
+  bool is_watchdog = false;
+
+  *hash = 0;
+  *last_stack_timestamp = 0;
+
+  // Find the last and second-to-last stack traces.  The latter is used when
+  // the panic is from a watchdog timeout.
+  while (line_re.FindAndConsume(&kernel_dump, &line)) {
+    std::string certainty;
+    std::string function_name;
+    if (stack_trace_start_re.PartialMatch(line, last_stack_timestamp)) {
+      if (print_diagnostics) {
+        printf("Stack trace starting.%s\n",
+               hashable.empty() ? "" : "  Saving prior trace.");
+      }
+      previous_hashable = hashable;
+      hashable.clear();
+      is_watchdog = false;
+    } else if (stack_entry_re.PartialMatch(line,
+                                           last_stack_timestamp,
+                                           &certainty,
+                                           &function_name)) {
+      bool is_certain = certainty.find('?') == std::string::npos;
+      if (print_diagnostics) {
+        printf("@%f: stack entry for %s (%s)\n",
+               *last_stack_timestamp,
+               function_name.c_str(),
+               is_certain ? "certain" : "uncertain");
+      }
+      // Do not include any uncertain (prefixed by '?') frames in our hash.
+      if (!is_certain)
+        continue;
+      if (!hashable.empty())
+        hashable.append("|");
+      if (function_name == "watchdog_timer_fn" ||
+          function_name == "watchdog") {
+        is_watchdog = true;
+      }
+      hashable.append(function_name);
+    }
+  }
+
+  // If the last stack trace contains a watchdog function we assume the panic
+  // is from the watchdog timer, and we hash the previous stack trace rather
+  // than the last one, assuming that the previous stack is that of the hung
+  // thread.
+  //
+  // In addition, if the hashable is empty (meaning all frames are uncertain,
+  // for whatever reason) also use the previous frame, as it cannot be any
+  // worse.
+  if (is_watchdog || hashable.empty()) {
+    hashable = previous_hashable;
+  }
+
+  *hash = HashString(hashable);
+  *is_watchdog_crash = is_watchdog;
+
+  if (print_diagnostics) {
+    printf("Hash based on stack trace: \"%s\" at %f.\n",
+           hashable.c_str(), *last_stack_timestamp);
+  }
+}
+
+// static
+KernelCollector::ArchKind KernelCollector::GetCompilerArch() {
+#if defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)
+  return kArchArm;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY)
+  return kArchMips;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_64)
+  return kArchX86_64;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY)
+  return kArchX86;
+#else
+  return kArchUnknown;
+#endif
+}
+
+bool KernelCollector::FindCrashingFunction(
+  pcrecpp::StringPiece kernel_dump,
+  bool print_diagnostics,
+  float stack_trace_timestamp,
+  std::string *crashing_function) {
+  float timestamp = 0;
+
+  // Use the correct regex for this architecture.
+  pcrecpp::RE eip_re(std::string(kTimestampRegex) + kPCRegex[arch_],
+                     pcrecpp::MULTILINE());
+
+  while (eip_re.FindAndConsume(&kernel_dump, &timestamp, crashing_function)) {
+    if (print_diagnostics) {
+      printf("@%f: found crashing function %s\n",
+             timestamp,
+             crashing_function->c_str());
+    }
+  }
+  if (timestamp == 0) {
+    if (print_diagnostics) {
+      printf("Found no crashing function.\n");
+    }
+    return false;
+  }
+  if (stack_trace_timestamp != 0 &&
+      abs(static_cast<int>(stack_trace_timestamp - timestamp))
+        > kSignatureTimestampWindow) {
+    if (print_diagnostics) {
+      printf("Found crashing function but not within window.\n");
+    }
+    return false;
+  }
+  if (print_diagnostics) {
+    printf("Found crashing function %s\n", crashing_function->c_str());
+  }
+  return true;
+}
+
+bool KernelCollector::FindPanicMessage(pcrecpp::StringPiece kernel_dump,
+                                       bool print_diagnostics,
+                                       std::string *panic_message) {
+  // Match lines such as the following and grab out "Fatal exception"
+  // <0>[  342.841135] Kernel panic - not syncing: Fatal exception
+  pcrecpp::RE kernel_panic_re(std::string(kTimestampRegex) +
+                              " Kernel panic[^\\:]*\\:\\s*(.*)",
+                              pcrecpp::MULTILINE());
+  float timestamp = 0;
+  while (kernel_panic_re.FindAndConsume(&kernel_dump,
+                                        &timestamp,
+                                        panic_message)) {
+    if (print_diagnostics) {
+      printf("@%f: panic message %s\n",
+             timestamp,
+             panic_message->c_str());
+    }
+  }
+  if (timestamp == 0) {
+    if (print_diagnostics) {
+      printf("Found no panic message.\n");
+    }
+    return false;
+  }
+  return true;
+}
+
+bool KernelCollector::ComputeKernelStackSignature(
+    const std::string &kernel_dump,
+    std::string *kernel_signature,
+    bool print_diagnostics) {
+  unsigned stack_hash = 0;
+  float last_stack_timestamp = 0;
+  std::string human_string;
+  bool is_watchdog_crash;
+
+  ProcessStackTrace(kernel_dump,
+                    print_diagnostics,
+                    &stack_hash,
+                    &last_stack_timestamp,
+                    &is_watchdog_crash);
+
+  if (!FindCrashingFunction(kernel_dump,
+                            print_diagnostics,
+                            last_stack_timestamp,
+                            &human_string)) {
+    if (!FindPanicMessage(kernel_dump, print_diagnostics, &human_string)) {
+      if (print_diagnostics) {
+        printf("Found no human readable string, using empty string.\n");
+      }
+      human_string.clear();
+    }
+  }
+
+  if (human_string.empty() && stack_hash == 0) {
+    if (print_diagnostics) {
+      printf("Found neither a stack nor a human readable string, failing.\n");
+    }
+    return false;
+  }
+
+  human_string = human_string.substr(0, kMaxHumanStringLength);
+  *kernel_signature = StringPrintf("%s-%s%s-%08X",
+                                   kKernelExecName,
+                                   (is_watchdog_crash ? "(HANG)-" : ""),
+                                   human_string.c_str(),
+                                   stack_hash);
+  return true;
+}
+
+bool KernelCollector::Collect() {
+  std::string kernel_dump;
+  FilePath root_crash_directory;
+
+  if (!LoadParameters()) {
+    return false;
+  }
+  if (!LoadPreservedDump(&kernel_dump)) {
+    return false;
+  }
+  StripSensitiveData(&kernel_dump);
+  if (kernel_dump.empty()) {
+    return false;
+  }
+  std::string signature;
+  if (!ComputeKernelStackSignature(kernel_dump, &signature, false)) {
+    signature = kDefaultKernelStackSignature;
+  }
+
+  std::string reason = "handling";
+  bool feedback = true;
+  if (IsDeveloperImage()) {
+    reason = "developer build - always dumping";
+    feedback = true;
+  } else if (!is_feedback_allowed_function_()) {
+    reason = "ignoring - no consent";
+    feedback = false;
+  }
+
+  LOG(INFO) << "Received prior crash notification from "
+            << "kernel (signature " << signature << ") (" << reason << ")";
+
+  if (feedback) {
+    count_crash_function_();
+
+    if (!GetCreatedCrashDirectoryByEuid(kRootUid,
+                                        &root_crash_directory,
+                                        nullptr)) {
+      return true;
+    }
+
+    std::string dump_basename =
+        FormatDumpBasename(kKernelExecName, time(nullptr), kKernelPid);
+    FilePath kernel_crash_path = root_crash_directory.Append(
+        StringPrintf("%s.kcrash", dump_basename.c_str()));
+
+    // We must use WriteNewFile instead of base::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_dump.data(),
+                     kernel_dump.length()) !=
+        static_cast<int>(kernel_dump.length())) {
+      LOG(INFO) << "Failed to write kernel dump to "
+                << kernel_crash_path.value().c_str();
+      return true;
+    }
+
+    AddCrashMetaData(kKernelSignatureKey, signature);
+    WriteCrashMetaData(
+        root_crash_directory.Append(
+            StringPrintf("%s.meta", dump_basename.c_str())),
+        kKernelExecName,
+        kernel_crash_path.value());
+
+    LOG(INFO) << "Stored kcrash to " << kernel_crash_path.value();
+  }
+
+  return true;
+}
diff --git a/kernel_collector.h b/kernel_collector.h
new file mode 100644
index 0000000..206ee26
--- /dev/null
+++ b/kernel_collector.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_H_
+#define CRASH_REPORTER_KERNEL_COLLECTOR_H_
+
+#include <pcrecpp.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "crash_collector.h"
+
+// Kernel crash collector.
+class KernelCollector : public CrashCollector {
+ public:
+  // Enumeration to specify architecture type.
+  enum ArchKind {
+    kArchUnknown,
+    kArchArm,
+    kArchMips,
+    kArchX86,
+    kArchX86_64,
+
+    kArchCount  // Number of architectures.
+  };
+
+  KernelCollector();
+
+  ~KernelCollector() override;
+
+  void OverridePreservedDumpPath(const base::FilePath &file_path);
+
+  // Enable collection.
+  bool Enable();
+
+  // Returns true if the kernel collection currently enabled.
+  bool is_enabled() const { return is_enabled_; }
+
+  // Collect any preserved kernel crash dump. Returns true if there was
+  // a dump (even if there were problems storing the dump), false otherwise.
+  bool Collect();
+
+  // Compute a stack signature string from a kernel dump.
+  bool ComputeKernelStackSignature(const std::string &kernel_dump,
+                                   std::string *kernel_signature,
+                                   bool print_diagnostics);
+
+  // Set the architecture of the crash dumps we are looking at.
+  void set_arch(ArchKind arch) { arch_ = arch; }
+  ArchKind arch() const { return arch_; }
+
+ private:
+  friend class KernelCollectorTest;
+  FRIEND_TEST(KernelCollectorTest, LoadPreservedDump);
+  FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBasic);
+  FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBulk);
+  FRIEND_TEST(KernelCollectorTest, StripSensitiveDataSample);
+  FRIEND_TEST(KernelCollectorTest, CollectOK);
+
+  virtual bool DumpDirMounted();
+
+  bool LoadPreservedDump(std::string *contents);
+  void StripSensitiveData(std::string *kernel_dump);
+
+  void GetRamoopsRecordPath(base::FilePath *path, size_t record);
+  bool LoadParameters();
+  bool HasMoreRecords();
+
+  // Read a record to string, modified from file_utils since that didn't
+  // provide a way to restrict the read length.
+  // Return value indicates (only) error state:
+  //  * false when we get an error (can't read from dump location).
+  //  * true if no error occured.
+  // Not finding a valid record is not an error state and is signaled by the
+  // record_found output parameter.
+  bool ReadRecordToString(std::string *contents,
+                          size_t current_record,
+                          bool *record_found);
+
+  void ProcessStackTrace(pcrecpp::StringPiece kernel_dump,
+                         bool print_diagnostics,
+                         unsigned *hash,
+                         float *last_stack_timestamp,
+                         bool *is_watchdog_crash);
+  bool FindCrashingFunction(pcrecpp::StringPiece kernel_dump,
+                            bool print_diagnostics,
+                            float stack_trace_timestamp,
+                            std::string *crashing_function);
+  bool FindPanicMessage(pcrecpp::StringPiece kernel_dump,
+                        bool print_diagnostics,
+                        std::string *panic_message);
+
+  // Returns the architecture kind for which we are built.
+  static ArchKind GetCompilerArch();
+
+  bool is_enabled_;
+  base::FilePath ramoops_dump_path_;
+  size_t records_;
+
+  // The architecture of kernel dump strings we are working with.
+  ArchKind arch_;
+
+  DISALLOW_COPY_AND_ASSIGN(KernelCollector);
+};
+
+#endif  // CRASH_REPORTER_KERNEL_COLLECTOR_H_
diff --git a/kernel_collector_test.cc b/kernel_collector_test.cc
new file mode 100644
index 0000000..60fd832
--- /dev/null
+++ b/kernel_collector_test.cc
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kernel_collector_test.h"
+
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/syslog_logging.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using base::StringPrintf;
+using brillo::FindLog;
+using brillo::GetLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = false;
+
+void CountCrash() {
+  ++s_crashes;
+}
+
+bool IsMetrics() {
+  return s_metrics;
+}
+
+}  // namespace
+
+class KernelCollectorTest : public ::testing::Test {
+ protected:
+  void WriteStringToFile(const FilePath &file_path,
+                         const char *data) {
+    unsigned int numBytesWritten =
+        base::WriteFile(file_path, data, strlen(data));
+    ASSERT_EQ(strlen(data), numBytesWritten);
+  }
+
+  void SetUpSuccessfulCollect();
+  void ComputeKernelStackSignatureCommon();
+
+  const FilePath &kcrash_file() const { return test_kcrash_; }
+  const FilePath &test_crash_directory() const { return test_crash_directory_; }
+
+  KernelCollectorMock collector_;
+
+ private:
+  void SetUp() override {
+    s_crashes = 0;
+    s_metrics = true;
+
+    EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+    collector_.Initialize(CountCrash, IsMetrics);
+    ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+    test_kcrash_ = scoped_temp_dir_.path().Append("kcrash");
+    ASSERT_TRUE(base::CreateDirectory(test_kcrash_));
+    collector_.OverridePreservedDumpPath(test_kcrash_);
+
+    test_kcrash_ = test_kcrash_.Append("dmesg-ramoops-0");
+    ASSERT_FALSE(base::PathExists(test_kcrash_));
+
+    test_crash_directory_ = scoped_temp_dir_.path().Append("crash_directory");
+    ASSERT_TRUE(base::CreateDirectory(test_crash_directory_));
+    brillo::ClearLog();
+  }
+
+  FilePath test_kcrash_;
+  FilePath test_crash_directory_;
+  base::ScopedTempDir scoped_temp_dir_;
+};
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureBase) {
+  // Make sure the normal build architecture is detected
+  EXPECT_NE(KernelCollector::kArchUnknown, collector_.arch());
+}
+
+TEST_F(KernelCollectorTest, LoadPreservedDump) {
+  ASSERT_FALSE(base::PathExists(kcrash_file()));
+  std::string dump;
+  dump.clear();
+
+  WriteStringToFile(kcrash_file(),
+      "CrashRecordWithoutRamoopsHeader\n<6>[    0.078852]");
+  ASSERT_TRUE(collector_.LoadParameters());
+  ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
+  ASSERT_EQ("CrashRecordWithoutRamoopsHeader\n<6>[    0.078852]", dump);
+
+  WriteStringToFile(kcrash_file(), "====1.1\nsomething");
+  ASSERT_TRUE(collector_.LoadParameters());
+  ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
+  ASSERT_EQ("something", dump);
+
+  WriteStringToFile(kcrash_file(), "\x01\x02\xfe\xff random blob");
+  ASSERT_TRUE(collector_.LoadParameters());
+  ASSERT_FALSE(collector_.LoadPreservedDump(&dump));
+  ASSERT_EQ("", dump);
+}
+
+TEST_F(KernelCollectorTest, EnableMissingKernel) {
+  ASSERT_FALSE(collector_.Enable());
+  ASSERT_FALSE(collector_.is_enabled());
+  ASSERT_TRUE(FindLog(
+      "Kernel does not support crash dumping"));
+  ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(KernelCollectorTest, EnableOK) {
+  WriteStringToFile(kcrash_file(), "");
+  EXPECT_CALL(collector_, DumpDirMounted()).WillOnce(::testing::Return(true));
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(collector_.is_enabled());
+  ASSERT_TRUE(FindLog("Enabling kernel crash handling"));
+  ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataBasic) {
+  // Basic tests of StripSensitiveData...
+
+  // Make sure we work OK with a string w/ no MAC addresses.
+  const std::string kCrashWithNoMacsOrig =
+      "<7>[111566.131728] PM: Entering mem sleep\n";
+  std::string crash_with_no_macs(kCrashWithNoMacsOrig);
+  collector_.StripSensitiveData(&crash_with_no_macs);
+  EXPECT_EQ(kCrashWithNoMacsOrig, crash_with_no_macs);
+
+  // Make sure that we handle the case where there's nothing before/after the
+  // MAC address.
+  const std::string kJustAMacOrig =
+      "11:22:33:44:55:66";
+  const std::string kJustAMacStripped =
+      "00:00:00:00:00:01";
+  std::string just_a_mac(kJustAMacOrig);
+  collector_.StripSensitiveData(&just_a_mac);
+  EXPECT_EQ(kJustAMacStripped, just_a_mac);
+
+  // Test MAC addresses crammed together to make sure it gets both of them.
+  //
+  // I'm not sure that the code does ideal on these two test cases (they don't
+  // look like two MAC addresses to me), but since we don't see them I think
+  // it's OK to behave as shown here.
+  const std::string kCrammedMacs1Orig =
+      "11:22:33:44:55:66:11:22:33:44:55:66";
+  const std::string kCrammedMacs1Stripped =
+      "00:00:00:00:00:01:00:00:00:00:00:01";
+  std::string crammed_macs_1(kCrammedMacs1Orig);
+  collector_.StripSensitiveData(&crammed_macs_1);
+  EXPECT_EQ(kCrammedMacs1Stripped, crammed_macs_1);
+
+  const std::string kCrammedMacs2Orig =
+      "11:22:33:44:55:6611:22:33:44:55:66";
+  const std::string kCrammedMacs2Stripped =
+      "00:00:00:00:00:0100:00:00:00:00:01";
+  std::string crammed_macs_2(kCrammedMacs2Orig);
+  collector_.StripSensitiveData(&crammed_macs_2);
+  EXPECT_EQ(kCrammedMacs2Stripped, crammed_macs_2);
+
+  // Test case-sensitiveness (we shouldn't be case-senstive).
+  const std::string kCapsMacOrig =
+      "AA:BB:CC:DD:EE:FF";
+  const std::string kCapsMacStripped =
+      "00:00:00:00:00:01";
+  std::string caps_mac(kCapsMacOrig);
+  collector_.StripSensitiveData(&caps_mac);
+  EXPECT_EQ(kCapsMacStripped, caps_mac);
+
+  const std::string kLowerMacOrig =
+      "aa:bb:cc:dd:ee:ff";
+  const std::string kLowerMacStripped =
+      "00:00:00:00:00:01";
+  std::string lower_mac(kLowerMacOrig);
+  collector_.StripSensitiveData(&lower_mac);
+  EXPECT_EQ(kLowerMacStripped, lower_mac);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataBulk) {
+  // Test calling StripSensitiveData w/ lots of MAC addresses in the "log".
+
+  // Test that stripping code handles more than 256 unique MAC addresses, since
+  // that overflows past the last byte...
+  // We'll write up some code that generates 258 unique MAC addresses.  Sorta
+  // cheating since the code is very similar to the current code in
+  // StripSensitiveData(), but would catch if someone changed that later.
+  std::string lotsa_macs_orig;
+  std::string lotsa_macs_stripped;
+  int i;
+  for (i = 0; i < 258; i++) {
+    lotsa_macs_orig += StringPrintf(" 11:11:11:11:%02X:%02x",
+                                  (i & 0xff00) >> 8, i & 0x00ff);
+    lotsa_macs_stripped += StringPrintf(" 00:00:00:00:%02X:%02x",
+                                     ((i+1) & 0xff00) >> 8, (i+1) & 0x00ff);
+  }
+  std::string lotsa_macs(lotsa_macs_orig);
+  collector_.StripSensitiveData(&lotsa_macs);
+  EXPECT_EQ(lotsa_macs_stripped, lotsa_macs);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataSample) {
+  // Test calling StripSensitiveData w/ some actual lines from a real crash;
+  // included two MAC addresses (though replaced them with some bogusness).
+  const std::string kCrashWithMacsOrig =
+      "<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
+        " filtered out\n"
+      "<7>[108539.540144] wlan0: authenticate with 11:22:33:44:55:66 (try 1)\n"
+      "<7>[108539.554973] wlan0: associate with 11:22:33:44:55:66 (try 1)\n"
+      "<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+        " QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
+      "<7>[110964.314648] wlan0: deauthenticated from 11:22:33:44:55:66"
+        " (Reason: 6)\n"
+      "<7>[110964.325057] phy0: Removed STA 11:22:33:44:55:66\n"
+      "<7>[110964.325115] phy0: Destroyed STA 11:22:33:44:55:66\n"
+      "<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+        " QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
+      "<7>[111566.131728] PM: Entering mem sleep\n";
+  const std::string kCrashWithMacsStripped =
+      "<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
+        " filtered out\n"
+      "<7>[108539.540144] wlan0: authenticate with 00:00:00:00:00:01 (try 1)\n"
+      "<7>[108539.554973] wlan0: associate with 00:00:00:00:00:01 (try 1)\n"
+      "<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+        " QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
+      "<7>[110964.314648] wlan0: deauthenticated from 00:00:00:00:00:01"
+        " (Reason: 6)\n"
+      "<7>[110964.325057] phy0: Removed STA 00:00:00:00:00:01\n"
+      "<7>[110964.325115] phy0: Destroyed STA 00:00:00:00:00:01\n"
+      "<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+        " QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
+      "<7>[111566.131728] PM: Entering mem sleep\n";
+  std::string crash_with_macs(kCrashWithMacsOrig);
+  collector_.StripSensitiveData(&crash_with_macs);
+  EXPECT_EQ(kCrashWithMacsStripped, crash_with_macs);
+}
+
+TEST_F(KernelCollectorTest, CollectPreservedFileMissing) {
+  ASSERT_FALSE(collector_.Collect());
+  ASSERT_FALSE(FindLog("Stored kcrash to "));
+  ASSERT_EQ(0, s_crashes);
+}
+
+void KernelCollectorTest::SetUpSuccessfulCollect() {
+  collector_.ForceCrashDirectory(test_crash_directory());
+  WriteStringToFile(kcrash_file(), "====1.1\nsomething");
+  ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(KernelCollectorTest, CollectOptedOut) {
+  SetUpSuccessfulCollect();
+  s_metrics = false;
+  ASSERT_TRUE(collector_.Collect());
+  ASSERT_TRUE(FindLog("(ignoring - no consent)"));
+  ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(KernelCollectorTest, CollectOK) {
+  SetUpSuccessfulCollect();
+  ASSERT_TRUE(collector_.Collect());
+  ASSERT_EQ(1, s_crashes);
+  ASSERT_TRUE(FindLog("(handling)"));
+  static const char kNamePrefix[] = "Stored kcrash to ";
+  std::string log = brillo::GetLog();
+  size_t pos = log.find(kNamePrefix);
+  ASSERT_NE(std::string::npos, pos)
+      << "Did not find string \"" << kNamePrefix << "\" in log: {\n"
+      << log << "}";
+  pos += strlen(kNamePrefix);
+  std::string filename = log.substr(pos, std::string::npos);
+  // Take the name up until \n
+  size_t end_pos = filename.find_first_of("\n");
+  ASSERT_NE(std::string::npos, end_pos);
+  filename = filename.substr(0, end_pos);
+  ASSERT_EQ(0U, filename.find(test_crash_directory().value()));
+  ASSERT_TRUE(base::PathExists(FilePath(filename)));
+  std::string contents;
+  ASSERT_TRUE(base::ReadFileToString(FilePath(filename), &contents));
+  ASSERT_EQ("something", contents);
+}
+
+// Perform tests which are common across architectures
+void KernelCollectorTest::ComputeKernelStackSignatureCommon() {
+  std::string signature;
+
+  const char kStackButNoPC[] =
+      "<4>[ 6066.829029]  [<790340af>] __do_softirq+0xa6/0x143\n";
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kStackButNoPC, &signature, false));
+  EXPECT_EQ("kernel--83615F0A", signature);
+
+  const char kMissingEverything[] =
+      "<4>[ 6066.829029]  [<790340af>] ? __do_softirq+0xa6/0x143\n";
+  EXPECT_FALSE(
+      collector_.ComputeKernelStackSignature(kMissingEverything,
+                                             &signature,
+                                             false));
+
+  // Long message.
+  const char kTruncatedMessage[] =
+      "<0>[   87.485611] Kernel panic - not syncing: 01234567890123456789"
+          "01234567890123456789X\n";
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kTruncatedMessage,
+                                             &signature,
+                                             false));
+  EXPECT_EQ("kernel-0123456789012345678901234567890123456789-00000000",
+            signature);
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureARM) {
+  const char kBugToPanic[] =
+      "<5>[  123.412524] Modules linked in:\n"
+      "<5>[  123.412534] CPU: 0    Tainted: G        W    "
+          "(2.6.37-01030-g51cee64 #153)\n"
+      "<5>[  123.412552] PC is at write_breakme+0xd0/0x1b4\n"
+      "<5>[  123.412560] LR is at write_breakme+0xc8/0x1b4\n"
+      "<5>[  123.412569] pc : [<c0058220>]    lr : [<c005821c>]    "
+          "psr: 60000013\n"
+      "<5>[  123.412574] sp : f4e0ded8  ip : c04d104c  fp : 000e45e0\n"
+      "<5>[  123.412581] r10: 400ff000  r9 : f4e0c000  r8 : 00000004\n"
+      "<5>[  123.412589] r7 : f4e0df80  r6 : f4820c80  r5 : 00000004  "
+          "r4 : f4e0dee8\n"
+      "<5>[  123.412598] r3 : 00000000  r2 : f4e0decc  r1 : c05f88a9  "
+          "r0 : 00000039\n"
+      "<5>[  123.412608] Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA "
+          "ARM  Segment user\n"
+      "<5>[  123.412617] Control: 10c53c7d  Table: 34dcc04a  DAC: 00000015\n"
+      "<0>[  123.412626] Process bash (pid: 1014, stack limit = 0xf4e0c2f8)\n"
+      "<0>[  123.412634] Stack: (0xf4e0ded8 to 0xf4e0e000)\n"
+      "<0>[  123.412641] dec0:                                              "
+          "         f4e0dee8 c0183678\n"
+      "<0>[  123.412654] dee0: 00000000 00000000 00677562 0000081f c06a6a78 "
+          "400ff000 f4e0dfb0 00000000\n"
+      "<0>[  123.412666] df00: bec7ab44 000b1719 bec7ab0c c004f498 bec7a314 "
+          "c024acc8 00000001 c018359c\n"
+      "<0>[  123.412679] df20: f4e0df34 c04d10fc f5803c80 271beb39 000e45e0 "
+          "f5803c80 c018359c c017bfe0\n"
+      "<0>[  123.412691] df40: 00000004 f4820c80 400ff000 f4e0df80 00000004 "
+          "f4e0c000 00000000 c01383e4\n"
+      "<0>[  123.412703] df60: f4820c80 400ff000 f4820c80 400ff000 00000000 "
+          "00000000 00000004 c0138578\n"
+      "<0>[  123.412715] df80: 00000000 00000000 00000004 00000000 00000004 "
+          "402f95d0 00000004 00000004\n"
+      "<0>[  123.412727] dfa0: c0054984 c00547c0 00000004 402f95d0 00000001 "
+          "400ff000 00000004 00000000\n"
+      "<0>[  123.412739] dfc0: 00000004 402f95d0 00000004 00000004 400ff000 "
+          "000c194c bec7ab58 000e45e0\n"
+      "<0>[  123.412751] dfe0: 00000000 bec7aad8 40232520 40284e9c 60000010 "
+          "00000001 00000000 00000000\n"
+      "<5>[   39.496577] Backtrace:\n"
+      "<5>[  123.412782] [<c0058220>] (__bug+0x20/0x2c) from [<c0183678>] "
+          "(write_breakme+0xdc/0x1bc)\n"
+      "<5>[  123.412798] [<c0183678>] (write_breakme+0xdc/0x1bc) from "
+          "[<c017bfe0>] (proc_reg_write+0x88/0x9c)\n";
+  std::string signature;
+
+  collector_.set_arch(KernelCollector::kArchArm);
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+  EXPECT_EQ("kernel-write_breakme-97D3E92F", signature);
+
+  ComputeKernelStackSignatureCommon();
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureMIPS) {
+  const char kBugToPanic[] =
+      "<5>[ 3378.472000] lkdtm: Performing direct entry BUG\n"
+      "<5>[ 3378.476000] Kernel bug detected[#1]:\n"
+      "<5>[ 3378.484000] CPU: 0 PID: 185 Comm: dash Not tainted 3.14.0 #1\n"
+      "<5>[ 3378.488000] task: 8fed5220 ti: 8ec4a000 task.ti: 8ec4a000\n"
+      "<5>[ 3378.496000] $ 0   : 00000000 804018b8 804010f0 7785b507\n"
+      "<5>[ 3378.500000] $ 4   : 8061ab64 81204478 81205b20 00000000\n"
+      "<5>[ 3378.508000] $ 8   : 80830000 20746365 72746e65 55422079\n"
+      "<5>[ 3378.512000] $12   : 8ec4be94 000000fc 00000000 00000048\n"
+      "<5>[ 3378.520000] $16   : 00000004 8ef54000 80710000 00000002\n"
+      "<5>[ 3378.528000] $20   : 7765b6d4 00000004 7fffffff 00000002\n"
+      "<5>[ 3378.532000] $24   : 00000001 803dc0dc                  \n"
+      "<5>[ 3378.540000] $28   : 8ec4a000 8ec4be20 7775438d 804018b8\n"
+      "<5>[ 3378.544000] Hi    : 00000000\n"
+      "<5>[ 3378.548000] Lo    : 49bf8080\n"
+      "<5>[ 3378.552000] epc   : 804010f0 lkdtm_do_action+0x68/0x3f8\n"
+      "<5>[ 3378.560000]     Not tainted\n"
+      "<5>[ 3378.564000] ra    : 804018b8 direct_entry+0x110/0x154\n"
+      "<5>[ 3378.568000] Status: 3100dc03 KERNEL EXL IE \n"
+      "<5>[ 3378.572000] Cause : 10800024\n"
+      "<5>[ 3378.576000] PrId  : 0001a120 (MIPS interAptiv (multi))\n"
+      "<5>[ 3378.580000] Modules linked in: uinput cfg80211 nf_conntrack_ipv6 "
+          "nf_defrag_ipv6 ip6table_filter ip6_tables pcnet32 mii fuse "
+          "ppp_async ppp_generic slhc tun\n"
+      "<5>[ 3378.600000] Process dash (pid: 185, threadinfo=8ec4a000, "
+          "task=8fed5220, tls=77632490)\n"
+      "<5>[ 3378.608000] Stack : 00000006 ffffff9c 00000000 00000000 00000000 "
+          "00000000 8083454a 00000022\n"
+      "<5>          7765baa1 00001fee 80710000 8ef54000 8ec4bf08 00000002 "
+          "7765b6d4 00000004\n"
+      "<5>          7fffffff 00000002 7775438d 805e5158 7fffffff 00000002 "
+          "00000000 7785b507\n"
+      "<5>          806a96bc 00000004 8ef54000 8ec4bf08 00000002 804018b8 "
+          "80710000 806a98bc\n"
+      "<5>          00000002 00000020 00000004 8d515600 77756450 00000004 "
+          "8ec4bf08 802377e4\n"
+      "<5>          ...\n"
+      "<5>[ 3378.652000] Call Trace:\n"
+      "<5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8\n"
+      "<5>[ 3378.660000] [<804018b8>] direct_entry+0x110/0x154\n"
+      "<5>[ 3378.664000] [<802377e4>] vfs_write+0xe0/0x1bc\n"
+      "<5>[ 3378.672000] [<80237f90>] SyS_write+0x78/0xf8\n"
+      "<5>[ 3378.676000] [<80111888>] handle_sys+0x128/0x14c\n"
+      "<5>[ 3378.680000] \n"
+      "<5>[ 3378.684000] \n"
+      "<5>Code: 3c04806b  0c1793aa  248494f0 <000c000d> 3c04806b  248494fc  "
+          "0c04cc7f  2405017a  08100514 \n"
+      "<5>[ 3378.696000] ---[ end trace 75067432f24bbc93 ]---\n";
+  std::string signature;
+
+  collector_.set_arch(KernelCollector::kArchMips);
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+  EXPECT_EQ("kernel-lkdtm_do_action-5E600A6B", signature);
+
+  ComputeKernelStackSignatureCommon();
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureX86) {
+  const char kBugToPanic[] =
+      "<4>[ 6066.829029]  [<79039d16>] ? run_timer_softirq+0x165/0x1e6\n"
+      "<4>[ 6066.829029]  [<790340af>] ignore_old_stack+0xa6/0x143\n"
+      "<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+"
+          "0xa3/0xb5 [mac80211] SS:ESP 0068:7951febc\n"
+      "<0>[ 6066.829029] CR2: 00000000323038a7\n"
+      "<4>[ 6066.845422] ---[ end trace 12b058bb46c43500 ]---\n"
+      "<0>[ 6066.845747] Kernel panic - not syncing: Fatal exception "
+          "in interrupt\n"
+      "<0>[ 6066.846902] Call Trace:\n"
+      "<4>[ 6066.846902]  [<7937a07b>] ? printk+0x14/0x19\n"
+      "<4>[ 6066.949779]  [<79379fc1>] panic+0x3e/0xe4\n"
+      "<4>[ 6066.949971]  [<7937c5c5>] oops_end+0x73/0x81\n"
+      "<4>[ 6066.950208]  [<7901b260>] no_context+0x10d/0x117\n";
+  std::string signature;
+
+  collector_.set_arch(KernelCollector::kArchX86);
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+  EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-DE253569", signature);
+
+  const char kPCButNoStack[] =
+      "<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+";
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kPCButNoStack, &signature, false));
+  EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-00000000", signature);
+
+  const char kBreakmeBug[] =
+      "<4>[  180.492137]  [<790970c6>] ? handle_mm_fault+0x67f/0x96d\n"
+      "<4>[  180.492137]  [<790dcdfe>] ? proc_reg_write+0x5f/0x73\n"
+      "<4>[  180.492137]  [<790e2224>] ? write_breakme+0x0/0x108\n"
+      "<4>[  180.492137]  [<790dcd9f>] ? proc_reg_write+0x0/0x73\n"
+      "<4>[  180.492137]  [<790ac0aa>] vfs_write+0x85/0xe4\n"
+      "<0>[  180.492137] Code: c6 44 05 b2 00 89 d8 e8 0c ef 09 00 85 c0 75 "
+      "0b c7 00 00 00 00 00 e9 8e 00 00 00 ba e6 75 4b 79 89 d8 e8 f1 ee 09 "
+      "00 85 c0 75 04 <0f> 0b eb fe ba 58 47 49 79 89 d8 e8 dd ee 09 00 85 "
+      "c0 75 0a 68\n"
+      "<0>[  180.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
+          "0068:aa3e9efc\n"
+      "<4>[  180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
+      "<0>[  180.502026] Kernel panic - not syncing: Fatal exception\n"
+      "<4>[  180.502026] Call Trace:\n"
+      "<4>[  180.502806]  [<79379aba>] ? printk+0x14/0x1a\n"
+      "<4>[  180.503033]  [<79379a00>] panic+0x3e/0xe4\n"
+      "<4>[  180.503287]  [<7937c005>] oops_end+0x73/0x81\n"
+      "<4>[  180.503520]  [<790055dd>] die+0x58/0x5e\n"
+      "<4>[  180.503538]  [<7937b96c>] do_trap+0x8e/0xa7\n"
+      "<4>[  180.503555]  [<79003d70>] ? do_invalid_op+0x0/0x80\n";
+
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kBreakmeBug, &signature, false));
+  EXPECT_EQ("kernel-write_breakme-122AB3CD", signature);
+
+  const char kPCLineTooOld[] =
+      "<4>[  174.492137]  [<790970c6>] ignored_function+0x67f/0x96d\n"
+      "<4>[  175.492137]  [<790970c6>] ignored_function2+0x67f/0x96d\n"
+      "<0>[  174.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
+          "0068:aa3e9efc\n"
+      "<4>[  180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
+      "<4>[  180.502026] Call Trace:\n"
+      "<0>[  180.502026] Kernel panic - not syncing: Fatal exception\n"
+      "<4>[  180.502806]  [<79379aba>] printk+0x14/0x1a\n";
+
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kPCLineTooOld, &signature, false));
+  EXPECT_EQ("kernel-Fatal exception-ED4C84FE", signature);
+
+  // Panic without EIP line.
+  const char kExamplePanicOnly[] =
+      "<0>[   87.485611] Kernel panic - not syncing: Testing panic\n"
+      "<4>[   87.485630] Pid: 2825, comm: bash Tainted: G         "
+          "C 2.6.32.23+drm33.10 #1\n"
+      "<4>[   87.485639] Call Trace:\n"
+      "<4>[   87.485660]  [<8133f71d>] ? printk+0x14/0x17\n"
+      "<4>[   87.485674]  [<8133f663>] panic+0x3e/0xe4\n"
+      "<4>[   87.485689]  [<810d062e>] write_breakme+0xaa/0x124\n";
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kExamplePanicOnly,
+                                             &signature,
+                                             false));
+  EXPECT_EQ("kernel-Testing panic-E0FC3552", signature);
+
+  // Panic from hung task.
+  const char kHungTaskBreakMe[] =
+      "<3>[  720.459157] INFO: task bash:2287 blocked blah blah\n"
+      "<5>[  720.459282] Call Trace:\n"
+      "<5>[  720.459307]  [<810a457b>] ? __dentry_open+0x186/0x23e\n"
+      "<5>[  720.459323]  [<810b9c71>] ? mntput_no_expire+0x29/0xe2\n"
+      "<5>[  720.459336]  [<810b9d48>] ? mntput+0x1e/0x20\n"
+      "<5>[  720.459350]  [<810ad135>] ? path_put+0x1a/0x1d\n"
+      "<5>[  720.459366]  [<8137cacc>] schedule+0x4d/0x4f\n"
+      "<5>[  720.459379]  [<8137ccfb>] schedule_timeout+0x26/0xaf\n"
+      "<5>[  720.459394]  [<8102127e>] ? should_resched+0xd/0x27\n"
+      "<5>[  720.459409]  [<81174d1f>] ? _copy_from_user+0x3c/0x50\n"
+      "<5>[  720.459423]  [<8137cd9e>] "
+      "schedule_timeout_uninterruptible+0x1a/0x1c\n"
+      "<5>[  720.459438]  [<810dee63>] write_breakme+0xb3/0x178\n"
+      "<5>[  720.459453]  [<810dedb0>] ? meminfo_proc_show+0x2f2/0x2f2\n"
+      "<5>[  720.459467]  [<810d94ae>] proc_reg_write+0x6d/0x87\n"
+      "<5>[  720.459481]  [<810d9441>] ? proc_reg_poll+0x76/0x76\n"
+      "<5>[  720.459493]  [<810a5e9e>] vfs_write+0x79/0xa5\n"
+      "<5>[  720.459505]  [<810a6011>] sys_write+0x40/0x65\n"
+      "<5>[  720.459519]  [<8137e677>] sysenter_do_call+0x12/0x26\n"
+      "<0>[  720.459530] Kernel panic - not syncing: hung_task: blocked tasks\n"
+      "<5>[  720.459768] Pid: 31, comm: khungtaskd Tainted: "
+      "G         C  3.0.8 #1\n"
+      "<5>[  720.459998] Call Trace:\n"
+      "<5>[  720.460140]  [<81378a35>] panic+0x53/0x14a\n"
+      "<5>[  720.460312]  [<8105f875>] watchdog+0x15b/0x1a0\n"
+      "<5>[  720.460495]  [<8105f71a>] ? hung_task_panic+0x16/0x16\n"
+      "<5>[  720.460693]  [<81043af3>] kthread+0x67/0x6c\n"
+      "<5>[  720.460862]  [<81043a8c>] ? __init_kthread_worker+0x2d/0x2d\n"
+      "<5>[  720.461106]  [<8137eb9e>] kernel_thread_helper+0x6/0x10\n";
+
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kHungTaskBreakMe,
+                                             &signature,
+                                             false));
+
+  EXPECT_EQ("kernel-(HANG)-hung_task: blocked tasks-600B37EA", signature);
+
+  // Panic with all question marks in the last stack trace.
+  const char kUncertainStackTrace[] =
+      "<0>[56279.689669] ------------[ cut here ]------------\n"
+      "<2>[56279.689677] kernel BUG at /build/x86-alex/tmp/portage/"
+      "sys-kernel/chromeos-kernel-0.0.1-r516/work/chromeos-kernel-0.0.1/"
+      "kernel/timer.c:844!\n"
+      "<0>[56279.689683] invalid opcode: 0000 [#1] SMP \n"
+      "<0>[56279.689688] last sysfs file: /sys/power/state\n"
+      "<5>[56279.689692] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat "
+      "gobi usbnet tsl2583(C) industrialio(C) snd_hda_codec_realtek "
+      "snd_hda_intel i2c_dev snd_hda_codec snd_hwdep qcserial snd_pcm usb_wwan "
+      "i2c_i801 snd_timer nm10_gpio snd_page_alloc rtc_cmos fuse "
+      "nf_conntrack_ipv6 nf_defrag_ipv6 uvcvideo videodev ip6table_filter "
+      "ath9k ip6_tables ipv6 mac80211 ath9k_common ath9k_hw ath cfg80211 "
+      "xt_mark\n"
+      "<5>[56279.689731] \n"
+      "<5>[56279.689738] Pid: 24607, comm: powerd_suspend Tainted: G        "
+      "WC  2.6.38.3+ #1 SAMSUNG ELECTRONICS CO., LTD. Alex/G100          \n"
+      "<5>[56279.689748] EIP: 0060:[<8103e3ea>] EFLAGS: 00210286 CPU: 3\n"
+      "<5>[56279.689758] EIP is at add_timer+0xd/0x1b\n"
+      "<5>[56279.689762] EAX: f5e00684 EBX: f5e003c0 ECX: 00000002 EDX: "
+      "00200246\n"
+      "<5>[56279.689767] ESI: f5e003c0 EDI: d28bc03c EBP: d2be5e40 ESP: "
+      "d2be5e40\n"
+      "<5>[56279.689772]  DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068\n"
+      "<0>[56279.689778] Process powerd_suspend (pid: 24607, ti=d2be4000 "
+      "task=f5dc9b60 task.ti=d2be4000)\n"
+      "<0>[56279.689782] Stack:\n"
+      "<5>[56279.689785]  d2be5e4c f8dccced f4ac02c0 d2be5e70 f8ddc752 "
+      "f5e003c0 f4ac0458 f4ac092c\n"
+      "<5>[56279.689797]  f4ac043c f4ac02c0 f4ac0000 f4ac007c d2be5e7c "
+      "f8dd4a33 f4ac0164 d2be5e94\n"
+      "<5>[56279.689809]  f87e0304 f69ff0cc f4ac0164 f87e02a4 f4ac0164 "
+      "d2be5eb0 81248968 00000000\n"
+      "<0>[56279.689821] Call Trace:\n"
+      "<5>[56279.689840]  [<f8dccced>] ieee80211_sta_restart+0x25/0x8c "
+      "[mac80211]\n"
+      "<5>[56279.689854]  [<f8ddc752>] ieee80211_reconfig+0x2e9/0x339 "
+      "[mac80211]\n"
+      "<5>[56279.689869]  [<f8dd4a33>] ieee80211_aes_cmac+0x182d/0x184e "
+      "[mac80211]\n"
+      "<5>[56279.689883]  [<f87e0304>] cfg80211_get_dev_from_info+0x29b/0x2c0 "
+      "[cfg80211]\n"
+      "<5>[56279.689895]  [<f87e02a4>] ? "
+      "cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
+      "<5>[56279.689904]  [<81248968>] legacy_resume+0x25/0x5d\n"
+      "<5>[56279.689910]  [<812490ae>] device_resume+0xdd/0x110\n"
+      "<5>[56279.689917]  [<812491c2>] dpm_resume_end+0xe1/0x271\n"
+      "<5>[56279.689925]  [<81060481>] suspend_devices_and_enter+0x18b/0x1de\n"
+      "<5>[56279.689932]  [<810605ba>] enter_state+0xe6/0x132\n"
+      "<5>[56279.689939]  [<8105fd4b>] state_store+0x91/0x9d\n"
+      "<5>[56279.689945]  [<8105fcba>] ? state_store+0x0/0x9d\n"
+      "<5>[56279.689953]  [<81178fb1>] kobj_attr_store+0x16/0x22\n"
+      "<5>[56279.689961]  [<810eea5e>] sysfs_write_file+0xc1/0xec\n"
+      "<5>[56279.689969]  [<810af443>] vfs_write+0x8f/0x101\n"
+      "<5>[56279.689975]  [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
+      "<5>[56279.689982]  [<810af556>] sys_write+0x40/0x65\n"
+      "<5>[56279.689989]  [<81002d57>] sysenter_do_call+0x12/0x26\n"
+      "<0>[56279.689993] Code: c1 d3 e2 4a 89 55 f4 f7 d2 21 f2 6a 00 31 c9 89 "
+      "d8 e8 6e fd ff ff 5a 8d 65 f8 5b 5e 5d c3 55 89 e5 3e 8d 74 26 00 83 38 "
+      "00 74 04 <0f> 0b eb fe 8b 50 08 e8 6f ff ff ff 5d c3 55 89 e5 3e 8d 74 "
+      "26 \n"
+      "<0>[56279.690009] EIP: [<8103e3ea>] add_timer+0xd/0x1b SS:ESP "
+      "0068:d2be5e40\n"
+      "<4>[56279.690113] ---[ end trace b71141bb67c6032a ]---\n"
+      "<7>[56279.694069] wlan0: deauthenticated from 00:00:00:00:00:01 "
+      "(Reason: 6)\n"
+      "<0>[56279.703465] Kernel panic - not syncing: Fatal exception\n"
+      "<5>[56279.703471] Pid: 24607, comm: powerd_suspend Tainted: G      D "
+      "WC  2.6.38.3+ #1\n"
+      "<5>[56279.703475] Call Trace:\n"
+      "<5>[56279.703483]  [<8136648c>] ? panic+0x55/0x152\n"
+      "<5>[56279.703491]  [<810057fa>] ? oops_end+0x73/0x81\n"
+      "<5>[56279.703497]  [<81005a44>] ? die+0xed/0xf5\n"
+      "<5>[56279.703503]  [<810033cb>] ? do_trap+0x7a/0x80\n"
+      "<5>[56279.703509]  [<8100369b>] ? do_invalid_op+0x0/0x80\n"
+      "<5>[56279.703515]  [<81003711>] ? do_invalid_op+0x76/0x80\n"
+      "<5>[56279.703522]  [<8103e3ea>] ? add_timer+0xd/0x1b\n"
+      "<5>[56279.703529]  [<81025e23>] ? check_preempt_curr+0x2e/0x69\n"
+      "<5>[56279.703536]  [<8102ef28>] ? ttwu_post_activation+0x5a/0x11b\n"
+      "<5>[56279.703543]  [<8102fa8d>] ? try_to_wake_up+0x213/0x21d\n"
+      "<5>[56279.703550]  [<81368b7f>] ? error_code+0x67/0x6c\n"
+      "<5>[56279.703557]  [<8103e3ea>] ? add_timer+0xd/0x1b\n"
+      "<5>[56279.703577]  [<f8dccced>] ? ieee80211_sta_restart+0x25/0x8c "
+      "[mac80211]\n"
+      "<5>[56279.703591]  [<f8ddc752>] ? ieee80211_reconfig+0x2e9/0x339 "
+      "[mac80211]\n"
+      "<5>[56279.703605]  [<f8dd4a33>] ? ieee80211_aes_cmac+0x182d/0x184e "
+      "[mac80211]\n"
+      "<5>[56279.703618]  [<f87e0304>] ? "
+      "cfg80211_get_dev_from_info+0x29b/0x2c0 [cfg80211]\n"
+      "<5>[56279.703630]  [<f87e02a4>] ? "
+      "cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
+      "<5>[56279.703637]  [<81248968>] ? legacy_resume+0x25/0x5d\n"
+      "<5>[56279.703643]  [<812490ae>] ? device_resume+0xdd/0x110\n"
+      "<5>[56279.703649]  [<812491c2>] ? dpm_resume_end+0xe1/0x271\n"
+      "<5>[56279.703657]  [<81060481>] ? "
+      "suspend_devices_and_enter+0x18b/0x1de\n"
+      "<5>[56279.703663]  [<810605ba>] ? enter_state+0xe6/0x132\n"
+      "<5>[56279.703670]  [<8105fd4b>] ? state_store+0x91/0x9d\n"
+      "<5>[56279.703676]  [<8105fcba>] ? state_store+0x0/0x9d\n"
+      "<5>[56279.703683]  [<81178fb1>] ? kobj_attr_store+0x16/0x22\n"
+      "<5>[56279.703690]  [<810eea5e>] ? sysfs_write_file+0xc1/0xec\n"
+      "<5>[56279.703697]  [<810af443>] ? vfs_write+0x8f/0x101\n"
+      "<5>[56279.703703]  [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
+      "<5>[56279.703709]  [<810af556>] ? sys_write+0x40/0x65\n"
+      "<5>[56279.703716]  [<81002d57>] ? sysenter_do_call+0x12/0x26\n";
+
+  EXPECT_TRUE(
+      collector_.ComputeKernelStackSignature(kUncertainStackTrace,
+                                             &signature,
+                                             false));
+  // The first trace contains only uncertain entries and its hash is 00000000,
+  // so, if we used that, the signature would be kernel-add_timer-00000000.
+  // Instead we use the second-to-last trace for the hash.
+  EXPECT_EQ("kernel-add_timer-B5178878", signature);
+
+  ComputeKernelStackSignatureCommon();
+}
diff --git a/kernel_collector_test.h b/kernel_collector_test.h
new file mode 100644
index 0000000..f689e7d
--- /dev/null
+++ b/kernel_collector_test.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+
+#include "kernel_collector.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+class KernelCollectorMock : public KernelCollector {
+ public:
+  MOCK_METHOD0(DumpDirMounted, bool());
+  MOCK_METHOD0(SetUpDBus, void());
+};
+
+#endif  // CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
diff --git a/kernel_log_collector.sh b/kernel_log_collector.sh
new file mode 100644
index 0000000..82512c2
--- /dev/null
+++ b/kernel_log_collector.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Usage example: "kernel_log_collector.sh XXX YYY"
+# This script searches logs in the /var/log/messages which have the keyword XXX.
+# And only those logs which are within the last YYY seconds of the latest log
+# that has the keyword XXX are printed.
+
+# Kernel log has the possible formats:
+# 2013-06-14T16:31:40.514513-07:00 localhost kernel: [    2.682472] MSG MSG ...
+# 2013-06-19T20:38:58.661826+00:00 localhost kernel: [    1.668092] MSG MSG ...
+
+search_key=$1
+time_duration=$2
+msg_pattern="^[0-9-]*T[0-9:.+-]* localhost kernel"
+
+die() {
+  echo "kernel_log_collector: $*" >&2
+  exit 1
+}
+
+get_timestamp() {
+  timestamp="$(echo $1 | cut -d " " -f 1)"
+  timestamp="$(date -d "${timestamp}" +%s)" || exit $?
+  echo "${timestamp}"
+}
+
+last_line=$(grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | tail -n 1)
+
+if [ -n "${last_line}" ]; then
+  if ! allowed_timestamp=$(get_timestamp "${last_line}"); then
+    die "coule not get timestamp from: ${last_line}"
+  fi
+  : $(( allowed_timestamp -= ${time_duration} ))
+  grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | while read line; do
+    if ! timestamp=$(get_timestamp "${line}"); then
+      die "could not get timestamp from: ${line}"
+    fi
+    if [ ${timestamp} -gt ${allowed_timestamp} ]; then
+      echo "${line}"
+    fi
+  done
+fi
+
+echo "END-OF-LOG"
+
diff --git a/kernel_warning_collector.cc b/kernel_warning_collector.cc
new file mode 100644
index 0000000..e28e8fd
--- /dev/null
+++ b/kernel_warning_collector.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kernel_warning_collector.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/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
+
+using base::FilePath;
+using base::StringPrintf;
+
+KernelWarningCollector::KernelWarningCollector() {
+}
+
+KernelWarningCollector::~KernelWarningCollector() {
+}
+
+bool KernelWarningCollector::LoadKernelWarning(std::string *content,
+                                               std::string *signature) {
+  FilePath kernel_warning_path(kKernelWarningPath);
+  if (!base::ReadFileToString(kernel_warning_path, content)) {
+    LOG(ERROR) << "Could not open " << kKernelWarningPath;
+    return false;
+  }
+  // The signature is in the first line.
+  std::string::size_type end_position = content->find('\n');
+  if (end_position == std::string::npos) {
+    LOG(ERROR) << "unexpected kernel warning format";
+    return false;
+  }
+  *signature = content->substr(0, end_position);
+  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_signature;
+  if (!LoadKernelWarning(&kernel_warning, &warning_signature)) {
+    return true;
+  }
+
+  FilePath root_crash_directory;
+  if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory,
+                                      nullptr)) {
+    return true;
+  }
+
+  std::string dump_basename =
+      FormatDumpBasename(kExecName, time(nullptr), kKernelPid);
+  FilePath kernel_crash_path = root_crash_directory.Append(
+      StringPrintf("%s.kcrash", dump_basename.c_str()));
+
+  // We must use WriteNewFile instead of base::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_signature);
+  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/kernel_warning_collector.h b/kernel_warning_collector.h
new file mode 100644
index 0000000..5ccb780
--- /dev/null
+++ b/kernel_warning_collector.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+#define CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "crash_collector.h"
+
+// Kernel warning collector.
+class KernelWarningCollector : public CrashCollector {
+ public:
+  KernelWarningCollector();
+
+  ~KernelWarningCollector() override;
+
+  // Collects warning.
+  bool Collect();
+
+ private:
+  friend class KernelWarningCollectorTest;
+  FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
+
+  // Reads the full content of the kernel warn dump and its signature.
+  bool LoadKernelWarning(std::string *content, std::string *signature);
+
+  DISALLOW_COPY_AND_ASSIGN(KernelWarningCollector);
+};
+
+#endif  // CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
diff --git a/list_proxies.cc b/list_proxies.cc
new file mode 100644
index 0000000..3374b5f
--- /dev/null
+++ b/list_proxies.cc
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sysexits.h>
+#include <unistd.h>  // for isatty()
+
+#include <string>
+#include <vector>
+
+#include <base/cancelable_callback.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/memory/weak_ptr.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+#include <brillo/daemons/dbus_daemon.h>
+#include <brillo/syslog_logging.h>
+
+#include "libcrosservice/dbus-proxies.h"
+
+using std::unique_ptr;
+
+namespace {
+
+const char kLibCrosProxyResolvedSignalInterface[] =
+    "org.chromium.CrashReporterLibcrosProxyResolvedInterface";
+const char kLibCrosProxyResolvedName[] = "ProxyResolved";
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kNoProxy[] = "direct://";
+
+const int kTimeoutDefaultSeconds = 5;
+
+const char kHelp[] = "help";
+const char kQuiet[] = "quiet";
+const char kTimeout[] = "timeout";
+const char kVerbose[] = "verbose";
+// Help message to show when the --help command line switch is specified.
+const char kHelpMessage[] =
+    "Chromium OS Crash helper: proxy lister\n"
+    "\n"
+    "Available Switches:\n"
+    "  --quiet      Only print the proxies\n"
+    "  --verbose    Print additional messages even when not run from a TTY\n"
+    "  --timeout=N  Set timeout for browser resolving proxies (default is 5)\n"
+    "  --help       Show this help.\n";
+
+// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
+// Parses the browser's answer for resolved proxies.  It returns a
+// list of strings, each of which is a resolved proxy.
+std::vector<std::string> ParseProxyString(const std::string& input) {
+  std::vector<std::string> ret;
+  // Some of this code taken from
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+  base::StringTokenizer entry_tok(input, ";");
+  while (entry_tok.GetNext()) {
+    std::string token = entry_tok.token();
+    base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+    // Start by finding the first space (if any).
+    std::string::iterator space;
+    for (space = token.begin(); space != token.end(); ++space) {
+      if (base::IsAsciiWhitespace(*space)) {
+        break;
+      }
+    }
+
+    std::string scheme = base::ToLowerASCII(std::string(token.begin(), space));
+    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+    if (scheme == "socks")
+      scheme += "4";
+    else if (scheme == "proxy")
+      scheme = "http";
+    else if (scheme != "https" &&
+             scheme != "socks4" &&
+             scheme != "socks5" &&
+             scheme != "direct")
+      continue;  // Invalid proxy scheme
+
+    std::string host_and_port = std::string(space, token.end());
+    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+    if (scheme != "direct" && host_and_port.empty())
+      continue;  // Must supply host/port when non-direct proxy used.
+    ret.push_back(scheme + "://" + host_and_port);
+  }
+  if (ret.empty() || *ret.rbegin() != kNoProxy)
+    ret.push_back(kNoProxy);
+  return ret;
+}
+
+// A class for interfacing with Chrome to resolve proxies for a given source
+// url.  The class is initialized with the given source url to check, the
+// signal interface and name that Chrome will reply to, and how long to wait
+// for the resolve request to timeout.  Once initialized, the Run() function
+// must be called, which blocks on the D-Bus call to Chrome.  The call returns
+// after either the timeout or the proxy has been resolved.  The resolved
+// proxies can then be accessed through the proxies() function.
+class ProxyResolver : public brillo::DBusDaemon {
+ public:
+  ProxyResolver(const std::string& source_url,
+                const std::string& signal_interface,
+                const std::string& signal_name,
+                base::TimeDelta timeout)
+      : source_url_(source_url),
+        signal_interface_(signal_interface),
+        signal_name_(signal_name),
+        timeout_(timeout),
+        weak_ptr_factory_(this),
+        timeout_callback_(base::Bind(&ProxyResolver::HandleBrowserTimeout,
+                                     weak_ptr_factory_.GetWeakPtr())) {}
+
+  ~ProxyResolver() override {}
+
+  const std::vector<std::string>& proxies() {
+    return proxies_;
+  }
+
+  int Run() override {
+    // Add task for if the browser proxy call times out.
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        timeout_callback_.callback(),
+        timeout_);
+
+    return brillo::DBusDaemon::Run();
+  }
+
+ protected:
+  // If the browser times out, quit the run loop.
+  void HandleBrowserTimeout() {
+    LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
+    Quit();
+  }
+
+  // If the signal handler connects successfully, call the browser's
+  // ResolveNetworkProxy D-Bus method.  Otherwise, don't do anything and let
+  // the timeout task quit the run loop.
+  void HandleDBusSignalConnected(const std::string& interface,
+                                 const std::string& signal,
+                                 bool success) {
+    if (!success) {
+      LOG(ERROR) << "Could not connect to signal " << interface << "."
+                 << signal;
+      timeout_callback_.Cancel();
+      Quit();
+      return;
+    }
+
+    brillo::ErrorPtr error;
+    call_proxy_->ResolveNetworkProxy(source_url_,
+                                     signal_interface_,
+                                     signal_name_,
+                                     &error);
+
+    if (error) {
+      LOG(ERROR) << "Call to ResolveNetworkProxy failed: "
+                 << error->GetMessage();
+      timeout_callback_.Cancel();
+      Quit();
+    }
+  }
+
+  // Handle incoming ProxyResolved signal.
+  void HandleProxyResolvedSignal(const std::string& source_url,
+                                 const std::string& proxy_info,
+                                 const std::string& error_message) {
+    timeout_callback_.Cancel();
+    proxies_ = ParseProxyString(proxy_info);
+    LOG(INFO) << "Found proxies via browser signal: "
+              << base::JoinString(proxies_, "x");
+
+    Quit();
+  }
+
+  int OnInit() override {
+    int return_code = brillo::DBusDaemon::OnInit();
+    if (return_code != EX_OK)
+      return return_code;
+
+    // Initialize D-Bus proxies.
+    call_proxy_.reset(
+        new org::chromium::LibCrosServiceInterfaceProxy(bus_,
+                                                        kLibCrosServiceName));
+    signal_proxy_.reset(
+        new org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy(
+            bus_,
+            kLibCrosServiceName));
+
+    // Set up the D-Bus signal handler.
+    // TODO(crbug.com/446115): Update ResolveNetworkProxy call to use an
+    //     asynchronous return value rather than a return signal.
+    signal_proxy_->RegisterProxyResolvedSignalHandler(
+        base::Bind(&ProxyResolver::HandleProxyResolvedSignal,
+                   weak_ptr_factory_.GetWeakPtr()),
+        base::Bind(&ProxyResolver::HandleDBusSignalConnected,
+                   weak_ptr_factory_.GetWeakPtr()));
+
+    return EX_OK;
+  }
+
+ private:
+  unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> call_proxy_;
+  unique_ptr<org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy>
+      signal_proxy_;
+
+  const std::string source_url_;
+  const std::string signal_interface_;
+  const std::string signal_name_;
+  base::TimeDelta timeout_;
+
+  std::vector<std::string> proxies_;
+  base::WeakPtrFactory<ProxyResolver> weak_ptr_factory_;
+
+  base::CancelableClosure timeout_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+static bool ShowBrowserProxies(std::string url, base::TimeDelta timeout) {
+  // Initialize and run the proxy resolver to watch for signals.
+  ProxyResolver resolver(url,
+                         kLibCrosProxyResolvedSignalInterface,
+                         kLibCrosProxyResolvedName,
+                         timeout);
+  resolver.Run();
+
+  std::vector<std::string> proxies = resolver.proxies();
+
+  // If proxies is empty, then the timeout was reached waiting for the proxy
+  // resolved signal.  If no proxies are defined, proxies will be populated
+  // with "direct://".
+  if (proxies.empty())
+    return false;
+
+  for (const auto& proxy : proxies) {
+    printf("%s\n", proxy.c_str());
+  }
+  return true;
+}
+
+}  // namespace
+
+int main(int argc, char *argv[]) {
+  base::CommandLine::Init(argc, argv);
+  base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+
+  if (cl->HasSwitch(kHelp)) {
+    LOG(INFO) << kHelpMessage;
+    return 0;
+  }
+
+  bool quiet = cl->HasSwitch(kQuiet);
+  bool verbose = cl->HasSwitch(kVerbose);
+
+  int timeout = kTimeoutDefaultSeconds;
+  std::string str_timeout = cl->GetSwitchValueASCII(kTimeout);
+  if (!str_timeout.empty() && !base::StringToInt(str_timeout, &timeout)) {
+    LOG(ERROR) << "Invalid timeout value: " << str_timeout;
+    return 1;
+  }
+
+  // Default to logging to syslog.
+  int init_flags = brillo::kLogToSyslog;
+  // Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
+  // was passed.
+
+  if ((!quiet && isatty(STDERR_FILENO)) || verbose)
+    init_flags |= brillo::kLogToStderr;
+  brillo::InitLog(init_flags);
+
+  std::string url;
+  base::CommandLine::StringVector urls = cl->GetArgs();
+  if (!urls.empty()) {
+    url = urls[0];
+    LOG(INFO) << "Resolving proxies for URL: " << url;
+  } else {
+    LOG(INFO) << "Resolving proxies without URL";
+  }
+
+  if (!ShowBrowserProxies(url, base::TimeDelta::FromSeconds(timeout))) {
+    LOG(ERROR) << "Error resolving proxies via the browser";
+    LOG(INFO) << "Assuming direct proxy";
+    printf("%s\n", kNoProxy);
+  }
+
+  return 0;
+}
diff --git a/periodic_scheduler b/periodic_scheduler
new file mode 100755
index 0000000..5408da7
--- /dev/null
+++ b/periodic_scheduler
@@ -0,0 +1,80 @@
+#!/system/bin/sh
+
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Run tasks periodically.
+# Usage: $0 <delay_seconds> <timeout_seconds> <task_name> <task_binary>
+#
+# Executes task <task_name> by running <task_binary> every <delay_seconds>.
+
+set -e -u
+
+SCRIPT_NAME="$(basename "$0")"
+CHECK_DELAY=300  # Check every 5 minutes.
+KILL_DELAY=10    # How long to let the job clean up after a timeout.
+# Let the unittests override.
+: ${SPOOL_DIR:=/data/misc/crash_reporter/spool/cron-lite}
+
+loginfo() {
+  log -p i -t "${SCRIPT_NAME}" "$@"
+}
+
+trap "loginfo 'exiting'" EXIT
+
+check_and_fix_spool_paths() {
+  # Avoid weird spool paths if possible.
+  rm -f "$(dirname "${SPOOL_DIR}")" "${SPOOL_DIR}" 2>/dev/null || :
+  mkdir -p "${SPOOL_DIR}"
+  if [ ! -O "${SPOOL_DIR}" -o ! -d "${SPOOL_DIR}" ]; then
+    loginfo "Spool directory is damaged. Aborting!"
+    exit 1
+  fi
+}
+
+main() {
+  local delay="$1"
+  local timeout="$2"
+  local name="$3"
+  local spool_file="${SPOOL_DIR}/${name}"
+  shift 3
+
+  [ -z "${delay}" ] && exit 1
+  [ -z "${timeout}" ] && exit 1
+  [ -z "${name}" ] && exit 1
+  [ $# -eq 0 ] && exit 1
+  check_and_fix_spool_paths
+
+  while true; do
+    # Allow the sleep to be killed manually without terminating the handler.
+    # Send stderr to /dev/null to suppress the shell's "Terminated" message.
+    sleep $(( CHECK_DELAY + KILL_DELAY )) 2>/dev/null || true
+
+    [ ! -e "${spool_file}" ] && touch "${spool_file}"
+
+    local last_rotation="$(stat -c "%Y" "${spool_file}" 2>/dev/null || echo 0)"
+    local now="$(date +%s)"
+    local time_diff=$((now - last_rotation))
+
+    if [ ${time_diff} -gt ${delay} ]; then
+      rm "${spool_file}" || true
+      touch "${spool_file}"
+      loginfo "${name}: running $*"
+      timeout -k ${KILL_DELAY} ${timeout} "$@" || true
+      loginfo "${name}: job completed"
+    fi
+  done
+}
+
+main "$@"
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..744cf10
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <brillo/test_helpers.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+  SetUpTests(&argc, argv, true);
+  return RUN_ALL_TESTS();
+}
diff --git a/udev_collector.cc b/udev_collector.cc
new file mode 100644
index 0000000..1e018db
--- /dev/null
+++ b/udev_collector.cc
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "udev_collector.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/process.h>
+
+using base::FilePath;
+
+namespace {
+
+const char kCollectUdevSignature[] = "crash_reporter-udev-collection";
+const char kGzipPath[] = "/bin/gzip";
+const char kUdevExecName[] = "udev";
+const char kUdevSignatureKey[] = "sig";
+const char kUdevSubsystemDevCoredump[] = "devcoredump";
+const char kDefaultDevCoredumpDirectory[] = "/sys/class/devcoredump";
+const char kDevCoredumpFilePrefixFormat[] = "devcoredump_%s";
+
+}  // namespace
+
+UdevCollector::UdevCollector()
+    : dev_coredump_directory_(kDefaultDevCoredumpDirectory) {}
+
+UdevCollector::~UdevCollector() {}
+
+bool UdevCollector::HandleCrash(const std::string &udev_event) {
+  if (IsDeveloperImage()) {
+    LOG(INFO) << "developer image - collect udev crash info.";
+  } else if (is_feedback_allowed_function_()) {
+    LOG(INFO) << "Consent given - collect udev crash info.";
+  } else {
+    LOG(INFO) << "Ignoring - Non-developer image and no consent given.";
+    return false;
+  }
+
+  // Process the udev event string.
+  // First get all the key-value pairs.
+  std::vector<std::pair<std::string, std::string>> udev_event_keyval;
+  base::SplitStringIntoKeyValuePairs(udev_event, '=', ':', &udev_event_keyval);
+  std::vector<std::pair<std::string, std::string>>::const_iterator iter;
+  std::map<std::string, std::string> udev_event_map;
+  for (iter = udev_event_keyval.begin();
+       iter != udev_event_keyval.end();
+       ++iter) {
+    udev_event_map[iter->first] = iter->second;
+  }
+
+  // Make sure the crash directory exists, or create it if it doesn't.
+  FilePath crash_directory;
+  if (!GetCreatedCrashDirectoryByEuid(0, &crash_directory, nullptr)) {
+    LOG(ERROR) << "Could not get crash directory.";
+    return false;
+  }
+
+  if (udev_event_map["SUBSYSTEM"] == kUdevSubsystemDevCoredump) {
+    int instance_number;
+    if (!base::StringToInt(udev_event_map["KERNEL_NUMBER"], &instance_number)) {
+      LOG(ERROR) << "Invalid kernel number: "
+                 << udev_event_map["KERNEL_NUMBER"];
+      return false;
+    }
+    return ProcessDevCoredump(crash_directory, instance_number);
+  }
+
+  return ProcessUdevCrashLogs(crash_directory,
+                              udev_event_map["ACTION"],
+                              udev_event_map["KERNEL"],
+                              udev_event_map["SUBSYSTEM"]);
+}
+
+bool UdevCollector::ProcessUdevCrashLogs(const FilePath& crash_directory,
+                                         const std::string& action,
+                                         const std::string& kernel,
+                                         const std::string& subsystem) {
+  // Construct the basename string for crash_reporter_logs.conf:
+  //   "crash_reporter-udev-collection-[action]-[name]-[subsystem]"
+  // If a udev field is not provided, "" is used in its place, e.g.:
+  //   "crash_reporter-udev-collection-[action]--[subsystem]"
+  // Hence, "" is used as a wildcard name string.
+  // TODO(sque, crosbug.com/32238): Implement wildcard checking.
+  std::string basename = action + "-" + kernel + "-" + subsystem;
+  std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
+                              basename;
+
+  // Create the destination path.
+  std::string log_file_name =
+      FormatDumpBasename(basename, time(nullptr), 0);
+  FilePath crash_path = GetCrashPath(crash_directory, log_file_name, "log");
+
+  // Handle the crash.
+  bool result = GetLogContents(log_config_path_, udev_log_name, crash_path);
+  if (!result) {
+    LOG(ERROR) << "Error reading udev log info " << udev_log_name;
+    return false;
+  }
+
+  // Compress the output using gzip.
+  brillo::ProcessImpl gzip_process;
+  gzip_process.AddArg(kGzipPath);
+  gzip_process.AddArg(crash_path.value());
+  int process_result = gzip_process.Run();
+  FilePath crash_path_zipped = FilePath(crash_path.value() + ".gz");
+  // If the zip file was not created, use the uncompressed file.
+  if (process_result != 0 || !base::PathExists(crash_path_zipped))
+    LOG(ERROR) << "Could not create zip file " << crash_path_zipped.value();
+  else
+    crash_path = crash_path_zipped;
+
+  std::string exec_name = std::string(kUdevExecName) + "-" + subsystem;
+  AddCrashMetaData(kUdevSignatureKey, udev_log_name);
+  WriteCrashMetaData(GetCrashPath(crash_directory, log_file_name, "meta"),
+                     exec_name, crash_path.value());
+  return true;
+}
+
+bool UdevCollector::ProcessDevCoredump(const FilePath& crash_directory,
+                                       int instance_number) {
+  FilePath coredump_path =
+      FilePath(base::StringPrintf("%s/devcd%d/data",
+                                  dev_coredump_directory_.c_str(),
+                                  instance_number));
+  if (!base::PathExists(coredump_path)) {
+    LOG(ERROR) << "Device coredump file " << coredump_path.value()
+               << " does not exist";
+    return false;
+  }
+
+  // Add coredump file to the crash directory.
+  if (!AppendDevCoredump(crash_directory, coredump_path, instance_number)) {
+    ClearDevCoredump(coredump_path);
+    return false;
+  }
+
+  // Clear the coredump data to allow generation of future device coredumps
+  // without having to wait for the 5-minutes timeout.
+  return ClearDevCoredump(coredump_path);
+}
+
+bool UdevCollector::AppendDevCoredump(const FilePath& crash_directory,
+                                      const FilePath& coredump_path,
+                                      int instance_number) {
+  // Retrieve the driver name of the failing device.
+  std::string driver_name = GetFailingDeviceDriverName(instance_number);
+  if (driver_name.empty()) {
+    LOG(ERROR) << "Failed to obtain driver name for instance: "
+               << instance_number;
+    return false;
+  }
+
+  std::string coredump_prefix =
+      base::StringPrintf(kDevCoredumpFilePrefixFormat, driver_name.c_str());
+
+  std::string dump_basename = FormatDumpBasename(coredump_prefix,
+                                                 time(nullptr),
+                                                 instance_number);
+  FilePath core_path = GetCrashPath(crash_directory, dump_basename, "devcore");
+  FilePath log_path = GetCrashPath(crash_directory, dump_basename, "log");
+  FilePath meta_path = GetCrashPath(crash_directory, dump_basename, "meta");
+
+  // Collect coredump data.
+  if (!base::CopyFile(coredump_path, core_path)) {
+    LOG(ERROR) << "Failed to copy device coredumpm file from "
+               << coredump_path.value() << " to " << core_path.value();
+    return false;
+  }
+
+  // Collect additional logs if one is specified in the config file.
+  std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
+      kUdevSubsystemDevCoredump + '-' + driver_name;
+  bool result = GetLogContents(log_config_path_, udev_log_name, log_path);
+  if (result) {
+    AddCrashMetaUploadFile("logs", log_path.value());
+  }
+
+  WriteCrashMetaData(meta_path, coredump_prefix, core_path.value());
+
+  return true;
+}
+
+bool UdevCollector::ClearDevCoredump(const FilePath& coredump_path) {
+  if (!base::WriteFile(coredump_path, "0", 1)) {
+    LOG(ERROR) << "Failed to delete the coredump data file "
+               << coredump_path.value();
+    return false;
+  }
+  return true;
+}
+
+std::string UdevCollector::GetFailingDeviceDriverName(int instance_number) {
+  FilePath failing_uevent_path =
+      FilePath(base::StringPrintf("%s/devcd%d/failing_device/uevent",
+                                  dev_coredump_directory_.c_str(),
+                                  instance_number));
+  if (!base::PathExists(failing_uevent_path)) {
+    LOG(ERROR) << "Failing uevent path " << failing_uevent_path.value()
+               << " does not exist";
+    return "";
+  }
+
+  std::string uevent_content;
+  if (!base::ReadFileToString(failing_uevent_path, &uevent_content)) {
+    LOG(ERROR) << "Failed to read uevent file " << failing_uevent_path.value();
+    return "";
+  }
+
+  // Parse uevent file contents as key-value pairs.
+  std::vector<std::pair<std::string, std::string>> uevent_keyval;
+  base::SplitStringIntoKeyValuePairs(uevent_content, '=', '\n', &uevent_keyval);
+  std::vector<std::pair<std::string, std::string>>::const_iterator iter;
+  for (iter = uevent_keyval.begin();
+       iter != uevent_keyval.end();
+       ++iter) {
+    if (iter->first == "DRIVER") {
+      return iter->second;
+    }
+  }
+
+  return "";
+}
diff --git a/udev_collector.h b/udev_collector.h
new file mode 100644
index 0000000..e267b75
--- /dev/null
+++ b/udev_collector.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_UDEV_COLLECTOR_H_
+#define CRASH_REPORTER_UDEV_COLLECTOR_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "crash_collector.h"
+
+// Udev crash collector.
+class UdevCollector : public CrashCollector {
+ public:
+  UdevCollector();
+
+  ~UdevCollector() override;
+
+  // The udev event string should be formatted as follows:
+  //   "ACTION=[action]:KERNEL=[name]:SUBSYSTEM=[subsystem]"
+  // The values don't have to be in any particular order. One or more of them
+  // could be omitted, in which case it would be treated as a wildcard (*).
+  bool HandleCrash(const std::string& udev_event);
+
+ protected:
+  std::string dev_coredump_directory_;
+
+ private:
+  friend class UdevCollectorTest;
+
+  // Process udev crash logs, collecting log files according to the config
+  // file (crash_reporter_logs.conf).
+  bool ProcessUdevCrashLogs(const base::FilePath& crash_directory,
+                            const std::string& action,
+                            const std::string& kernel,
+                            const std::string& subsystem);
+  // Process device coredump, collecting device coredump file.
+  // |instance_number| is the kernel number of the virtual device for the device
+  // coredump instance.
+  bool ProcessDevCoredump(const base::FilePath& crash_directory,
+                          int instance_number);
+  // Copy device coredump file to crash directory, and perform necessary
+  // coredump file management.
+  bool AppendDevCoredump(const base::FilePath& crash_directory,
+                         const base::FilePath& coredump_path,
+                         int instance_number);
+  // Clear the device coredump file by performing a dummy write to it.
+  bool ClearDevCoredump(const base::FilePath& coredump_path);
+  // Return the driver name of the device that generates the coredump.
+  std::string GetFailingDeviceDriverName(int instance_number);
+
+  // Mutator for unit testing.
+  void set_log_config_path(const std::string& path) {
+    log_config_path_ = base::FilePath(path);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(UdevCollector);
+};
+
+#endif  // CRASH_REPORTER_UDEV_COLLECTOR_H_
diff --git a/udev_collector_test.cc b/udev_collector_test.cc
new file mode 100644
index 0000000..5474f48
--- /dev/null
+++ b/udev_collector_test.cc
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "udev_collector.h"
+
+using base::FilePath;
+
+namespace {
+
+// Dummy log config file name.
+const char kLogConfigFileName[] = "log_config_file";
+
+// Dummy directory for storing device coredumps.
+const char kDevCoredumpDirectory[] = "devcoredump";
+
+// A bunch of random rules to put into the dummy log config file.
+const char kLogConfigFileContents[] =
+    "crash_reporter-udev-collection-change-card0-drm=echo change card0 drm\n"
+    "crash_reporter-udev-collection-add-state0-cpu=echo change state0 cpu\n"
+    "crash_reporter-udev-collection-devcoredump-iwlwifi=echo devcoredump\n"
+    "cros_installer=echo not for udev";
+
+const char kCrashLogFilePattern[] = "*.log.gz";
+const char kDevCoredumpFilePattern[] = "*.devcore";
+
+// Dummy content for device coredump data file.
+const char kDevCoredumpDataContents[] = "coredump";
+
+// Content for failing device's uevent file.
+const char kFailingDeviceUeventContents[] = "DRIVER=iwlwifi\n";
+
+void CountCrash() {}
+
+bool s_consent_given = true;
+
+bool IsMetrics() {
+  return s_consent_given;
+}
+
+// Returns the number of files found in the given path that matches the
+// specified file name pattern.
+int GetNumFiles(const FilePath& path, const std::string& file_pattern) {
+  base::FileEnumerator enumerator(path, false, base::FileEnumerator::FILES,
+                                  file_pattern);
+  int num_files = 0;
+  for (FilePath file_path = enumerator.Next();
+       !file_path.value().empty();
+       file_path = enumerator.Next()) {
+    num_files++;
+  }
+  return num_files;
+}
+
+}  // namespace
+
+class UdevCollectorMock : public UdevCollector {
+ public:
+  MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UdevCollectorTest : public ::testing::Test {
+ protected:
+  base::ScopedTempDir temp_dir_generator_;
+
+  void HandleCrash(const std::string &udev_event) {
+    collector_.HandleCrash(udev_event);
+  }
+
+  void GenerateDevCoredump(const std::string& device_name) {
+    // Generate coredump data file.
+    ASSERT_TRUE(CreateDirectory(
+        FilePath(base::StringPrintf("%s/%s",
+                                    collector_.dev_coredump_directory_.c_str(),
+                                    device_name.c_str()))));
+    FilePath data_path =
+        FilePath(base::StringPrintf("%s/%s/data",
+                                    collector_.dev_coredump_directory_.c_str(),
+                                    device_name.c_str()));
+    ASSERT_EQ(strlen(kDevCoredumpDataContents),
+              base::WriteFile(data_path,
+                              kDevCoredumpDataContents,
+                              strlen(kDevCoredumpDataContents)));
+    // Generate uevent file for failing device.
+    ASSERT_TRUE(CreateDirectory(
+        FilePath(base::StringPrintf("%s/%s/failing_device",
+                                    collector_.dev_coredump_directory_.c_str(),
+                                    device_name.c_str()))));
+    FilePath uevent_path =
+        FilePath(base::StringPrintf("%s/%s/failing_device/uevent",
+                                    collector_.dev_coredump_directory_.c_str(),
+                                    device_name.c_str()));
+    ASSERT_EQ(strlen(kFailingDeviceUeventContents),
+              base::WriteFile(uevent_path,
+                              kFailingDeviceUeventContents,
+                              strlen(kFailingDeviceUeventContents)));
+  }
+
+ private:
+  void SetUp() override {
+    s_consent_given = true;
+
+    EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+    collector_.Initialize(CountCrash, IsMetrics);
+
+    ASSERT_TRUE(temp_dir_generator_.CreateUniqueTempDir());
+
+    FilePath log_config_path =
+        temp_dir_generator_.path().Append(kLogConfigFileName);
+    collector_.log_config_path_ = log_config_path;
+    collector_.ForceCrashDirectory(temp_dir_generator_.path());
+
+    FilePath dev_coredump_path =
+        temp_dir_generator_.path().Append(kDevCoredumpDirectory);
+    collector_.dev_coredump_directory_ = dev_coredump_path.value();
+
+    // Write to a dummy log config file.
+    ASSERT_EQ(strlen(kLogConfigFileContents),
+              base::WriteFile(log_config_path,
+                              kLogConfigFileContents,
+                              strlen(kLogConfigFileContents)));
+
+    brillo::ClearLog();
+  }
+
+  UdevCollectorMock collector_;
+};
+
+TEST_F(UdevCollectorTest, TestNoConsent) {
+  s_consent_given = false;
+  HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
+  EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestNoMatch) {
+  // No rule should match this.
+  HandleCrash("ACTION=change:KERNEL=foo:SUBSYSTEM=bar");
+  EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestMatches) {
+  // Try multiple udev events in sequence.  The number of log files generated
+  // should increase.
+  HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
+  EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+  HandleCrash("ACTION=add:KERNEL=state0:SUBSYSTEM=cpu");
+  EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestDevCoredump) {
+  GenerateDevCoredump("devcd0");
+  HandleCrash("ACTION=add:KERNEL_NUMBER=0:SUBSYSTEM=devcoredump");
+  EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(),
+                           kDevCoredumpFilePattern));
+  GenerateDevCoredump("devcd1");
+  HandleCrash("ACTION=add:KERNEL_NUMBER=1:SUBSYSTEM=devcoredump");
+  EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(),
+                           kDevCoredumpFilePattern));
+}
+
+// TODO(sque, crosbug.com/32238) - test wildcard cases, multiple identical udev
+// events.
diff --git a/unclean_shutdown_collector.cc b/unclean_shutdown_collector.cc
new file mode 100644
index 0000000..8a092ec
--- /dev/null
+++ b/unclean_shutdown_collector.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unclean_shutdown_collector.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+
+static const char kUncleanShutdownFile[] =
+    "/var/lib/crash_reporter/pending_clean_shutdown";
+
+// Files created by power manager used for crash reporting.
+static const char kPowerdTracePath[] = "/var/lib/power_manager";
+// Presence of this file indicates that the system was suspended
+static const char kPowerdSuspended[] = "powerd_suspended";
+
+using base::FilePath;
+
+UncleanShutdownCollector::UncleanShutdownCollector()
+    : unclean_shutdown_file_(kUncleanShutdownFile),
+      powerd_trace_path_(kPowerdTracePath),
+      powerd_suspended_file_(powerd_trace_path_.Append(kPowerdSuspended)) {
+}
+
+UncleanShutdownCollector::~UncleanShutdownCollector() {
+}
+
+bool UncleanShutdownCollector::Enable() {
+  FilePath file_path(unclean_shutdown_file_);
+  base::CreateDirectory(file_path.DirName());
+  if (base::WriteFile(file_path, "", 0) != 0) {
+    LOG(ERROR) << "Unable to create shutdown check file";
+    return false;
+  }
+  return true;
+}
+
+bool UncleanShutdownCollector::DeleteUncleanShutdownFiles() {
+  if (!base::DeleteFile(FilePath(unclean_shutdown_file_), false)) {
+    LOG(ERROR) << "Failed to delete unclean shutdown file "
+               << unclean_shutdown_file_;
+    return false;
+  }
+  // Delete power manager state file if it exists.
+  base::DeleteFile(powerd_suspended_file_, false);
+  return true;
+}
+
+bool UncleanShutdownCollector::Collect() {
+  FilePath unclean_file_path(unclean_shutdown_file_);
+  if (!base::PathExists(unclean_file_path)) {
+    return false;
+  }
+  LOG(WARNING) << "Last shutdown was not clean";
+  if (DeadBatteryCausedUncleanShutdown()) {
+    DeleteUncleanShutdownFiles();
+    return false;
+  }
+  DeleteUncleanShutdownFiles();
+
+  if (is_feedback_allowed_function_()) {
+    count_crash_function_();
+  }
+  return true;
+}
+
+bool UncleanShutdownCollector::Disable() {
+  LOG(INFO) << "Clean shutdown signalled";
+  return DeleteUncleanShutdownFiles();
+}
+
+bool UncleanShutdownCollector::DeadBatteryCausedUncleanShutdown() {
+  // Check for case of battery running out while suspended.
+  if (base::PathExists(powerd_suspended_file_)) {
+    LOG(INFO) << "Unclean shutdown occurred while suspended. Not counting "
+              << "toward unclean shutdown statistic.";
+    return true;
+  }
+  return false;
+}
diff --git a/unclean_shutdown_collector.h b/unclean_shutdown_collector.h
new file mode 100644
index 0000000..5bc9968
--- /dev/null
+++ b/unclean_shutdown_collector.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
+#define CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "crash_collector.h"
+
+// Unclean shutdown collector.
+class UncleanShutdownCollector : public CrashCollector {
+ public:
+  UncleanShutdownCollector();
+  ~UncleanShutdownCollector() override;
+
+  // Enable collection - signal that a boot has started.
+  bool Enable();
+
+  // Collect if there is was an unclean shutdown. Returns true if
+  // there was, false otherwise.
+  bool Collect();
+
+  // Disable collection - signal that the system has been shutdown cleanly.
+  bool Disable();
+
+ private:
+  friend class UncleanShutdownCollectorTest;
+  FRIEND_TEST(UncleanShutdownCollectorTest, EnableCannotWrite);
+  FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatterySuspended);
+
+  bool DeleteUncleanShutdownFiles();
+
+  // Check for unclean shutdown due to battery running out by analyzing powerd
+  // trace files.
+  bool DeadBatteryCausedUncleanShutdown();
+
+  const char *unclean_shutdown_file_;
+  base::FilePath powerd_trace_path_;
+  base::FilePath powerd_suspended_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(UncleanShutdownCollector);
+};
+
+#endif  // CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
diff --git a/unclean_shutdown_collector_test.cc b/unclean_shutdown_collector_test.cc
new file mode 100644
index 0000000..36372ae
--- /dev/null
+++ b/unclean_shutdown_collector_test.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unclean_shutdown_collector.h"
+
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <brillo/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using ::brillo::FindLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = true;
+
+void CountCrash() {
+  ++s_crashes;
+}
+
+bool IsMetrics() {
+  return s_metrics;
+}
+
+}  // namespace
+
+class UncleanShutdownCollectorMock : public UncleanShutdownCollector {
+ public:
+  MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UncleanShutdownCollectorTest : public ::testing::Test {
+  void SetUp() {
+    s_crashes = 0;
+
+    EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+    collector_.Initialize(CountCrash,
+                          IsMetrics);
+
+    EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
+
+    test_directory_ = test_dir_.path().Append("test");
+    test_unclean_ = test_dir_.path().Append("test/unclean");
+
+    collector_.unclean_shutdown_file_ = test_unclean_.value().c_str();
+    base::DeleteFile(test_unclean_, true);
+    // Set up an alternate power manager state file as well
+    collector_.powerd_suspended_file_ =
+        test_dir_.path().Append("test/suspended");
+    brillo::ClearLog();
+  }
+
+ protected:
+  void WriteStringToFile(const FilePath &file_path,
+                         const char *data) {
+    unsigned int numBytesWritten =
+        base::WriteFile(file_path, data, strlen(data));
+    ASSERT_EQ(strlen(data), numBytesWritten);
+  }
+
+  UncleanShutdownCollectorMock collector_;
+
+  // Temporary directory used for tests.
+  base::ScopedTempDir test_dir_;
+  FilePath test_directory_;
+  FilePath test_unclean_;
+};
+
+TEST_F(UncleanShutdownCollectorTest, EnableWithoutParent) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(base::PathExists(test_unclean_));
+}
+
+TEST_F(UncleanShutdownCollectorTest, EnableWithParent) {
+  mkdir(test_directory_.value().c_str(), 0777);
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(base::PathExists(test_unclean_));
+}
+
+TEST_F(UncleanShutdownCollectorTest, EnableCannotWrite) {
+  collector_.unclean_shutdown_file_ = "/bad/path";
+  ASSERT_FALSE(collector_.Enable());
+  ASSERT_TRUE(FindLog("Unable to create shutdown check file"));
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectTrue) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(base::PathExists(test_unclean_));
+  ASSERT_TRUE(collector_.Collect());
+  ASSERT_FALSE(base::PathExists(test_unclean_));
+  ASSERT_EQ(1, s_crashes);
+  ASSERT_TRUE(FindLog("Last shutdown was not clean"));
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectFalse) {
+  ASSERT_FALSE(collector_.Collect());
+  ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectDeadBatterySuspended) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(base::PathExists(test_unclean_));
+  base::WriteFile(collector_.powerd_suspended_file_, "", 0);
+  ASSERT_FALSE(collector_.Collect());
+  ASSERT_FALSE(base::PathExists(test_unclean_));
+  ASSERT_FALSE(base::PathExists(collector_.powerd_suspended_file_));
+  ASSERT_EQ(0, s_crashes);
+  ASSERT_TRUE(FindLog("Unclean shutdown occurred while suspended."));
+}
+
+TEST_F(UncleanShutdownCollectorTest, Disable) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(base::PathExists(test_unclean_));
+  ASSERT_TRUE(collector_.Disable());
+  ASSERT_FALSE(base::PathExists(test_unclean_));
+  ASSERT_FALSE(collector_.Collect());
+}
+
+TEST_F(UncleanShutdownCollectorTest, DisableWhenNotEnabled) {
+  ASSERT_TRUE(collector_.Disable());
+}
+
+TEST_F(UncleanShutdownCollectorTest, CantDisable) {
+  mkdir(test_directory_.value().c_str(), 0700);
+  if (mkdir(test_unclean_.value().c_str(), 0700)) {
+    ASSERT_EQ(EEXIST, errno)
+        << "Error while creating directory '" << test_unclean_.value()
+        << "': " << strerror(errno);
+  }
+  ASSERT_EQ(0, base::WriteFile(test_unclean_.Append("foo"), "", 0))
+      << "Error while creating empty file '"
+      << test_unclean_.Append("foo").value() << "': " << strerror(errno);
+  ASSERT_FALSE(collector_.Disable());
+  rmdir(test_unclean_.value().c_str());
+}
diff --git a/user_collector.cc b/user_collector.cc
new file mode 100644
index 0000000..48b64e9
--- /dev/null
+++ b/user_collector.cc
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "user_collector.h"
+
+#include <elf.h>
+#include <fcntl.h>
+#include <grp.h>  // For struct group.
+#include <pcrecpp.h>
+#include <pwd.h>  // For struct passwd.
+#include <stdint.h>
+#include <sys/cdefs.h>  // For __WORDSIZE
+#include <sys/fsuid.h>
+#include <sys/types.h>  // For getpwuid_r, getgrnam_r, WEXITSTATUS.
+#include <unistd.h>  // For setgroups
+
+#include <iostream>  // For std::oct
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/process.h>
+#include <brillo/syslog_logging.h>
+#include <cutils/properties.h>
+#include <private/android_filesystem_config.h>
+
+static const char kCollectionErrorSignature[] =
+    "crash_reporter-user-collection";
+static const char kCorePatternProperty[] = "crash_reporter.coredump.enabled";
+static const char kCoreToMinidumpConverterPath[] = "/system/bin/core2md";
+
+static const char kStatePrefix[] = "State:\t";
+
+static const char kCoreTempFolder[] = "/data/misc/crash_reporter/tmp";
+
+// Define an otherwise invalid value that represents an unknown UID and GID.
+static const uid_t kUnknownUid = -1;
+static const gid_t kUnknownGid = -1;
+
+const char *UserCollector::kUserId = "Uid:\t";
+const char *UserCollector::kGroupId = "Gid:\t";
+
+
+using base::FilePath;
+using base::StringPrintf;
+
+UserCollector::UserCollector()
+    : generate_diagnostics_(false),
+      initialized_(false) {
+}
+
+void UserCollector::Initialize(
+    UserCollector::CountCrashFunction count_crash_function,
+    const std::string &our_path,
+    UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
+    bool generate_diagnostics,
+    bool core2md_failure,
+    bool directory_failure,
+    const std::string &filter_in) {
+  CrashCollector::Initialize(count_crash_function,
+                             is_feedback_allowed_function);
+  our_path_ = our_path;
+  initialized_ = true;
+  generate_diagnostics_ = generate_diagnostics;
+  core2md_failure_ = core2md_failure;
+  directory_failure_ = directory_failure;
+  filter_in_ = filter_in;
+
+  gid_t groups[] = { AID_ROOT, AID_SYSTEM, AID_DBUS, AID_READPROC };
+  if (setgroups(arraysize(groups), groups) != 0) {
+    PLOG(FATAL) << "Unable to set groups to root, system, dbus, and readproc";
+  }
+}
+
+UserCollector::~UserCollector() {
+}
+
+std::string UserCollector::GetErrorTypeSignature(ErrorType error_type) const {
+  switch (error_type) {
+    case kErrorSystemIssue:
+      return "system-issue";
+    case kErrorReadCoreData:
+      return "read-core-data";
+    case kErrorUnusableProcFiles:
+      return "unusable-proc-files";
+    case kErrorInvalidCoreFile:
+      return "invalid-core-file";
+    case kErrorUnsupported32BitCoreFile:
+      return "unsupported-32bit-core-file";
+    case kErrorCore2MinidumpConversion:
+      return "core2md-conversion";
+    default:
+      return "";
+  }
+}
+
+bool UserCollector::SetUpInternal(bool enabled) {
+  CHECK(initialized_);
+  LOG(INFO) << (enabled ? "Enabling" : "Disabling") << " user crash handling";
+
+  property_set(kCorePatternProperty, enabled ? "1" : "0");
+
+  return true;
+}
+
+bool UserCollector::GetFirstLineWithPrefix(
+    const std::vector<std::string> &lines,
+    const char *prefix, std::string *line) {
+  std::vector<std::string>::const_iterator line_iterator;
+  for (line_iterator = lines.begin(); line_iterator != lines.end();
+       ++line_iterator) {
+    if (line_iterator->find(prefix) == 0) {
+      *line = *line_iterator;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool UserCollector::GetIdFromStatus(
+    const char *prefix, IdKind kind,
+    const std::vector<std::string> &status_lines, int *id) {
+  // From fs/proc/array.c:task_state(), this file contains:
+  // \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n
+  std::string id_line;
+  if (!GetFirstLineWithPrefix(status_lines, prefix, &id_line)) {
+    return false;
+  }
+  std::string id_substring = id_line.substr(strlen(prefix), std::string::npos);
+  std::vector<std::string> ids = base::SplitString(
+      id_substring, "\t", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) {
+    return false;
+  }
+  const char *number = ids[kind].c_str();
+  char *end_number = nullptr;
+  *id = strtol(number, &end_number, 10);
+  if (*end_number != '\0') {
+    return false;
+  }
+  return true;
+}
+
+bool UserCollector::GetStateFromStatus(
+    const std::vector<std::string> &status_lines, std::string *state) {
+  std::string state_line;
+  if (!GetFirstLineWithPrefix(status_lines, kStatePrefix, &state_line)) {
+    return false;
+  }
+  *state = state_line.substr(strlen(kStatePrefix), std::string::npos);
+  return true;
+}
+
+void UserCollector::EnqueueCollectionErrorLog(pid_t pid,
+                                              ErrorType error_type,
+                                              const std::string &exec) {
+  FilePath crash_path;
+  LOG(INFO) << "Writing conversion problems as separate crash report.";
+  if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, nullptr)) {
+    LOG(ERROR) << "Could not even get log directory; out of space?";
+    return;
+  }
+  AddCrashMetaData("sig", kCollectionErrorSignature);
+  AddCrashMetaData("error_type", GetErrorTypeSignature(error_type));
+  std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
+  std::string error_log = brillo::GetLog();
+  FilePath diag_log_path = GetCrashPath(crash_path, dump_basename, "diaglog");
+  if (GetLogContents(FilePath(log_config_path_), kCollectionErrorSignature,
+                     diag_log_path)) {
+    // We load the contents of diag_log into memory and append it to
+    // the error log.  We cannot just append to files because we need
+    // to always create new files to prevent attack.
+    std::string diag_log_contents;
+    base::ReadFileToString(diag_log_path, &diag_log_contents);
+    error_log.append(diag_log_contents);
+    base::DeleteFile(diag_log_path, false);
+  }
+  FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
+  FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
+  // We must use WriteNewFile instead of base::WriteFile as we do
+  // not want to write with root access to a symlink that an attacker
+  // might have created.
+  if (WriteNewFile(log_path, error_log.data(), error_log.length()) < 0) {
+    LOG(ERROR) << "Error writing new file " << log_path.value();
+    return;
+  }
+  WriteCrashMetaData(meta_path, exec, log_path.value());
+}
+
+bool UserCollector::CopyOffProcFiles(pid_t pid,
+                                     const FilePath &container_dir) {
+  if (!base::CreateDirectory(container_dir)) {
+    PLOG(ERROR) << "Could not create " << container_dir.value();
+    return false;
+  }
+  int dir_mask = base::FILE_PERMISSION_READ_BY_USER
+                 | base::FILE_PERMISSION_WRITE_BY_USER
+                 | base::FILE_PERMISSION_EXECUTE_BY_USER
+                 | base::FILE_PERMISSION_READ_BY_GROUP
+                 | base::FILE_PERMISSION_WRITE_BY_GROUP;
+  if (!base::SetPosixFilePermissions(container_dir,
+                                     base::FILE_PERMISSION_MASK & dir_mask)) {
+    PLOG(ERROR) << "Could not set permissions for " << container_dir.value()
+                << " to " << std::oct
+                << (base::FILE_PERMISSION_MASK & dir_mask);
+    return false;
+  }
+  FilePath process_path = GetProcessPath(pid);
+  if (!base::PathExists(process_path)) {
+    LOG(ERROR) << "Path " << process_path.value() << " does not exist";
+    return false;
+  }
+  static const char *proc_files[] = {
+    "auxv",
+    "cmdline",
+    "environ",
+    "maps",
+    "status"
+  };
+  for (unsigned i = 0; i < arraysize(proc_files); ++i) {
+    if (!base::CopyFile(process_path.Append(proc_files[i]),
+                        container_dir.Append(proc_files[i]))) {
+      LOG(ERROR) << "Could not copy " << proc_files[i] << " file";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool UserCollector::ValidateProcFiles(const FilePath &container_dir) const {
+  // Check if the maps file is empty, which could be due to the crashed
+  // process being reaped by the kernel before finishing a core dump.
+  int64_t file_size = 0;
+  if (!base::GetFileSize(container_dir.Append("maps"), &file_size)) {
+    LOG(ERROR) << "Could not get the size of maps file";
+    return false;
+  }
+  if (file_size == 0) {
+    LOG(ERROR) << "maps file is empty";
+    return false;
+  }
+  return true;
+}
+
+UserCollector::ErrorType UserCollector::ValidateCoreFile(
+    const FilePath &core_path) const {
+  int fd = HANDLE_EINTR(open(core_path.value().c_str(), O_RDONLY));
+  if (fd < 0) {
+    PLOG(ERROR) << "Could not open core file " << core_path.value();
+    return kErrorInvalidCoreFile;
+  }
+
+  char e_ident[EI_NIDENT];
+  bool read_ok = base::ReadFromFD(fd, e_ident, sizeof(e_ident));
+  IGNORE_EINTR(close(fd));
+  if (!read_ok) {
+    LOG(ERROR) << "Could not read header of core file";
+    return kErrorInvalidCoreFile;
+  }
+
+  if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
+      e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
+    LOG(ERROR) << "Invalid core file";
+    return kErrorInvalidCoreFile;
+  }
+
+#if __WORDSIZE == 64
+  // TODO(benchan, mkrebs): Remove this check once core2md can
+  // handles both 32-bit and 64-bit ELF on a 64-bit platform.
+  if (e_ident[EI_CLASS] == ELFCLASS32) {
+    LOG(ERROR) << "Conversion of 32-bit core file on 64-bit platform is "
+               << "currently not supported";
+    return kErrorUnsupported32BitCoreFile;
+  }
+#endif
+
+  return kErrorNone;
+}
+
+bool UserCollector::GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
+                                             FilePath *crash_file_path,
+                                             bool *out_of_capacity) {
+  FilePath process_path = GetProcessPath(pid);
+  std::string status;
+  if (directory_failure_) {
+    LOG(ERROR) << "Purposefully failing to create spool directory";
+    return false;
+  }
+
+  uid_t uid;
+  if (base::ReadFileToString(process_path.Append("status"), &status)) {
+    std::vector<std::string> status_lines = base::SplitString(
+        status, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+    std::string process_state;
+    if (!GetStateFromStatus(status_lines, &process_state)) {
+      LOG(ERROR) << "Could not find process state in status file";
+      return false;
+    }
+    LOG(INFO) << "State of crashed process [" << pid << "]: " << process_state;
+
+    // Get effective UID of crashing process.
+    int id;
+    if (!GetIdFromStatus(kUserId, kIdEffective, status_lines, &id)) {
+      LOG(ERROR) << "Could not find euid in status file";
+      return false;
+    }
+    uid = id;
+  } else if (supplied_ruid != kUnknownUid) {
+    LOG(INFO) << "Using supplied UID " << supplied_ruid
+              << " for crashed process [" << pid
+              << "] due to error reading status file";
+    uid = supplied_ruid;
+  } else {
+    LOG(ERROR) << "Could not read status file and kernel did not supply UID";
+    LOG(INFO) << "Path " << process_path.value() << " DirectoryExists: "
+              << base::DirectoryExists(process_path);
+    return false;
+  }
+
+  if (!GetCreatedCrashDirectoryByEuid(uid, crash_file_path, out_of_capacity)) {
+    LOG(ERROR) << "Could not create crash directory";
+    return false;
+  }
+  return true;
+}
+
+bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
+  // Copy off all stdin to a core file.
+  FilePath stdin_path("/proc/self/fd/0");
+  if (base::CopyFile(stdin_path, core_path)) {
+    return true;
+  }
+
+  PLOG(ERROR) << "Could not write core file";
+  // If the file system was full, make sure we remove any remnants.
+  base::DeleteFile(core_path, false);
+  return false;
+}
+
+bool UserCollector::RunCoreToMinidump(const FilePath &core_path,
+                                      const FilePath &procfs_directory,
+                                      const FilePath &minidump_path,
+                                      const FilePath &temp_directory) {
+  FilePath output_path = temp_directory.Append("output");
+  brillo::ProcessImpl core2md;
+  core2md.RedirectOutput(output_path.value());
+  core2md.AddArg(kCoreToMinidumpConverterPath);
+  core2md.AddArg(core_path.value());
+  core2md.AddArg(procfs_directory.value());
+
+  if (!core2md_failure_) {
+    core2md.AddArg(minidump_path.value());
+  } else {
+    // To test how core2md errors are propagaged, cause an error
+    // by forgetting a required argument.
+  }
+
+  int errorlevel = core2md.Run();
+
+  std::string output;
+  base::ReadFileToString(output_path, &output);
+  if (errorlevel != 0) {
+    LOG(ERROR) << "Problem during " << kCoreToMinidumpConverterPath
+               << " [result=" << errorlevel << "]: " << output;
+    return false;
+  }
+
+  if (!base::PathExists(minidump_path)) {
+    LOG(ERROR) << "Minidump file " << minidump_path.value()
+               << " was not created";
+    return false;
+  }
+  return true;
+}
+
+UserCollector::ErrorType UserCollector::ConvertCoreToMinidump(
+    pid_t pid,
+    const FilePath &container_dir,
+    const FilePath &core_path,
+    const FilePath &minidump_path) {
+  // If proc files are unuable, we continue to read the core file from stdin,
+  // but only skip the core-to-minidump conversion, so that we may still use
+  // the core file for debugging.
+  bool proc_files_usable =
+      CopyOffProcFiles(pid, container_dir) && ValidateProcFiles(container_dir);
+
+  // Switch back to the original UID/GID.
+  gid_t rgid, egid, sgid;
+  if (getresgid(&rgid, &egid, &sgid) != 0) {
+    PLOG(FATAL) << "Unable to read saved gid";
+  }
+  if (setresgid(sgid, sgid, -1) != 0) {
+    PLOG(FATAL) << "Unable to set real group ID back to saved gid";
+  } else {
+    if (getresgid(&rgid, &egid, &sgid) != 0) {
+      // If the groups cannot be read at this point, the rgid variable will
+      // contain the previously read group ID from before changing it.  This
+      // will cause the chown call below to set the incorrect group for
+      // non-root crashes.  But do not treat this as a fatal error, so that
+      // the rest of the collection will continue for potential manual
+      // collection by a developer.
+      PLOG(ERROR) << "Unable to read real group ID after setting it";
+    }
+  }
+
+  uid_t ruid, euid, suid;
+  if (getresuid(&ruid, &euid, &suid) != 0) {
+    PLOG(FATAL) << "Unable to read saved uid";
+  }
+  if (setresuid(suid, suid, -1) != 0) {
+    PLOG(FATAL) << "Unable to set real user ID back to saved uid";
+  } else {
+    if (getresuid(&ruid, &euid, &suid) != 0) {
+      // If the user ID cannot be read at this point, the ruid variable will
+      // contain the previously read user ID from before changing it.  This
+      // will cause the chown call below to set the incorrect user for
+      // non-root crashes.  But do not treat this as a fatal error, so that
+      // the rest of the collection will continue for potential manual
+      // collection by a developer.
+      PLOG(ERROR) << "Unable to read real user ID after setting it";
+    }
+  }
+
+  if (!CopyStdinToCoreFile(core_path)) {
+    return kErrorReadCoreData;
+  }
+
+  if (!proc_files_usable) {
+    LOG(INFO) << "Skipped converting core file to minidump due to "
+              << "unusable proc files";
+    return kErrorUnusableProcFiles;
+  }
+
+  ErrorType error = ValidateCoreFile(core_path);
+  if (error != kErrorNone) {
+    return error;
+  }
+
+  // Chown the temp container directory back to the original user/group that
+  // crash_reporter is run as, so that additional files can be written to
+  // the temp folder.
+  if (chown(container_dir.value().c_str(), ruid, rgid) < 0) {
+    PLOG(ERROR) << "Could not set owner for " << container_dir.value();
+  }
+
+  if (!RunCoreToMinidump(core_path,
+                         container_dir,  // procfs directory
+                         minidump_path,
+                         container_dir)) {  // temporary directory
+    return kErrorCore2MinidumpConversion;
+  }
+
+  LOG(INFO) << "Stored minidump to " << minidump_path.value();
+  return kErrorNone;
+}
+
+UserCollector::ErrorType UserCollector::ConvertAndEnqueueCrash(
+    pid_t pid, const std::string &exec, uid_t supplied_ruid,
+    bool *out_of_capacity) {
+  FilePath crash_path;
+  if (!GetCreatedCrashDirectory(pid, supplied_ruid, &crash_path,
+      out_of_capacity)) {
+    LOG(ERROR) << "Unable to find/create process-specific crash path";
+    return kErrorSystemIssue;
+  }
+
+  // Directory like /tmp/crash_reporter/1234 which contains the
+  // procfs entries and other temporary files used during conversion.
+  FilePath container_dir(StringPrintf("%s/%d", kCoreTempFolder, pid));
+  // Delete a pre-existing directory from crash reporter that may have
+  // been left around for diagnostics from a failed conversion attempt.
+  // If we don't, existing files can cause forking to fail.
+  base::DeleteFile(container_dir, true);
+  std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
+  FilePath core_path = GetCrashPath(crash_path, dump_basename, "core");
+  FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
+  FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp");
+  FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
+
+  if (GetLogContents(FilePath(log_config_path_), exec, log_path))
+    AddCrashMetaData("log", log_path.value());
+
+  ErrorType error_type =
+      ConvertCoreToMinidump(pid, container_dir, core_path, minidump_path);
+  if (error_type != kErrorNone) {
+    LOG(INFO) << "Leaving core file at " << core_path.value()
+              << " due to conversion error";
+    return error_type;
+  }
+
+  // Here we commit to sending this file.  We must not return false
+  // after this point or we will generate a log report as well as a
+  // crash report.
+  WriteCrashMetaData(meta_path,
+                     exec,
+                     minidump_path.value());
+
+  if (!IsDeveloperImage()) {
+    base::DeleteFile(core_path, false);
+  } else {
+    LOG(INFO) << "Leaving core file at " << core_path.value()
+              << " due to developer image";
+  }
+
+  base::DeleteFile(container_dir, true);
+  return kErrorNone;
+}
+
+bool UserCollector::ParseCrashAttributes(const std::string &crash_attributes,
+                                         pid_t *pid, int *signal, uid_t *uid,
+                                         gid_t *gid,
+                                         std::string *kernel_supplied_name) {
+  pcrecpp::RE re("(\\d+):(\\d+):(\\d+):(\\d+):(.*)");
+  if (re.FullMatch(crash_attributes, pid, signal, uid, gid,
+                   kernel_supplied_name))
+    return true;
+
+  LOG(INFO) << "Falling back to parsing crash attributes '"
+            << crash_attributes << "' without UID and GID";
+  pcrecpp::RE re_without_uid("(\\d+):(\\d+):(.*)");
+  *uid = kUnknownUid;
+  *gid = kUnknownGid;
+  return re_without_uid.FullMatch(crash_attributes, pid, signal,
+      kernel_supplied_name);
+}
+
+bool UserCollector::ShouldDump(bool has_owner_consent,
+                               bool is_developer,
+                               std::string *reason) {
+  reason->clear();
+
+  // For developer builds, we always want to keep the crash reports unless
+  // we're testing the crash facilities themselves.  This overrides
+  // feedback.  Crash sending still obeys consent.
+  if (is_developer) {
+    *reason = "developer build - not testing - always dumping";
+    return true;
+  }
+
+  if (!has_owner_consent) {
+    *reason = "ignoring - no consent";
+    return false;
+  }
+
+  *reason = "handling";
+  return true;
+}
+
+bool UserCollector::HandleCrash(const std::string &crash_attributes,
+                                const char *force_exec) {
+  CHECK(initialized_);
+  pid_t pid = 0;
+  int signal = 0;
+  uid_t supplied_ruid = kUnknownUid;
+  gid_t supplied_rgid = kUnknownGid;
+  std::string kernel_supplied_name;
+
+  if (!ParseCrashAttributes(crash_attributes, &pid, &signal, &supplied_ruid,
+                            &supplied_rgid, &kernel_supplied_name)) {
+    LOG(ERROR) << "Invalid parameter: --user=" <<  crash_attributes;
+    return false;
+  }
+
+  // Switch to the group and user that ran the crashing binary in order to
+  // access their /proc files.  Do not set suid/sgid, so that we can switch
+  // back after copying the necessary files.
+  if (setresgid(supplied_rgid, supplied_rgid, -1) != 0) {
+    PLOG(FATAL) << "Unable to set real group ID to access process files";
+  }
+  if (setresuid(supplied_ruid, supplied_ruid, -1) != 0) {
+    PLOG(FATAL) << "Unable to set real user ID to access process files";
+  }
+
+  std::string exec;
+  if (force_exec) {
+    exec.assign(force_exec);
+  } else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
+    // If we cannot find the exec name, use the kernel supplied name.
+    // We don't always use the kernel's since it truncates the name to
+    // 16 characters.
+    exec = StringPrintf("supplied_%s", kernel_supplied_name.c_str());
+  }
+
+  // Allow us to test the crash reporting mechanism successfully even if
+  // other parts of the system crash.
+  if (!filter_in_.empty() &&
+      (filter_in_ == "none" ||
+       filter_in_ != exec)) {
+    // We use a different format message to make it more obvious in tests
+    // which crashes are test generated and which are real.
+    LOG(WARNING) << "Ignoring crash from " << exec << "[" << pid << "] while "
+                 << "filter_in=" << filter_in_ << ".";
+    return true;
+  }
+
+  std::string reason;
+  bool dump = ShouldDump(is_feedback_allowed_function_(),
+                         IsDeveloperImage(),
+                         &reason);
+
+  LOG(WARNING) << "Received crash notification for " << exec << "[" << pid
+               << "] sig " << signal << ", user " << supplied_ruid
+               << " (" << reason << ")";
+
+  if (dump) {
+    count_crash_function_();
+
+    if (generate_diagnostics_) {
+      bool out_of_capacity = false;
+      ErrorType error_type =
+          ConvertAndEnqueueCrash(pid, exec, supplied_ruid, &out_of_capacity);
+      if (error_type != kErrorNone) {
+        if (!out_of_capacity)
+          EnqueueCollectionErrorLog(pid, error_type, exec);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
diff --git a/user_collector.h b/user_collector.h
new file mode 100644
index 0000000..7261ed4
--- /dev/null
+++ b/user_collector.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_USER_COLLECTOR_H_
+#define CRASH_REPORTER_USER_COLLECTOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "crash_collector.h"
+
+class SystemLogging;
+
+// User crash collector.
+class UserCollector : public CrashCollector {
+ public:
+  UserCollector();
+
+  // Initialize the user crash collector for detection of crashes,
+  // given a crash counting function, the path to this executable,
+  // metrics collection enabled oracle, and system logger facility.
+  // Crash detection/reporting is not enabled until Enable is called.
+  // |generate_diagnostics| is used to indicate whether or not to try
+  // to generate a minidump from crashes.
+  void Initialize(CountCrashFunction count_crash,
+                  const std::string &our_path,
+                  IsFeedbackAllowedFunction is_metrics_allowed,
+                  bool generate_diagnostics,
+                  bool core2md_failure,
+                  bool directory_failure,
+                  const std::string &filter_in);
+
+  ~UserCollector() override;
+
+  // Enable collection.
+  bool Enable() { return SetUpInternal(true); }
+
+  // Disable collection.
+  bool Disable() { return SetUpInternal(false); }
+
+  // Handle a specific user crash.  Returns true on success.
+  bool HandleCrash(const std::string &crash_attributes,
+                   const char *force_exec);
+
+ private:
+  friend class UserCollectorTest;
+  FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPath);
+  FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPid);
+  FRIEND_TEST(UserCollectorTest, CopyOffProcFilesOK);
+  FRIEND_TEST(UserCollectorTest, GetExecutableBaseNameFromPid);
+  FRIEND_TEST(UserCollectorTest, GetFirstLineWithPrefix);
+  FRIEND_TEST(UserCollectorTest, GetIdFromStatus);
+  FRIEND_TEST(UserCollectorTest, GetStateFromStatus);
+  FRIEND_TEST(UserCollectorTest, GetProcessPath);
+  FRIEND_TEST(UserCollectorTest, GetSymlinkTarget);
+  FRIEND_TEST(UserCollectorTest, GetUserInfoFromName);
+  FRIEND_TEST(UserCollectorTest, ParseCrashAttributes);
+  FRIEND_TEST(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage);
+  FRIEND_TEST(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent);
+  FRIEND_TEST(UserCollectorTest, ShouldDumpUseConsentProductionImage);
+  FRIEND_TEST(UserCollectorTest, ValidateProcFiles);
+  FRIEND_TEST(UserCollectorTest, ValidateCoreFile);
+
+  // Enumeration to pass to GetIdFromStatus.  Must match the order
+  // that the kernel lists IDs in the status file.
+  enum IdKind {
+    kIdReal = 0,  // uid and gid
+    kIdEffective = 1,  // euid and egid
+    kIdSet = 2,  // suid and sgid
+    kIdFileSystem = 3,  // fsuid and fsgid
+    kIdMax
+  };
+
+  enum ErrorType {
+    kErrorNone,
+    kErrorSystemIssue,
+    kErrorReadCoreData,
+    kErrorUnusableProcFiles,
+    kErrorInvalidCoreFile,
+    kErrorUnsupported32BitCoreFile,
+    kErrorCore2MinidumpConversion,
+  };
+
+  static const int kForkProblem = 255;
+
+  // Returns an error type signature for a given |error_type| value,
+  // which is reported to the crash server along with the
+  // crash_reporter-user-collection signature.
+  std::string GetErrorTypeSignature(ErrorType error_type) const;
+
+  bool SetUpInternal(bool enabled);
+
+  // Returns, via |line|, the first line in |lines| that starts with |prefix|.
+  // Returns true if a line is found, or false otherwise.
+  bool GetFirstLineWithPrefix(const std::vector<std::string> &lines,
+                              const char *prefix, std::string *line);
+
+  // Returns the identifier of |kind|, via |id|, found in |status_lines| on
+  // the line starting with |prefix|. |status_lines| contains the lines in
+  // the status file. Returns true if the identifier can be determined.
+  bool GetIdFromStatus(const char *prefix,
+                       IdKind kind,
+                       const std::vector<std::string> &status_lines,
+                       int *id);
+
+  // Returns the process state, via |state|, found in |status_lines|, which
+  // contains the lines in the status file. Returns true if the process state
+  // can be determined.
+  bool GetStateFromStatus(const std::vector<std::string> &status_lines,
+                          std::string *state);
+
+  void LogCollectionError(const std::string &error_message);
+  void EnqueueCollectionErrorLog(pid_t pid, ErrorType error_type,
+                                 const std::string &exec_name);
+
+  bool CopyOffProcFiles(pid_t pid, const base::FilePath &container_dir);
+
+  // Validates the proc files at |container_dir| and returns true if they
+  // are usable for the core-to-minidump conversion later. For instance, if
+  // a process is reaped by the kernel before the copying of its proc files
+  // takes place, some proc files like /proc/<pid>/maps may contain nothing
+  // and thus become unusable.
+  bool ValidateProcFiles(const base::FilePath &container_dir) const;
+
+  // Validates the core file at |core_path| and returns kErrorNone if
+  // the file contains the ELF magic bytes and an ELF class that matches the
+  // platform (i.e. 32-bit ELF on a 32-bit platform or 64-bit ELF on a 64-bit
+  // platform), which is due to the limitation in core2md. It returns an error
+  // type otherwise.
+  ErrorType ValidateCoreFile(const base::FilePath &core_path) const;
+
+  // Determines the crash directory for given pid based on pid's owner,
+  // and creates the directory if necessary with appropriate permissions.
+  // Returns true whether or not directory needed to be created, false on
+  // any failure.
+  bool GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
+                                base::FilePath *crash_file_path,
+                                bool *out_of_capacity);
+  bool CopyStdinToCoreFile(const base::FilePath &core_path);
+  bool RunCoreToMinidump(const base::FilePath &core_path,
+                         const base::FilePath &procfs_directory,
+                         const base::FilePath &minidump_path,
+                         const base::FilePath &temp_directory);
+  ErrorType ConvertCoreToMinidump(pid_t pid,
+                                  const base::FilePath &container_dir,
+                                  const base::FilePath &core_path,
+                                  const base::FilePath &minidump_path);
+  ErrorType ConvertAndEnqueueCrash(pid_t pid, const std::string &exec_name,
+                                   uid_t supplied_ruid, bool *out_of_capacity);
+  bool ParseCrashAttributes(const std::string &crash_attributes,
+                            pid_t *pid, int *signal, uid_t *uid, gid_t *gid,
+                            std::string *kernel_supplied_name);
+
+  bool ShouldDump(bool has_owner_consent,
+                  bool is_developer,
+                  std::string *reason);
+
+  bool generate_diagnostics_;
+  std::string our_path_;
+  bool initialized_;
+
+  bool core2md_failure_;
+  bool directory_failure_;
+  std::string filter_in_;
+
+  static const char *kUserId;
+  static const char *kGroupId;
+
+  DISALLOW_COPY_AND_ASSIGN(UserCollector);
+};
+
+#endif  // CRASH_REPORTER_USER_COLLECTOR_H_
diff --git a/user_collector_test.cc b/user_collector_test.cc
new file mode 100644
index 0000000..16a5cd5
--- /dev/null
+++ b/user_collector_test.cc
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "user_collector.h"
+
+#include <elf.h>
+#include <sys/cdefs.h>  // For __WORDSIZE
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_split.h>
+#include <brillo/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using brillo::FindLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = false;
+
+const char kFilePath[] = "/my/path";
+
+void CountCrash() {
+  ++s_crashes;
+}
+
+bool IsMetrics() {
+  return s_metrics;
+}
+
+}  // namespace
+
+class UserCollectorMock : public UserCollector {
+ public:
+  MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UserCollectorTest : public ::testing::Test {
+  void SetUp() {
+    s_crashes = 0;
+
+    EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+    collector_.Initialize(CountCrash,
+                          kFilePath,
+                          IsMetrics,
+                          false,
+                          false,
+                          false,
+                          "");
+
+    EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
+
+    mkdir(test_dir_.path().Append("test").value().c_str(), 0777);
+    pid_ = getpid();
+    brillo::ClearLog();
+  }
+
+ protected:
+  void ExpectFileEquals(const char *golden,
+                        const FilePath &file_path) {
+    std::string contents;
+    EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
+    EXPECT_EQ(golden, contents);
+  }
+
+  std::vector<std::string> SplitLines(const std::string &lines) const {
+    return base::SplitString(lines, "\n", base::TRIM_WHITESPACE,
+                             base::SPLIT_WANT_ALL);
+  }
+
+  UserCollectorMock collector_;
+  pid_t pid_;
+  base::ScopedTempDir test_dir_;
+};
+
+TEST_F(UserCollectorTest, ParseCrashAttributes) {
+  pid_t pid;
+  int signal;
+  uid_t uid;
+  gid_t gid;
+  std::string exec_name;
+  EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:1000:2000:foobar",
+      &pid, &signal, &uid, &gid, &exec_name));
+  EXPECT_EQ(123456, pid);
+  EXPECT_EQ(11, signal);
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(2000U, gid);
+  EXPECT_EQ("foobar", exec_name);
+  EXPECT_TRUE(collector_.ParseCrashAttributes("4321:6:barfoo",
+      &pid, &signal, &uid, &gid, &exec_name));
+  EXPECT_EQ(4321, pid);
+  EXPECT_EQ(6, signal);
+  EXPECT_EQ(-1U, uid);
+  EXPECT_EQ(-1U, gid);
+  EXPECT_EQ("barfoo", exec_name);
+
+  EXPECT_FALSE(collector_.ParseCrashAttributes("123456:11",
+      &pid, &signal, &uid, &gid, &exec_name));
+
+  EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:exec:extra",
+      &pid, &signal, &uid, &gid, &exec_name));
+  EXPECT_EQ("exec:extra", exec_name);
+
+  EXPECT_FALSE(collector_.ParseCrashAttributes("12345p:11:foobar",
+      &pid, &signal, &uid, &gid, &exec_name));
+
+  EXPECT_FALSE(collector_.ParseCrashAttributes("123456:1 :foobar",
+      &pid, &signal, &uid, &gid, &exec_name));
+
+  EXPECT_FALSE(collector_.ParseCrashAttributes("123456::foobar",
+      &pid, &signal, &uid, &gid, &exec_name));
+}
+
+TEST_F(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent) {
+  std::string reason;
+  EXPECT_TRUE(collector_.ShouldDump(false, true, &reason));
+  EXPECT_EQ("developer build - not testing - always dumping", reason);
+
+  // When running a crash test, behave as normal.
+  EXPECT_FALSE(collector_.ShouldDump(false, false, &reason));
+  EXPECT_EQ("ignoring - no consent", reason);
+}
+
+TEST_F(UserCollectorTest, ShouldDumpUseConsentProductionImage) {
+  std::string result;
+  EXPECT_FALSE(collector_.ShouldDump(false, false, &result));
+  EXPECT_EQ("ignoring - no consent", result);
+
+  EXPECT_TRUE(collector_.ShouldDump(true, false, &result));
+  EXPECT_EQ("handling", result);
+}
+
+TEST_F(UserCollectorTest, HandleCrashWithoutConsent) {
+  s_metrics = false;
+  collector_.HandleCrash("20:10:ignored", "foobar");
+  EXPECT_TRUE(FindLog(
+      "Received crash notification for foobar[20] sig 10"));
+  ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(UserCollectorTest, HandleNonChromeCrashWithConsent) {
+  s_metrics = true;
+  collector_.HandleCrash("5:2:ignored", "chromeos-wm");
+  EXPECT_TRUE(FindLog(
+      "Received crash notification for chromeos-wm[5] sig 2"));
+  ASSERT_EQ(s_crashes, 1);
+}
+
+TEST_F(UserCollectorTest, GetProcessPath) {
+  FilePath path = collector_.GetProcessPath(100);
+  ASSERT_EQ("/proc/100", path.value());
+}
+
+TEST_F(UserCollectorTest, GetSymlinkTarget) {
+  FilePath result;
+  ASSERT_FALSE(collector_.GetSymlinkTarget(FilePath("/does_not_exist"),
+                                           &result));
+  ASSERT_TRUE(FindLog(
+      "Readlink failed on /does_not_exist with 2"));
+  std::string long_link = test_dir_.path().value();
+  for (int i = 0; i < 50; ++i)
+    long_link += "0123456789";
+  long_link += "/gold";
+
+  for (size_t len = 1; len <= long_link.size(); ++len) {
+    std::string this_link;
+    static const char* kLink =
+        test_dir_.path().Append("test/this_link").value().c_str();
+    this_link.assign(long_link.c_str(), len);
+    ASSERT_EQ(len, this_link.size());
+    ASSERT_EQ(0, symlink(this_link.c_str(), kLink));
+    ASSERT_TRUE(collector_.GetSymlinkTarget(FilePath(kLink), &result));
+    ASSERT_EQ(this_link, result.value());
+    unlink(kLink);
+  }
+}
+
+TEST_F(UserCollectorTest, GetExecutableBaseNameFromPid) {
+  std::string base_name;
+  EXPECT_FALSE(collector_.GetExecutableBaseNameFromPid(0, &base_name));
+  EXPECT_TRUE(FindLog(
+      "Readlink failed on /proc/0/exe with 2"));
+  EXPECT_TRUE(FindLog(
+      "GetSymlinkTarget failed - Path /proc/0 DirectoryExists: 0"));
+  EXPECT_TRUE(FindLog("stat /proc/0/exe failed: -1 2"));
+
+  brillo::ClearLog();
+  pid_t my_pid = getpid();
+  EXPECT_TRUE(collector_.GetExecutableBaseNameFromPid(my_pid, &base_name));
+  EXPECT_FALSE(FindLog("Readlink failed"));
+  EXPECT_EQ("crash_reporter_tests", base_name);
+}
+
+TEST_F(UserCollectorTest, GetFirstLineWithPrefix) {
+  std::vector<std::string> lines;
+  std::string line;
+
+  EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
+  EXPECT_EQ("", line);
+
+  lines.push_back("Name:\tls");
+  lines.push_back("State:\tR (running)");
+  lines.push_back(" Foo:\t1000");
+
+  line.clear();
+  EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
+  EXPECT_EQ(lines[0], line);
+
+  line.clear();
+  EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "State:", &line));
+  EXPECT_EQ(lines[1], line);
+
+  line.clear();
+  EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Foo:", &line));
+  EXPECT_EQ("", line);
+
+  line.clear();
+  EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, " Foo:", &line));
+  EXPECT_EQ(lines[2], line);
+
+  line.clear();
+  EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Bar:", &line));
+  EXPECT_EQ("", line);
+}
+
+TEST_F(UserCollectorTest, GetIdFromStatus) {
+  int id = 1;
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                          UserCollector::kIdEffective,
+                                          SplitLines("nothing here"),
+                                          &id));
+  EXPECT_EQ(id, 1);
+
+  // Not enough parameters.
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                          UserCollector::kIdReal,
+                                          SplitLines("line 1\nUid:\t1\n"),
+                                          &id));
+
+  const std::vector<std::string> valid_contents =
+      SplitLines("\nUid:\t1\t2\t3\t4\nGid:\t5\t6\t7\t8\n");
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                         UserCollector::kIdReal,
+                                         valid_contents,
+                                         &id));
+  EXPECT_EQ(1, id);
+
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                         UserCollector::kIdEffective,
+                                         valid_contents,
+                                         &id));
+  EXPECT_EQ(2, id);
+
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                         UserCollector::kIdFileSystem,
+                                         valid_contents,
+                                         &id));
+  EXPECT_EQ(4, id);
+
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+                                         UserCollector::kIdEffective,
+                                         valid_contents,
+                                         &id));
+  EXPECT_EQ(6, id);
+
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+                                         UserCollector::kIdSet,
+                                         valid_contents,
+                                         &id));
+  EXPECT_EQ(7, id);
+
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+                                          UserCollector::IdKind(5),
+                                          valid_contents,
+                                          &id));
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+                                          UserCollector::IdKind(-1),
+                                          valid_contents,
+                                          &id));
+
+  // Fail if junk after number
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                          UserCollector::kIdReal,
+                                          SplitLines("Uid:\t1f\t2\t3\t4\n"),
+                                          &id));
+  EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                         UserCollector::kIdReal,
+                                         SplitLines("Uid:\t1\t2\t3\t4\n"),
+                                         &id));
+  EXPECT_EQ(1, id);
+
+  // Fail if more than 4 numbers.
+  EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+                                          UserCollector::kIdReal,
+                                          SplitLines("Uid:\t1\t2\t3\t4\t5\n"),
+                                          &id));
+}
+
+TEST_F(UserCollectorTest, GetStateFromStatus) {
+  std::string state;
+  EXPECT_FALSE(collector_.GetStateFromStatus(SplitLines("nothing here"),
+                                             &state));
+  EXPECT_EQ("", state);
+
+  EXPECT_TRUE(collector_.GetStateFromStatus(SplitLines("State:\tR (running)"),
+                                            &state));
+  EXPECT_EQ("R (running)", state);
+
+  EXPECT_TRUE(collector_.GetStateFromStatus(
+      SplitLines("Name:\tls\nState:\tZ (zombie)\n"), &state));
+  EXPECT_EQ("Z (zombie)", state);
+}
+
+TEST_F(UserCollectorTest, GetUserInfoFromName) {
+  gid_t gid = 100;
+  uid_t uid = 100;
+  EXPECT_TRUE(collector_.GetUserInfoFromName("root", &uid, &gid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(0U, gid);
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesBadPath) {
+  // Try a path that is not writable.
+  ASSERT_FALSE(collector_.CopyOffProcFiles(pid_, FilePath("/bad/path")));
+  EXPECT_TRUE(FindLog("Could not create /bad/path"));
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesBadPid) {
+  FilePath container_path(test_dir_.path().Append("test/container"));
+  ASSERT_FALSE(collector_.CopyOffProcFiles(0, container_path));
+  EXPECT_TRUE(FindLog("Path /proc/0 does not exist"));
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesOK) {
+  FilePath container_path(test_dir_.path().Append("test/container"));
+  ASSERT_TRUE(collector_.CopyOffProcFiles(pid_, container_path));
+  EXPECT_FALSE(FindLog("Could not copy"));
+  static struct {
+    const char *name;
+    bool exists;
+  } expectations[] = {
+    { "auxv", true },
+    { "cmdline", true },
+    { "environ", true },
+    { "maps", true },
+    { "mem", false },
+    { "mounts", false },
+    { "sched", false },
+    { "status", true }
+  };
+  for (unsigned i = 0; i < sizeof(expectations)/sizeof(expectations[0]); ++i) {
+    EXPECT_EQ(expectations[i].exists,
+              base::PathExists(
+                  container_path.Append(expectations[i].name)));
+  }
+}
+
+TEST_F(UserCollectorTest, ValidateProcFiles) {
+  FilePath container_dir = test_dir_.path();
+
+  // maps file not exists (i.e. GetFileSize fails)
+  EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
+
+  // maps file is empty
+  FilePath maps_file = container_dir.Append("maps");
+  ASSERT_EQ(0, base::WriteFile(maps_file, nullptr, 0));
+  ASSERT_TRUE(base::PathExists(maps_file));
+  EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
+
+  // maps file is not empty
+  const char data[] = "test data";
+  unsigned int numBytesWritten =
+      base::WriteFile(maps_file, data, sizeof(data));
+  ASSERT_EQ(sizeof(data), numBytesWritten);
+  ASSERT_TRUE(base::PathExists(maps_file));
+  EXPECT_TRUE(collector_.ValidateProcFiles(container_dir));
+}
+
+TEST_F(UserCollectorTest, ValidateCoreFile) {
+  FilePath container_dir = test_dir_.path();
+  FilePath core_file = container_dir.Append("core");
+
+  // Core file does not exist
+  EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+            collector_.ValidateCoreFile(core_file));
+  char e_ident[EI_NIDENT];
+  e_ident[EI_MAG0] = ELFMAG0;
+  e_ident[EI_MAG1] = ELFMAG1;
+  e_ident[EI_MAG2] = ELFMAG2;
+  e_ident[EI_MAG3] = ELFMAG3;
+#if __WORDSIZE == 32
+  e_ident[EI_CLASS] = ELFCLASS32;
+#elif __WORDSIZE == 64
+  e_ident[EI_CLASS] = ELFCLASS64;
+#else
+#error Unknown/unsupported value of __WORDSIZE.
+#endif
+
+  // Core file has the expected header
+  ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+  EXPECT_EQ(UserCollector::kErrorNone,
+            collector_.ValidateCoreFile(core_file));
+
+#if __WORDSIZE == 64
+  // 32-bit core file on 64-bit platform
+  e_ident[EI_CLASS] = ELFCLASS32;
+  ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+  EXPECT_EQ(UserCollector::kErrorUnsupported32BitCoreFile,
+            collector_.ValidateCoreFile(core_file));
+  e_ident[EI_CLASS] = ELFCLASS64;
+#endif
+
+  // Invalid core files
+  ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident) - 1));
+  EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+            collector_.ValidateCoreFile(core_file));
+
+  e_ident[EI_MAG0] = 0;
+  ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+  EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+            collector_.ValidateCoreFile(core_file));
+}
diff --git a/warn_collector.l b/warn_collector.l
new file mode 100644
index 0000000..70ab25c
--- /dev/null
+++ b/warn_collector.l
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * 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.
+ */
+
+%option noyywrap
+
+%{
+#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, yy_size_t *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()) {
+                                  /* yytext is
+                                     "file:line func+offset/offset()\n" */
+                                  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;
+
+__attribute__((__format__(__printf__, 1, 2)))
+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;
+}
+
+#pragma GCC diagnostic ignored "-Wwrite-strings"
+int WarnStart(void) {
+  uint32_t hash;
+  char *spacep;
+
+  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));
+  spacep = strchr(yytext, ' ');
+  if (spacep == NULL || spacep[1] == '\0')
+    spacep = "unknown-function";
+  fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
+  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) {
+    /* Go directly to the end of the file.  We don't want to parse the same
+     * warnings multiple times on reboot/restart.  We might miss some
+     * warnings, but so be it---it's too hard to keep track reliably of the
+     * last parsed position in the syslog.
+     */
+    if (lseek(yyin_fd, 0, SEEK_END) < 0)
+      Die("could not lseek %s: %s\n", path, strerror(errno));
+    /* 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, yy_size_t *result, size_t max_size) {
+  while (1) {
+    ssize_t ret = read(yyin_fd, buf, max_size);
+    if (ret < 0)
+      Die("read: %s", strerror(errno));
+    *result = ret;
+    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;
+    while (1) {
+      int n = read(i_fd, &event, sizeof(event));
+      if (n <= 0) {
+        if (errno == EINTR)
+          continue;
+        else
+          Die("inotify: %s\n", strerror(errno));
+      } else
+        break;
+    }
+    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);
+}
diff --git a/warn_collector_test.c b/warn_collector_test.c
new file mode 100644
index 0000000..7ebe0a8
--- /dev/null
+++ b/warn_collector_test.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Test driver for the warn_collector daemon.
+ */
+#include <stdlib.h>
+
+int main(int ac, char **av) {
+  int status = system("exec \"${SRC}\"/warn_collector_test.sh");
+  return status < 0 ? EXIT_FAILURE : WEXITSTATUS(status);
+}
diff --git a/warn_collector_test.sh b/warn_collector_test.sh
new file mode 100755
index 0000000..a5af16c
--- /dev/null
+++ b/warn_collector_test.sh
@@ -0,0 +1,90 @@
+#! /bin/bash
+
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# 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
+
+fail() {
+  printf '[ FAIL ] %b\n' "$*"
+  exit 1
+}
+
+if [[ -z ${SYSROOT} ]]; then
+  fail "SYSROOT must be set for this test to work"
+fi
+: ${OUT:=${PWD}}
+cd "${OUT}"
+PATH=${OUT}:${PATH}
+TESTLOG="${OUT}/warn-test-log"
+
+echo "Testing: $(which warn_collector)"
+
+cleanup() {
+  # Kill daemon (if started) on exit
+  kill %
+}
+
+check_log() {
+  local n_expected=$1
+  if [[ ! -f ${TESTLOG} ]]; then
+    fail "${TESTLOG} was not created"
+  fi
+  if [[ $(wc -l < "${TESTLOG}") -ne ${n_expected} ]]; then
+    fail "expected ${n_expected} lines in ${TESTLOG}, found this instead:
+$(<"${TESTLOG}")"
+  fi
+  if egrep -qv '^[0-9a-f]{8}' "${TESTLOG}"; then
+    fail "found bad lines in ${TESTLOG}:
+$(<"${TESTLOG}")"
+  fi
+}
+
+rm -f "${TESTLOG}"
+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 &
+trap cleanup EXIT
+
+# 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/warn_collector_test_reporter.sh b/warn_collector_test_reporter.sh
new file mode 100755
index 0000000..b6096ed
--- /dev/null
+++ b/warn_collector_test_reporter.sh
@@ -0,0 +1,27 @@
+#! /bin/sh
+
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# 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