blob: 929527bdae11dc0d9a8f6857f22688ec81196c67 [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/crash_register.h"
#include <lib/syslog/cpp/macros.h>
#include "src/developer/forensics/feedback/annotations/constants.h"
#include "src/developer/forensics/feedback/constants.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/fostr/fidl/fuchsia/feedback/formatting.h"
#include "third_party/rapidjson/include/rapidjson/error/en.h"
#include "third_party/rapidjson/include/rapidjson/prettywriter.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
namespace forensics {
namespace crash_reports {
CrashRegister::CrashRegister(std::shared_ptr<InfoContext> info_context,
std::string register_filepath)
: info_(std::move(info_context)), register_filepath_(register_filepath) {
RestoreFromJson();
}
namespace {
Product ToInternalProduct(const fuchsia::feedback::CrashReportingProduct& fidl_product) {
FX_CHECK(fidl_product.has_name());
return {.name = fidl_product.name(),
.version = fidl_product.has_version() ? ErrorOr<std::string>(fidl_product.version())
: ErrorOr<std::string>(Error::kMissingValue),
.channel = fidl_product.has_channel() ? ErrorOr<std::string>(fidl_product.channel())
: ErrorOr<std::string>(Error::kMissingValue)};
}
} // namespace
void CrashRegister::Upsert(std::string component_url,
fuchsia::feedback::CrashReportingProduct product) {
if (!product.has_name()) {
FX_LOGS(WARNING) << "Missing required name in product:" << product;
return;
}
const Product internal_product = ToInternalProduct(product);
info_.UpsertComponentToProductMapping(component_url, internal_product);
UpdateJson(component_url, internal_product);
component_to_products_.insert_or_assign(component_url, std::move(internal_product));
}
void CrashRegister::UpsertWithAck(std::string component_url,
fuchsia::feedback::CrashReportingProduct product,
UpsertWithAckCallback callback) {
Upsert(std::move(component_url), std::move(product));
callback();
}
bool CrashRegister::HasProduct(const std::string& program_name) const {
return component_to_products_.count(program_name) != 0;
}
Product CrashRegister::GetProduct(const std::string& program_name) const {
FX_CHECK(HasProduct(program_name)) << "No product for " << program_name;
return component_to_products_.at(program_name);
}
void CrashRegister::AddVersionAndChannel(Product& product,
const feedback::Annotations& annotations) {
if (annotations.count(feedback::kBuildVersionKey) != 0) {
product.version = annotations.at(feedback::kBuildVersionKey);
}
if (annotations.count(feedback::kSystemUpdateChannelCurrentKey) != 0) {
product.channel = annotations.at(feedback::kSystemUpdateChannelCurrentKey);
}
}
// The content of the component register will be stored as json where each product for a component
// url is comprised of an object made up of string-string pairs for the name, version, and
// channel, with the latter two being optional.
//
// For example, imagine there are 2 component urls, "foo" and "bar". "foo"'s product has the name
// "foo-product", a version of "1", and a channel of "foo-channel" and "bar"'s product only has a
// name, "bar-product", the json will look like:
// {
// "foo": {
// "name": "foo-product",
// "version": "1",
// "channel": "foo-channel"
// },
// "bar": {
// "name": "bar-product"
// }
// }
void CrashRegister::UpdateJson(const std::string& component_url, const Product& product) {
using namespace rapidjson;
auto& allocator = register_json_.GetAllocator();
// If there is already a product for |component_url|, delete it.
if (register_json_.HasMember(component_url)) {
register_json_.RemoveMember(component_url);
}
// Make an empty object for |component_url|.
register_json_.AddMember(Value(component_url, register_json_.GetAllocator()),
Value(rapidjson::kObjectType), allocator);
const auto& json_product = register_json_[component_url].GetObject();
json_product.AddMember(Value("name", allocator), Value(product.name, allocator), allocator);
if (product.version.HasValue()) {
json_product.AddMember(Value("version", allocator), Value(product.version.Value(), allocator),
allocator);
}
if (product.channel.HasValue()) {
json_product.AddMember(Value("channel", allocator), Value(product.channel.Value(), allocator),
allocator);
}
StringBuffer buffer;
PrettyWriter<StringBuffer> writer(buffer);
register_json_.Accept(writer);
if (!files::WriteFile(register_filepath_, buffer.GetString(), buffer.GetLength())) {
FX_LOGS(ERROR) << "Failed to write crash register contents to " << register_filepath_;
}
}
void CrashRegister::RestoreFromJson() {
using namespace rapidjson;
register_json_.SetObject();
// If the file doesn't exit, return.
if (!files::IsFile(register_filepath_)) {
return;
}
// Check-fail if the file can't be read.
std::string json;
FX_CHECK(files::ReadFileToString(register_filepath_, &json));
ParseResult ok = register_json_.Parse(json);
if (!ok) {
FX_LOGS(ERROR) << "Error parsing crash register as JSON at offset " << ok.Offset() << " "
<< GetParseError_En(ok.Code());
files::DeletePath(register_filepath_, /*recursive=*/true);
return;
}
// Each product in the register is represented by an object containing string-string pairs
// that are the product content.
FX_CHECK(register_json_.IsObject());
for (const auto& member : register_json_.GetObject()) {
// Skip any non-object members.
if (!member.value.IsObject()) {
continue;
}
const std::string component_url = member.name.GetString();
const auto& json_product = member.value;
// If the product doesn't have a name, skip it.
if (!json_product.HasMember("name") || !json_product["name"].IsString()) {
continue;
}
Product internal_product{
.name = json_product["name"].GetString(),
.version = Error::kMissingValue,
.channel = Error::kMissingValue,
};
if (json_product.HasMember("version") && json_product["version"].IsString()) {
internal_product.version = json_product["version"].GetString();
}
if (json_product.HasMember("channel") && json_product["channel"].IsString()) {
internal_product.channel = json_product["channel"].GetString();
}
component_to_products_.insert_or_assign(component_url, std::move(internal_product));
}
}
} // namespace crash_reports
} // namespace forensics