mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
c63c073d27
Include check_op.h directly, instead of relying on the transitive include from logging.h. This transitive include does not exist in Chromium's //base. Change-Id: I15962a9cdc26ac206032157b8d2659cf263ad695 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4950200 Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org>
948 lines
29 KiB
C++
948 lines
29 KiB
C++
// Copyright 2018 The Crashpad Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "client/crash_report_database.h"
|
|
|
|
#include <stdint.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <mutex>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/logging.h"
|
|
#include "build/build_config.h"
|
|
#include "client/settings.h"
|
|
#include "util/file/directory_reader.h"
|
|
#include "util/file/filesystem.h"
|
|
#include "util/misc/initialization_state_dcheck.h"
|
|
#include "util/misc/memory_sanitizer.h"
|
|
|
|
namespace crashpad {
|
|
|
|
namespace {
|
|
|
|
base::FilePath ReplaceFinalExtension(
|
|
const base::FilePath& path,
|
|
const base::FilePath::StringType extension) {
|
|
return base::FilePath(path.RemoveFinalExtension().value() + extension);
|
|
}
|
|
|
|
UUID UUIDFromReportPath(const base::FilePath& path) {
|
|
UUID uuid;
|
|
uuid.InitializeFromString(path.RemoveFinalExtension().BaseName().value());
|
|
return uuid;
|
|
}
|
|
|
|
using OperationStatus = CrashReportDatabase::OperationStatus;
|
|
|
|
constexpr base::FilePath::CharType kSettings[] =
|
|
FILE_PATH_LITERAL("settings.dat");
|
|
|
|
constexpr base::FilePath::CharType kCrashReportExtension[] =
|
|
FILE_PATH_LITERAL(".dmp");
|
|
constexpr base::FilePath::CharType kMetadataExtension[] =
|
|
FILE_PATH_LITERAL(".meta");
|
|
constexpr base::FilePath::CharType kLockExtension[] =
|
|
FILE_PATH_LITERAL(".lock");
|
|
|
|
constexpr base::FilePath::CharType kNewDirectory[] = FILE_PATH_LITERAL("new");
|
|
constexpr base::FilePath::CharType kPendingDirectory[] =
|
|
FILE_PATH_LITERAL("pending");
|
|
constexpr base::FilePath::CharType kCompletedDirectory[] =
|
|
FILE_PATH_LITERAL("completed");
|
|
|
|
constexpr const base::FilePath::CharType* kReportDirectories[] = {
|
|
kNewDirectory,
|
|
kPendingDirectory,
|
|
kCompletedDirectory,
|
|
};
|
|
|
|
enum {
|
|
//! \brief Corresponds to uploaded bit of the report state.
|
|
kAttributeUploaded = 1 << 0,
|
|
|
|
//! \brief Corresponds to upload_explicity_requested bit of the report state.
|
|
kAttributeUploadExplicitlyRequested = 1 << 1,
|
|
};
|
|
|
|
struct ReportMetadata {
|
|
static constexpr int32_t kVersion = 1;
|
|
|
|
int32_t version = kVersion;
|
|
int32_t upload_attempts = 0;
|
|
int64_t last_upload_attempt_time = 0;
|
|
time_t creation_time = 0;
|
|
uint8_t attributes = 0;
|
|
};
|
|
|
|
// A lock held while using database resources.
|
|
class ScopedLockFile {
|
|
public:
|
|
ScopedLockFile() = default;
|
|
|
|
ScopedLockFile(const ScopedLockFile&) = delete;
|
|
ScopedLockFile& operator=(const ScopedLockFile&) = delete;
|
|
|
|
~ScopedLockFile() = default;
|
|
|
|
ScopedLockFile& operator=(ScopedLockFile&& other) {
|
|
lock_file_.reset(other.lock_file_.release());
|
|
return *this;
|
|
}
|
|
|
|
// Attempt to acquire a lock for the report at report_path.
|
|
// Return `true` on success, otherwise `false`.
|
|
bool ResetAcquire(const base::FilePath& report_path) {
|
|
lock_file_.reset();
|
|
|
|
base::FilePath lock_path(report_path.RemoveFinalExtension().value() +
|
|
kLockExtension);
|
|
ScopedFileHandle lock_fd(LoggingOpenFileForWrite(
|
|
lock_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly));
|
|
if (!lock_fd.is_valid()) {
|
|
return false;
|
|
}
|
|
lock_file_.reset(lock_path);
|
|
|
|
time_t timestamp = time(nullptr);
|
|
if (!LoggingWriteFile(lock_fd.get(), ×tamp, sizeof(timestamp))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns `true` if the lock is held.
|
|
bool is_valid() const { return lock_file_.is_valid(); }
|
|
|
|
// Returns `true` if the lockfile at lock_path has expired.
|
|
static bool IsExpired(const base::FilePath& lock_path, time_t lockfile_ttl) {
|
|
time_t now = time(nullptr);
|
|
|
|
timespec filetime;
|
|
if (FileModificationTime(lock_path, &filetime) &&
|
|
filetime.tv_sec > now + lockfile_ttl) {
|
|
return false;
|
|
}
|
|
|
|
ScopedFileHandle lock_fd(LoggingOpenFileForReadAndWrite(
|
|
lock_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly));
|
|
if (!lock_fd.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
time_t timestamp;
|
|
if (!LoggingReadFileExactly(lock_fd.get(), ×tamp, sizeof(timestamp))) {
|
|
return false;
|
|
}
|
|
|
|
return now >= timestamp + lockfile_ttl;
|
|
}
|
|
|
|
private:
|
|
ScopedRemoveFile lock_file_;
|
|
};
|
|
} // namespace
|
|
|
|
class CrashReportDatabaseGeneric : public CrashReportDatabase {
|
|
public:
|
|
CrashReportDatabaseGeneric();
|
|
|
|
CrashReportDatabaseGeneric(const CrashReportDatabaseGeneric&) = delete;
|
|
CrashReportDatabaseGeneric& operator=(const CrashReportDatabaseGeneric&) =
|
|
delete;
|
|
|
|
~CrashReportDatabaseGeneric() override;
|
|
|
|
bool Initialize(const base::FilePath& path, bool may_create);
|
|
|
|
// CrashReportDatabase:
|
|
Settings* GetSettings() override;
|
|
OperationStatus PrepareNewCrashReport(
|
|
std::unique_ptr<NewReport>* report) override;
|
|
OperationStatus FinishedWritingCrashReport(std::unique_ptr<NewReport> report,
|
|
UUID* uuid) override;
|
|
OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override;
|
|
OperationStatus GetPendingReports(std::vector<Report>* reports) override;
|
|
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
|
OperationStatus GetReportForUploading(
|
|
const UUID& uuid,
|
|
std::unique_ptr<const UploadReport>* report,
|
|
bool report_metrics) override;
|
|
OperationStatus SkipReportUpload(const UUID& uuid,
|
|
Metrics::CrashSkippedReason reason) override;
|
|
OperationStatus DeleteReport(const UUID& uuid) override;
|
|
OperationStatus RequestUpload(const UUID& uuid) override;
|
|
int CleanDatabase(time_t lockfile_ttl) override;
|
|
base::FilePath DatabasePath() override;
|
|
|
|
private:
|
|
struct LockfileUploadReport : public UploadReport {
|
|
ScopedLockFile lock_file;
|
|
};
|
|
|
|
enum ReportState : int32_t {
|
|
kUninitialized = -1,
|
|
|
|
// Being created by a caller of PrepareNewCrashReport().
|
|
kNew,
|
|
|
|
// Created by FinishedWritingCrashReport(), but not yet uploaded.
|
|
kPending,
|
|
|
|
// Upload completed or skipped.
|
|
kCompleted,
|
|
|
|
// Specifies either kPending or kCompleted.
|
|
kSearchable,
|
|
};
|
|
|
|
// CrashReportDatabase:
|
|
OperationStatus RecordUploadAttempt(UploadReport* report,
|
|
bool successful,
|
|
const std::string& id) override;
|
|
|
|
// Builds a filepath for the report with the specified uuid and state.
|
|
base::FilePath ReportPath(const UUID& uuid, ReportState state);
|
|
|
|
// Locates the report with id uuid and returns its file path in path and a
|
|
// lock for the report in lock_file. This method succeeds as long as the
|
|
// report file exists and the lock can be acquired. No validation is done on
|
|
// the existence or content of the metadata file.
|
|
OperationStatus LocateAndLockReport(const UUID& uuid,
|
|
ReportState state,
|
|
base::FilePath* path,
|
|
ScopedLockFile* lock_file);
|
|
|
|
// Locates, locks, and reads the metadata for the report with the specified
|
|
// uuid and state. This method will fail and may remove reports if invalid
|
|
// metadata is detected. state may be kPending, kCompleted, or kSearchable.
|
|
OperationStatus CheckoutReport(const UUID& uuid,
|
|
ReportState state,
|
|
base::FilePath* path,
|
|
ScopedLockFile* lock_file,
|
|
Report* report);
|
|
|
|
// Reads metadata for all reports in state and returns it in reports.
|
|
OperationStatus ReportsInState(ReportState state,
|
|
std::vector<Report>* reports);
|
|
|
|
// Cleans lone metadata, reports, or expired locks in a particular state.
|
|
int CleanReportsInState(ReportState state, time_t lockfile_ttl);
|
|
|
|
// Cleans any attachments that have no associated report in any state.
|
|
void CleanOrphanedAttachments();
|
|
|
|
// Reads the metadata for a report from path and returns it in report.
|
|
bool ReadMetadata(const base::FilePath& path, Report* report);
|
|
|
|
// Wraps ReadMetadata and removes the report from the database on failure.
|
|
bool CleaningReadMetadata(const base::FilePath& path, Report* report);
|
|
|
|
// Writes metadata for a new report to the filesystem at path.
|
|
static bool WriteNewMetadata(const base::FilePath& path);
|
|
|
|
// Writes the metadata for report to the filesystem at path.
|
|
static bool WriteMetadata(const base::FilePath& path, const Report& report);
|
|
|
|
Settings& SettingsInternal() {
|
|
std::call_once(settings_init_, [this]() {
|
|
settings_.Initialize(base_dir_.Append(kSettings));
|
|
});
|
|
return settings_;
|
|
}
|
|
|
|
base::FilePath base_dir_;
|
|
Settings settings_;
|
|
std::once_flag settings_init_;
|
|
InitializationStateDcheck initialized_;
|
|
};
|
|
|
|
CrashReportDatabaseGeneric::CrashReportDatabaseGeneric() = default;
|
|
|
|
CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default;
|
|
|
|
bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path,
|
|
bool may_create) {
|
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
|
base_dir_ = path;
|
|
|
|
if (!IsDirectory(base_dir_, true) &&
|
|
!(may_create &&
|
|
LoggingCreateDirectory(base_dir_, FilePermissions::kOwnerOnly, true))) {
|
|
return false;
|
|
}
|
|
|
|
for (const base::FilePath::CharType* subdir : kReportDirectories) {
|
|
if (!LoggingCreateDirectory(
|
|
base_dir_.Append(subdir), FilePermissions::kOwnerOnly, true)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!LoggingCreateDirectory(
|
|
AttachmentsRootPath(), FilePermissions::kOwnerOnly, true)) {
|
|
return false;
|
|
}
|
|
|
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
|
const base::FilePath& path) {
|
|
auto database = std::make_unique<CrashReportDatabaseGeneric>();
|
|
return database->Initialize(path, true) ? std::move(database) : nullptr;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<CrashReportDatabase>
|
|
CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) {
|
|
auto database = std::make_unique<CrashReportDatabaseGeneric>();
|
|
return database->Initialize(path, false) ? std::move(database) : nullptr;
|
|
}
|
|
|
|
base::FilePath CrashReportDatabaseGeneric::DatabasePath() {
|
|
return base_dir_;
|
|
}
|
|
|
|
Settings* CrashReportDatabaseGeneric::GetSettings() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
return &SettingsInternal();
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::PrepareNewCrashReport(
|
|
std::unique_ptr<NewReport>* report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
auto new_report = std::make_unique<NewReport>();
|
|
if (!new_report->Initialize(
|
|
this, base_dir_.Append(kNewDirectory), kCrashReportExtension)) {
|
|
return kFileSystemError;
|
|
}
|
|
|
|
report->reset(new_report.release());
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::FinishedWritingCrashReport(
|
|
std::unique_ptr<NewReport> report,
|
|
UUID* uuid) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
base::FilePath path = ReportPath(report->ReportID(), kPending);
|
|
ScopedLockFile lock_file;
|
|
if (!lock_file.ResetAcquire(path)) {
|
|
return kBusyError;
|
|
}
|
|
|
|
if (!WriteNewMetadata(ReplaceFinalExtension(path, kMetadataExtension))) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
FileOffset size = report->Writer()->Seek(0, SEEK_END);
|
|
|
|
report->Writer()->Close();
|
|
if (!MoveFileOrDirectory(report->file_remover_.get(), path)) {
|
|
return kFileSystemError;
|
|
}
|
|
// We've moved the report to pending, so it no longer needs to be removed.
|
|
std::ignore = report->file_remover_.release();
|
|
|
|
// Close all the attachments and disarm their removers too.
|
|
for (auto& writer : report->attachment_writers_) {
|
|
writer->Close();
|
|
}
|
|
for (auto& remover : report->attachment_removers_) {
|
|
std::ignore = remover.release();
|
|
}
|
|
|
|
*uuid = report->ReportID();
|
|
|
|
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
|
|
Metrics::CrashReportSize(size);
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::LookUpCrashReport(const UUID& uuid,
|
|
Report* report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
ScopedLockFile lock_file;
|
|
base::FilePath path;
|
|
return CheckoutReport(uuid, kSearchable, &path, &lock_file, report);
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::GetPendingReports(
|
|
std::vector<Report>* reports) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
return ReportsInState(kPending, reports);
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::GetCompletedReports(
|
|
std::vector<Report>* reports) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
return ReportsInState(kCompleted, reports);
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::GetReportForUploading(
|
|
const UUID& uuid,
|
|
std::unique_ptr<const UploadReport>* report,
|
|
bool report_metrics) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
auto upload_report = std::make_unique<LockfileUploadReport>();
|
|
|
|
base::FilePath path;
|
|
OperationStatus os = CheckoutReport(
|
|
uuid, kPending, &path, &upload_report->lock_file, upload_report.get());
|
|
if (os != kNoError) {
|
|
return os;
|
|
}
|
|
|
|
if (!upload_report->Initialize(path, this)) {
|
|
return kFileSystemError;
|
|
}
|
|
upload_report->report_metrics_ = report_metrics;
|
|
|
|
report->reset(upload_report.release());
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::SkipReportUpload(
|
|
const UUID& uuid,
|
|
Metrics::CrashSkippedReason reason) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
Metrics::CrashUploadSkipped(reason);
|
|
|
|
base::FilePath path;
|
|
ScopedLockFile lock_file;
|
|
Report report;
|
|
OperationStatus os =
|
|
CheckoutReport(uuid, kPending, &path, &lock_file, &report);
|
|
if (os != kNoError) {
|
|
return os;
|
|
}
|
|
|
|
base::FilePath completed_path(ReportPath(uuid, kCompleted));
|
|
ScopedLockFile completed_lock_file;
|
|
if (!completed_lock_file.ResetAcquire(completed_path)) {
|
|
return kBusyError;
|
|
}
|
|
|
|
report.upload_explicitly_requested = false;
|
|
if (!WriteMetadata(completed_path, report)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
if (!MoveFileOrDirectory(path, completed_path)) {
|
|
return kFileSystemError;
|
|
}
|
|
|
|
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::DeleteReport(const UUID& uuid) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
base::FilePath path;
|
|
ScopedLockFile lock_file;
|
|
OperationStatus os =
|
|
LocateAndLockReport(uuid, kSearchable, &path, &lock_file);
|
|
if (os != kNoError) {
|
|
return os;
|
|
}
|
|
|
|
if (!LoggingRemoveFile(path)) {
|
|
return kFileSystemError;
|
|
}
|
|
|
|
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
RemoveAttachmentsByUUID(uuid);
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::RequestUpload(const UUID& uuid) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
base::FilePath path;
|
|
ScopedLockFile lock_file;
|
|
Report report;
|
|
OperationStatus os =
|
|
CheckoutReport(uuid, kSearchable, &path, &lock_file, &report);
|
|
if (os != kNoError) {
|
|
return os;
|
|
}
|
|
|
|
if (report.uploaded) {
|
|
return kCannotRequestUpload;
|
|
}
|
|
|
|
report.upload_explicitly_requested = true;
|
|
base::FilePath pending_path = ReportPath(uuid, kPending);
|
|
if (!MoveFileOrDirectory(path, pending_path)) {
|
|
return kFileSystemError;
|
|
}
|
|
|
|
if (!WriteMetadata(pending_path, report)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
if (pending_path != path) {
|
|
if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) {
|
|
return kDatabaseError;
|
|
}
|
|
}
|
|
|
|
Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated);
|
|
return kNoError;
|
|
}
|
|
|
|
int CrashReportDatabaseGeneric::CleanDatabase(time_t lockfile_ttl) {
|
|
int removed = 0;
|
|
time_t now = time(nullptr);
|
|
|
|
DirectoryReader reader;
|
|
const base::FilePath new_dir(base_dir_.Append(kNewDirectory));
|
|
if (reader.Open(new_dir)) {
|
|
base::FilePath filename;
|
|
DirectoryReader::Result result;
|
|
while ((result = reader.NextFile(&filename)) ==
|
|
DirectoryReader::Result::kSuccess) {
|
|
const base::FilePath filepath(new_dir.Append(filename));
|
|
timespec filetime;
|
|
if (!FileModificationTime(filepath, &filetime)) {
|
|
continue;
|
|
}
|
|
if (filetime.tv_sec <= now - lockfile_ttl) {
|
|
if (LoggingRemoveFile(filepath)) {
|
|
++removed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
removed += CleanReportsInState(kPending, lockfile_ttl);
|
|
removed += CleanReportsInState(kCompleted, lockfile_ttl);
|
|
CleanOrphanedAttachments();
|
|
#if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED
|
|
base::FilePath settings_path(kSettings);
|
|
if (Settings::IsLockExpired(settings_path, lockfile_ttl)) {
|
|
base::FilePath lockfile_path(settings_path.value() +
|
|
Settings::kLockfileExtension);
|
|
if (LoggingRemoveFile(lockfile_path)) {
|
|
++removed;
|
|
}
|
|
}
|
|
#endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED
|
|
return removed;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt(
|
|
UploadReport* report,
|
|
bool successful,
|
|
const std::string& id) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
if (report->report_metrics_) {
|
|
Metrics::CrashUploadAttempted(successful);
|
|
}
|
|
time_t now = time(nullptr);
|
|
|
|
report->id = id;
|
|
report->uploaded = successful;
|
|
report->last_upload_attempt_time = now;
|
|
++report->upload_attempts;
|
|
|
|
base::FilePath report_path(report->file_path);
|
|
|
|
ScopedLockFile lock_file;
|
|
if (successful) {
|
|
report->upload_explicitly_requested = false;
|
|
|
|
base::FilePath completed_report_path = ReportPath(report->uuid, kCompleted);
|
|
|
|
if (!lock_file.ResetAcquire(completed_report_path)) {
|
|
return kBusyError;
|
|
}
|
|
|
|
report->Reader()->Close();
|
|
if (!MoveFileOrDirectory(report_path, completed_report_path)) {
|
|
return kFileSystemError;
|
|
}
|
|
|
|
LoggingRemoveFile(ReplaceFinalExtension(report_path, kMetadataExtension));
|
|
report_path = completed_report_path;
|
|
}
|
|
|
|
if (!WriteMetadata(report_path, *report)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
if (!SettingsInternal().SetLastUploadAttemptTime(now)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid,
|
|
ReportState state) {
|
|
DCHECK_NE(state, kUninitialized);
|
|
DCHECK_NE(state, kSearchable);
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
const std::wstring uuid_string = uuid.ToWString();
|
|
#else
|
|
const std::string uuid_string = uuid.ToString();
|
|
#endif
|
|
|
|
return base_dir_.Append(kReportDirectories[state])
|
|
.Append(uuid_string + kCrashReportExtension);
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::LocateAndLockReport(
|
|
const UUID& uuid,
|
|
ReportState desired_state,
|
|
base::FilePath* path,
|
|
ScopedLockFile* lock_file) {
|
|
std::vector<ReportState> searchable_states;
|
|
if (desired_state == kSearchable) {
|
|
searchable_states.push_back(kPending);
|
|
searchable_states.push_back(kCompleted);
|
|
} else {
|
|
DCHECK(desired_state == kPending || desired_state == kCompleted);
|
|
searchable_states.push_back(desired_state);
|
|
}
|
|
|
|
for (const ReportState state : searchable_states) {
|
|
base::FilePath local_path(ReportPath(uuid, state));
|
|
ScopedLockFile local_lock;
|
|
if (!local_lock.ResetAcquire(local_path)) {
|
|
return kBusyError;
|
|
}
|
|
|
|
if (!IsRegularFile(local_path)) {
|
|
continue;
|
|
}
|
|
|
|
*path = local_path;
|
|
*lock_file = std::move(local_lock);
|
|
return kNoError;
|
|
}
|
|
|
|
return kReportNotFound;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::CheckoutReport(
|
|
const UUID& uuid,
|
|
ReportState state,
|
|
base::FilePath* path,
|
|
ScopedLockFile* lock_file,
|
|
Report* report) {
|
|
ScopedLockFile local_lock;
|
|
base::FilePath local_path;
|
|
OperationStatus os =
|
|
LocateAndLockReport(uuid, state, &local_path, &local_lock);
|
|
if (os != kNoError) {
|
|
return os;
|
|
}
|
|
|
|
if (!CleaningReadMetadata(local_path, report)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
*path = local_path;
|
|
*lock_file = std::move(local_lock);
|
|
return kNoError;
|
|
}
|
|
|
|
OperationStatus CrashReportDatabaseGeneric::ReportsInState(
|
|
ReportState state,
|
|
std::vector<Report>* reports) {
|
|
DCHECK(reports->empty());
|
|
DCHECK_NE(state, kUninitialized);
|
|
DCHECK_NE(state, kSearchable);
|
|
DCHECK_NE(state, kNew);
|
|
|
|
const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state]));
|
|
DirectoryReader reader;
|
|
if (!reader.Open(dir_path)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
base::FilePath filename;
|
|
DirectoryReader::Result result;
|
|
while ((result = reader.NextFile(&filename)) ==
|
|
DirectoryReader::Result::kSuccess) {
|
|
const base::FilePath::StringType extension(filename.FinalExtension());
|
|
if (extension.compare(kCrashReportExtension) != 0) {
|
|
continue;
|
|
}
|
|
|
|
const base::FilePath filepath(dir_path.Append(filename));
|
|
ScopedLockFile lock_file;
|
|
if (!lock_file.ResetAcquire(filepath)) {
|
|
continue;
|
|
}
|
|
|
|
Report report;
|
|
if (!CleaningReadMetadata(filepath, &report)) {
|
|
continue;
|
|
}
|
|
reports->push_back(report);
|
|
reports->back().file_path = filepath;
|
|
}
|
|
return kNoError;
|
|
}
|
|
|
|
int CrashReportDatabaseGeneric::CleanReportsInState(ReportState state,
|
|
time_t lockfile_ttl) {
|
|
const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state]));
|
|
DirectoryReader reader;
|
|
if (!reader.Open(dir_path)) {
|
|
return 0;
|
|
}
|
|
|
|
int removed = 0;
|
|
base::FilePath filename;
|
|
DirectoryReader::Result result;
|
|
while ((result = reader.NextFile(&filename)) ==
|
|
DirectoryReader::Result::kSuccess) {
|
|
const base::FilePath::StringType extension(filename.FinalExtension());
|
|
const base::FilePath filepath(dir_path.Append(filename));
|
|
|
|
// Remove any report files without metadata.
|
|
if (extension.compare(kCrashReportExtension) == 0) {
|
|
const base::FilePath metadata_path(
|
|
ReplaceFinalExtension(filepath, kMetadataExtension));
|
|
ScopedLockFile report_lock;
|
|
if (report_lock.ResetAcquire(filepath) && !IsRegularFile(metadata_path) &&
|
|
LoggingRemoveFile(filepath)) {
|
|
++removed;
|
|
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Remove any metadata files without report files.
|
|
if (extension.compare(kMetadataExtension) == 0) {
|
|
const base::FilePath report_path(
|
|
ReplaceFinalExtension(filepath, kCrashReportExtension));
|
|
ScopedLockFile report_lock;
|
|
if (report_lock.ResetAcquire(report_path) &&
|
|
!IsRegularFile(report_path) && LoggingRemoveFile(filepath)) {
|
|
++removed;
|
|
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Remove any expired locks only if we can remove the report and metadata.
|
|
if (extension.compare(kLockExtension) == 0 &&
|
|
ScopedLockFile::IsExpired(filepath, lockfile_ttl)) {
|
|
const base::FilePath no_ext(filepath.RemoveFinalExtension());
|
|
const base::FilePath report_path(no_ext.value() + kCrashReportExtension);
|
|
const base::FilePath metadata_path(no_ext.value() + kMetadataExtension);
|
|
if ((IsRegularFile(report_path) && !LoggingRemoveFile(report_path)) ||
|
|
(IsRegularFile(metadata_path) && !LoggingRemoveFile(metadata_path))) {
|
|
continue;
|
|
}
|
|
|
|
if (LoggingRemoveFile(filepath)) {
|
|
++removed;
|
|
RemoveAttachmentsByUUID(UUIDFromReportPath(filepath));
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
void CrashReportDatabaseGeneric::CleanOrphanedAttachments() {
|
|
base::FilePath root_attachments_dir(AttachmentsRootPath());
|
|
DirectoryReader reader;
|
|
if (!reader.Open(root_attachments_dir)) {
|
|
return;
|
|
}
|
|
|
|
base::FilePath filename;
|
|
DirectoryReader::Result result;
|
|
while ((result = reader.NextFile(&filename)) ==
|
|
DirectoryReader::Result::kSuccess) {
|
|
const base::FilePath report_attachment_dir(
|
|
root_attachments_dir.Append(filename));
|
|
if (IsDirectory(report_attachment_dir, false)) {
|
|
UUID uuid;
|
|
if (!uuid.InitializeFromString(filename.value())) {
|
|
LOG(ERROR) << "unexpected attachment dir name " << filename.value();
|
|
continue;
|
|
}
|
|
|
|
// Check to see if the report is being created in "new".
|
|
base::FilePath new_dir_path =
|
|
base_dir_.Append(kNewDirectory)
|
|
.Append(uuid.ToString() + kCrashReportExtension);
|
|
if (IsRegularFile(new_dir_path)) {
|
|
continue;
|
|
}
|
|
|
|
// Check to see if the report is in "pending" or "completed".
|
|
ScopedLockFile local_lock;
|
|
base::FilePath local_path;
|
|
OperationStatus os =
|
|
LocateAndLockReport(uuid, kSearchable, &local_path, &local_lock);
|
|
if (os != kReportNotFound) {
|
|
continue;
|
|
}
|
|
|
|
// Couldn't find a report, assume these attachments are orphaned.
|
|
RemoveAttachmentsByUUID(uuid);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
|
|
Report* report) {
|
|
const base::FilePath metadata_path(
|
|
ReplaceFinalExtension(path, kMetadataExtension));
|
|
|
|
ScopedFileHandle handle(LoggingOpenFileForRead(metadata_path));
|
|
if (!handle.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
UUID uuid;
|
|
if (!uuid.InitializeFromString(
|
|
path.BaseName().RemoveFinalExtension().value())) {
|
|
LOG(ERROR) << "Couldn't interpret report uuid";
|
|
return false;
|
|
}
|
|
|
|
ReportMetadata metadata;
|
|
if (!LoggingReadFileExactly(handle.get(), &metadata, sizeof(metadata))) {
|
|
return false;
|
|
}
|
|
|
|
if (metadata.version != ReportMetadata::kVersion) {
|
|
LOG(ERROR) << "metadata version mismatch";
|
|
return false;
|
|
}
|
|
|
|
if (!LoggingReadToEOF(handle.get(), &report->id)) {
|
|
return false;
|
|
}
|
|
|
|
// Seed the total size with the main report size and then add the sizes of any
|
|
// potential attachments.
|
|
uint64_t total_size = GetFileSize(path);
|
|
total_size += GetDirectorySize(AttachmentsPath(uuid));
|
|
|
|
report->uuid = uuid;
|
|
report->upload_attempts = metadata.upload_attempts;
|
|
report->last_upload_attempt_time = metadata.last_upload_attempt_time;
|
|
report->creation_time = metadata.creation_time;
|
|
report->uploaded = (metadata.attributes & kAttributeUploaded) != 0;
|
|
report->upload_explicitly_requested =
|
|
(metadata.attributes & kAttributeUploadExplicitlyRequested) != 0;
|
|
report->file_path = path;
|
|
report->total_size = total_size;
|
|
return true;
|
|
}
|
|
|
|
bool CrashReportDatabaseGeneric::CleaningReadMetadata(
|
|
const base::FilePath& path,
|
|
Report* report) {
|
|
if (ReadMetadata(path, report)) {
|
|
return true;
|
|
}
|
|
|
|
LoggingRemoveFile(path);
|
|
LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension));
|
|
RemoveAttachmentsByUUID(report->uuid);
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) {
|
|
const base::FilePath metadata_path(
|
|
ReplaceFinalExtension(path, kMetadataExtension));
|
|
|
|
ScopedFileHandle handle(LoggingOpenFileForWrite(metadata_path,
|
|
FileWriteMode::kCreateOrFail,
|
|
FilePermissions::kOwnerOnly));
|
|
if (!handle.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
ReportMetadata metadata;
|
|
#if defined(MEMORY_SANITIZER)
|
|
// memset() + re-initialization is required to zero padding bytes for MSan.
|
|
memset(&metadata, 0, sizeof(metadata));
|
|
#endif // defined(MEMORY_SANITIZER)
|
|
metadata = {};
|
|
metadata.creation_time = time(nullptr);
|
|
|
|
return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata));
|
|
}
|
|
|
|
// static
|
|
bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path,
|
|
const Report& report) {
|
|
const base::FilePath metadata_path(
|
|
ReplaceFinalExtension(path, kMetadataExtension));
|
|
|
|
ScopedFileHandle handle(
|
|
LoggingOpenFileForWrite(metadata_path,
|
|
FileWriteMode::kTruncateOrCreate,
|
|
FilePermissions::kOwnerOnly));
|
|
if (!handle.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
ReportMetadata metadata;
|
|
#if defined(MEMORY_SANITIZER)
|
|
// memset() + re-initialization is required to zero padding bytes for MSan.
|
|
memset(&metadata, 0, sizeof(metadata));
|
|
#endif // defined(MEMORY_SANITIZER)
|
|
metadata = {};
|
|
metadata.creation_time = report.creation_time;
|
|
metadata.last_upload_attempt_time = report.last_upload_attempt_time;
|
|
metadata.upload_attempts = report.upload_attempts;
|
|
metadata.attributes =
|
|
(report.uploaded ? kAttributeUploaded : 0) |
|
|
(report.upload_explicitly_requested ? kAttributeUploadExplicitlyRequested
|
|
: 0);
|
|
|
|
return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)) &&
|
|
LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size());
|
|
}
|
|
|
|
} // namespace crashpad
|