#include "remoting/base/crash/crashpad_database_manager.h"
#include <stdlib.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/time_formatting.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "remoting/base/file_path_util_linux.h"
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
#include "third_party/crashpad/crashpad/client/settings.h"
#if BUILDFLAG(IS_WIN)
#include "base/base_paths.h"
#include "base/strings/utf_string_conversions.h"
#endif
namespace {
const base::FilePath::CharType kChromotingCrashpadDatabasePath[] =
FILE_PATH_LITERAL("crashpad");
const size_t kMaxReportsToLog = 2;
const size_t kMaxReportsToRetain = 20;
const size_t kMaxReportAgeDays = 7;
}
namespace remoting {
base::FilePath GetCrashpadDatabasePath() {
base::FilePath database_path;
#if BUILDFLAG(IS_WIN)
base::PathService::Get(base::BasePathKey::DIR_ASSETS, &database_path);
#else
database_path = GetConfigDirectoryPath();
#endif
return database_path.Append(kChromotingCrashpadDatabasePath);
}
CrashpadDatabaseManager::CrashpadDatabaseManager(Logger& logger)
: logger_(logger) {}
CrashpadDatabaseManager::~CrashpadDatabaseManager() = default;
bool CrashpadDatabaseManager::InitializeCrashpadDatabase() {
base::FilePath database_path = GetCrashpadDatabasePath();
base::File::Error error;
if (!base::CreateDirectoryAndGetError(database_path, &error)) {
#if BUILDFLAG(IS_WIN)
logger_->LogError("Unable to get directory for crash database: " +
base::WideToUTF8(database_path.value()));
#else
logger_->LogError("Unable to get directory for crash database: " +
database_path.value());
#endif
logger_->LogError("File Error: " + base::File::ErrorToString(error));
return false;
}
database_ = crashpad::CrashReportDatabase::Initialize(database_path);
if (!database_) {
logger_->LogError("Unable to initialize crash database");
return false;
}
if (!LoadCompletedReports()) {
return false;
}
if (!LoadPendingReports()) {
return false;
}
return true;
}
bool CrashpadDatabaseManager::EnableReportUploads() {
if (!database_ || !database_->GetSettings()->SetUploadsEnabled(true)) {
logger_->LogError("Unable to enable Crashpad report uploads");
return false;
}
return true;
}
void CrashpadDatabaseManager::LogCompletedCrashpadReports() {
if (!database_) {
logger_->LogError("Crashpad database has not been initialized");
return;
}
LogCrashReports(completed_reports_, "Completed");
}
void CrashpadDatabaseManager::LogPendingCrashpadReports() {
if (!database_) {
logger_->LogError("Crashpad database has not been initialized");
return;
}
LogCrashReports(pending_reports_, "Pending");
}
bool CrashpadDatabaseManager::CleanupCompletedCrashpadReports() {
if (!database_) {
logger_->LogError("Crashpad database has not been initialized");
return false;
}
CleanupCrashReports(completed_reports_);
return LoadCompletedReports();
}
bool CrashpadDatabaseManager::LoadCompletedReports() {
crashpad::CrashReportDatabase::OperationStatus status =
database_->GetCompletedReports(&completed_reports_);
if (status != crashpad::CrashReportDatabase::OperationStatus::kNoError) {
logger_->LogError("Unable to read completed crash reports: " +
base::NumberToString(status));
return false;
}
SortCrashReports(completed_reports_);
return true;
}
bool CrashpadDatabaseManager::LoadPendingReports() {
crashpad::CrashReportDatabase::OperationStatus status =
database_->GetPendingReports(&pending_reports_);
if (status != crashpad::CrashReportDatabase::OperationStatus::kNoError) {
logger_->LogError("Unable to read pending crash reports: " +
base::NumberToString(status));
return false;
}
SortCrashReports(pending_reports_);
return true;
}
void CrashpadDatabaseManager::SortCrashReports(
std::vector<crashpad::CrashReportDatabase::Report>& reports) {
std::sort(reports.begin(), reports.end(),
[](crashpad::CrashReportDatabase::Report const& a,
crashpad::CrashReportDatabase::Report const& b) {
return a.creation_time > b.creation_time;
});
}
void CrashpadDatabaseManager::LogCrashReportInfo(
const crashpad::CrashReportDatabase::Report& report) {
const std::string& id = report.id;
if (id.empty()) {
logger_->Log(" Crash id: <unassigned>");
} else {
logger_->Log(" Crash id: " + id + " (http://go/crash/" + id + ")");
}
#if BUILDFLAG(IS_WIN)
logger_->Log(" path: " + base::WideToUTF8(report.file_path.value()));
#else
logger_->Log(" path: " + report.file_path.value());
#endif
logger_->Log(" uuid: " + report.uuid.ToString());
logger_->Log(" created: " +
TimeFormatHTTP(base::Time::FromTimeT(report.creation_time)));
std::string uploaded = report.uploaded ? "yes" : "no";
logger_->Log(" uploaded: " + uploaded + " (attempts: " +
base::NumberToString(report.upload_attempts) + ")");
}
void CrashpadDatabaseManager::LogCrashReports(
std::vector<crashpad::CrashReportDatabase::Report>& reports,
std::string_view report_type) {
size_t num_reports = reports.size();
if (num_reports > kMaxReportsToLog) {
logger_->Log("Recent " + std::string(report_type) + " crash reports: " +
base::NumberToString(num_reports) + " (most recent " +
base::NumberToString(kMaxReportsToLog) + " shown)");
} else {
logger_->Log("Recent " + std::string(report_type) +
" crash reports: " + base::NumberToString(num_reports));
}
for (size_t i = 0; i < num_reports && i < kMaxReportsToLog; ++i) {
LogCrashReportInfo(reports[i]);
}
}
bool CrashpadDatabaseManager::CleanupCrashReports(
std::vector<crashpad::CrashReportDatabase::Report>& reports) {
if (!database_) {
return false;
}
size_t num_reports = reports.size();
if (num_reports > kMaxReportsToRetain) {
logger_->Log("Too many crash reports in database. Retaining most recent " +
base::NumberToString(kMaxReportsToRetain));
for (size_t i = kMaxReportsToRetain; i < num_reports; ++i) {
const auto& report = reports[i];
std::string creation_time =
base::TimeFormatHTTP(base::Time::FromTimeT(report.creation_time));
logger_->Log(" Deleting crash report: " + report.id + " (" +
report.uuid.ToString() + ") " + creation_time);
auto status = database_->DeleteReport(report.uuid);
if (status != crashpad::CrashReportDatabase::OperationStatus::kNoError) {
logger_->LogError(
" Unable to delete crash report: " + base::NumberToString(status) +
" " + report.id + " (" + report.uuid.ToString() + ")");
return false;
}
}
reports.resize(kMaxReportsToRetain);
}
base::Time now = base::Time::Now();
base::Time threshold = now - base::Days(kMaxReportAgeDays);
bool header_shown = false;
for (const auto& report : reports) {
base::Time created = base::Time::FromTimeT(report.creation_time);
if (report.uploaded && created < threshold) {
if (!header_shown) {
header_shown = true;
logger_->Log("Deleting uploaded crash reports older than " +
base::NumberToString(kMaxReportAgeDays) + " days:");
}
logger_->Log(" Deleting crash report: " + report.id + " (" +
base::TimeFormatHTTP(created) + ")");
auto status = database_->DeleteReport(report.uuid);
if (status != crashpad::CrashReportDatabase::OperationStatus::kNoError) {
logger_->LogError(" Unable to delete uploaded crash report: " +
base::NumberToString(status) + " " + report.id +
" (" + report.uuid.ToString() + ")");
return false;
}
}
}
header_shown = false;
for (const auto& report : reports) {
base::Time created = base::Time::FromTimeT(report.creation_time);
if (!report.uploaded && created < threshold) {
if (!header_shown) {
header_shown = true;
logger_->Log("Deleting crash reports older than " +
base::NumberToString(kMaxReportAgeDays) + " days:");
}
logger_->Log(" Deleting crash report: " + report.uuid.ToString() + " (" +
TimeFormatHTTP(created) + ")");
auto status = database_->DeleteReport(report.uuid);
if (status != crashpad::CrashReportDatabase::OperationStatus::kNoError) {
logger_->LogError(" Unable to delete old crash report: " +
base::NumberToString(status) + " (" +
report.uuid.ToString() + ")");
return false;
}
}
}
return true;
}
}