| // 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. |
| |
| #include "crash-reporter/chrome_collector.h" |
| |
| #include <glib.h> |
| #include <pcrecpp.h> |
| #include <stdint.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <chromeos/dbus/dbus.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <chromeos/process.h> |
| #include <chromeos/syslog_logging.h> |
| |
| const char kDefaultMinidumpName[] = "upload_file_minidump"; |
| const char kTarPath[] = "/bin/tar"; |
| // From //net/crash/collector/collector.h |
| const int kDefaultMaxUploadBytes = 1024 * 1024; |
| |
| using base::FilePath; |
| using base::StringPrintf; |
| |
| namespace { |
| |
| // Extract a string delimited by the given character, from the given offset |
| // into a source string. Returns false if the string is zero-sized or no |
| // delimiter was found. |
| bool GetDelimitedString(const std::string &str, char ch, size_t offset, |
| std::string *substr) { |
| size_t at = str.find_first_of(ch, offset); |
| if (at == std::string::npos || at == offset) |
| return false; |
| *substr = str.substr(offset, at - offset); |
| return true; |
| } |
| |
| bool GetDriErrorState(const chromeos::dbus::Proxy &proxy, |
| const FilePath &error_state_path) { |
| chromeos::glib::ScopedError error; |
| gchar *error_state = nullptr; |
| if (!dbus_g_proxy_call(proxy.gproxy(), debugd::kGetLog, |
| &chromeos::Resetter(&error).lvalue(), |
| G_TYPE_STRING, "i915_error_state", G_TYPE_INVALID, |
| G_TYPE_STRING, &error_state, G_TYPE_INVALID)) { |
| LOG(ERROR) << "Error performing D-Bus proxy call " |
| << "'" << debugd::kGetLog << "'" |
| << ": " << (error ? error->message : ""); |
| g_free(error_state); |
| return false; |
| } |
| |
| std::string error_state_str(error_state); |
| g_free(error_state); |
| |
| if (error_state_str == "<empty>") |
| return false; |
| |
| const char kBase64Header[] = "<base64>: "; |
| const size_t kBase64HeaderLength = sizeof(kBase64Header) - 1; |
| if (error_state_str.compare(0, kBase64HeaderLength, kBase64Header)) { |
| LOG(ERROR) << "i915_error_state is missing base64 header"; |
| return false; |
| } |
| |
| gsize len; |
| guchar *decoded_error_state = |
| g_base64_decode(error_state_str.c_str() + kBase64HeaderLength, &len); |
| |
| int written = |
| base::WriteFile(error_state_path, |
| reinterpret_cast<const char *>(decoded_error_state), len); |
| g_free(decoded_error_state); |
| |
| if (written < 0 || (gsize)written != len) { |
| LOG(ERROR) << "Could not write file " << error_state_path.value(); |
| base::DeleteFile(error_state_path, false); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool GetAdditionalLogs(const FilePath &log_path) { |
| chromeos::dbus::BusConnection dbus = chromeos::dbus::GetSystemBusConnection(); |
| if (!dbus.HasConnection()) { |
| LOG(ERROR) << "Error connecting to system D-Bus"; |
| return false; |
| } |
| |
| chromeos::dbus::Proxy proxy(dbus, |
| debugd::kDebugdServiceName, |
| debugd::kDebugdServicePath, |
| debugd::kDebugdInterface); |
| if (!proxy) { |
| LOG(ERROR) << "Error creating D-Bus proxy to interface " |
| << "'" << debugd::kDebugdServiceName << "'"; |
| return false; |
| } |
| |
| FilePath error_state_path = |
| log_path.DirName().Append("i915_error_state.log.xz"); |
| if (!GetDriErrorState(proxy, error_state_path)) |
| return false; |
| |
| chromeos::ProcessImpl tar_process; |
| tar_process.AddArg(kTarPath); |
| tar_process.AddArg("cfJ"); |
| tar_process.AddArg(log_path.value()); |
| tar_process.AddStringOption("-C", log_path.DirName().value()); |
| tar_process.AddArg(error_state_path.BaseName().value()); |
| int res = tar_process.Run(); |
| |
| base::DeleteFile(error_state_path, false); |
| |
| if (res || !base::PathExists(log_path)) { |
| LOG(ERROR) << "Could not tar file " << log_path.value(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| |
| ChromeCollector::ChromeCollector() : output_file_ptr_(stdout) {} |
| |
| ChromeCollector::~ChromeCollector() {} |
| |
| bool ChromeCollector::HandleCrash(const FilePath &file_path, |
| const std::string &pid_string, |
| const std::string &uid_string, |
| const std::string &exe_name) { |
| if (!is_feedback_allowed_function_()) |
| return true; |
| |
| LOG(WARNING) << "Received crash notification for " << exe_name << "[" |
| << pid_string << "] user " << uid_string << " (called directly)"; |
| |
| if (exe_name.find('/') != std::string::npos) { |
| LOG(ERROR) << "exe_name contains illegal characters: " << exe_name; |
| return false; |
| } |
| |
| FilePath dir; |
| uid_t uid = atoi(uid_string.c_str()); |
| pid_t pid = atoi(pid_string.c_str()); |
| if (!GetCreatedCrashDirectoryByEuid(uid, &dir, nullptr)) { |
| LOG(ERROR) << "Can't create crash directory for uid " << uid; |
| return false; |
| } |
| |
| std::string dump_basename = FormatDumpBasename(exe_name, time(nullptr), pid); |
| FilePath meta_path = GetCrashPath(dir, dump_basename, "meta"); |
| FilePath minidump_path = GetCrashPath(dir, dump_basename, "dmp"); |
| FilePath log_path = GetCrashPath(dir, dump_basename, "log.tar.xz"); |
| |
| std::string data; |
| if (!base::ReadFileToString(file_path, &data)) { |
| LOG(ERROR) << "Can't read crash log: " << file_path.value(); |
| return false; |
| } |
| |
| if (!ParseCrashLog(data, dir, minidump_path, dump_basename)) { |
| LOG(ERROR) << "Failed to parse Chrome's crash log"; |
| return false; |
| } |
| |
| if (GetAdditionalLogs(log_path)) { |
| int64_t minidump_size = 0; |
| int64_t log_size = 0; |
| if (base::GetFileSize(minidump_path, &minidump_size) && |
| base::GetFileSize(log_path, &log_size) && |
| minidump_size > 0 && log_size > 0 && |
| minidump_size + log_size < kDefaultMaxUploadBytes) { |
| AddCrashMetaData("log", log_path.value()); |
| } else { |
| LOG(INFO) << "Skipping logs upload to prevent discarding minidump " |
| "because of report size limit < " << minidump_size + log_size; |
| base::DeleteFile(log_path, false); |
| } |
| } |
| |
| // We're done. |
| WriteCrashMetaData(meta_path, exe_name, minidump_path.value()); |
| |
| fprintf(output_file_ptr_, "%s", kSuccessMagic); |
| fflush(output_file_ptr_); |
| |
| return true; |
| } |
| |
| bool ChromeCollector::ParseCrashLog(const std::string &data, |
| const FilePath &dir, |
| const FilePath &minidump, |
| const std::string &basename) { |
| size_t at = 0; |
| while (at < data.size()) { |
| // Look for a : followed by a decimal number, followed by another : |
| // followed by N bytes of data. |
| std::string name, size_string; |
| if (!GetDelimitedString(data, ':', at, &name)) { |
| LOG(ERROR) << "Can't find : after name @ offset " << at; |
| break; |
| } |
| at += name.size() + 1; // Skip the name & : delimiter. |
| |
| if (!GetDelimitedString(data, ':', at, &size_string)) { |
| LOG(ERROR) << "Can't find : after size @ offset " << at; |
| break; |
| } |
| at += size_string.size() + 1; // Skip the size & : delimiter. |
| |
| size_t size; |
| if (!base::StringToSizeT(size_string, &size)) { |
| LOG(ERROR) << "String not convertible to integer: " << size_string; |
| break; |
| } |
| |
| // Data would run past the end, did we get a truncated file? |
| if (at + size > data.size()) { |
| LOG(ERROR) << "Overrun, expected " << size << " bytes of data, got " |
| << (data.size() - at); |
| break; |
| } |
| |
| if (name.find("filename") != std::string::npos) { |
| // File. |
| // Name will be in a semi-MIME format of |
| // <descriptive name>"; filename="<name>" |
| // Descriptive name will be upload_file_minidump for the dump. |
| std::string desc, filename; |
| pcrecpp::RE re("(.*)\" *; *filename=\"(.*)\""); |
| if (!re.FullMatch(name.c_str(), &desc, &filename)) { |
| LOG(ERROR) << "Filename was not in expected format: " << name; |
| break; |
| } |
| |
| if (desc.compare(kDefaultMinidumpName) == 0) { |
| // The minidump. |
| WriteNewFile(minidump, data.c_str() + at, size); |
| } else { |
| // Some other file. |
| FilePath path = GetCrashPath(dir, basename + "-" + filename, "other"); |
| if (WriteNewFile(path, data.c_str() + at, size) >= 0) { |
| AddCrashMetaUploadFile(desc, path.value()); |
| } |
| } |
| } else { |
| // Other attribute. |
| std::string value_str; |
| value_str.reserve(size); |
| |
| // Since metadata is one line/value the values must be escaped properly. |
| for (size_t i = at; i < at + size; i++) { |
| switch (data[i]) { |
| case '"': |
| case '\\': |
| value_str.push_back('\\'); |
| value_str.push_back(data[i]); |
| break; |
| |
| case '\r': |
| value_str += "\\r"; |
| break; |
| |
| case '\n': |
| value_str += "\\n"; |
| break; |
| |
| case '\t': |
| value_str += "\\t"; |
| break; |
| |
| case '\0': |
| value_str += "\\0"; |
| break; |
| |
| default: |
| value_str.push_back(data[i]); |
| break; |
| } |
| } |
| AddCrashMetaUploadData(name, value_str); |
| } |
| |
| at += size; |
| } |
| |
| return at == data.size(); |
| } |
| |
| // static |
| const char ChromeCollector::kSuccessMagic[] = "_sys_cr_finished"; |