blob: 5bd28124be3f7a5bb9f5dee6b19c28dbbd20f130 [file] [log] [blame]
// Copyright 2020 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/forensics/crash_reports/store.h"
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <set>
#include <vector>
#include "src/developer/forensics/utils/sized_data.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
#include "third_party/rapidjson/include/rapidjson/prettywriter.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
namespace forensics {
namespace crash_reports {
namespace {
constexpr char kAnnotationsFilename[] = "annotations.json";
constexpr char kMinidumpFilename[] = "minidump.dmp";
const std::set<std::string> kReservedAttachmentNames = {
kAnnotationsFilename,
kMinidumpFilename,
};
bool WriteAnnotationsAsJson(const std::string& path,
const std::map<std::string, std::string>& annotations) {
rapidjson::Document json(rapidjson::kObjectType);
auto& allocator = json.GetAllocator();
for (const auto& [k, v] : annotations) {
auto key = rapidjson::Value(k, allocator);
auto val = rapidjson::Value(v, allocator);
json.AddMember(key, val, allocator);
}
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
json.Accept(writer);
const std::string annotations_json = buffer.GetString();
return files::WriteFile(path, annotations_json);
}
bool ReadAnnotations(const std::string& path, std::map<std::string, std::string>* annotations) {
std::string json;
if (!files::ReadFileToString(path, &json)) {
return false;
}
rapidjson::Document document;
document.Parse(json);
if (!document.IsObject()) {
return false;
}
std::map<std::string, std::string> local_annotations;
for (const auto& member : document.GetObject()) {
if (!member.value.IsString()) {
return false;
}
local_annotations[member.name.GetString()] = member.value.GetString();
}
local_annotations.swap(*annotations);
return true;
}
bool WriteAttachment(const std::string& path, const SizedData& attachment) {
return files::WriteFile(path, reinterpret_cast<const char*>(attachment.data()),
attachment.size());
}
bool ReadAttachment(const std::string& path, SizedData* attachment) {
return files::ReadFileToVector(path, attachment);
}
// Get the contents of a directory without ".".
std::vector<std::string> GetDirectoryContents(const std::string& dir) {
std::vector<std::string> contents;
files::ReadDirContents(dir, &contents);
contents.erase(std::remove(contents.begin(), contents.end(), "."), contents.end());
return contents;
}
} // namespace
Store::Store(const std::string& root_dir) : root_dir_(root_dir) {}
std::optional<Store::Uid> Store::Add(const Report report) {
for (const auto& key : kReservedAttachmentNames) {
if (report.Attachments().find(key) != report.Attachments().end()) {
FX_LOGS(ERROR) << "Attachment is using reserved key: " << key;
return std::nullopt;
}
}
const Uid id = next_id_++;
const std::string report_dir =
files::JoinPath(files::JoinPath(root_dir_, report.ProgramShortname()), std::to_string(id));
auto cleanup_on_error =
fit::defer([report_dir] { files::DeletePath(report_dir, /*recursive=*/true); });
if (!files::CreateDirectory(report_dir)) {
FX_LOGS(ERROR) << "Failed to create directory for report: " << report_dir;
return std::nullopt;
}
auto MakeFilepath = [report_dir](const std::string& filename) {
return files::JoinPath(report_dir, filename);
};
if (!WriteAnnotationsAsJson(MakeFilepath(kAnnotationsFilename), report.Annotations())) {
FX_LOGS(ERROR) << "Failed to write annotations";
return std::nullopt;
}
for (const auto& [filename, attachment] : report.Attachments()) {
if (!WriteAttachment(MakeFilepath(filename), attachment)) {
FX_LOGS(ERROR) << "Failed to write attachment " << filename;
return std::nullopt;
}
}
if (report.Minidump().has_value()) {
if (!WriteAttachment(MakeFilepath(kMinidumpFilename), report.Minidump().value())) {
FX_LOGS(ERROR) << "Failed to write minidump";
return std::nullopt;
}
}
id_to_metadata_[id] =
Metadata{.report_dir = report_dir, .program_shortname = report.ProgramShortname()};
cleanup_on_error.cancel();
return id;
}
std::optional<Report> Store::Get(const Store::Uid& id) {
if (!Contains(id)) {
return std::nullopt;
}
const std::vector<std::string> report_files =
GetDirectoryContents(id_to_metadata_[id].report_dir);
if (report_files.empty()) {
return std::nullopt;
}
auto MakeFilepath = [report_dir = id_to_metadata_[id].report_dir](const std::string& filename) {
return files::JoinPath(report_dir, filename);
};
std::map<std::string, std::string> annotations;
std::map<std::string, SizedData> attachments;
std::optional<SizedData> minidump;
for (const auto& filename : report_files) {
if (filename == "annotations.json") {
if (!ReadAnnotations(MakeFilepath(filename), &annotations)) {
return std::nullopt;
}
} else {
SizedData attachment;
if (!ReadAttachment(MakeFilepath(filename), &attachment)) {
return std::nullopt;
}
if (filename == "minidump.dmp") {
minidump = std::move(attachment);
} else {
attachments.emplace(filename, std::move(attachment));
}
}
}
return Report(id_to_metadata_[id].program_shortname, std::move(annotations),
std::move(attachments), std::move(minidump));
}
bool Store::Contains(const Uid& id) const {
return id_to_metadata_.find(id) != id_to_metadata_.end();
}
void Store::Remove(const Uid& id) {
if (!Contains(id)) {
return;
}
// The report is stored under /tmp/store/<program shortname>/$id.
// We first delete /tmp/store/<program shortname>/$id and then if $id was the only report for
// <program shortname>, we also delete /tmp/store/<program name>.
if (!files::DeletePath(id_to_metadata_.at(id).report_dir, /*recursive=*/true)) {
FX_LOGS(ERROR) << "Failed to delete reports at " << id_to_metadata_.at(id).report_dir;
}
const std::string program_path =
files::JoinPath(root_dir_, id_to_metadata_.at(id).program_shortname);
const std::vector<std::string> dir_contents = GetDirectoryContents(program_path);
if (dir_contents.empty()) {
if (!files::DeletePath(program_path, /*recursive=*/true)) {
FX_LOGS(ERROR) << "Failed to delete " << program_path;
}
}
id_to_metadata_.erase(id);
}
void Store::RemoveAll() {
if (!files::DeletePath(root_dir_, /*recursive=*/true)) {
FX_LOGS(ERROR) << "Failed to delete all reports";
}
files::CreateDirectory(root_dir_);
id_to_metadata_.clear();
}
} // namespace crash_reports
} // namespace forensics