Initial commit of crash reporter repo
diff --git a/crash_reporter/Makefile b/crash_reporter/Makefile
new file mode 100644
index 0000000..1b78f27
--- /dev/null
+++ b/crash_reporter/Makefile
@@ -0,0 +1,38 @@
+# Copyright (c) 2010 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.
+
+CRASH_BIN = crash_reporter
+CRASH_LIB = libcrash.so
+CRASH_OBJS = system_logging.o user_collector.o
+TEST_OBJS = $(CRASH_OBJS) system_logging_mock.o
+TEST_BINS = user_collector_test
+
+LIBS = -lbase -lpthread -lgflags -lrt -lmetrics
+
+TEST_LIBS = $(LIBS) -lgtest -lgmock
+INCLUDE_DIRS = -I.. -I$(SYSROOT)/usr/include/google-breakpad
+LIB_DIRS =
+
+# We need -fPIC for linking objects into shared objects.
+CXXFLAGS += -fPIC -Wall -Werror
+
+all:
+	echo "Specify either $(CRASH_BIN) or $(CRASH_LIB)"
+
+$(CRASH_LIB): crash_dumper.o
+	$(CXX) -shared -lbreakpad_client $^ -o $@
+
+$(CRASH_BIN): crash_reporter.o $(CRASH_OBJS)
+	$(CXX) $(CXXFLAGS) $(LIB_DIRS) $^ -lcrash $(LIBS) -o $@
+
+tests: $(TEST_BINS)
+
+user_collector_test: user_collector_test.o $(TEST_OBJS)
+	$(CXX) $(CXXFLAGS) $(LIB_DIRS) $^ $(TEST_LIBS) -o $@
+
+.cc.o:
+	$(CXX) $(CXXFLAGS) $(INCLUDE_DIRS) -c $< -o $@
+
+clean:
+	rm -rf *.o $(CRASH_BIN) $(TEST_BINS) $(CRASH_LIB)
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
new file mode 100644
index 0000000..951d3fa
--- /dev/null
+++ b/crash_reporter/crash_reporter.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "crash/system_logging.h"
+#include "crash/user_collector.h"
+#include "gflags/gflags.h"
+#include "metrics/metrics_library.h"
+
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+DEFINE_bool(init, false, "Initialize crash logging");
+DEFINE_bool(clean_shutdown, false, "Signal clean shutdown");
+DEFINE_bool(crash_test, false, "Crash test");
+DEFINE_string(exec, "", "Executable name crashed");
+DEFINE_int32(pid, -1, "Crashing PID");
+DEFINE_int32(signal, -1, "Signal causing crash");
+DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
+#pragma GCC diagnostic error "-Wstrict-aliasing"
+
+static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
+static const char kUncleanShutdownFile[] =
+    "/var/lib/crash_reporter/pending_clean_shutdown";
+
+// Enumeration of kinds of crashes to be used in the CrashCounter histogram.
+enum CrashKinds {
+  CRASH_KIND_KERNEL = 1,
+  CRASH_KIND_USER   = 2,
+  CRASH_KIND_MAX
+};
+
+static MetricsLibrary s_metrics_lib;
+static SystemLoggingImpl s_system_log;
+
+static bool IsMetricsCollectionAllowed() {
+  // TODO(kmixter): Eventually check system tainted state and
+  // move this down in metrics library where it would be explicitly
+  // checked when asked to send stats.
+  return true;
+}
+
+static void CheckUncleanShutdown() {
+  FilePath unclean_file_path(kUncleanShutdownFile);
+  if (!file_util::PathExists(unclean_file_path)) {
+    return;
+  }
+  s_system_log.LogWarning("Last shutdown was not clean");
+  if (IsMetricsCollectionAllowed()) {
+    s_metrics_lib.SendEnumToUMA(std::string(kCrashCounterHistogram),
+                                CRASH_KIND_KERNEL,
+                                CRASH_KIND_MAX);
+  }
+  if (!file_util::Delete(unclean_file_path, false)) {
+    s_system_log.LogError("Failed to delete unclean shutdown file %s",
+                          kUncleanShutdownFile);
+  }
+}
+
+static bool PrepareUncleanShutdownCheck() {
+  static const char empty[] = "";
+  FilePath file_path(kUncleanShutdownFile);
+  file_util::CreateDirectory(file_path.DirName());
+  return file_util::WriteFile(file_path, empty, 0) == 0;
+}
+
+static void SignalCleanShutdown() {
+  s_system_log.LogInfo("Clean shutdown signalled");
+  file_util::Delete(FilePath(kUncleanShutdownFile), false);
+}
+
+static void CountUserCrash() {
+  CHECK(IsMetricsCollectionAllowed());
+  s_metrics_lib.SendEnumToUMA(std::string(kCrashCounterHistogram),
+                              CRASH_KIND_USER,
+                              CRASH_KIND_MAX);
+}
+
+int main(int argc, char *argv[]) {
+  google::ParseCommandLineFlags(&argc, &argv, true);
+  FilePath my_path(argv[0]);
+  file_util::AbsolutePath(&my_path);
+  s_metrics_lib.Init();
+  s_system_log.Initialize(my_path.BaseName().value().c_str());
+  UserCollector user_collector;
+  user_collector.Initialize(CountUserCrash,
+                            my_path.value(),
+                            IsMetricsCollectionAllowed,
+                            &s_system_log);
+
+  if (FLAGS_init) {
+    CHECK(!FLAGS_clean_shutdown) << "Incompatible options";
+    user_collector.Enable();
+    if (FLAGS_unclean_check) {
+      CheckUncleanShutdown();
+      if (!PrepareUncleanShutdownCheck()) {
+        s_system_log.LogError("Unable to create shutdown check file");
+      }
+    }
+    return 0;
+  }
+
+  if (FLAGS_clean_shutdown) {
+    SignalCleanShutdown();
+    user_collector.Disable();
+    return 0;
+  }
+
+  // Handle a specific user space crash.
+  CHECK(FLAGS_signal != -1) << "Signal must be set";
+  CHECK(FLAGS_pid != -1) << "PID must be set";
+  CHECK(FLAGS_exec != "") << "Executable name must be set";
+
+  // Make it possible to test what happens when we crash while
+  // handling a crash.
+  if (FLAGS_crash_test) {
+    *(char *)0 = 0;
+    return 0;
+  }
+
+  user_collector.HandleCrash(FLAGS_signal, FLAGS_pid, FLAGS_exec);
+
+  return 0;
+}
diff --git a/crash_reporter/crash_sender b/crash_reporter/crash_sender
new file mode 100644
index 0000000..4332117
--- /dev/null
+++ b/crash_reporter/crash_sender
@@ -0,0 +1,206 @@
+#!/bin/sh
+
+# Copyright (c) 2010 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.
+
+set -e
+
+# Product ID in crash report
+CHROMEOS_PRODUCT=ChromeOS
+
+# Send up to 8 crashes per day.
+MAX_CRASH_RATE=8
+
+# Minidump uploading tool (provided by Google Breakpad).
+MINIDUMP_UPLOADER=/usr/bin/minidump_upload
+
+# URL to send non-official build crashes to.
+MINIDUMP_UPLOAD_STAGING_URL="http://clients2.google.com/cr/staging_report"
+
+# URL to send official build crashes to.
+MINIDUMP_UPLOAD_PROD_URL="http://clients2.google.com/cr/report"
+
+# File whose existence mocks crash sending.  If empty we pretend the
+# crash sending was successful, otherwise unsuccessful.
+MOCK_CRASH_SENDING="/tmp/mock-crash-sending"
+
+# File whose existence causes crash sending to be delayed (for testing).
+PAUSE_CRASH_SENDING="/tmp/pause-crash-sending"
+
+# File whose existence implies we're running and not to start again.
+RUN_FILE="/var/run/crash_sender.pid"
+
+# Maximum time to sleep between sends.
+SECONDS_SEND_SPREAD=600
+
+# 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="/var/lib/crash_sender"
+
+lecho() {
+  logger -t "${TAG}" "$@"
+}
+
+remove_run_file() {
+  rm -f "${RUN_FILE}"
+}
+
+check_not_already_running() {
+  if [ ! -f "${RUN_FILE}" ]; then
+    return
+  fi
+  local last_pid=$(cat "${RUN_FILE}")
+  if [ ! -f "/proc/${last_pid}/cmdline" ]; then
+    trap remove_run_file EXIT
+    echo $$ > "${RUN_FILE}"
+    return
+  fi
+  # This could just be an unrelated process, but it's ok to be conservative.
+  lecho "Already running.  Exiting now."
+  exit 1
+}
+
+get_version() {
+  grep ^CHROMEOS_RELEASE_VERSION /etc/lsb-release | cut -d = -f 2-
+}
+
+is_official() {
+  grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | cut -d = -f 2- | \
+      grep Official
+}
+
+# Generate a uniform random number in 0..max-1.
+generate_uniform_random() {
+  local max=$1
+  local random="$(od -An -N4 -tu /dev/urandom)"
+  echo $((random % max))
+}
+
+is_feedback_disabled() {
+  # See crosbug.com/3303.
+  return 1
+}
+
+is_on_3g() {
+  # See crosbug.com/3304.
+  return 1
+}
+
+# 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 -mmin +$((24 * 60)) -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}"/XXXX > /dev/null
+  return 0
+}
+
+send_crash() {
+  local sleep_time=$(generate_uniform_random $SECONDS_SEND_SPREAD)
+  local url="${MINIDUMP_UPLOAD_STAGING_URL}"
+  if is_official; then
+    url="${MINIDUMP_UPLOAD_PROD_URL}"
+  fi
+  lecho "Sending crash:"
+  lecho "  Scheduled to send in ${sleep_time}s"
+  lecho "  Minidump: ${minidump_path}"
+  lecho "  URL: ${url}"
+  lecho "  Product: ${CHROMEOS_PRODUCT}"
+  lecho "  Version: ${chromeos_version}"
+  if [ -s "${minidump_path}" ]; then
+    # We cannot tell much from the minidump without symbols, but we can tell
+    # at least what modules were loaded at the time of crash
+    local modules="$(/usr/bin/minidump_dump "${minidump_path}" 2>&- | \
+                     grep 'code_file' | sed -e 's/^.* = "//g;s/"//g' | \
+                     tr '\n' ' ')"
+    lecho "  Mapped: ${modules}"
+  fi
+  if [ -f "${MOCK_CRASH_SENDING}" ]; then
+    local mock_in=$(cat "${MOCK_CRASH_SENDING}")
+    if [ "${mock_in}" = "" ]; then
+      lecho "Mocking successful send"
+      return 0
+    else
+      lecho "Mocking unsuccessful send"
+      return 1
+    fi
+  fi
+
+  if ! sleep ${sleep_time}; then
+    lecho "Sleep failed"
+    return 1
+  fi
+
+  "${MINIDUMP_UPLOADER}" -p "${CHROMEOS_PRODUCT}" \
+          -v "${chromeos_version}" "${minidump_path}" "${url}"
+  return $?
+}
+
+# Send all crashes from the given directory.  The directory is currently
+# expected to just contain a bunch of minidumps - but this will change
+# over time to be a directory of directories where the minidump and core
+# file are in the directory as well as other metadata about the context
+# of the crash (executable name for instance).
+send_crashes() {
+  local dir="$1"
+  lecho "Considering crashes in ${dir}"
+  # Cycle through minidumps, most recent first.  That way if we're about
+  # to exceed the daily rate, we send the most recent minidumps.
+  if [ ! -d "${dir}" ]; then
+    return
+  fi
+  for file in $(ls -1t "${dir}"); do
+    local minidump_path="${dir}/${file}"
+    lecho "Considering crash ${minidump_path}"
+    if ! check_rate; then
+      lecho "Sending ${minidump_path} would exceed rate.  Leaving for later."
+      return 0
+   fi
+    local chromeos_version=$(get_version)
+    if is_feedback_disabled; then
+      lecho "Uploading is disabled.  Removing crash."
+      rm "${minidump_path}"
+    elif is_on_3g; then
+      lecho "Not sending crash report while on 3G, saving for later."
+    elif send_crash ${minidump_path}; then
+      # Send was successful, now remove
+      lecho "Successfully sent crash ${minidump_path} and removing"
+      rm "${minidump_path}"
+    else
+      lecho "Problem sending ${minidump_path}, not removing"
+    fi
+  done
+}
+
+main() {
+  lecho "Starting"
+  if [ -e "${PAUSE_CRASH_SENDING}" ]; then
+    lecho "Exiting early due to ${PAUSE_CRASH_SENDING}"
+    exit 1
+  fi
+
+  check_not_already_running
+
+  # Send system-wide crashes
+  send_crashes "/var/spool/crash"
+
+  # Send user-specific crashes
+  send_crashes "/home/chronos/user/crash"
+
+  lecho "Done"
+}
+
+main
diff --git a/crash_reporter/crash_sender.hourly b/crash_reporter/crash_sender.hourly
new file mode 100644
index 0000000..09c8151
--- /dev/null
+++ b/crash_reporter/crash_sender.hourly
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Copyright (c) 2010 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.
+
+set -e
+
+# Background the sender so it can trickle out crash dumps.  It will
+# exit early if already running.
+/sbin/crash_sender 2>&1 &
diff --git a/crash_reporter/inherit-review-settings-ok b/crash_reporter/inherit-review-settings-ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crash_reporter/inherit-review-settings-ok
diff --git a/crash_reporter/make_tests.sh b/crash_reporter/make_tests.sh
new file mode 100755
index 0000000..609069e
--- /dev/null
+++ b/crash_reporter/make_tests.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# Copyright (c) 2010 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.
+
+# Builds tests.
+
+set -e
+
+SOURCE_DIR=$(readlink -f $(dirname $0))
+pushd "$SCRIPT_DIR"
+make tests
+mkdir -p "${OUT_DIR}"
+cp *_test "${OUT_DIR}"
+popd
diff --git a/crash_reporter/system_logging.cc b/crash_reporter/system_logging.cc
new file mode 100644
index 0000000..e366ef2
--- /dev/null
+++ b/crash_reporter/system_logging.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdarg.h>
+#include <syslog.h>
+
+#include "crash/system_logging.h"
+
+std::string SystemLoggingImpl::identity_;
+
+SystemLoggingImpl::SystemLoggingImpl() {
+}
+
+SystemLoggingImpl::~SystemLoggingImpl() {
+}
+
+void SystemLoggingImpl::Initialize(const char *ident) {
+  // Man page does not specify if openlog copies its string or assumes
+  // the pointer is always valid, so make its scope global.
+  identity_ = ident;
+  openlog(identity_.c_str(), LOG_PID, LOG_USER);
+}
+
+void SystemLoggingImpl::LogInfo(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  vsyslog(LOG_INFO, format, vl);
+  va_end(vl);
+}
+
+void SystemLoggingImpl::LogWarning(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  vsyslog(LOG_WARNING, format, vl);
+  va_end(vl);
+}
+
+void SystemLoggingImpl::LogError(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  vsyslog(LOG_ERR, format, vl);
+  va_end(vl);
+}
diff --git a/crash_reporter/system_logging.h b/crash_reporter/system_logging.h
new file mode 100644
index 0000000..8c946b0
--- /dev/null
+++ b/crash_reporter/system_logging.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_SYSTEM_LOGGING_H_
+#define CRASH_SYSTEM_LOGGING_H_
+
+#include <string>
+
+class SystemLogging {
+ public:
+  virtual void Initialize(const char *ident) = 0;
+  virtual void LogInfo(const char *format, ...) = 0;
+  virtual void LogWarning(const char *format, ...) = 0;
+  virtual void LogError(const char *format, ...) = 0;
+};
+
+class SystemLoggingImpl : public SystemLogging {
+ public:
+  SystemLoggingImpl();
+  virtual ~SystemLoggingImpl();
+  virtual void Initialize(const char *ident);
+  virtual void LogInfo(const char *format, ...);
+  virtual void LogWarning(const char *format, ...);
+  virtual void LogError(const char *format, ...);
+ private:
+  static std::string identity_;
+};
+
+#endif  // CRASH_SYSTEM_LOGGING_H_
diff --git a/crash_reporter/system_logging_mock.cc b/crash_reporter/system_logging_mock.cc
new file mode 100644
index 0000000..3da1956
--- /dev/null
+++ b/crash_reporter/system_logging_mock.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdarg.h>
+
+#include "base/string_util.h"
+#include "crash/system_logging_mock.h"
+
+void SystemLoggingMock::LogInfo(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  log_ += ident_ + "info: ";
+  StringAppendV(&log_, format, vl);
+  log_ += "\n";
+  va_end(vl);
+}
+
+void SystemLoggingMock::LogWarning(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  log_ += ident_ + "warning: ";
+  StringAppendV(&log_, format, vl);
+  log_ += "\n";
+  va_end(vl);
+}
+
+void SystemLoggingMock::LogError(const char *format, ...) {
+  va_list vl;
+  va_start(vl, format);
+  log_ += ident_ + "error: ";
+  StringAppendV(&log_, format, vl);
+  log_ += "\n";
+  va_end(vl);
+}
diff --git a/crash_reporter/system_logging_mock.h b/crash_reporter/system_logging_mock.h
new file mode 100644
index 0000000..983c724
--- /dev/null
+++ b/crash_reporter/system_logging_mock.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_SYSTEM_LOGGING_MOCK_H_
+#define CRASH_SYSTEM_LOGGING_MOCK_H_
+
+#include <string>
+
+#include "crash/system_logging.h"
+
+class SystemLoggingMock : public SystemLogging {
+ public:
+  void Initialize(const char *ident) {}
+  virtual void LogInfo(const char *format, ...);
+  virtual void LogWarning(const char *format, ...);
+  virtual void LogError(const char *format, ...);
+
+  const std::string &log() { return log_; }
+
+ private:
+  static std::string identity_;
+  std::string log_;
+  std::string ident_;
+};
+
+#endif  // CRASH_SYSTEM_LOGGING_H_
diff --git a/crash_reporter/user_collector.cc b/crash_reporter/user_collector.cc
new file mode 100644
index 0000000..7033ada
--- /dev/null
+++ b/crash_reporter/user_collector.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "crash/user_collector.h"
+#include "metrics/metrics_library.h"
+
+// This procfs file is used to cause kernel core file writing to
+// instead pipe the core file into a user space process.  See
+// core(5) man page.
+static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
+
+UserCollector::UserCollector()
+    : core_pattern_file_(kCorePatternFile),
+      count_crash_function_(NULL),
+      initialized_(false),
+      is_feedback_allowed_function_(NULL),
+      logger_(NULL) {
+}
+
+void UserCollector::Initialize(
+    UserCollector::CountCrashFunction count_crash_function,
+    const std::string &our_path,
+    UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
+    SystemLogging *logger) {
+  CHECK(count_crash_function != NULL);
+  CHECK(is_feedback_allowed_function != NULL);
+  CHECK(logger != NULL);
+
+  count_crash_function_ = count_crash_function;
+  our_path_ = our_path;
+  is_feedback_allowed_function_ = is_feedback_allowed_function;
+  logger_ = logger;
+  initialized_ = true;
+}
+
+UserCollector::~UserCollector() {
+}
+
+std::string UserCollector::GetPattern(bool enabled) const {
+  if (enabled) {
+    return StringPrintf("|%s --signal=%%s --pid=%%p --exec=%%e",
+                        our_path_.c_str());
+  } else {
+    return "core";
+  }
+}
+
+bool UserCollector::SetUpInternal(bool enabled) {
+  CHECK(initialized_);
+  logger_->LogInfo("%s crash handling", enabled ? "Enabling" : "Disabling");
+  std::string pattern = GetPattern(enabled);
+  if (file_util::WriteFile(FilePath(core_pattern_file_),
+                           pattern.c_str(),
+                           pattern.length()) !=
+      static_cast<int>(pattern.length())) {
+    logger_->LogError("Unable to write %s", core_pattern_file_.c_str());
+    return false;
+  }
+  return true;
+}
+
+void UserCollector::HandleCrash(int signal, int pid, const std::string &exec) {
+  CHECK(initialized_);
+  logger_->LogWarning("Received crash notification for %s[%d] sig %d",
+                      exec.c_str(), pid, signal);
+
+  if (is_feedback_allowed_function_()) {
+    count_crash_function_();
+  }
+}
diff --git a/crash_reporter/user_collector.h b/crash_reporter/user_collector.h
new file mode 100644
index 0000000..7f63224
--- /dev/null
+++ b/crash_reporter/user_collector.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef _CRASH_USER_COLLECTOR_H_
+#define _CRASH_USER_COLLECTOR_H_
+
+#include <string>
+
+#include "crash/system_logging.h"
+
+class FilePath;
+
+// User crash collector.
+class UserCollector {
+ public:
+  typedef void (*CountCrashFunction)();
+  typedef bool (*IsFeedbackAllowedFunction)();
+
+  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.
+  void Initialize(CountCrashFunction count_crash,
+                  const std::string &our_path,
+                  IsFeedbackAllowedFunction is_metrics_allowed,
+                  SystemLogging *logger);
+
+  virtual ~UserCollector();
+
+  // Enable collection.
+  bool Enable() { return SetUpInternal(true); }
+
+  // Disable collection.
+  bool Disable() { return SetUpInternal(false); }
+
+  // Handle a specific user crash.
+  void HandleCrash(int signal, int pid, const std::string &exec);
+
+  // Set (override the default) core file pattern.
+  void set_core_pattern_file(const std::string &pattern) {
+    core_pattern_file_ = pattern;
+  }
+
+ private:
+  friend class UserCollectorTest;
+
+  std::string GetPattern(bool enabled) const;
+  bool SetUpInternal(bool enabled);
+
+  std::string core_pattern_file_;
+  CountCrashFunction count_crash_function_;
+  std::string our_path_;
+  bool initialized_;
+  IsFeedbackAllowedFunction is_feedback_allowed_function_;
+  SystemLogging *logger_;
+};
+
+#endif  // _CRASH_USER_COLLECTOR_H_
diff --git a/crash_reporter/user_collector_test.cc b/crash_reporter/user_collector_test.cc
new file mode 100644
index 0000000..6398ce7
--- /dev/null
+++ b/crash_reporter/user_collector_test.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gflags/gflags.h>
+#include <gtest/gtest.h>
+
+#include "base/file_util.h"
+#include "crash/system_logging_mock.h"
+#include "crash/user_collector.h"
+
+int s_crashes = 0;
+bool s_metrics = false;
+
+static const char kFilePath[] = "/my/path";
+
+void CountCrash() {
+  ++s_crashes;
+}
+
+bool IsMetrics() {
+  return s_metrics;
+}
+
+class UserCollectorTest : public ::testing::Test {
+  void SetUp() {
+    s_crashes = 0;
+    collector_.Initialize(CountCrash,
+                          kFilePath,
+                          IsMetrics,
+                          &logging_);
+    mkdir("test", 0777);
+    collector_.set_core_pattern_file("test/core_pattern");
+  }
+ protected:
+  SystemLoggingMock logging_;
+  UserCollector collector_;
+};
+
+TEST_F(UserCollectorTest, EnableOK) {
+  std::string contents;
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(file_util::ReadFileToString(FilePath("test/core_pattern"),
+                                                   &contents));
+  ASSERT_STREQ(contents.c_str(),
+               "|/my/path --signal=%s --pid=%p --exec=%e");
+  ASSERT_EQ(s_crashes, 0);
+  ASSERT_NE(logging_.log().find("Enabling crash handling"), std::string::npos);
+}
+
+TEST_F(UserCollectorTest, EnableNoFileAccess) {
+  collector_.set_core_pattern_file("/does_not_exist");
+  ASSERT_FALSE(collector_.Enable());
+  ASSERT_EQ(s_crashes, 0);
+  ASSERT_NE(logging_.log().find("Enabling crash handling"), std::string::npos);
+  ASSERT_NE(logging_.log().find("Unable to write /does_not_exist"),
+            std::string::npos);
+}
+
+TEST_F(UserCollectorTest, DisableOK) {
+  std::string contents;
+  ASSERT_TRUE(collector_.Disable());
+  ASSERT_TRUE(file_util::ReadFileToString(FilePath("test/core_pattern"),
+                                          &contents));
+  ASSERT_STREQ(contents.c_str(), "core");
+  ASSERT_EQ(s_crashes, 0);
+  ASSERT_NE(logging_.log().find("Disabling crash handling"),
+            std::string::npos);
+}
+
+TEST_F(UserCollectorTest, DisableNoFileAccess) {
+  collector_.set_core_pattern_file("/does_not_exist");
+  ASSERT_FALSE(collector_.Disable());
+  ASSERT_EQ(s_crashes, 0);
+  ASSERT_NE(logging_.log().find("Disabling crash handling"), std::string::npos);
+  ASSERT_NE(logging_.log().find("Unable to write /does_not_exist"),
+            std::string::npos);
+}
+
+
+TEST_F(UserCollectorTest, HandleCrashWithoutMetrics) {
+  s_metrics = false;
+  collector_.HandleCrash(10, 20, "foobar");
+  ASSERT_NE(logging_.log().find(
+      "Received crash notification for foobar[20] sig 10"),
+      std::string::npos);
+  ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(UserCollectorTest, HandleCrashWithMetrics) {
+  s_metrics = true;
+  collector_.HandleCrash(2, 5, "chrome");
+  ASSERT_NE(logging_.log().find(
+      "Received crash notification for chrome[5] sig 2"),
+      std::string::npos);
+  ASSERT_EQ(s_crashes, 1);
+}
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}