Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Unified Diff: client/crash_report_database_mac.mm

Issue 842513002: Create CrashReportDatabase interface, a test, and a Mac implementation. (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698