blob: 03e16484a825a1118c7e6e46d783a6b986ba789b [file]
// Copyright 2026 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/attachments/disk_backed_system_log.h"
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/result.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <string>
#include <utility>
#include "src/developer/forensics/feedback_data/constants.h"
#include "src/developer/forensics/utils/errors.h"
#include "src/developer/forensics/utils/vmo.h"
namespace forensics::feedback {
namespace {
struct LogData {
std::string contents;
std::optional<zx::time_boot> first_timestamp;
std::optional<zx::time_boot> last_timestamp;
std::optional<std::string> source;
};
// Creates a callable object that can be used to complete the system log collection flow with an
// ok status or a timeout and a promise to consume that result.
auto CompletesAndConsume() {
::fpromise::bridge<LogData, Error> bridge;
auto completer =
std::make_shared<::fpromise::completer<LogData, Error>>(std::move(bridge.completer));
return std::make_tuple(
[completer](LogData data) {
if (!*completer) {
return;
}
completer->complete_ok(std::move(data));
},
[completer](Error error) {
if (!*completer) {
return;
}
FX_LOGS(WARNING) << "System log collection error " << ToString(error);
completer->complete_error(error);
},
std::move(bridge.consumer).promise_or(::fpromise::error(Error::kLogicError)));
}
} // namespace
DiskBackedSystemLog::DiskBackedSystemLog(
async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> system_log_recorder_services,
std::unique_ptr<backoff::Backoff> backoff, RedactorBase* redactor, cobalt::Logger* cobalt)
: dispatcher_(dispatcher),
system_log_recorder_services_(std::move(system_log_recorder_services)),
backoff_(std::move(backoff)),
redactor_(redactor),
cobalt_(cobalt) {
Connect();
}
void DiskBackedSystemLog::Connect() {
if (system_log_recorder_services_ == nullptr) {
FX_LOGS(WARNING)
<< "SystemLogRecorder service directory is null, cannot connect to SystemLogRecorder";
return;
}
zx::result endpoints = fidl::CreateEndpoints<fuchsia_feedback_internal::SystemLogRecorder>();
if (endpoints.is_error()) {
FX_LOGS(ERROR) << "Failed to create endpoints: " << endpoints.status_string();
return;
}
system_log_recorder_services_->Connect(
fuchsia_feedback_internal::SystemLogRecorder::kDiscoverableName,
endpoints->server.TakeChannel());
client_ = fidl::Client(std::move(endpoints->client), dispatcher_, this);
}
void DiskBackedSystemLog::on_fidl_error(fidl::UnbindInfo error) {
if (error.status() == ZX_ERR_NOT_FOUND) {
// Invalidate the client so that future requests aren't made.
client_ = fidl::Client<fuchsia_feedback_internal::SystemLogRecorder>();
FX_LOGS(ERROR)
<< "SystemLogRecorder not found, will not attempt to reconnect. This should never happen.";
return;
}
FX_LOGS(WARNING) << "Lost connection to SystemLogRecorder: " << error;
reconnect_task_.PostDelayed(dispatcher_, backoff_->GetNext());
}
void DiskBackedSystemLog::handle_unknown_event(
fidl::UnknownEventMetadata<fuchsia_feedback_internal::SystemLogRecorder> metadata) {
FX_LOGS(ERROR) << "Unexpected event ordinal: " << metadata.event_ordinal;
}
::fpromise::promise<AttachmentData> DiskBackedSystemLog::Get(uint64_t ticket) {
FX_CHECK(!completers_.contains(ticket)) << "Ticket used twice: " << ticket;
AttachmentMetadata metadata({
{feedback_data::kAttachmentMetadataSourceKey, feedback_data::kAttachmentMetadataSourceDisk},
});
if (!client_.is_valid()) {
return ::fpromise::make_ok_promise(
AttachmentData(Error::kConnectionError, std::move(metadata)));
}
auto [complete_ok, complete_error, consume] = CompletesAndConsume();
completers_[ticket] = complete_error;
client_->GetCurrentBootLogs().Then(
[this, complete_ok = std::move(complete_ok), complete_error = std::move(complete_error),
redactor = redactor_](
fidl::Result<fuchsia_feedback_internal::SystemLogRecorder::GetCurrentBootLogs>&
result) mutable {
if (result.is_error()) {
FX_LOGS(ERROR) << "GetCurrentBootLogs failed: "
<< result.error_value().FormatDescription();
const Error error = result.error_value().is_framework_error() ? Error::kConnectionError
: Error::kFileReadFailure;
complete_error(error);
return;
}
backoff_->Reset();
if (!result->logs().has_value() || !result->logs()->is_valid()) {
FX_LOGS(ERROR) << "GetCurrentBootLogs returned missing or invalid VMO";
complete_error(Error::kBadValue);
return;
}
LogData log_data;
if (result->metadata().has_value()) {
log_data.first_timestamp = result->metadata()->first_timestamp();
log_data.last_timestamp = result->metadata()->last_timestamp();
if (result->metadata()->source().has_value()) {
switch (*result->metadata()->source()) {
case fuchsia_feedback_internal::SystemLogSource::kDisk:
log_data.source = feedback_data::kAttachmentMetadataSourceDisk;
break;
case fuchsia_feedback_internal::SystemLogSource::kStream:
log_data.source = feedback_data::kAttachmentMetadataSourceStream;
break;
default:
FX_LOGS(WARNING) << "Unknown SystemLogSource enum value: "
<< static_cast<uint32_t>(*result->metadata()->source());
break;
}
}
}
const zx::vmo vmo = std::move(*result->logs());
zx::result<std::string> contents = StringFromVmo(vmo);
if (contents.is_error()) {
complete_error(Error::kBadValue);
return;
}
redactor->Redact(*contents);
if (contents->empty()) {
complete_error(Error::kMissingValue);
return;
}
log_data.contents = std::move(*contents);
complete_ok(std::move(log_data));
});
fxl::WeakPtr<DiskBackedSystemLog> self = ptr_factory_.GetWeakPtr();
return consume.then([self, ticket, metadata = std::move(metadata)](
::fpromise::result<LogData, Error>& result) mutable
-> ::fpromise::result<AttachmentData> {
if (!self) {
return ::fpromise::ok(AttachmentData(Error::kLogicError, std::move(metadata)));
}
self->completers_.erase(ticket);
if (result.is_error()) {
if (result.error() == Error::kLogicError) {
FX_LOGS(FATAL) << "Log collection promise was incorrectly dropped";
}
return ::fpromise::ok(AttachmentData(result.error(), std::move(metadata)));
}
LogData& data = result.value();
if (data.source.has_value()) {
metadata = {{feedback_data::kAttachmentMetadataSourceKey, *data.source}};
}
// The logs are sorted, so we can assume the platform's log buffer is at capacity if the
// first log has a timestamp > 0.
if (data.first_timestamp.has_value() && *data.first_timestamp > zx::time_boot(0)) {
self->cobalt_->LogIntegerEvent(cobalt_registry::kSyslogBytesAtCapacityMetricId,
data.contents.size());
if (data.last_timestamp.has_value()) {
const int64_t duration = (*data.last_timestamp - *data.first_timestamp).to_mins();
self->cobalt_->LogIntegerEvent(cobalt_registry::kSyslogDurationAtCapacityMetricId,
duration);
}
}
return ::fpromise::ok(AttachmentData(std::move(data.contents), std::move(metadata)));
});
}
void DiskBackedSystemLog::ForceCompletion(const uint64_t ticket, const Error error) {
// Extract the completer from the map before calling it to avoid potential use-after-free.
if (auto node = completers_.extract(ticket); node) {
node.mapped()(error);
}
}
} // namespace forensics::feedback