blob: 14f6bd87f9f47b7e264af15b2cddac56557e9b48 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "crash-reporter/crash_serializer.h"
#include <stdio.h>
#include <string>
#include <utility>
#include <base/compiler_specific.h> // FALLTHROUGH
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/optional.h>
#include <base/strings/string_util.h>
#include <base/threading/platform_thread.h>
#include <base/time/default_clock.h>
#include <base/time/time.h>
#include "crash-reporter/crash_sender_base.h"
#include "crash-reporter/crash_sender_util.h"
#include "crash-reporter/paths.h"
#include "crash-reporter/util.h"
namespace crash_serializer {
namespace {
void AddMetaField(crash::CrashInfo* info,
const std::string& key,
const std::string& value) {
if (!base::IsStringUTF8(key)) {
LOG(ERROR) << "key was not UTF8: " << key;
return;
}
if (!base::IsStringUTF8(value)) {
LOG(ERROR) << "value for key '" << key << "' was not UTF8";
return;
}
crash::CrashMetadata* meta = info->add_fields();
meta->set_key(key);
meta->set_text(value);
}
base::Optional<crash::CrashBlob> MakeBlob(const std::string& name,
const base::FilePath& file) {
if (!base::IsStringUTF8(name)) {
LOG(ERROR) << "key was not UTF8: " << name;
return base::nullopt;
}
std::string contents;
if (!base::ReadFileToString(file, &contents)) {
return base::nullopt;
}
crash::CrashBlob b;
b.set_key(name);
b.set_blob(contents);
b.set_filename(file.BaseName().value());
return b;
}
} // namespace
Serializer::Serializer(std::unique_ptr<base::Clock> clock,
const Options& options)
: util::SenderBase(std::move(clock), options),
out_("/dev/stdout"),
fetch_cores_(options.fetch_coredumps) {}
// The serializer doesn't remove crashes, so do nothing.
void Serializer::RecordCrashRemoveReason(CrashRemoveReason reason) {}
void Serializer::PickCrashFiles(const base::FilePath& crash_dir,
std::vector<util::MetaFile>* to_send) {
std::vector<base::FilePath> meta_files = util::GetMetaFiles(crash_dir);
for (const auto& meta_file : meta_files) {
LOG(INFO) << "Checking metadata: " << meta_file.value();
std::string reason;
util::CrashInfo info;
switch (EvaluateMetaFileMinimal(meta_file, /*allow_old_os_timestamps=*/true,
&reason, &info,
/*processing_file=*/nullptr)) {
case kRemove:
FALLTHROUGH; // Don't remove; rather, ignore the report.
case kIgnore:
LOG(INFO) << "Ignoring: " << reason;
break;
case kSend:
to_send->push_back(std::make_pair(meta_file, std::move(info)));
break;
default:
NOTREACHED();
}
}
}
void Serializer::SerializeCrashes(
const std::vector<util::MetaFile>& crash_meta_files) {
if (crash_meta_files.empty()) {
return;
}
std::string client_id = util::GetClientId();
base::File lock(AcquireLockFileOrDie());
int64_t crash_id = -1;
for (const auto& pair : crash_meta_files) {
crash_id++;
const base::FilePath& meta_file = pair.first;
const util::CrashInfo& info = pair.second;
LOG(INFO) << "Evaluating crash report: " << meta_file.value();
base::TimeDelta sleep_time;
if (!util::GetSleepTime(meta_file, /*max_spread_time=*/base::TimeDelta(),
hold_off_time_, &sleep_time)) {
LOG(WARNING) << "Failed to compute sleep time for " << meta_file.value();
continue;
}
LOG(INFO) << "Scheduled to send in " << sleep_time.InSeconds() << "s";
lock.Close(); // Don't hold lock during sleep.
if (!util::IsMock()) {
base::PlatformThread::Sleep(sleep_time);
} else if (!sleep_function_.is_null()) {
sleep_function_.Run(sleep_time);
}
lock = AcquireLockFileOrDie();
// Mark the crash as being processed so that if we crash, we don't try to
// send the crash again.
util::ScopedProcessingFile processing(meta_file);
// User-specific crash reports become inaccessible if the user signs out
// while sleeping, thus we need to check if the metadata is still
// accessible.
if (!base::PathExists(meta_file)) {
LOG(INFO) << "Metadata is no longer accessible: " << meta_file.value();
continue;
}
const util::CrashDetails details = {
.meta_file = meta_file,
.payload_file = info.payload_file,
.payload_kind = info.payload_kind,
.client_id = client_id,
.metadata = info.metadata,
};
crash::FetchCrashesResponse resp;
resp.set_crash_id(crash_id);
std::vector<crash::CrashBlob> blobs;
base::FilePath core_path;
if (!SerializeCrash(details, resp.mutable_crash(), &blobs, &core_path)) {
LOG(ERROR) << "Failed to serialize " << meta_file.value();
continue;
}
// TODO(mutexlox): Write response to the output file.
}
}
bool Serializer::SerializeCrash(const util::CrashDetails& details,
crash::CrashInfo* info,
std::vector<crash::CrashBlob>* blobs,
base::FilePath* core_path) {
util::FullCrash crash = ReadMetaFile(details);
// Add fields that are present directly in the FullCrash struct
info->set_exec_name(crash.exec_name);
AddMetaField(info, "board", crash.board);
AddMetaField(info, "hwclass", crash.hwclass);
info->set_prod(crash.prod);
info->set_ver(crash.ver);
info->set_sig(crash.sig);
AddMetaField(info, "sig2", crash.sig);
AddMetaField(info, "image_type", crash.image_type);
AddMetaField(info, "boot_mode", crash.boot_mode);
AddMetaField(info, "error_type", crash.error_type);
AddMetaField(info, "guid", crash.guid);
// Add fields from key_vals
for (const auto& kv : crash.key_vals) {
const std::string& key = kv.first;
const std::string& val = kv.second;
if (key == "in_progress_integration_test") {
info->set_in_progress_integration_test(val);
} else if (key == "collector") {
info->set_collector(val);
} else {
AddMetaField(info, key, val);
}
}
// Add payload file
base::Optional<crash::CrashBlob> payload =
MakeBlob(crash.payload.first, crash.payload.second);
if (!payload) {
return false;
}
blobs->push_back(*payload);
// Add files
for (const auto& kv : crash.files) {
base::Optional<crash::CrashBlob> blob = MakeBlob(kv.first, kv.second);
if (blob) {
blobs->push_back(*blob);
}
}
if (fetch_cores_) {
base::FilePath maybe_core = details.meta_file.ReplaceExtension(".core");
if (base::PathExists(maybe_core)) {
*core_path = maybe_core;
}
}
return true;
}
} // namespace crash_serializer