blob: b7fa7ca2ec9ec81efb69f3c8b21488e76ad42b03 [file] [log] [blame]
// 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;
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";