| Index: client/crash_report_database_mac.mm
|
| diff --git a/client/crash_report_database_mac.mm b/client/crash_report_database_mac.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e86135dceac523bdd8e5b0529a96de1099c0886d
|
| --- /dev/null
|
| +++ b/client/crash_report_database_mac.mm
|
| @@ -0,0 +1,461 @@
|
| +// Copyright 2015 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 <fcntl.h>
|
| +#import <Foundation/Foundation.h>
|
| +#include <stdio.h>
|
| +#include <sys/errno.h>
|
| +#include <sys/stat.h>
|
| +#include <sys/types.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/rand_util.h"
|
| +#include "base/posix/eintr_wrapper.h"
|
| +#include "base/scoped_generic.h"
|
| +#include "base/strings/string_piece.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "util/mac/xattr.h"
|
| +
|
| +namespace crashpad {
|
| +
|
| +namespace {
|
| +
|
| +const char kDatabaseDirectoryName[] = "Crashpad Database";
|
| +
|
| +const char kWriteDirectory[] = "In Progress";
|
| +
|
| +const char kUploadPendingDirectory[] = "Reports";
|
| +
|
| +const char kUploadedDirectory[] = "Uploaded";
|
| +
|
| +const char kPlaceholderReportFileExtension[] = "placeholder";
|
| +const char kCrashReportFileExtension[] = "dmp";
|
| +
|
| +const char kXattrUUID[] = "uuid";
|
| +const char kXattrCollectorID[] = "id";
|
| +const char kXattrIsUploaded[] = "uploaded";
|
| +const char kXattrLastUploadTime[] = "last_upload_time";
|
| +const char kXattrUploadAttemptCount[] = "upload_count";
|
| +
|
| +const char kXattrDatabaseInitialized[] = "initialized";
|
| +
|
| +struct ScopedFileLockTraits {
|
| + static int InvalidValue() {
|
| + return 0;
|
| + }
|
| +
|
| + static void Free(int fd) {
|
| + PCHECK(IGNORE_EINTR(close(fd)) == 0) << "close";
|
| + }
|
| +};
|
| +
|
| +using ScopedFileLock = base::ScopedGeneric<int, ScopedFileLockTraits>;
|
| +
|
| +void GenerateUUID(UUID* uuid) {
|
| + uint8_t bytes[sizeof(*uuid)];
|
| + base::RandBytes(bytes, sizeof(bytes));
|
| + uuid->InitializeFromBytes(bytes);
|
| +}
|
| +
|
| +class CrashReportDatabaseMac : public CrashReportDatabase {
|
| + public:
|
| + explicit CrashReportDatabaseMac(const base::FilePath& path);
|
| + virtual ~CrashReportDatabaseMac();
|
| +
|
| + bool Initialize();
|
| +
|
| + // CrashReportDatabase:
|
| + OperationStatus PrepareNewCrashReport(Report* report) override;
|
| + OperationStatus FinishedWritingCrashReport(const UUID& uuid) override;
|
| + OperationStatus LookUpCrashReport(const UUID& uuid,
|
| + Report* report) override;
|
| + OperationStatus GetNotUploadedReports(
|
| + std::vector<const Report>* reports) override;
|
| + OperationStatus GetUploadedReports(
|
| + std::vector<const Report>* reports) override;
|
| + OperationStatus RecordUploadAttempt(const UUID& uuid,
|
| + bool successful,
|
| + const std::string& id) override;
|
| +
|
| + private:
|
| + // Locates a crash report by |uuid| in the database and returns the full path
|
| + // to the report file, or an empty path if it cannot be found.
|
| + base::FilePath LocateCrashReport(const UUID& uuid);
|
| +
|
| + // Obtains an advisory lock on the file at |path|. The flock is used to
|
| + // prevent cross-process concurrent metadata reads or writes. While xattrs
|
| + // do not observe the lock, if the lock-then-mutate protocol is observed by
|
| + // all clients of the database, it still enforces synchronization.
|
| + ScopedFileLock ObtainReportLock(const base::FilePath& path);
|
| +
|
| + // Reads all the database xattrs from a file at |path| into the |report|. The
|
| + // flock must be obtained before calling this.
|
| + bool ReadReportMetadataLocked(const base::FilePath& path,
|
| + Report* report);
|
| +
|
| + // Reads the metadata from all the reports in a database subdirectory.
|
| + // Invalid reports are skipped.
|
| + OperationStatus ReportsInDirectory(const base::FilePath& path,
|
| + std::vector<const Report>* reports);
|
| +
|
| +
|
| + // Creates a database xattr name from the short constant name.
|
| + static std::string XattrName(const base::StringPiece& name);
|
| +
|
| + base::FilePath base_dir_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
|
| +};
|
| +
|
| +CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
|
| + : CrashReportDatabase(), base_dir_(path) {
|
| +}
|
| +
|
| +CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
|
| +
|
| +bool CrashReportDatabaseMac::Initialize() {
|
| + const std::string is_initialized_flag = XattrName(kXattrDatabaseInitialized);
|
| + NSFileManager* file_manager = [NSFileManager defaultManager];
|
| +
|
| + // Check if the database already exists.
|
| + BOOL is_dir = NO;
|
| + if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(base_dir_.value())
|
| + isDirectory:&is_dir]) {
|
| + if (!is_dir) {
|
| + LOG(ERROR) << "Database exists at " << base_dir_.value()
|
| + << " but is not a directory";
|
| + return false;
|
| + }
|
| +
|
| + // The |base_dir_| path exists and is a directory, make sure it is marked
|
| + // as initialized. This will also ensure the filesystem has xattr support.
|
| + bool is_initialized = false;
|
| + if (ReadXattrBool(base_dir_, is_initialized_flag, &is_initialized) &&
|
| + is_initialized) {
|
| + return true;
|
| + }
|
| + } else {
|
| + // The database directory does not exist, so create it.
|
| + if (mkdir(base_dir_.value().c_str(), S_IRWXU | S_IRWXG)) {
|
| + PLOG(ERROR) << "mkdir " << base_dir_.value();
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // Create the three processing directories for the database.
|
| + const char* paths[] = {
|
| + kWriteDirectory,
|
| + kUploadPendingDirectory,
|
| + kUploadedDirectory
|
| + };
|
| + for (size_t i = 0; i < arraysize(paths); ++i) {
|
| + base::FilePath path = base_dir_.Append(paths[i]);
|
| + if (mkdir(path.value().c_str(), S_IRWXU | S_IRWXG)) {
|
| + PLOG(ERROR) << "mkdir " << path.value();
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return WriteXattrBool(base_dir_, is_initialized_flag, true);
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::PrepareNewCrashReport(
|
| + CrashReportDatabase::Report* report) {
|
| + *report = Report();
|
| + GenerateUUID(&report->uuid);
|
| +
|
| + base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
|
| +
|
| + // Create a placeholder file that will be used to store a record of this
|
| + // operation. The crash report file is written by a client of this class.
|
| + base::FilePath placeholder = write_dir.Append(
|
| + report->uuid.ToString() + "." + kPlaceholderReportFileExtension);
|
| + NSFileManager* manager = [NSFileManager defaultManager];
|
| + if (![manager createFileAtPath:base::SysUTF8ToNSString(placeholder.value())
|
| + contents:[NSData data]
|
| + attributes:nil]) {
|
| + LOG(ERROR) << "Failed to create report placeholder file "
|
| + << placeholder.value();
|
| + return kFileSystemError;
|
| + }
|
| +
|
| + report->file_path = write_dir.Append(
|
| + report->uuid.ToString() + "." + kCrashReportFileExtension);
|
| +
|
| + return kNoError;
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::FinishedWritingCrashReport(const UUID& uuid) {
|
| + NSFileManager* manager = [NSFileManager defaultManager];
|
| + base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
|
| +
|
| + // Look up the placeholder file to ensure this UUID was prepared.
|
| + base::FilePath placeholder = write_dir.Append(
|
| + uuid.ToString() + "." + kPlaceholderReportFileExtension);
|
| + if (![manager fileExistsAtPath:base::SysUTF8ToNSString(placeholder.value())
|
| + isDirectory:nullptr]) {
|
| + LOG(ERROR) << "Failed to find placeholder report " << placeholder.value();
|
| + return kReportNotFound;
|
| + }
|
| +
|
| + // Delete the placeholder.
|
| + if (unlink(placeholder.value().c_str())) {
|
| + PLOG(ERROR) << "unlink " << placeholder.value();
|
| + // While not a good sign, continue processing if possible.
|
| + }
|
| +
|
| + // Now make sure the crash report was actually written.
|
| + base::FilePath report_path = write_dir.Append(
|
| + uuid.ToString() + "." + kCrashReportFileExtension);
|
| + if (![manager fileExistsAtPath:base::SysUTF8ToNSString(report_path.value())
|
| + isDirectory:nullptr]) {
|
| + LOG(ERROR) << "Failed to find crash report " << report_path.value();
|
| + return kReportNotFound;
|
| + }
|
| +
|
| + ScopedFileLock lock(ObtainReportLock(report_path));
|
| +
|
| + // Record the UUID of this crash report.
|
| + if (!WriteXattr(report_path, XattrName(kXattrUUID), uuid.ToString())) {
|
| + return kDatabaseError;
|
| + }
|
| +
|
| + // Move the report to its new location for uploading.
|
| + base::FilePath new_path =
|
| + base_dir_.Append(kUploadPendingDirectory).Append(report_path.BaseName());
|
| + if (rename(report_path.value().c_str(), new_path.value().c_str())) {
|
| + PLOG(ERROR) << "rename " << report_path.value() << " to "
|
| + << new_path.value();
|
| + return kFileSystemError;
|
| + }
|
| +
|
| + return kNoError;
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
|
| + CrashReportDatabase::Report* report) {
|
| + base::FilePath path = LocateCrashReport(uuid);
|
| + if (path.empty())
|
| + return kReportNotFound;
|
| +
|
| + ScopedFileLock lock(ObtainReportLock(path));
|
| +
|
| + *report = Report();
|
| + report->file_path = path;
|
| + if (!ReadReportMetadataLocked(path, report)) {
|
| + LOG(ERROR) << "Failed to look up crash report " << uuid.ToString();
|
| + return kDatabaseError;
|
| + }
|
| + return kNoError;
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::GetNotUploadedReports(
|
| + std::vector<const CrashReportDatabase::Report>* reports) {
|
| + return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::GetUploadedReports(
|
| + std::vector<const CrashReportDatabase::Report>* reports) {
|
| + return ReportsInDirectory(base_dir_.Append(kUploadedDirectory), reports);
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::RecordUploadAttempt(const UUID& uuid,
|
| + bool successful,
|
| + const std::string& id) {
|
| + DCHECK(successful || id.empty());
|
| +
|
| + base::FilePath report_path = LocateCrashReport(uuid);
|
| + if (report_path.empty())
|
| + return kReportNotFound;
|
| +
|
| + ScopedFileLock lock(ObtainReportLock(report_path));
|
| +
|
| + if (successful) {
|
| + base::FilePath new_path =
|
| + base_dir_.Append(kUploadedDirectory).Append(report_path.BaseName());
|
| + if (rename(report_path.value().c_str(), new_path.value().c_str())) {
|
| + PLOG(ERROR) << "rename " << report_path.value() << " to "
|
| + << new_path.value();
|
| + return kFileSystemError;
|
| + }
|
| + report_path = new_path;
|
| + lock.reset(); // Reset before re-taking.
|
| + lock.reset(ObtainReportLock(report_path).release());
|
| + }
|
| +
|
| + if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) {
|
| + return kDatabaseError;
|
| + }
|
| + if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) {
|
| + return kDatabaseError;
|
| + }
|
| +
|
| + int upload_attempts = 0;
|
| + std::string name = XattrName(kXattrUploadAttemptCount);
|
| + if (HasXattr(report_path, name) &&
|
| + !ReadXattrInt(report_path, name, &upload_attempts)) {
|
| + return kDatabaseError;
|
| + }
|
| + if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
|
| + return kDatabaseError;
|
| + }
|
| +
|
| + if (!WriteXattrTimeT(report_path,
|
| + XattrName(kXattrLastUploadTime),
|
| + time(nullptr))) {
|
| + return kDatabaseError;
|
| + }
|
| +
|
| + return kNoError;
|
| +}
|
| +
|
| +base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
|
| + const std::string target_uuid = uuid.ToString();
|
| + NSString* path_ns_string = base::SysUTF8ToNSString(base_dir_.value());
|
| + NSString* dump_extension =
|
| + [NSString stringWithUTF8String:kCrashReportFileExtension];
|
| + NSDirectoryEnumerator* dir_it =
|
| + [[NSFileManager defaultManager] enumeratorAtPath:path_ns_string];
|
| + NSString* file = nil;
|
| + while ((file = [dir_it nextObject])) {
|
| + if (![[file pathExtension] isEqualToString:dump_extension])
|
| + continue;
|
| +
|
| + base::FilePath full_path =
|
| + base_dir_.Append([file fileSystemRepresentation]);
|
| +
|
| + ScopedFileLock lock(ObtainReportLock(full_path));
|
| + std::string uuid_string;
|
| + if (ReadXattr(full_path, XattrName(kXattrUUID), &uuid_string) &&
|
| + uuid_string == target_uuid) {
|
| + return full_path;
|
| + }
|
| + }
|
| + return base::FilePath();
|
| +}
|
| +
|
| +ScopedFileLock CrashReportDatabaseMac::ObtainReportLock(
|
| + const base::FilePath& path) {
|
| + int fd = HANDLE_EINTR(open(path.value().c_str(),
|
| + O_RDONLY | O_NONBLOCK | O_EXLOCK | O_CLOEXEC));
|
| + PCHECK(fd >= 0) << "open lock " << path.value();
|
| + return ScopedFileLock(fd);
|
| +}
|
| +
|
| +bool CrashReportDatabaseMac::ReadReportMetadataLocked(
|
| + const base::FilePath& path, Report* report) {
|
| + std::string uuid_string;
|
| + if (!ReadXattr(path, XattrName(kXattrUUID), &uuid_string) ||
|
| + !report->uuid.InitializeFromString(uuid_string)) {
|
| + return false;
|
| + }
|
| + if (!HasXattr(path, XattrName(kXattrCollectorID))) {
|
| + report->id = std::string();
|
| + } else if (!ReadXattr(path, XattrName(kXattrCollectorID), &report->id)) {
|
| + return false;
|
| + }
|
| + if (!HasXattr(path, XattrName(kXattrIsUploaded))) {
|
| + report->uploaded = false;
|
| + } else if (!ReadXattrBool(path, XattrName(kXattrIsUploaded),
|
| + &report->uploaded)) {
|
| + return false;
|
| + }
|
| + if (!HasXattr(path, XattrName(kXattrLastUploadTime))) {
|
| + report->last_upload_attempt_time = 0;
|
| + } else if (!ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
|
| + &report->last_upload_attempt_time)) {
|
| + return false;
|
| + }
|
| + if (!HasXattr(path, XattrName(kXattrUploadAttemptCount))) {
|
| + report->upload_attempts = 0;
|
| + } else if (!ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
|
| + &report->upload_attempts)) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +CrashReportDatabase::OperationStatus
|
| +CrashReportDatabaseMac::ReportsInDirectory(
|
| + const base::FilePath& path,
|
| + std::vector<const CrashReportDatabase::Report>* reports) {
|
| + DCHECK(reports->empty());
|
| +
|
| + NSError* error = nil;
|
| + NSArray* paths = [[NSFileManager defaultManager]
|
| + contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value())
|
| + error:&error];
|
| + if (error) {
|
| + LOG(ERROR) << "Failed to enumerate reports in directory " << path.value()
|
| + << ": " << [[error description] UTF8String];
|
| + return kFileSystemError;
|
| + }
|
| +
|
| + reports->reserve([paths count]);
|
| + for (NSString* entry in paths) {
|
| + base::FilePath report_path = path.Append([entry fileSystemRepresentation]);
|
| + ScopedFileLock lock(ObtainReportLock(report_path));
|
| + Report report;
|
| + if (!ReadReportMetadataLocked(report_path, &report)) {
|
| + LOG(ERROR) << "Failed to read report metadata for "
|
| + << report_path.value();
|
| + continue;
|
| + }
|
| + reports->push_back(report);
|
| + }
|
| +
|
| + return kNoError;
|
| +}
|
| +
|
| +std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
|
| + return base::StringPrintf("com.google.crashpad.%s", name.data());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
| + const base::FilePath& path) {
|
| + scoped_ptr<CrashReportDatabase> database;
|
| +
|
| + NSFileManager* file_manager = [NSFileManager defaultManager];
|
| + BOOL is_dir = NO;
|
| + if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(path.value())
|
| + isDirectory:&is_dir] &&
|
| + !is_dir) {
|
| + LOG(ERROR) << "File exists at " << path.value()
|
| + << " but is not a directory";
|
| + return database;
|
| + }
|
| +
|
| + scoped_ptr<CrashReportDatabaseMac> database_mac(
|
| + new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
|
| + if (!database_mac->Initialize())
|
| + database_mac.reset();
|
| +
|
| + database.reset(database_mac.release());
|
| + return database;
|
| +}
|
| +
|
| +} // namespace crashpad
|
|
|