blob: 010e016899ad72e91cfbd616b1b3037088bb56c0 [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/previous_boot_inspect.h"
#include <lib/async/cpp/task.h>
#include <lib/fdio/fd.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>
#include <utility>
#include "src/developer/forensics/feedback/attachments/types.h"
#include "src/developer/forensics/utils/errors.h"
#include "src/lib/files/file.h"
namespace forensics::feedback {
PreviousBootInspect::PreviousBootInspect(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services,
std::unique_ptr<backoff::Backoff> backoff,
RedactorBase* redactor)
: backoff_(std::move(backoff)), redactor_(redactor) {
data_provider_.set_error_handler([this, dispatcher, services](const zx_status_t status) {
if (cached_value_.has_value()) {
return;
}
FX_PLOGS(WARNING, status)
<< "Lost connection to fuchsia.diagnostics.persistence.PreviousBootDataProvider";
async::PostDelayedTask(
dispatcher,
[self = ptr_factory_.GetWeakPtr(), dispatcher, services] {
if (self) {
self->WatchPreviousBootData(dispatcher, services);
}
},
backoff_->GetNext());
});
WatchPreviousBootData(dispatcher, std::move(services));
}
void PreviousBootInspect::WatchPreviousBootData(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services) {
if (cached_value_.has_value()) {
return;
}
services->Connect(data_provider_.NewRequest(dispatcher));
fuchsia::diagnostics::persistence::PreviousBootDataProviderOptions options;
options.set_format(fuchsia::diagnostics::persistence::InspectFormat::JSON);
data_provider_->WatchPreviousBootData(std::move(options), [this](auto result) {
if (result.is_response()) {
OnDataReceived(std::move(result.response().data));
} else {
OnError();
}
});
}
void PreviousBootInspect::OnError() {
if (cached_value_.has_value()) {
return;
}
FX_LOGS(WARNING) << "Failed to watch previous boot inspect data";
cached_value_ = AttachmentData(Error::kMissingValue);
if (data_provider_.is_bound()) {
data_provider_.Unbind();
}
auto completers = std::move(completers_);
for (auto& [ticket, completer] : completers) {
if (completer != nullptr) {
completer(cached_value_->Clone());
}
}
}
void PreviousBootInspect::OnDataReceived(fuchsia::diagnostics::persistence::PreviousBootData data) {
if (cached_value_.has_value()) {
return;
}
if (!data.has_inspect() || !data.inspect().is_valid()) {
FX_LOGS(WARNING) << "Previous boot inspect data is missing or invalid";
cached_value_ = AttachmentData(Error::kMissingValue);
} else {
zx::channel channel = data.mutable_inspect()->TakeChannel();
int fd = -1;
if (const zx_status_t status = fdio_fd_create(channel.release(), &fd); status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to create fd from previous boot inspect file";
cached_value_ = AttachmentData(Error::kFileReadFailure);
} else {
std::string inspect_json;
if (!files::ReadFileDescriptorToString(fd, &inspect_json) || inspect_json.empty()) {
FX_LOGS(WARNING) << "Failed to read previous boot inspect file content";
cached_value_ = AttachmentData(Error::kMissingValue);
} else {
redactor_->RedactJson(inspect_json);
cached_value_ = AttachmentData(std::move(inspect_json));
}
close(fd);
}
}
if (data_provider_.is_bound()) {
data_provider_.Unbind();
}
auto completers = std::move(completers_);
for (auto& [ticket, completer] : completers) {
if (completer != nullptr) {
completer(cached_value_->Clone());
}
}
}
::fpromise::promise<AttachmentData> PreviousBootInspect::Get(const uint64_t ticket) {
FX_CHECK(!completers_.contains(ticket)) << "Ticket used twice: " << ticket;
if (cached_value_.has_value()) {
return ::fpromise::make_ok_promise(cached_value_->Clone());
}
::fpromise::bridge<AttachmentData, void> bridge;
completers_[ticket] = [completer = std::move(bridge.completer)](AttachmentData value) mutable {
completer.complete_ok(std::move(value));
};
return bridge.consumer.promise_or(::fpromise::ok(AttachmentData(Error::kLogicError)));
}
void PreviousBootInspect::ForceCompletion(const uint64_t ticket, const Error error) {
if (completers_.contains(ticket) && completers_[ticket] != nullptr) {
auto completer = std::move(completers_[ticket]);
completers_.erase(ticket);
completer(AttachmentData(error));
}
}
} // namespace forensics::feedback