| // 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/datastore.h" |
| |
| #include <lib/fpromise/promise.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <utility> |
| |
| #include "src/developer/forensics/feedback/device_id_provider.h" |
| #include "src/developer/forensics/feedback/redactor_factory.h" |
| #include "src/developer/forensics/feedback_data/annotations/annotation_provider_factory.h" |
| #include "src/developer/forensics/feedback_data/annotations/static_annotations.h" |
| #include "src/developer/forensics/feedback_data/annotations/types.h" |
| #include "src/developer/forensics/feedback_data/attachments/inspect.h" |
| #include "src/developer/forensics/feedback_data/attachments/kernel_log_ptr.h" |
| #include "src/developer/forensics/feedback_data/attachments/static_attachments.h" |
| #include "src/developer/forensics/feedback_data/attachments/system_log.h" |
| #include "src/developer/forensics/feedback_data/attachments/types.h" |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/developer/forensics/utils/cobalt/metrics.h" |
| #include "src/developer/forensics/utils/previous_boot_file.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace forensics { |
| namespace feedback_data { |
| |
| Datastore::Datastore(async_dispatcher_t* dispatcher, |
| std::shared_ptr<sys::ServiceDirectory> services, cobalt::Logger* cobalt, |
| RedactorBase* redactor, const AnnotationKeys& annotation_allowlist, |
| const AttachmentKeys& attachment_allowlist, |
| feedback::AnnotationManager* annotation_manager, |
| feedback::DeviceIdProvider* device_id_provider, |
| InspectDataBudget* inspect_data_budget) |
| : dispatcher_(dispatcher), |
| services_(services), |
| cobalt_(cobalt), |
| redactor_(redactor), |
| annotation_allowlist_(annotation_allowlist), |
| attachment_allowlist_(attachment_allowlist), |
| annotation_metrics_(cobalt_), |
| annotation_manager_(annotation_manager), |
| static_attachments_(feedback_data::GetStaticAttachments(attachment_allowlist_)), |
| system_log_(dispatcher_, services_, &clock_, redactor_, kActiveLoggingPeriod), |
| reusable_annotation_providers_( |
| GetReusableProviders(dispatcher_, services_, device_id_provider)), |
| inspect_data_budget_(inspect_data_budget) { |
| FX_CHECK(annotation_allowlist_.size() <= kMaxNumPlatformAnnotations) |
| << "Requesting more platform annotations than the maximum number of platform annotations " |
| "allowed"; |
| |
| if (annotation_allowlist_.empty()) { |
| FX_LOGS(WARNING) |
| << "Annotation allowlist is empty, no platform annotations will be collected or returned"; |
| } |
| if (attachment_allowlist_.empty()) { |
| FX_LOGS(WARNING) |
| << "Attachment allowlist is empty, no platform attachments will be collected or returned"; |
| } |
| } |
| |
| Datastore::Datastore(async_dispatcher_t* dispatcher, |
| std::shared_ptr<sys::ServiceDirectory> services, |
| feedback::DeviceIdProvider* device_id_provider, |
| const char* limit_data_flag_path) |
| : dispatcher_(dispatcher), |
| services_(services), |
| // Somewhat risky, but the Cobalt's constructor sets up a bunch of stuff and this constructor |
| // is intended for tests. |
| cobalt_(nullptr), |
| // Somewhat risky, but redaction isn't needed in tests that use this constructor. |
| redactor_(nullptr), |
| annotation_allowlist_({}), |
| attachment_allowlist_({}), |
| annotation_metrics_(cobalt_), |
| // Somewhat risky, but the AnnotationManager depends on a bunch of stuff and this constructor |
| // is intended for tests. |
| annotation_manager_(nullptr), |
| static_attachments_({}), |
| system_log_(dispatcher_, services_, &clock_, redactor_, zx::sec(30)), |
| reusable_annotation_providers_( |
| GetReusableProviders(dispatcher_, services_, device_id_provider)) {} |
| |
| ::fpromise::promise<Annotations> Datastore::GetAnnotations(const zx::duration timeout) { |
| if (annotation_allowlist_.empty()) { |
| return ::fpromise::make_result_promise<Annotations>(::fpromise::error()); |
| } |
| |
| std::vector<::fpromise::promise<Annotations>> annotations; |
| |
| // We seed the returned annotations with the annotations from the annotation manager. |
| // |
| // The number of platform and non-platform annotations are capped so this is safe to do. |
| annotations.push_back(annotation_manager_->GetAll(timeout)); |
| |
| for (auto& provider : reusable_annotation_providers_) { |
| annotations.push_back(provider->GetAnnotations(timeout, annotation_allowlist_)); |
| } |
| |
| return ::fpromise::join_promise_vector(std::move(annotations)) |
| .and_then([this](std::vector<::fpromise::result<Annotations>>& annotations) |
| -> ::fpromise::result<Annotations> { |
| Annotations ok_annotations; |
| |
| // We then augment the returned annotations with the dynamic platform annotations. |
| for (auto& result : annotations) { |
| if (result.is_ok()) { |
| for (const auto& [key, value] : result.take_value()) { |
| ok_annotations.insert({key, value}); |
| } |
| } |
| } |
| |
| for (const auto& key : annotation_allowlist_) { |
| if (ok_annotations.find(key) == ok_annotations.end()) { |
| FX_LOGS(ERROR) << "No provider collected annotation " << key; |
| ok_annotations.insert({key, Error::kMissingValue}); |
| } |
| } |
| annotation_metrics_.LogMetrics(ok_annotations); |
| |
| return ::fpromise::ok(ok_annotations); |
| }); |
| } |
| |
| ::fpromise::promise<Attachments> Datastore::GetAttachments(const zx::duration timeout) { |
| if (attachment_allowlist_.empty()) { |
| return ::fpromise::make_result_promise<Attachments>(::fpromise::error()); |
| } |
| |
| std::vector<::fpromise::promise<Attachment>> attachments; |
| for (const auto& key : attachment_allowlist_) { |
| attachments.push_back(BuildAttachment(key, timeout)); |
| } |
| |
| return ::fpromise::join_promise_vector(std::move(attachments)) |
| .and_then([this](std::vector<::fpromise::result<Attachment>>& attachments) |
| -> ::fpromise::result<Attachments> { |
| // We seed the returned attachments with the static ones. |
| Attachments ok_attachments(static_attachments_.begin(), static_attachments_.end()); |
| |
| // We then augment them with the dynamic ones. |
| for (auto& result : attachments) { |
| if (result.is_ok()) { |
| Attachment attachment = result.take_value(); |
| ok_attachments.insert({attachment.first, attachment.second}); |
| } |
| } |
| |
| if (ok_attachments.empty()) { |
| return ::fpromise::error(); |
| } |
| |
| // Make sure all attachments are correctly categorized. Any complete or partial attachments |
| // that have empty values should be categorized as missing to not be included in the final |
| // snapshot and marked as such in the integrity manifest. |
| for (auto& [_, attachment] : ok_attachments) { |
| if (attachment.HasValue() && attachment.Value().empty()) { |
| // In case there is an error and a value, i.e. a partial attachment, preserve the error. |
| if (attachment.HasError()) { |
| attachment = AttachmentValue(attachment.Error()); |
| } else { |
| attachment = AttachmentValue(Error::kMissingValue); |
| } |
| } |
| } |
| |
| return ::fpromise::ok(ok_attachments); |
| }); |
| } |
| |
| ::fpromise::promise<Attachment> Datastore::BuildAttachment(const AttachmentKey& key, |
| const zx::duration timeout) { |
| return BuildAttachmentValue(key, timeout) |
| .and_then([key](AttachmentValue& value) -> ::fpromise::result<Attachment> { |
| return ::fpromise::ok(Attachment(key, value)); |
| }); |
| } |
| |
| ::fpromise::promise<AttachmentValue> Datastore::BuildAttachmentValue(const AttachmentKey& key, |
| const zx::duration timeout) { |
| if (key == kAttachmentLogKernel) { |
| return CollectKernelLog(dispatcher_, services_, |
| MakeCobaltTimeout(cobalt::TimedOutData::kKernelLog, timeout), |
| redactor_); |
| } else if (key == kAttachmentLogSystem) { |
| return system_log_.Get(timeout).and_then([this](AttachmentValue& log) { |
| if (log.HasError() && log.Error() == Error::kTimeout) { |
| cobalt_->LogOccurrence(cobalt::TimedOutData::kSystemLog); |
| } |
| return ::fpromise::ok(std::move(log)); |
| }); |
| } else if (key == kAttachmentInspect) { |
| return CollectInspectData(dispatcher_, services_, |
| MakeCobaltTimeout(cobalt::TimedOutData::kInspect, timeout), |
| inspect_data_budget_->SizeInBytes()); |
| } |
| // There are static attachments in the allowlist that we just skip here. |
| return ::fpromise::make_result_promise<AttachmentValue>(::fpromise::error()); |
| } |
| |
| void Datastore::DropStaticAttachment(const AttachmentKey& key, const Error error) { |
| if (static_attachments_.find(key) == static_attachments_.end()) { |
| return; |
| } |
| |
| static_attachments_.insert_or_assign(key, AttachmentValue(error)); |
| } |
| |
| fit::Timeout Datastore::MakeCobaltTimeout(cobalt::TimedOutData data, const zx::duration timeout) { |
| return fit::Timeout(timeout, |
| /*action=*/[cobalt = cobalt_, data] { cobalt->LogOccurrence(data); }); |
| } |
| |
| } // namespace feedback_data |
| } // namespace forensics |