blob: 04fb23cab3f3e0931488ae2a1531f8efdc593079 [file] [log] [blame]
// Copyright 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.
#include "crash-reporter/crash_sender_service.h"
#include <curl/curl.h>
#include <algorithm>
#include <utility>
#include <base/bind.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/format_macros.h>
#include <base/logging.h>
#include <base/message_loop/message_loop.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
#include <dbus/bus.h>
#include "vboot/crossystem.h"
namespace {
// Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
const char kDefaultProduct[] = "ChromeOS";
// File whose existence implies crash reports may be sent, and whose
// contents includes our machine's anonymized guid.
const char kConsentIdPath[] = "/home/chronos/Consent To Send Stats";
// Crash sender lock in case the sender is already running.
const char kCrashSenderLockPath[] = "/var/lock/crash_sender";
// Crash sender lock in case the sender is already running for tests.
const char kCrashSenderLockForTestsPath[] = "/var/lock/crash_sender_test";
// Path to file that indicates a crash test is currently running.
const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
// Path to hardware class description.
const char kHWClassPath[] = "/sys/devices/platform/chromeos_acpi/HWID";
// Path to file that indicates this is a developer image.
const char kLeaveCorePath[] = "/root/.leave_core";
// File whose existence causes crash sending to be delayed (for testing).
// Must be stateful to enable testing kernel crashes.
const char kPauseCrashSendingPath[] = "/var/lib/crash_sender_paused";
// Path to a directory of restricted certificates which includes
// a certificate for ${REPORT_UPLOAD_PROD_URL}.
const char kRestrictedCertificatesPath[] =
"/usr/share/chromeos-ca-certificates";
// File whose existence implies we're running and not to start again.
const char kRunFilePath[] = "/var/run/crash_sender.pid";
// Directory to store timestamp files indicating the uploads in the past 24
// hours.
const char kTimestampsDirPath[] = "/var/lib/crash_sender";
// Chrome's crash report log file.
const char kChromeCrashLogPath[] = "/var/log/chrome/Crash Reports/uploads.log";
// File whose existence mocks crash sending. If empty we pretend the crash
// sending was successful, otherwise unsuccessful.
const char kMockCrashSendingPath[] = "/tmp/mock-crash-sending";
// Configuration keys.
const char kForceOfficial[] = "FORCE_OFFICIAL";
const char kMaxCrashRate[] = "MAX_CRASH_RATE";
const char kMockDeveloperMode[] = "MOCK_DEVELOPER_MODE";
const char kOverridePauseSending[] = "OVERRIDE_PAUSE_SENDING";
const char kReportUploadProdUrl[] = "REPORT_UPLOAD_PROD_URL";
const char kSecondsSendSpread[] = "SECONDS_SEND_SPREAD";
// Owns a curl handle.
class ScopedCurl {
public:
explicit ScopedCurl(bool mock) : mock_(mock) {
if (!mock)
curl_ = curl_easy_init();
}
~ScopedCurl() {
if (mock_)
return;
if (post_)
curl_formfree(post_);
curl_easy_cleanup(curl_);
}
CURL* curl() const { return curl_; }
void AddMultipartContent(std::string key, std::string value) {
LOG(INFO) << key << ": " << value;
if (mock_)
return;
curl_formadd(&post_, &last_,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_COPYCONTENTS, value.c_str(),
CURLFORM_END);
}
void AddFile(std::string key, base::FilePath file) {
LOG(INFO) << key << ": " << file.value();
if (mock_)
return;
curl_formadd(&post_, &last_,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_FILE, file.value().c_str(),
CURLFORM_END);
}
CURLcode perform() {
CHECK(!mock_);
curl_easy_setopt(curl_, CURLOPT_HTTPPOST, post_);
return curl_easy_perform(curl_);
}
private:
bool mock_;
CURL* curl_ = nullptr;
struct curl_httppost* post_ = nullptr;
struct curl_httppost* last_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ScopedCurl);
};
// Comparison function.
bool order_meta_files(const crash_reporter::MetaFile& f1,
const crash_reporter::MetaFile& f2) {
return std::make_tuple(f1.modification_time, f1.path.value()) <
std::make_tuple(f2.modification_time, f2.path.value());
}
// Return the list of directories containing crashes.
std::vector<base::FilePath> GetCrashDirectories() {
std::vector<base::FilePath> result;
base::FilePath system_wide("/var/spool/crash");
if (base::DirectoryExists(system_wide))
result.push_back(system_wide);
base::FilePath main_user("/home/chronos/crash");
if (base::DirectoryExists(main_user))
result.push_back(main_user);
base::FileEnumerator enumCrashDirectories(
base::FilePath("/home/chronos"), false, base::FileEnumerator::DIRECTORIES,
FILE_PATH_LITERAL("u-*"));
for (base::FilePath dir = enumCrashDirectories.Next(); !dir.empty();
dir = enumCrashDirectories.Next()) {
base::FilePath crash_dir = dir.Append("crash");
if (base::DirectoryExists(crash_dir))
result.push_back(crash_dir);
}
return result;
}
// Returns all the files in a given directory with the time of last
// modification.
std::vector<std::pair<base::Time, base::FilePath>> GetOrderedFiles(
const base::FilePath& dir) {
std::vector<std::pair<base::Time, base::FilePath>> files;
base::FileEnumerator enumFiles(dir, false, base::FileEnumerator::FILES);
for (base::FilePath file = enumFiles.Next(); !file.empty();
file = enumFiles.Next()) {
files.push_back(
std::make_pair(enumFiles.GetInfo().GetLastModifiedTime(), file));
}
return files;
}
// Parse a file containing key value of the form:
// KEY=VALUE
std::map<std::string, std::string> ParseKeyValueFile(
const base::FilePath& file) {
std::map<std::string, std::string> result;
std::string content;
if (!base::ReadFileToString(file, &content)) {
LOG(WARNING) << "Unable to read key values from: " << file.value();
return result;
}
base::StringPairs pairs;
base::SplitStringIntoKeyValuePairs(content, '=', '\n', &pairs);
for (base::StringPairs::const_iterator pair = pairs.begin();
pair != pairs.end(); ++pair) {
if (!pair->first.empty() && pair->first[0] != '#')
result[pair->first] = pair->second;
}
return result;
}
// Returns the value associated to |key| in |map|, or |default_value| if |map|
// doesn't contain |key|.
std::string GetValue(const std::map<std::string, std::string>& map,
const std::string& key, const std::string& default_value) {
std::map<std::string, std::string>::const_iterator it = map.find(key);
if (it != map.end())
return it->second;
return default_value;
}
// As |GetValue| for values of type |base::FilePath|.
base::FilePath GetPathValue(const std::map<std::string, std::string>& map,
const std::string& key,
const base::FilePath& default_value) {
std::map<std::string, std::string>::const_iterator it = map.find(key);
if (it != map.end())
return base::FilePath(it->second);
return default_value;
}
// As |GetValue| for values of type |int|.
int GetIntValue(const std::map<std::string, std::string>& map,
const std::string& key, int default_value) {
std::map<std::string, std::string>::const_iterator it = map.find(key);
if (it != map.end()) {
int result;
if (base::StringToInt(it->second, &result)) {
return result;
}
}
return default_value;
}
// Remove the report for the given |meta_file|.
void RemoveReport(const base::FilePath& meta_file) {
LOG(INFO) << "Removing report: " << meta_file.value();
base::FilePath directory = meta_file.DirName();
base::FilePath template_value = meta_file.ReplaceExtension(".*").BaseName();
base::FileEnumerator filesToDelete(
directory, false, base::FileEnumerator::FILES, template_value.value());
for (base::FilePath file = filesToDelete.Next(); !file.empty();
file = filesToDelete.Next()) {
if (!base::DeleteFile(file, false))
LOG(WARNING) << "Unable to delete " << file.value();
}
}
// Returns the extenstion of the given file, stripping .gz if the file is
// compressed.
std::string GetExtension(const base::FilePath& file) {
std::string extension = file.FinalExtension();
if (extension == ".gz")
extension = file.RemoveFinalExtension().FinalExtension();
if (!extension.empty()) {
DCHECK_EQ(extension[0], '.');
extension = extension.substr(1);
}
return extension;
}
// Returns the report kind.
std::string GetKind(const crash_reporter::MetaFile& meta_file) {
base::FilePath payload =
GetPathValue(meta_file.meta_information, "payload", base::FilePath());
if (payload.value().empty() || !base::PathExists(payload)) {
LOG(WARNING) << "Missing payload on file: " << meta_file.path.value();
return "";
}
std::string kind = GetExtension(payload);
if (kind == "dmp") {
return "minidump";
}
return kind;
}
// Callback function for curl. It delegates to a callback passed as additional
// data.
size_t CurlWriteData(void* buffer, size_t size, size_t nmemb, void* data) {
base::Callback<size_t(void*, size_t)>* callback =
static_cast<base::Callback<size_t(void*, size_t)>*>(data);
return callback->Run(buffer, size * nmemb);
}
size_t AppendDataToString(std::string* data, const void* buffer, size_t size) {
data->append(reinterpret_cast<const char*>(buffer), size);
return size;
}
} // namespace
namespace crash_reporter {
CrashSenderService::CrashSenderService(const CrashSenderConfiguration& config)
: config_(config) {
metrics_lib_.Init();
}
CrashSenderService::~CrashSenderService() {}
bool CrashSenderService::Start(ProxyResolver* proxy_resolver) {
proxy_resolver_ = proxy_resolver;
std::map<std::string, std::string> lsb_release_values =
ParseKeyValueFile(base::FilePath("/etc/lsb-release"));
board_ = lsb_release_values["CHROMEOS_RELEASE_BOARD"];
if (board_.empty()) {
LOG(ERROR) << "Unable to retrieve board information.";
return false;
}
channel_ = lsb_release_values["CHROMEOS_RELEASE_TRACK"];
const char kChannelSuffix[] = "-channel";
if (EndsWith(channel_, kChannelSuffix, true))
channel_ =
channel_.substr(0, channel_.size() - arraysize(kChannelSuffix) + 1);
if (channel_.empty()) {
LOG(ERROR) << "Unable to retrieve channel information.";
return false;
}
official_ =
(lsb_release_values["CHROMEOS_RELEASE_DESCRIPTION"].find("Official") !=
std::string::npos);
std::map<std::string, std::string> os_release_values =
ParseKeyValueFile(base::FilePath("/etc/os-release"));
default_product_ = GetValue(os_release_values, "GOOGLE_CRASH_ID", "");
if (default_product_.empty())
default_product_ = GetValue(os_release_values, "ID", "");
default_version_ = GetValue(os_release_values, "GOOGLE_CRASH_VERSION_ID", "");
if (default_version_.empty())
default_version_ = GetValue(os_release_values, "VERSION_ID", "");
return ReapplyConfig(config_);
}
void CrashSenderService::Restart(const CrashSenderConfiguration& config) {
CrashSenderConfiguration old_config = config_;
config_ = config;
if (!ReapplyConfig(config_)) {
LOG(ERROR) << "Restarting failed. Reapplying old configuration.";
config_ = old_config;
CHECK(ReapplyConfig(config_));
}
}
CrashSenderConfiguration CrashSenderService::ParseConfiguration(
const base::FilePath& config_file) {
CrashSenderConfiguration result;
std::map<std::string, std::string> key_values =
ParseKeyValueFile(config_file);
result.force_official = GetIntValue(key_values, kForceOfficial, false);
result.max_crash_rate = GetIntValue(key_values, kMaxCrashRate, 32);
result.mock_developer_mode =
GetIntValue(key_values, kMockDeveloperMode, false);
result.override_pause_sending =
GetIntValue(key_values, kOverridePauseSending, false);
result.report_upload_prod_url =
GetValue(key_values, kReportUploadProdUrl,
"https://clients2.google.com/cr/report");
result.seconds_send_spread = GetIntValue(key_values, kSecondsSendSpread, 600);
return result;
}
bool CrashSenderService::ReapplyConfig(const CrashSenderConfiguration& config) {
bool test_run = IsMock();
const char* lock_path =
test_run ? kCrashSenderLockForTestsPath : kCrashSenderLockPath;
lock_file_.reset(
new base::File(base::FilePath(lock_path), base::File::FLAG_OPEN_ALWAYS |
base::File::FLAG_READ |
base::File::FLAG_WRITE));
if (lock_file_->Lock() != base::File::FILE_OK) {
LOG(ERROR) << "Already running; quitting.";
return false;
}
base::FilePath run_file(kRunFilePath);
run_file_deleter_.Reset(
base::Bind(base::IgnoreResult(&base::DeleteFile), run_file, false));
std::string pid = base::IntToString(getpid()) + "\n";
base::WriteFile(run_file, pid.data(), pid.length());
CollectAllCrashes();
if (test_run) {
base::MessageLoop::current()->PostTask(FROM_HERE,
base::MessageLoop::QuitClosure());
LOG(INFO) << "crash_sender done.";
}
return true;
}
bool CrashSenderService::IsCrashTestInProgress() const {
return base::PathExists(base::FilePath(kCrashTestInProgressPath));
}
bool CrashSenderService::IsTestImage() const {
if (IsCrashTestInProgress())
return false;
return StartsWithASCII(channel_, "test", true);
}
bool CrashSenderService::IsMock() const {
return base::PathExists(base::FilePath(kMockCrashSendingPath));
}
bool CrashSenderService::IsMockSuccessful() const {
std::string content;
if (base::ReadFileToString(base::FilePath(kMockCrashSendingPath), &content))
return content.empty();
return false;
}
bool CrashSenderService::IsOfficialImage() const {
return config_.force_official || official_;
}
bool CrashSenderService::IsDeveloperMode() const {
if (config_.mock_developer_mode)
return true;
if (IsCrashTestInProgress())
return false;
return VbGetSystemPropertyInt("devsw_boot");
}
bool CrashSenderService::IsDeveloperImage() const {
// Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
if (IsCrashTestInProgress())
return false;
return base::PathExists(base::FilePath(kLeaveCorePath));
}
std::string CrashSenderService::GetHardwareClass() const {
std::string content;
if (base::ReadFileToString(base::FilePath(kHWClassPath), &content))
return content;
char buffer[VB_MAX_STRING_PROPERTY];
const char* hwid =
VbGetSystemPropertyString("hwid", buffer, VB_MAX_STRING_PROPERTY);
if (hwid)
return hwid;
return "undefined";
}
std::string CrashSenderService::GetConsentId() const {
std::string content;
if (base::ReadFileToString(base::FilePath(kConsentIdPath), &content)) {
content.erase(std::remove(content.begin(), content.end(), '-'),
content.end());
return content;
}
return "undefined";
}
void CrashSenderService::CollectCrashes(const base::FilePath& dir) {
std::vector<std::pair<base::Time, base::FilePath>> files =
GetOrderedFiles(dir);
base::Time now = base::Time::Now();
for (const std::pair<base::Time, base::FilePath>& file : files) {
if (file.second.FinalExtension() == ".meta") {
MetaFile info;
info.modification_time = file.first;
info.path = file.second;
info.meta_information = ParseKeyValueFile(info.path);
switch (FilterCrashes(info)) {
case CAN_UPLOAD:
current_crashes_.push_back(info);
break;
case DELETE:
RemoveReport(info.path);
break;
case WAIT:
// Nothing
break;
}
} else if ((now - file.first >= base::TimeDelta::FromDays(1)) &&
!base::PathExists(file.second.ReplaceExtension(".meta"))) {
if (base::DeleteFile(file.second, false)) {
LOG(INFO) << "Removing old orphaned file: " << file.second.value();
} else {
LOG(WARNING) << "Unable to delete: " << file.second.value();
}
}
}
}
void CrashSenderService::CollectAllCrashes() {
current_crashes_.clear();
std::vector<base::FilePath> crash_directories = GetCrashDirectories();
for (const base::FilePath& path : crash_directories) {
CrashSenderService::CollectCrashes(path);
}
std::sort(current_crashes_.begin(), current_crashes_.end(),
&order_meta_files);
if (current_crashes_.empty()) {
// If no crash is present, wait for an hour.
ScheduleNext();
return;
}
PrepareToSendNextCrash();
}
CrashSenderService::FileStatus CrashSenderService::FilterCrashes(
const MetaFile& file) {
if (!metrics_lib_.AreMetricsEnabled()) {
LOG(INFO) << "Crash reporting is disabled. Removing crash.";
return DELETE;
}
if (!IsMock() && !IsOfficialImage()) {
LOG(INFO) << "Not an official OS version. Removing crash.";
return DELETE;
}
if (GetValue(file.meta_information, "done", "") != "1") {
// This report is incomplete, so if it's old, just remove it
if (base::Time::Now() - file.modification_time >=
base::TimeDelta::FromDays(1)) {
LOG(INFO) << "Removing old incomplete metadata.";
return DELETE;
} else {
LOG(INFO) << "Ignoring recent incomplete metadata.";
return WAIT;
}
}
std::string report_kind = GetKind(file);
if (report_kind != "minidump" && report_kind != "kcrash" &&
report_kind != "log") {
LOG(INFO) << "Unknown report kind " << report_kind << ". Removing report.";
return DELETE;
}
return CAN_UPLOAD;
}
bool CrashSenderService::MustThrottle() const {
base::FilePath timestamps_dir(kTimestampsDirPath);
if (!base::CreateDirectoryAndGetError(timestamps_dir, nullptr)) {
LOG(WARNING) << "Unable to create directory: " << timestamps_dir.value();
return true;
}
base::Time now = base::Time::Now();
base::FileEnumerator timestamps(timestamps_dir, false,
base::FileEnumerator::FILES);
int sends_in_24hrs = 0;
for (base::FilePath file = timestamps.Next(); !file.empty();
file = timestamps.Next()) {
if (now - timestamps.GetInfo().GetLastModifiedTime() >=
base::TimeDelta::FromDays(1)) {
base::DeleteFile(file, false);
} else {
++sends_in_24hrs;
}
}
LOG(INFO) << "Current send rate: " << sends_in_24hrs << "sends/24hrs";
if (sends_in_24hrs >= config_.max_crash_rate) {
LOG(INFO) << "Cannot send more crashes: current " << sends_in_24hrs
<< "send/24hrs >= max " << config_.max_crash_rate << "send/24hrs";
return true;
}
base::FilePath tmp_file;
if (!base::CreateTemporaryFileInDir(timestamps_dir, &tmp_file)) {
LOG(WARNING) << "Unable to create a file in " << timestamps_dir.value();
return true;
}
return false;
}
void CrashSenderService::PrepareToSendNextCrash() {
// If we cannot send any crashes, wait one hour.
if (!CanSendNextCrash()) {
ScheduleNext();
return;
}
// If there is no crash to send, collect crashes and return.
if (current_crashes_.empty()) {
CollectAllCrashes();
return;
}
const MetaFile& file = current_crashes_.front();
base::TimeDelta time_to_wait =
std::max(file.modification_time + base::TimeDelta::FromSeconds(30) -
base::Time::Now(),
base::TimeDelta::FromSeconds(
base::RandInt(1, config_.seconds_send_spread)));
LOG(INFO) << "Scheduled to send " << file.path.value() << " in "
<< time_to_wait.InSeconds() << "s.";
if (IsMock()) {
SendNextCrash();
} else {
timer_.Start(FROM_HERE, time_to_wait, this,
&CrashSenderService::SendNextCrash);
}
}
bool CrashSenderService::CanSendNextCrash() {
// Handle pause crash sending
base::FilePath pause_crash_sending(kPauseCrashSendingPath);
if (base::PathExists(pause_crash_sending) &&
!config_.override_pause_sending) {
LOG(INFO) << "Not sending crashes due to " << pause_crash_sending.value();
return false;
}
// Handle is test image
if (IsTestImage()) {
LOG(INFO) << "Not sending crashes due to test image.";
return false;
}
// Handle certificate path
base::FilePath restricted_certificates_path(kRestrictedCertificatesPath);
if (!base::DirectoryExists(restricted_certificates_path)) {
LOG(INFO) << "Not sending crashes due to "
<< restricted_certificates_path.value() << " missing.";
return false;
}
// Guest mode.
if (metrics_lib_.IsGuestMode()) {
LOG(INFO)
<< "Guest mode has been entered. Delaying crash sending until exited.";
return false;
}
return true;
}
void CrashSenderService::SendNextCrash() {
// Ensure the timer will be called again if this is exited early due to
// exceptional conditions.
ScheduleNext();
if (!CanSendNextCrash())
return;
// Check uploads rate
if (MustThrottle()) {
LOG(INFO) << "Sending a report would exceed rate. Leaving for later.";
return;
}
const MetaFile file = current_crashes_[0];
current_crashes_.erase(current_crashes_.begin());
// Trying to send a crash. Preparing next crash.
base::ScopedClosureRunner send_next_crash(base::Bind(
&CrashSenderService::PrepareToSendNextCrash, base::Unretained(this)));
// Delete the report whatever the result.
base::ScopedClosureRunner report_delete(base::Bind(&RemoveReport, file.path));
ScopedCurl curl(IsMock());
LOG(INFO) << "Sending crash:";
std::string product = GetValue(file.meta_information, "upload_var_prod", "");
if (product.empty())
product = default_product_;
if (product.empty())
product = kDefaultProduct;
curl.AddMultipartContent("prod", product);
if (product != kDefaultProduct)
LOG(INFO) << "Sending crash report on behalf of " << product;
LOG(INFO) << "Metadata: " << file.path.value() << " (" << GetKind(file)
<< ")";
std::string version = GetValue(file.meta_information, "upload_var_ver", "");
if (version.empty())
version = default_version_;
if (version.empty())
version = GetValue(file.meta_information, "ver", "");
curl.AddMultipartContent("ver", version);
curl.AddMultipartContent("board", board_);
curl.AddMultipartContent("hwclass", GetHardwareClass());
curl.AddMultipartContent(
"exec_name", GetValue(file.meta_information, "exec_name", "undefined"));
std::string image_type;
if (IsTestImage()) {
image_type = "test";
} else if (IsDeveloperImage()) {
image_type = "dev";
} else if (config_.force_official) {
image_type = "force-official";
} else if (IsMock() && !IsMockSuccessful()) {
image_type = "mock-fail";
}
if (!image_type.empty())
curl.AddMultipartContent("image_type", image_type);
if (VbGetSystemPropertyInt("cros_debug") && IsDeveloperMode())
curl.AddMultipartContent("boot_mode", "dev");
std::string error_type = GetValue(file.meta_information, "error_type", "");
if (!error_type.empty())
curl.AddMultipartContent("error_type", error_type);
curl.AddMultipartContent("guid", GetConsentId());
curl.AddMultipartContent(
"write_payload_size",
GetValue(file.meta_information, "payload_size", "undefined"));
base::FilePath payload =
GetPathValue(file.meta_information, "payload", base::FilePath());
if (!payload.value().empty()) {
int64 file_size;
if (base::GetFileSize(payload, &file_size)) {
curl.AddMultipartContent("send_payload_size",
base::Int64ToString(file_size));
curl.AddFile("upload_file_" + GetKind(file), payload);
}
}
std::string signature = GetValue(file.meta_information, "sig", "");
if (!signature.empty()) {
curl.AddMultipartContent("sig", signature);
curl.AddMultipartContent("sig2", signature);
}
base::FilePath log =
GetPathValue(file.meta_information, "log", base::FilePath());
if (base::PathExists(log))
curl.AddFile("log", log);
std::string upload_prefix =
GetValue(file.meta_information, "upload_prefix", "");
const char kUploadVarPrefix[] = "upload_var_";
const char kUploadFilePrefix[] = "upload_file_";
for (const auto& pair : file.meta_information) {
if (StartsWithASCII(pair.first, kUploadVarPrefix, true)) {
curl.AddMultipartContent(
upload_prefix + pair.first.substr(arraysize(kUploadVarPrefix) - 1),
pair.second);
}
if (StartsWithASCII(pair.first, kUploadFilePrefix, true)) {
curl.AddFile(
upload_prefix + pair.first.substr(arraysize(kUploadFilePrefix) - 1),
base::FilePath(pair.second));
}
}
if (IsMock()) {
if (IsMockSuccessful()) {
LOG(INFO) << "Mocking successful send";
} else {
LOG(INFO) << "Mocking unsuccessful send";
}
return;
}
curl_easy_setopt(curl.curl(), CURLOPT_URL,
config_.report_upload_prod_url.c_str());
curl_easy_setopt(curl.curl(), CURLOPT_POST, 1L);
std::vector<std::string> proxies = proxy_resolver_->GetProxiesForUrl(
config_.report_upload_prod_url, base::TimeDelta::FromSeconds(5));
if (proxies.size() && proxies[0] != "direct://")
curl_easy_setopt(curl.curl(), CURLOPT_PROXY, proxies[0].c_str());
// TODO(qsr) Remove
curl_easy_setopt(curl.curl(), CURLOPT_PROXY, "http://192.168.45.1:8888");
std::string received_data = "";
base::Callback<size_t(const void*, size_t)> callback =
base::Bind(&AppendDataToString, &received_data);
curl_easy_setopt(curl.curl(), CURLOPT_WRITEFUNCTION, &CurlWriteData);
curl_easy_setopt(curl.curl(), CURLOPT_WRITEDATA, &callback);
CURLcode success = curl.perform();
if (success != 0 || received_data.size() > 20) {
LOG(ERROR) << "Unable to upload crash report. Error code: " << success;
return;
}
std::string product_name;
if (product == "Chrome_ChromeOS") {
if (IsOfficialImage()) {
product_name = "Chrome";
} else {
product_name = "Chromium";
}
} else {
if (IsOfficialImage()) {
product_name = "ChromeOS";
} else {
product_name = "ChromiumOS";
}
}
std::string log_string = base::StringPrintf(
"%" PRIu64 ",%s,%s\n", static_cast<uint64_t>(base::Time::Now().ToTimeT()),
received_data.c_str(), product_name.c_str());
if (base::AppendToFile(base::FilePath(kChromeCrashLogPath), log_string.data(),
log_string.size()) == -1) {
LOG(ERROR) << "Unable to update crash log.";
}
}
void CrashSenderService::ScheduleNext() {
timer_.Start(FROM_HERE, base::TimeDelta::FromHours(1), this,
&CrashSenderService::PrepareToSendNextCrash);
}
DbusCrashSenderServiceImpl::DbusCrashSenderServiceImpl(
const CrashSenderConfiguration& config)
: CrashSenderService(config) {}
DbusCrashSenderServiceImpl::~DbusCrashSenderServiceImpl() {}
bool DbusCrashSenderServiceImpl::Start(dbus::Bus* bus) {
if (!bus || !bus->Connect()) {
LOG(ERROR) << "Failed to connect to DBus";
return false;
}
bus_ = bus;
proxy_resolver_.reset(new DBusProxyResolver(bus_));
proxy_resolver_->Init();
return CrashSenderService::Start(proxy_resolver_.get());
}
} // namespace crash_reporter