| // Copyright 2018 The Crashpad Authors. All rights reserved. |
| // |
| // 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 <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #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; |
| } |
| |
| bool AttachmentNameIsOK(const std::string& name) { |
| for (const char c : name) { |
| if (c != '_' && c != '-' && c != '.' && !isalnum(c)) |
| return false; |
| } |
| return true; |
| } |
| |
| 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 base::FilePath::CharType kAttachmentsDirectory[] = |
| FILE_PATH_LITERAL("attachments"); |
| |
| 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() = 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedLockFile); |
| }; |
| |
| off_t GetFileSize(const base::FilePath& filepath) { |
| struct stat statbuf; |
| if (stat(filepath.value().c_str(), &statbuf) == 0) { |
| return statbuf.st_size; |
| } |
| PLOG(ERROR) << "stat " << filepath.value(); |
| return 0; |
| } |
| |
| void AddAttachmentSize(const base::FilePath& attachments_dir, uint64_t* size) { |
| // Early return if the attachment directory does not exist. |
| if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { |
| return; |
| } |
| DirectoryReader reader; |
| if (!reader.Open(attachments_dir)) { |
| return; |
| } |
| base::FilePath attachment_filename; |
| DirectoryReader::Result result; |
| while ((result = reader.NextFile(&attachment_filename)) == |
| DirectoryReader::Result::kSuccess) { |
| const base::FilePath attachment_filepath( |
| attachments_dir.Append(attachment_filename)); |
| *size += GetFileSize(attachment_filepath); |
| } |
| } |
| |
| bool WasCreatedSooner(CrashReportDatabase::Report a, |
| CrashReportDatabase::Report b) { |
| return a.creation_time < b.creation_time; |
| } |
| |
| } // namespace |
| |
| class CrashReportDatabaseGeneric : public CrashReportDatabase { |
| public: |
| CrashReportDatabaseGeneric(); |
| ~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; |
| OperationStatus RemoveOldReports(int num_reports_to_keep) override; |
| |
| // Build a filepath for the directory for the report to hold attachments. |
| base::FilePath AttachmentsPath(const UUID& uuid); |
| |
| 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(); |
| |
| // Attempt to remove any attachments associated with the given report UUID. |
| // There may not be any, so failing is not an error. |
| void RemoveAttachmentsByUUID(const UUID& uuid); |
| |
| // 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); |
| |
| base::FilePath base_dir_; |
| Settings settings_; |
| InitializationStateDcheck initialized_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseGeneric); |
| }; |
| |
| FileWriter* CrashReportDatabase::NewReport::AddAttachment( |
| const std::string& name) { |
| if (!AttachmentNameIsOK(name)) { |
| LOG(ERROR) << "invalid name for attachment " << name; |
| return nullptr; |
| } |
| |
| base::FilePath attachments_dir = |
| static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath( |
| uuid_); |
| if (!LoggingCreateDirectory( |
| attachments_dir, FilePermissions::kOwnerOnly, true)) { |
| return nullptr; |
| } |
| |
| base::FilePath path = attachments_dir.Append(name); |
| |
| auto writer = std::make_unique<FileWriter>(); |
| if (!writer->Open( |
| path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) { |
| LOG(ERROR) << "could not open " << path.value(); |
| return nullptr; |
| } |
| attachment_writers_.emplace_back(std::move(writer)); |
| attachment_removers_.emplace_back(ScopedRemoveFile(path)); |
| return attachment_writers_.back().get(); |
| } |
| |
| void CrashReportDatabase::UploadReport::InitializeAttachments() { |
| base::FilePath attachments_dir = |
| static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath( |
| uuid); |
| if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { |
| return; |
| } |
| DirectoryReader reader; |
| if (!reader.Open(attachments_dir)) { |
| return; |
| } |
| |
| base::FilePath filename; |
| DirectoryReader::Result dir_result; |
| while ((dir_result = reader.NextFile(&filename)) == |
| DirectoryReader::Result::kSuccess) { |
| const base::FilePath filepath(attachments_dir.Append(filename)); |
| std::unique_ptr<FileReader> reader(std::make_unique<FileReader>()); |
| if (!reader->Open(filepath)) { |
| LOG(ERROR) << "attachment " << filepath.value() |
| << " couldn't be opened, skipping"; |
| continue; |
| } |
| attachment_readers_.emplace_back(std::move(reader)); |
| attachment_map_[filename.value()] = attachment_readers_.back().get(); |
| } |
| } |
| |
| 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(base_dir_.Append(kAttachmentsDirectory), |
| FilePermissions::kOwnerOnly, |
| true)) { |
| return false; |
| } |
| |
| if (!settings_.Initialize(base_dir_.Append(kSettings))) { |
| 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; |
| } |
| |
| Settings* CrashReportDatabaseGeneric::GetSettings() { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return &settings_; |
| } |
| |
| 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. |
| ignore_result(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_) { |
| ignore_result(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(); |
| return removed; |
| } |
| |
| OperationStatus CrashReportDatabaseGeneric::RemoveOldReports( |
| int num_reports_to_keep) { |
| std::vector<CrashReportDatabase::Report> pending_reports; |
| std::vector<CrashReportDatabase::Report> completed_reports; |
| std::vector<CrashReportDatabase::Report> all_reports; |
| |
| GetPendingReports(&pending_reports); |
| GetCompletedReports(&completed_reports); |
| |
| all_reports.insert( |
| all_reports.end(), pending_reports.begin(), pending_reports.end()); |
| all_reports.insert( |
| all_reports.end(), completed_reports.begin(), completed_reports.end()); |
| std::sort(all_reports.begin(), all_reports.end(), WasCreatedSooner); |
| |
| while (all_reports.size() > num_reports_to_keep) { |
| OperationStatus os = DeleteReport((*all_reports.begin()).uuid); |
| if (os != kNoError) { |
| return os; |
| } |
| all_reports.erase(all_reports.begin()); |
| } |
| |
| return kNoError; |
| } |
| |
| 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 (!settings_.SetLastUploadAttemptTime(now)) { |
| return kDatabaseError; |
| } |
| |
| return kNoError; |
| } |
| |
| base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid, |
| ReportState state) { |
| DCHECK_NE(state, kUninitialized); |
| DCHECK_NE(state, kSearchable); |
| |
| #if defined(OS_WIN) |
| const std::wstring uuid_string = uuid.ToString16(); |
| #else |
| const std::string uuid_string = uuid.ToString(); |
| #endif |
| |
| return base_dir_.Append(kReportDirectories[state]) |
| .Append(uuid_string + kCrashReportExtension); |
| } |
| |
| base::FilePath CrashReportDatabaseGeneric::AttachmentsPath(const UUID& uuid) { |
| #if defined(OS_WIN) |
| const std::wstring uuid_string = uuid.ToString16(); |
| #else |
| const std::string uuid_string = uuid.ToString(); |
| #endif |
| |
| return base_dir_.Append(kAttachmentsDirectory).Append(uuid_string); |
| } |
| |
| 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(base_dir_.Append(kAttachmentsDirectory)); |
| 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 path(root_attachments_dir.Append(filename)); |
| if (IsDirectory(path, 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); |
| } |
| } |
| } |
| |
| void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) { |
| base::FilePath attachments_dir = AttachmentsPath(uuid); |
| if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) { |
| return; |
| } |
| DirectoryReader reader; |
| if (!reader.Open(attachments_dir)) { |
| return; |
| } |
| |
| base::FilePath filename; |
| DirectoryReader::Result result; |
| while ((result = reader.NextFile(&filename)) == |
| DirectoryReader::Result::kSuccess) { |
| const base::FilePath filepath(attachments_dir.Append(filename)); |
| LoggingRemoveFile(filepath); |
| } |
| |
| LoggingRemoveDirectory(attachments_dir); |
| } |
| |
| 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); |
| AddAttachmentSize(AttachmentsPath(uuid), &total_size); |
| |
| 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 |