blob: e47fbe2dd2dedf6d63098842a3f6c1092fd8fc5c [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/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