| // 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/feedback_data/data_register.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "src/developer/forensics/feedback_data/annotations/types.h" |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/path.h" |
| #include "src/lib/fxl/strings/string_printf.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 feedback_data { |
| namespace { |
| |
| const char kDefaultNamespace[] = "misc"; |
| |
| const char kNamespaceSeparator[] = "."; |
| |
| Annotations Flatten(const std::map<std::string, Annotations>& namespaced_annotations) { |
| Annotations flat_annotations; |
| for (const auto& [namespace_, annotations] : namespaced_annotations) { |
| for (const auto& [k, v] : annotations) { |
| flat_annotations.insert({namespace_ + kNamespaceSeparator + k, v}); |
| } |
| } |
| return flat_annotations; |
| } |
| |
| } // namespace |
| |
| DataRegister::DataRegister(Datastore* datastore, std::string register_filepath) |
| : datastore_(datastore), register_filepath_(register_filepath) { |
| FX_CHECK(datastore_); |
| RestoreFromJson(); |
| } |
| |
| void DataRegister::Upsert(fuchsia::feedback::ComponentData data, UpsertCallback callback) { |
| if (!data.has_annotations()) { |
| FX_LOGS(WARNING) << "No non-platform annotations to upsert"; |
| callback(); |
| return; |
| } |
| |
| std::string namespace_; |
| if (!data.has_namespace()) { |
| FX_LOGS(WARNING) << "No namespace specified, defaulting to " << kDefaultNamespace; |
| namespace_ = kDefaultNamespace; |
| } else if (kReservedAnnotationNamespaces.find(data.namespace_()) != |
| kReservedAnnotationNamespaces.end()) { |
| FX_LOGS(WARNING) << fxl::StringPrintf( |
| "Ignoring non-platform annotations, %s is a reserved namespace", data.namespace_().c_str()); |
| // TODO(fxbug.dev/48664): close connection with ZX_ERR_INVALID_ARGS instead. |
| callback(); |
| return; |
| } else { |
| namespace_ = data.namespace_(); |
| } |
| |
| for (const auto& annotation : data.annotations()) { |
| namespaced_annotations_[namespace_].insert_or_assign(annotation.key, |
| AnnotationOr(annotation.value)); |
| } |
| |
| UpdateJson(namespace_, namespaced_annotations_[namespace_]); |
| |
| // TODO(fxbug.dev/48666): close all connections if false. |
| datastore_->TrySetNonPlatformAnnotations(Flatten(namespaced_annotations_)); |
| |
| callback(); |
| } |
| |
| // The content of the data register will be stored as json where each namespace is comprised of an |
| // object made up of string-string pairs. |
| // |
| // For example, if there are 2 namespaces, "foo" and "bar". "foo" has 2 set of annotations, |
| // {"k1", "v1} and {"k2, "v2"}, and "bar" has 1 annotation, {"k3", "v3"}, the json will look like: |
| // { |
| // "foo": { |
| // "k1": "v1", |
| // "k2": "v2" |
| // }, |
| // "bar": { |
| // "k3": "v3" |
| // } |
| // } |
| void DataRegister::UpdateJson(const std::string& _namespace, const Annotations& annotations) { |
| using namespace rapidjson; |
| |
| auto& allocator = register_json_.GetAllocator(); |
| |
| // If there are already annotations for |_namespace|, delete them. |
| if (register_json_.HasMember(_namespace)) { |
| register_json_.RemoveMember(_namespace); |
| } |
| |
| // Make an empty object for |_namespace|. |
| register_json_.AddMember(Value(_namespace, register_json_.GetAllocator()), |
| Value(rapidjson::kObjectType), allocator); |
| |
| const auto& json_annotations = register_json_[_namespace].GetObject(); |
| for (const auto& [k, v] : annotations) { |
| auto key = Value(k, allocator); |
| auto val = Value(v.Value(), allocator); |
| json_annotations.AddMember(key, val, 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 data register contents to " << register_filepath_; |
| } |
| } |
| |
| void DataRegister::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 data register as JSON at offset " << ok.Offset() << " " |
| << GetParseError_En(ok.Code()); |
| files::DeletePath(register_filepath_, /*recursive=*/true); |
| return; |
| } |
| |
| // Each namespace in the register is represented by an object containing at string-string pairs |
| // that are the annotations. |
| 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 _namespace = member.name.GetString(); |
| for (const auto& annotation : member.value.GetObject()) { |
| // Annotations must be string pairs. |
| if (!annotation.value.IsString()) { |
| continue; |
| } |
| |
| const std::string key = annotation.name.GetString(); |
| const std::string value = annotation.value.GetString(); |
| namespaced_annotations_[_namespace].emplace(key, AnnotationOr(value)); |
| } |
| } |
| |
| datastore_->TrySetNonPlatformAnnotations(Flatten(namespaced_annotations_)); |
| } |
| |
| } // namespace feedback_data |
| } // namespace forensics |