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();
+}