blob: 1b1b43678ae2f34e9bb3c2b623c8d5b0b8d831dd [file]
// Copyright 2019 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/last_reboot/reporter.h"
#include <lib/async/cpp/task.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/result.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/types.h>
#include <string>
#include <vector>
#include "src/developer/forensics/feedback/config.h"
#include "src/developer/forensics/utils/errors.h"
#include "src/lib/files/file.h"
#include "src/lib/fsl/vmo/strings.h"
namespace forensics {
namespace last_reboot {
namespace {
constexpr char kHasReportedOnPath[] = "/tmp/has_reported_on_reboot_log.txt";
} // namespace
Reporter::Reporter(async_dispatcher_t* dispatcher, cobalt::Logger* cobalt, RedactorBase* redactor,
fuchsia::feedback::CrashReporter* crash_reporter)
: dispatcher_(dispatcher),
executor_(dispatcher),
cobalt_(cobalt),
redactor_(redactor),
crash_reporter_(crash_reporter) {}
void Reporter::ReportOn(const feedback::RebootLog& reboot_log, zx::duration crash_reporting_delay,
feedback::SpontaneousRebootReason spontaneous_reboot_reason) {
if (files::IsFile(kHasReportedOnPath)) {
FX_LOGS(INFO)
<< "Reboot log has already been reported on in another instance of this component "
"for this boot cycle";
return;
}
if (!files::WriteFile(kHasReportedOnPath, /*data=*/"", /*size=*/0)) {
FX_LOGS(ERROR) << "Failed to record reboot log as reported on";
}
const zx::duration uptime = reboot_log.GetFinalShutdownInfo().Uptime().value_or(zx::usec(0));
cobalt_->LogDuration(reboot_log.GetFinalShutdownInfo().ToCobaltLastRebootReason(), uptime);
if (!reboot_log.GetFinalShutdownInfo().IsCrash()) {
return;
}
executor_.schedule_task(
FileCrashReport(reboot_log, crash_reporting_delay, spontaneous_reboot_reason));
}
namespace {
fuchsia::feedback::CrashReport CreateCrashReport(
const feedback::RebootLog& reboot_log, RedactorBase* redactor,
feedback::SpontaneousRebootReason spontaneous_reboot_reason) {
// Build the crash report.
fuchsia::feedback::CrashReport report;
const feedback::FinalShutdownInfo& final_shutdown_info = reboot_log.GetFinalShutdownInfo();
report.set_program_name(final_shutdown_info.ToCrashProgramName())
.set_crash_signature(final_shutdown_info.ToCrashSignature(spontaneous_reboot_reason))
.set_is_fatal(true);
if (final_shutdown_info.Uptime().has_value()) {
report.set_program_uptime(final_shutdown_info.Uptime()->get());
}
std::string reboot_log_str = reboot_log.RebootLogStr();
// Build the crash report attachments.
fsl::SizedVmo vmo;
if (fsl::VmoFromString(redactor->Redact(reboot_log_str), &vmo)) {
std::vector<fuchsia::feedback::Attachment> attachments(1);
attachments.back().key = "reboot_crash_log";
attachments.back().value = std::move(vmo).ToTransport();
report.set_attachments(std::move(attachments));
}
return report;
}
} // namespace
::fpromise::promise<void> Reporter::FileCrashReport(
const feedback::RebootLog& reboot_log, const zx::duration delay,
feedback::SpontaneousRebootReason spontaneous_reboot_reason) {
auto report = CreateCrashReport(reboot_log, redactor_, spontaneous_reboot_reason);
::fpromise::bridge<void, Error> bridge;
auto completer =
std::make_shared<::fpromise::completer<void, Error>>(std::move(bridge.completer));
delayed_crash_reporting_.Reset([this, report = std::move(report), completer]() mutable {
crash_reporter_->FileReport(
std::move(report), [completer](fuchsia::feedback::CrashReporter_FileReport_Result result) {
if (!*completer) {
return;
}
if (result.is_err()) {
completer->complete_error(Error::kBadValue);
} else {
completer->complete_ok();
}
});
});
if (const zx_status_t status = async::PostDelayedTask(
dispatcher_, [cb = delayed_crash_reporting_.callback()] { cb(); }, delay);
status != ZX_OK) {
completer->complete_error(Error::kAsyncTaskPostFailure);
}
return bridge.consumer.promise_or(::fpromise::error(Error::kLogicError))
.then([this](const ::fpromise::result<void, Error>& result) {
delayed_crash_reporting_.Cancel();
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to file a crash report: " << ToString(result.error());
}
return ::fpromise::ok();
});
}
} // namespace last_reboot
} // namespace forensics