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 |