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

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..2c3ace8c575727f3b1eb0c9e8ea62792bafdeb32
--- /dev/null
+++ b/client/crash_report_database_mac.mm
@@ -0,0 +1,456 @@
+// 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>
Mark Mentovai 2015/01/08 22:38:10 Just <errno.h>
Robert Sesek 2015/01/13 16:18:23 Done.
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+
+#include "base/logging.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";
Mark Mentovai 2015/01/08 22:38:10 If I saw these three directories side-by-side on t
Robert Sesek 2015/01/13 16:18:23 This seems self-contradictory to say that it shoul
+
+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>;
+
+class CrashReportDatabaseMac : public CrashReportDatabase {
Mark Mentovai 2015/01/08 22:38:10 Either this file or this class should have some co
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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() {
+ 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;
+ }
+ } else {
+ // The database directory does not exist, so create it.
+ if (mkdir(base_dir_.value().c_str(), S_IRWXU | S_IRWXG)) {
Mark Mentovai 2015/01/08 22:38:10 Why are you giving group (but not other) permissio
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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]);
+ BOOL is_dir = NO;
+ if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(path.value())
Mark Mentovai 2015/01/08 22:38:10 You’ve got this pattern twice now, maybe you want
Robert Sesek 2015/01/13 16:18:23 Done.
+ isDirectory:&is_dir] &&
+ is_dir) {
+ continue;
+ }
+
+ if (mkdir(path.value().c_str(), S_IRWXU | S_IRWXG)) {
+ PLOG(ERROR) << "mkdir " << path.value();
+ return false;
+ }
+ }
+
+ // Write an xattr as the last step, to ensure the filesystem has support for
+ // them. This attribute will never be read.
Mark Mentovai 2015/01/08 22:38:10 Smart!
+ return WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true);
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::PrepareNewCrashReport(
+ CrashReportDatabase::Report* report) {
+ *report = Report();
+ uuid_t uuid;
+ uuid_generate(uuid);
+ report->uuid.InitializeFromBytes(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())
Mark Mentovai 2015/01/08 22:38:10 This doesn’t have O_EXCL semantics, but it probabl
Mark Mentovai 2015/01/08 22:38:10 Honestly, I was kinda hoping that the on-disk file
Robert Sesek 2015/01/13 16:18:23 I'm not sure the PID is useful. Timestamp definite
+ contents:[NSData data]
+ attributes:nil]) {
+ LOG(ERROR) << "Failed to create report placeholder file "
+ << placeholder.value();
+ return kFileSystemError;
+ }
+
+ report->file_path = write_dir.Append(
Mark Mentovai 2015/01/08 22:38:10 Why is there a separate placeholder file? Why don’
Robert Sesek 2015/01/13 16:18:23 I'm glad you suggested returning the FD. I went ba
+ 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();
Mark Mentovai 2015/01/08 22:38:09 For things that you’re tolerating, WARNING is prob
+ // 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())
Mark Mentovai 2015/01/08 22:38:10 I don’t know if you need any of these file-exists
Robert Sesek 2015/01/13 16:18:22 Done.
+ 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))
+ return kDatabaseError;
Mark Mentovai 2015/01/08 22:38:10 The interface documentation said that this return
Robert Sesek 2015/01/13 16:18:22 ReadXattr and friends, do, though. Do you want mor
+
+ 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));
Mark Mentovai 2015/01/08 22:38:10 The file needs to stay locked while the uploader i
Robert Sesek 2015/01/13 16:18:23 Done. Now the caller has a two-phase call like wit
+
+ 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;
+ }
+
+ 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,
Mark Mentovai 2015/01/08 22:38:10 I’d set the time_t before doing the upload_attempt
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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];
Mark Mentovai 2015/01/08 22:38:09 Directory enumeration is kind of heavy. Why isn’t
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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_EXLOCK | O_CLOEXEC));
+ PCHECK(fd >= 0) << "open lock " << path.value();
Mark Mentovai 2015/01/08 22:38:10 CHECK is a little harsh here: 1. The filesystem m
Robert Sesek 2015/01/13 16:18:23 1) Callers now check the result of ObtainReportLoc
+ return ScopedFileLock(fd);
+}
+
+bool CrashReportDatabaseMac::ReadReportMetadataLocked(
Mark Mentovai 2015/01/08 22:38:09 This one can be static.
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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))) {
Mark Mentovai 2015/01/08 22:38:10 HasXattr(), ReadXattr(), HasXattr(), ReadXattr(),
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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(
Mark Mentovai 2015/01/08 22:38:09 This one can be static too.
Robert Sesek 2015/01/13 16:18:22 Done.
+ 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];
Mark Mentovai 2015/01/08 22:38:10 Not really sure that this is any better than opend
Robert Sesek 2015/01/13 16:18:23 I don't have to manage the DIR* or handle dot-dirs
+ 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 "
Mark Mentovai 2015/01/08 22:38:09 Since you continue on past this, it’s more of a WA
Robert Sesek 2015/01/13 16:18:23 Done.
+ << report_path.value();
+ continue;
+ }
+ reports->push_back(report);
+ }
+
+ return kNoError;
+}
+
+// static
+std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
+ return base::StringPrintf("com.google.crashpad.%s", name.data());
Mark Mentovai 2015/01/08 22:38:10 org.googlecode
Mark Mentovai 2015/01/09 14:26:09 I wrote:
Robert Sesek 2015/01/13 16:18:23 Done.
+}
+
+} // namespace
+
+// static
+scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
+ const base::FilePath& path) {
+ scoped_ptr<CrashReportDatabase> database;
+
+ NSFileManager* file_manager = [NSFileManager defaultManager];
Mark Mentovai 2015/01/08 22:38:10 I don’t think you need this check, because CrashRe
Robert Sesek 2015/01/13 16:18:23 Done.
+ 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