blob: c8bc040d59d19a1939e25adb174069011ed3b59e [file] [log] [blame]
// Copyright 2021 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/feedback/config.h"
#include <lib/syslog/cpp/macros.h>
#include <optional>
#include "src/developer/forensics/feedback/constants.h"
#include "src/developer/forensics/utils/storage_size.h"
#include "src/lib/files/file.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
#include "third_party/rapidjson/include/rapidjson/error/en.h"
#include "third_party/rapidjson/include/rapidjson/error/error.h"
#include "third_party/rapidjson/include/rapidjson/schema.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
namespace forensics::feedback {
namespace {
template <typename T>
std::optional<T> ReadConfig(const std::string& schema_str,
std::function<std::optional<T>(const rapidjson::Document&)> convert_fn,
const std::string& filepath) {
std::string config_str;
if (!files::ReadFileToString(filepath, &config_str)) {
FX_LOGS(ERROR) << "Error reading config file at " << filepath;
return std::nullopt;
}
rapidjson::Document config;
if (const rapidjson::ParseResult result = config.Parse(config_str.c_str()); !result) {
FX_LOGS(ERROR) << "Error parsing config as JSON at offset " << result.Offset() << " "
<< rapidjson::GetParseError_En(result.Code());
return std::nullopt;
}
rapidjson::Document schema;
if (const rapidjson::ParseResult result = schema.Parse(schema_str); !result) {
FX_LOGS(ERROR) << "Error parsing config schema at offset " << result.Offset() << " "
<< rapidjson::GetParseError_En(result.Code());
return std::nullopt;
}
rapidjson::SchemaDocument schema_doc(schema);
if (rapidjson::SchemaValidator validator(schema_doc); !config.Accept(validator)) {
rapidjson::StringBuffer buf;
validator.GetInvalidSchemaPointer().StringifyUriFragment(buf);
FX_LOGS(ERROR) << "Config does not match schema, violating '"
<< validator.GetInvalidSchemaKeyword() << "' rule";
return std::nullopt;
}
return convert_fn(config);
}
template <typename T>
std::optional<T> GetConfig(const std::string& schema_str,
std::function<std::optional<T>(const rapidjson::Document&)> convert_fn,
const std::string& config_type, const std::string& default_path,
const std::optional<std::string>& override_path) {
std::optional<T> config;
if (override_path.has_value() && files::IsFile(*override_path)) {
if (config = ReadConfig<T>(schema_str, convert_fn, *override_path); !config.has_value()) {
FX_LOGS(ERROR) << "Failed to read override " << config_type << " config file at "
<< *override_path;
}
}
if (!config.has_value()) {
if (config = ReadConfig<T>(schema_str, convert_fn, default_path); !config.has_value()) {
FX_LOGS(ERROR) << "Failed to read default " << config_type << " config file at "
<< default_path;
}
}
return config;
}
constexpr char kProductConfigSchema[] = R"({
"type": "object",
"properties": {
"persisted_logs_num_files": {
"type": "number"
},
"persisted_logs_total_size_kib": {
"type": "number"
},
"snapshot_persistence_max_tmp_size_mib": {
"type": "number"
},
"snapshot_persistence_max_cache_size_mib": {
"type": "number"
}
},
"required": [
"persisted_logs_num_files",
"persisted_logs_total_size_kib",
"snapshot_persistence_max_tmp_size_mib",
"snapshot_persistence_max_cache_size_mib"
],
"additionalProperties": false
})";
std::optional<ProductConfig> ParseProductConfig(const rapidjson::Document& json) {
ProductConfig config;
if (const int64_t num_files = json[kPersistedLogsNumFilesKey].GetInt64(); num_files > 0) {
config.persisted_logs_num_files = num_files;
} else {
config.persisted_logs_num_files = std::nullopt;
}
if (const int64_t total_size_kib = json[kPersistedLogsTotalSizeKey].GetInt64();
total_size_kib > 0) {
config.persisted_logs_total_size = StorageSize::Kilobytes(total_size_kib);
} else {
config.persisted_logs_total_size = std::nullopt;
}
if (config.persisted_logs_num_files.has_value() ^ config.persisted_logs_total_size.has_value()) {
FX_LOGS(ERROR)
<< "Can't only have one of the two persisted_logs fields set to a positive value";
return std::nullopt;
}
if (const int64_t max_tmp_size_mib = json[kSnapshotPersistenceMaxTmpSizeKey].GetInt64();
max_tmp_size_mib > 0) {
config.snapshot_persistence_max_tmp_size = StorageSize::Megabytes(max_tmp_size_mib);
} else {
config.snapshot_persistence_max_tmp_size = std::nullopt;
}
if (const int64_t max_cache_size_mib = json[kSnapshotPersistenceMaxCacheSizeKey].GetInt64();
max_cache_size_mib > 0) {
config.snapshot_persistence_max_cache_size = StorageSize::Megabytes(max_cache_size_mib);
} else {
config.snapshot_persistence_max_cache_size = std::nullopt;
}
return config;
}
const char kBuildTypeConfigSchema[] = R"({
"type": "object",
"properties": {
"crash_report_upload_policy": {
"type": "string",
"enum": [
"disabled",
"enabled",
"read_from_privacy_settings"
]
},
"daily_per_product_crash_report_quota": {
"type": "number"
},
"enable_data_redaction": {
"type": "boolean"
},
"enable_hourly_snapshots": {
"type": "boolean"
},
"enable_limit_inspect_data": {
"type": "boolean"
}
},
"required": [
"crash_report_upload_policy",
"daily_per_product_crash_report_quota",
"enable_data_redaction",
"enable_hourly_snapshots",
"enable_limit_inspect_data"
],
"additionalProperties": false
})";
std::optional<BuildTypeConfig> ParseBuildTypeConfig(const rapidjson::Document& json) {
BuildTypeConfig config{
.enable_data_redaction = json[kEnableDataRedactionKey].GetBool(),
.enable_hourly_snapshots = json[kEnableHourlySnapshotsKey].GetBool(),
.enable_limit_inspect_data = json[kEnableLimitInspectDataKey].GetBool(),
};
if (const std::string policy = json[kCrashReportUploadPolicyKey].GetString();
policy == "disabled") {
config.crash_report_upload_policy = CrashReportUploadPolicy::kDisabled;
} else if (policy == "enabled") {
config.crash_report_upload_policy = CrashReportUploadPolicy::kEnabled;
} else if (policy == "read_from_privacy_settings") {
config.crash_report_upload_policy = CrashReportUploadPolicy::kReadFromPrivacySettings;
} else {
FX_LOGS(FATAL) << "Upload policy '" << policy << "' not permitted by schema";
}
if (const int64_t quota = json[kDailyPerProductCrashReportQuotaKey].GetInt64(); quota > 0) {
config.daily_per_product_crash_report_quota = quota;
} else {
config.daily_per_product_crash_report_quota = std::nullopt;
}
return config;
}
constexpr char kSnapshotConfigSchema[] = R"({
"type": "object",
"properties": {
"annotation_allowlist": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"attachment_allowlist": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
},
"required": [
"annotation_allowlist",
"attachment_allowlist"
],
"additionalProperties": false
})";
std::optional<SnapshotConfig> ParseSnapshotConfig(const rapidjson::Document& json) {
SnapshotConfig config;
for (const auto& k : json["annotation_allowlist"].GetArray()) {
config.annotation_allowlist.insert(k.GetString());
}
for (const auto& k : json["attachment_allowlist"].GetArray()) {
config.attachment_allowlist.insert(k.GetString());
}
return config;
}
} // namespace
std::optional<ProductConfig> GetProductConfig(const std::string& default_path,
const std::string& override_path) {
return GetConfig<ProductConfig>(kProductConfigSchema, ParseProductConfig, "product", default_path,
override_path);
}
std::optional<BuildTypeConfig> GetBuildTypeConfig(const std::string& default_path,
const std::string& override_path) {
return GetConfig<BuildTypeConfig>(kBuildTypeConfigSchema, ParseBuildTypeConfig, "build type",
default_path, override_path);
}
std::optional<SnapshotConfig> GetSnapshotConfig(const std::string& default_path) {
return GetConfig<SnapshotConfig>(kSnapshotConfigSchema, ParseSnapshotConfig, "snapshot",
default_path, std::nullopt);
}
void ExposeConfig(inspect::Node& inspect_root, const BuildTypeConfig& build_type_config,
const ProductConfig& product_config) {
const std::string crash_report_quota =
build_type_config.daily_per_product_crash_report_quota.has_value()
? std::to_string(*build_type_config.daily_per_product_crash_report_quota)
: "none";
const std::string persisted_logs_num_files =
product_config.persisted_logs_num_files.has_value()
? std::to_string(*product_config.persisted_logs_num_files)
: "none";
const std::string persisted_logs_total_size =
product_config.persisted_logs_total_size.has_value()
? std::to_string(product_config.persisted_logs_total_size->ToKilobytes())
: "none";
const std::string snapshot_persistence_tmp_size =
product_config.snapshot_persistence_max_tmp_size.has_value()
? std::to_string(product_config.snapshot_persistence_max_tmp_size->ToMegabytes())
: "none";
const std::string snapshot_persistence_cache_size =
product_config.snapshot_persistence_max_cache_size.has_value()
? std::to_string(product_config.snapshot_persistence_max_cache_size->ToMegabytes())
: "none";
inspect_root.RecordChild(
kInspectConfigKey, [&build_type_config, &crash_report_quota, &persisted_logs_num_files,
&persisted_logs_total_size, &snapshot_persistence_tmp_size,
&snapshot_persistence_cache_size](inspect::Node& node) {
node.RecordString(kCrashReportUploadPolicyKey,
ToString(build_type_config.crash_report_upload_policy));
node.RecordString(kDailyPerProductCrashReportQuotaKey, crash_report_quota);
node.RecordBool(kEnableDataRedactionKey, build_type_config.enable_data_redaction);
node.RecordBool(kEnableHourlySnapshotsKey, build_type_config.enable_hourly_snapshots);
node.RecordBool(kEnableLimitInspectDataKey, build_type_config.enable_limit_inspect_data);
node.RecordString(kPersistedLogsNumFilesKey, persisted_logs_num_files);
node.RecordString(kPersistedLogsTotalSizeKey, persisted_logs_total_size);
node.RecordString(kSnapshotPersistenceMaxTmpSizeKey, snapshot_persistence_tmp_size);
node.RecordString(kSnapshotPersistenceMaxCacheSizeKey, snapshot_persistence_cache_size);
});
}
std::string ToString(const CrashReportUploadPolicy upload_policy) {
switch (upload_policy) {
case CrashReportUploadPolicy::kDisabled:
return "DISABLED";
case CrashReportUploadPolicy::kEnabled:
return "ENABLED";
case CrashReportUploadPolicy::kReadFromPrivacySettings:
return "READ_FROM_PRIVACY_SETTINGS";
}
}
} // namespace forensics::feedback