blob: 481897aced67fe94870aaad2491519381d0d42b9 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/developer/feedback/crashpad_agent/database.h"
#include <memory>
#include "src/developer/feedback/crashpad_agent/report_util.h"
#include "src/lib/files/directory.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/syslog/cpp/logger.h"
#include "third_party/crashpad/client/prune_crash_reports.h"
#include "third_party/crashpad/util/misc/metrics.h"
namespace feedback {
using crashpad::FileReader;
using crashpad::UUID;
using CrashSkippedReason = crashpad::Metrics::CrashSkippedReason;
using OperationStatus = crashpad::CrashReportDatabase::OperationStatus;
constexpr char kCrashpadDatabasePath[] = "/tmp/crashes";
std::unique_ptr<Database> Database::TryCreate(CrashpadDatabaseConfig config) {
if (!files::IsDirectory(kCrashpadDatabasePath)) {
files::CreateDirectory(kCrashpadDatabasePath);
}
auto crashpad_database =
crashpad::CrashReportDatabase::Initialize(base::FilePath(kCrashpadDatabasePath));
if (!crashpad_database) {
FX_LOGS(ERROR) << fxl::StringPrintf("Error initializing local crash report database at %s",
kCrashpadDatabasePath);
return nullptr;
}
return std::unique_ptr<Database>(new Database(config, std::move(crashpad_database)));
}
Database::Database(CrashpadDatabaseConfig config,
std::unique_ptr<crashpad::CrashReportDatabase> database)
: config_(config), database_(std::move(database)) {
FXL_DCHECK(database_);
}
const char* Database::path() { return kCrashpadDatabasePath; }
bool Database::MakeNewReport(const std::map<std::string, fuchsia::mem::Buffer>& attachments,
const std::optional<fuchsia::mem::Buffer>& minidump,
const std::map<std::string, std::string>& annotations,
crashpad::UUID* local_report_id) {
// Create local Crashpad report.
std::unique_ptr<crashpad::CrashReportDatabase::NewReport> report;
if (const auto status = database_->PrepareNewCrashReport(&report);
status != OperationStatus::kNoError) {
FX_LOGS(ERROR) << fxl::StringPrintf("Error creating local Crashpad report (%u)", status);
return false;
}
// Write attachments.
for (const auto& [filename, content] : attachments) {
AddAttachment(filename, content, report.get());
}
// Optionally write minidump.
if (minidump.has_value()) {
if (!WriteVMO(minidump.value(), report->Writer())) {
FX_LOGS(WARNING) << "Error attaching minidump to Crashpad report";
}
}
// Finish new local Crashpad report.
if (const auto status = database_->FinishedWritingCrashReport(std::move(report), local_report_id);
status != OperationStatus::kNoError) {
FX_LOGS(ERROR) << fxl::StringPrintf("Error writing local Crashpad report (%u)", status);
return false;
}
additional_data_[*local_report_id] = {annotations, minidump.has_value()};
return true;
}
std::unique_ptr<UploadReport> Database::GetUploadReport(const UUID& local_report_id) {
if (additional_data_.find(local_report_id) == additional_data_.end()) {
FX_LOGS(ERROR) << fxl::StringPrintf("Error fetching additional data for local crash report %s",
local_report_id.ToString().c_str());
// The database no longer contains the report (it was most likely pruned).
return nullptr;
}
auto upload_report = std::make_unique<const crashpad::CrashReportDatabase::UploadReport>();
if (const auto status = database_->GetReportForUploading(local_report_id, &upload_report);
status != OperationStatus::kNoError) {
FX_LOGS(ERROR) << fxl::StringPrintf(
"Error getting upload report for local id %s from the database (%u)",
local_report_id.ToString().c_str(), status);
return nullptr;
}
return std::make_unique<UploadReport>(std::move(upload_report),
additional_data_.at(local_report_id).annotations,
additional_data_.at(local_report_id).has_minidump);
}
bool Database::MarkAsUploaded(std::unique_ptr<UploadReport> upload_report,
const std::string& server_report_id) {
if (!upload_report) {
FX_LOGS(ERROR) << "upload report is null";
return false;
}
const UUID local_report_id = upload_report->GetUUID();
if (const auto status =
database_->RecordUploadComplete(upload_report->TransferUploadReport(), server_report_id);
status != OperationStatus::kNoError) {
FX_LOGS(ERROR) << fxl::StringPrintf(
"Unable to record local crash report %s as uploaded in the database (%u)",
local_report_id.ToString().c_str(), status);
return false;
}
CleanUp(local_report_id);
return true;
}
bool Database::MarkAsTooManyUploadAttempts(std::unique_ptr<UploadReport> upload_report) {
if (!upload_report) {
FX_LOGS(ERROR) << "upload report is null";
return false;
}
const UUID local_report_id = upload_report->GetUUID();
upload_report.reset(); // Release the lockfile.
return Archive(local_report_id);
}
bool Database::Archive(const crashpad::UUID& local_report_id) {
if (const auto status =
database_->SkipReportUpload(local_report_id, CrashSkippedReason::kUploadFailed);
status != OperationStatus::kNoError) {
FX_LOGS(ERROR) << fxl::StringPrintf(
"Unable to record local crash report %s as skipped in the database (%u)",
local_report_id.ToString().c_str(), status);
return false;
}
CleanUp(local_report_id);
return true;
}
void Database::CleanUp(const UUID& local_report_id) { additional_data_.erase(local_report_id); }
size_t Database::GarbageCollect() {
// We need to create a new condition every time we prune as it internally maintains a cumulated
// total size as it iterates over the reports in the database and we want to reset that cumulated
// total size every time we prune.
crashpad::DatabaseSizePruneCondition pruning_condition(config_.max_size_in_kb);
const size_t num_pruned = crashpad::PruneCrashReportDatabase(database_.get(), &pruning_condition);
if (num_pruned > 0) {
FX_LOGS(INFO) << fxl::StringPrintf("Pruned %lu crash report(s) from Crashpad database",
num_pruned);
}
// We set the |lockfile_ttl| to one day to ensure that reports in new aren't removed until
// a period of time has passed in which it is certain they are orphaned.
const size_t num_cleaned = database_->CleanDatabase(/*lockfile_ttl=*/60 * 60 * 24);
if (num_cleaned > 0) {
FX_LOGS(INFO) << fxl::StringPrintf("Cleaned %lu crash report(s) from Crashpad database",
num_cleaned);
}
if (num_pruned + num_cleaned > 0) {
// We need to store the |UUID|s to be removed from |additional_data_| because erasing a
// |UUID| within the loop will invalidate the generated iterator and cause a use-after-free bug.
std::vector<UUID> clean_up;
crashpad::CrashReportDatabase::Report report;
for (const auto& [uuid, _] : additional_data_) {
if (database_->LookUpCrashReport(uuid, &report) != OperationStatus::kNoError) {
clean_up.push_back(uuid);
}
}
for (const auto& uuid : clean_up) {
CleanUp(uuid);
}
}
return num_pruned + num_cleaned;
}
} // namespace feedback