aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2016-06-24 11:25:21 -0700
committerXin Li <delphij@google.com>2016-06-24 11:29:58 -0700
commit4f92832ef26e4fbb6eedd42ad0ee0040877534bb (patch)
tree865dca4a11fa42b8e57b1e4392a0d3bcf5bc5932
parentb7c2af415708e949599ed3bd635136f564af6ee3 (diff)
parent85e7f90c3ce6e54fd4072a029ce3444618716cfa (diff)
downloadcrash_reporter-4f92832ef26e4fbb6eedd42ad0ee0040877534bb.tar.gz
Merge branch 'rewrite-crash_reporter' into merge-crash_reporterHEADmastermain
Initial import of crash_reporter from platform/system/core BUG: 29548040
-rw-r--r--.project_alias1
-rw-r--r--99-crash-reporter.rules6
-rw-r--r--Android.mk147
-rw-r--r--OWNERS2
-rw-r--r--README.md61
-rw-r--r--TEST_WARNING31
-rw-r--r--crash_collector.cc454
-rw-r--r--crash_collector.h174
-rw-r--r--crash_collector_test.cc269
-rw-r--r--crash_collector_test.h35
-rw-r--r--crash_reporter.cc331
-rw-r--r--crash_reporter.rc37
-rw-r--r--crash_reporter_logs.conf122
-rw-r--r--crash_reporter_logs_test.cc41
-rwxr-xr-xcrash_sender719
-rw-r--r--dbus_bindings/org.chromium.LibCrosService.xml20
-rw-r--r--init/crash-reporter.conf27
-rw-r--r--init/crash-sender.conf11
-rw-r--r--init/warn-collector.conf12
-rw-r--r--kernel_collector.cc603
-rw-r--r--kernel_collector.h123
-rw-r--r--kernel_collector_test.cc679
-rw-r--r--kernel_collector_test.h31
-rw-r--r--kernel_log_collector.sh59
-rw-r--r--kernel_warning_collector.cc113
-rw-r--r--kernel_warning_collector.h47
-rw-r--r--list_proxies.cc302
-rwxr-xr-xperiodic_scheduler80
-rw-r--r--testrunner.cc23
-rw-r--r--udev_collector.cc244
-rw-r--r--udev_collector.h76
-rw-r--r--udev_collector_test.cc183
-rw-r--r--unclean_shutdown_collector.cc93
-rw-r--r--unclean_shutdown_collector.h62
-rw-r--r--unclean_shutdown_collector_test.cc155
-rw-r--r--user_collector.cc640
-rw-r--r--user_collector.h189
-rw-r--r--user_collector_test.cc440
-rw-r--r--warn_collector.l335
-rw-r--r--warn_collector_test.c25
-rwxr-xr-xwarn_collector_test.sh90
-rwxr-xr-xwarn_collector_test_reporter.sh27
42 files changed, 7119 insertions, 0 deletions
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