crash-reporter: Capture and send recent update_engine logs when it crashes

We also disallow more than 4 nested crashes.  This way we know if core2md crashes for instance, but not if crash_reporter crashes while handling the core2md crash that came from crash_reporter.  Note that the kernel does not know about parent/child process relationships, it just counts concurrent handling, so it is possible that some of many crashing processes will be discarded.

See crash report id 473c22ed428d33a9 for an example report with update_engine logs.

Change-Id: I9ff9f03a94dc87d2d42840511c2e5e42ee37cea8

BUG=9176
TEST=UserCrash,CrashSender,unit tests

Review URL: http://codereview.chromium.org/5814001
diff --git a/crash_reporter/crash_collector.cc b/crash_reporter/crash_collector.cc
index 7abe2a5..89fe41d 100644
--- a/crash_reporter/crash_collector.cc
+++ b/crash_reporter/crash_collector.cc
@@ -21,6 +21,7 @@
 
 static const char kDefaultUserName[] = "chronos";
 static const char kLsbRelease[] = "/etc/lsb-release";
+static const char kShellPath[] = "/bin/sh";
 static const char kSystemCrashPath[] = "/var/spool/crash";
 static const char kUserCrashPath[] = "/home/chronos/user/crash";
 
@@ -109,7 +110,7 @@
   }
 
   if (pid == 0) {
-    int output_handle = HANDLE_EINTR(creat(output_file, 0700));
+    int output_handle = HANDLE_EINTR(creat(output_file, 0600));
     if (output_handle < 0) {
       logger_->LogError("Could not create %s: %d", output_file, errno);
       // Avoid exit() to avoid atexit handlers from parent.
@@ -307,6 +308,11 @@
   return !full;
 }
 
+bool CrashCollector::IsCommentLine(const std::string &line) {
+  size_t found = line.find_first_not_of(" ");
+  return found != std::string::npos && line[found] == '#';
+}
+
 bool CrashCollector::ReadKeyValueFile(
     const FilePath &path,
     const char separator,
@@ -324,6 +330,9 @@
     // Allow empty strings.
     if (line->empty())
       continue;
+    // Allow comment lines.
+    if (IsCommentLine(*line))
+      continue;
     StringVector sides;
     SplitString(*line, separator, &sides);
     if (sides.size() != 2) {
@@ -335,6 +344,34 @@
   return !any_errors;
 }
 
+bool CrashCollector::GetLogContents(const FilePath &config_path,
+                                    const std::string &exec_name,
+                                    const FilePath &output_file) {
+  std::map<std::string, std::string> log_commands;
+  if (!ReadKeyValueFile(config_path, ':', &log_commands)) {
+    logger_->LogInfo("Unable to read log configuration file %s",
+                     config_path.value().c_str());
+    return false;
+  }
+
+  if (log_commands.find(exec_name) == log_commands.end())
+    return false;
+
+  std::vector<const char *> command;
+  command.push_back(kShellPath);
+  command.push_back("-c");
+  std::string shell_command = log_commands[exec_name];
+  command.push_back(shell_command.c_str());
+
+  int fork_result = ForkExecAndPipe(command, output_file.value().c_str());
+  if (fork_result != 0) {
+    logger_->LogInfo("Running shell command %s failed with: %d",
+                     shell_command.c_str(), fork_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()));