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

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: Address comments 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
« no previous file with comments | « client/crash_report_database.cc ('k') | client/crash_report_database_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..fe3e13a360a465531ac08d0182ff6532d0da32ec
--- /dev/null
+++ b/client/crash_report_database_mac.mm
@@ -0,0 +1,541 @@
+// 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 <errno.h>
+#include <fcntl.h>
+#import <Foundation/Foundation.h>
+#include <stdio.h>
+#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/file/file_io.h"
+#include "util/mac/xattr.h"
+
+namespace crashpad {
+
+namespace {
+
+const char kDatabaseDirectoryName[] = "Crashpad";
+
+const char kWriteDirectory[] = "new";
+const char kUploadPendingDirectory[] = "pending";
+const char kCompletedDirectory[] = "completed";
+
+const char* const kReportDirectories[] = {
+ kWriteDirectory,
+ kUploadPendingDirectory,
+ kCompletedDirectory,
+};
+
+const char kCrashReportFileExtension[] = "dmp";
+
+const char kXattrUUID[] = "uuid";
+const char kXattrCollectorID[] = "id";
+const char kXattrCreationTime[] = "creation_time";
+const char kXattrIsUploaded[] = "uploaded";
+const char kXattrLastUploadTime[] = "last_upload_time";
+const char kXattrUploadAttemptCount[] = "upload_count";
+
+const char kXattrDatabaseInitialized[] = "initialized";
+
+// Ensures that the node at |path| is a directory, and creates it if it does
+// not exist. If the |path| points to a file, rather than a directory, or the
+// directory could not be created, returns false. Otherwise, returns true,
+// indicating that |path| already was or now is a directory.
+bool CreateOrEnsureDirectoryExists(const base::FilePath& path) {
+ if (mkdir(path.value().c_str(), 0755) == 0) {
+ return true;
+ } else if (errno == EEXIST) {
+ struct stat st;
+ if (stat(path.value().c_str(), &st) != 0) {
+ PLOG(ERROR) << "stat";
+ return false;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ return true;
+ } else {
+ LOG(ERROR) << "not a directory";
+ return false;
+ }
+ } else {
+ PLOG(ERROR) << "mkdir";
+ return false;
+ }
+}
+
+//! \brief A CrashReportDatabase that uses HFS+ extended attributes to store
+//! report metadata.
+//!
+//! The database maintains three directories of reports: `"new"` to hold crash
+//! reports that are in the process of being written, `"completed"` to hold
+//! reports that have been written and are awaing upload, and `"uploaded"` to
+//! hold reports successfully uploaded to a collection server. If the user has
+//! opted out of report collection, reports will still be written and moved
+//! to the completed directory, but they just will not be uploaded.
+//!
+//! The database stores its metadata in extended filesystem attributes. To
+//! ensure safe access, the report file is locked using `O_EXLOCK` during all
+//! extended attribute operations. The lock should be obtained using
+//! ObtainReportLock().
+class CrashReportDatabaseMac : public CrashReportDatabase {
+ public:
+ explicit CrashReportDatabaseMac(const base::FilePath& path);
+ virtual ~CrashReportDatabaseMac();
+
+ bool Initialize();
+
+ // CrashReportDatabase:
+ OperationStatus PrepareNewCrashReport(NewReport** report) override;
+ OperationStatus FinishedWritingCrashReport(NewReport* report,
+ UUID* uuid) override;
+ OperationStatus LookUpCrashReport(const UUID& uuid,
+ Report* report) override;
+ OperationStatus GetPendingReports(
+ std::vector<const Report>* reports) override;
+ OperationStatus GetCompletedReports(
+ std::vector<const Report>* reports) override;
+ OperationStatus GetReportForUploading(const UUID& uuid,
+ const Report** report) override;
+ OperationStatus RecordUploadAttempt(const Report* report,
+ bool successful,
+ const std::string& id) override;
+ OperationStatus SkipReportUpload(const UUID& uuid) override;
+
+ private:
+ //! \brief A private extension of the Report class that maintains bookkeeping
+ //! information of the database.
+ struct UploadReport : public Report {
+ //! \brief Stores the flock of the file for the duration of
+ //! GetReportForUploading() and RecordUploadAttempt().
+ int lock_fd;
+ };
+
+ //! \brief Locates a crash report in the database by UUID.
+ //!
+ //! \param[in] uuid The UUID of the crash report to locate.
+ //!
+ //! \return The full path to the report file, or an empty path if it cannot be
+ //! found.
+ base::FilePath LocateCrashReport(const UUID& uuid);
+
+ //! \brief Obtains an exclusive advisory lock on a file.
+ //!
+ //! 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.
+ //!
+ //! This does not block, and so callers must ensure that the lock is valid
+ //! after calling.
+ //!
+ //! \param[in] path The path of the file to lcok.
+ //!
+ //! \return A scoped lock object. If the result is not valid, an error is
+ //! logged.
+ static base::ScopedFD ObtainReportLock(const base::FilePath& path);
+
+ //! \brief Reads all the database xattrs from a file into a Report. The file
+ //! must be locked with ObtainReportLock.
+ //!
+ //! \param[in] path The path of the report.
+ //! \param[out] report The object into which data will be read.
+ //!
+ //! \return `true` if all the metadata was read successfully, `false`
+ //! otherwise.
+ static bool ReadReportMetadataLocked(const base::FilePath& path,
+ Report* report);
+
+ //! \brief Reads the metadata from all the reports in a database subdirectory.
+ //! Invalid reports are skipped.
+ //!
+ //! \param[in] path The database subdirectory path.
+ //! \param[out] reports An empty vector of reports, which will be filled.
+ //!
+ //! \return The operation status code.
+ static OperationStatus ReportsInDirectory(const base::FilePath& path,
+ std::vector<const Report>* reports);
+
+
+ //! \brief Creates a database xattr name from the short constant name.
+ //!
+ //! \param[in] name The short name of the extended attribute.
+ //!
+ //! \return The long name of the extended attribute.
+ 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() {
+ // Check if the database already exists.
+ if (!CreateOrEnsureDirectoryExists(base_dir_))
+ return false;
+
+ // Create the three processing directories for the database.
+ for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
+ if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
+ return false;
+ }
+
+ // Write an xattr as the last step, to ensure the filesystem has support for
+ // them. This attribute will never be read.
+ return WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true);
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) {
+ uuid_t uuid_gen;
+ uuid_generate(uuid_gen);
+ UUID uuid(uuid_gen);
+
+ scoped_ptr<NewReport> report(new NewReport());
+
+ report->path =
+ base_dir_.Append(kWriteDirectory)
+ .Append(uuid.ToString() + "." + kCrashReportFileExtension);
+
+ report->handle = HANDLE_EINTR(open(report->path.value().c_str(),
+ O_CREAT | O_WRONLY | O_EXCL | O_EXLOCK,
+ 0600));
+ if (report->handle < 0) {
+ PLOG(ERROR) << "open " << report->path.value();
+ return kFileSystemError;
+ }
+
+ // TODO(rsesek): Potentially use an fsetxattr() here instead.
+ if (!WriteXattr(report->path, XattrName(kXattrUUID), uuid.ToString())) {
+ PLOG_IF(ERROR, IGNORE_EINTR(close(report->handle)) != 0) << "close";
+ return kDatabaseError;
+ }
+
+ *out_report = report.release();
+
+ return kNoError;
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::FinishedWritingCrashReport(NewReport* report,
+ UUID* uuid) {
+ // Takes ownership of the |handle| and the O_EXLOCK.
+ base::ScopedFD lock(report->handle);
+
+ // Take ownership of the report.
+ scoped_ptr<NewReport> scoped_report(report);
+
+ // Get the report's UUID to return.
+ std::string uuid_string;
+ if (ReadXattr(report->path, XattrName(kXattrUUID),
+ &uuid_string) != XattrStatus::kOK ||
+ !uuid->InitializeFromString(uuid_string)) {
+ LOG(ERROR) << "Failed to read UUID for crash report "
+ << report->path.value();
+ return kDatabaseError;
+ }
+
+ // Record the creation time of this report.
+ if (!WriteXattrTimeT(report->path, XattrName(kXattrCreationTime),
+ time(nullptr))) {
+ 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()) != 0) {
+ 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;
+
+ base::ScopedFD lock(ObtainReportLock(path));
+ if (!lock.is_valid())
+ return kBusyError;
+
+ *report = Report();
+ report->file_path = path;
+ if (!ReadReportMetadataLocked(path, report))
+ return kDatabaseError;
+
+ return kNoError;
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::GetPendingReports(
+ std::vector<const CrashReportDatabase::Report>* reports) {
+ return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::GetCompletedReports(
+ std::vector<const CrashReportDatabase::Report>* reports) {
+ return ReportsInDirectory(base_dir_.Append(kCompletedDirectory), reports);
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid,
+ const Report** report) {
+ base::FilePath report_path = LocateCrashReport(uuid);
+ if (report_path.empty())
+ return kReportNotFound;
+
+ scoped_ptr<UploadReport> upload_report(new UploadReport());
+ upload_report->file_path = report_path;
+
+ base::ScopedFD lock(ObtainReportLock(report_path));
+ if (!lock.is_valid())
+ return kBusyError;
+
+ if (!ReadReportMetadataLocked(report_path, upload_report.get()))
+ return kDatabaseError;
+
+ upload_report->lock_fd = lock.release();
+ *report = upload_report.release();
+ return kNoError;
+}
+
+CrashReportDatabase::OperationStatus
+CrashReportDatabaseMac::RecordUploadAttempt(const Report* report,
+ bool successful,
+ const std::string& id) {
+ DCHECK(report);
+ DCHECK(successful || id.empty());
+
+ base::FilePath report_path = LocateCrashReport(report->uuid);
+ if (report_path.empty())
+ return kReportNotFound;
+
+ scoped_ptr<const UploadReport> upload_report(
+ static_cast<const UploadReport*>(report));
+
+ base::ScopedFD lock(upload_report->lock_fd);
+ if (!lock.is_valid())
+ return kBusyError;
+
+ if (successful) {
+ base::FilePath new_path =
+ base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
+ if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
+ 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;
+ }
+ if (!WriteXattrTimeT(report_path,
+ XattrName(kXattrLastUploadTime),
+ time(nullptr))) {
+ return kDatabaseError;
+ }
+
+ int upload_attempts = 0;
+ std::string name = XattrName(kXattrUploadAttemptCount);
+ if (ReadXattrInt(report_path, name, &upload_attempts) ==
+ XattrStatus::kOtherError) {
+ return kDatabaseError;
+ }
+ if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
+ return kDatabaseError;
+ }
+
+ return kNoError;
+}
+
+CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload(
+ const UUID& uuid) {
+ base::FilePath report_path = LocateCrashReport(uuid);
+ if (report_path.empty())
+ return kReportNotFound;
+
+ base::ScopedFD lock(ObtainReportLock(report_path));
+ if (!lock.is_valid())
+ return kBusyError;
+
+ base::FilePath new_path =
+ base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
+ if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
+ PLOG(ERROR) << "rename " << report_path.value() << " to "
+ << new_path.value();
+ return kFileSystemError;
+ }
+
+ return kNoError;
+}
+
+base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
+ const std::string target_uuid = uuid.ToString();
+ for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
+ base::FilePath path =
+ base_dir_.Append(kReportDirectories[i])
+ .Append(target_uuid + "." + kCrashReportFileExtension);
+
+ // Test if the path exists.
+ struct stat st;
+ if (lstat(path.value().c_str(), &st)) {
+ continue;
+ }
+
+ // Check that the UUID of the report matches.
+ std::string uuid_string;
+ if (ReadXattr(path, XattrName(kXattrUUID),
+ &uuid_string) == XattrStatus::kOK &&
+ uuid_string == target_uuid) {
+ return path;
+ }
+ }
+
+ return base::FilePath();
+}
+
+// static
+base::ScopedFD CrashReportDatabaseMac::ObtainReportLock(
+ const base::FilePath& path) {
+ int fd = HANDLE_EINTR(open(path.value().c_str(),
+ O_RDONLY | O_EXLOCK | O_CLOEXEC | O_NONBLOCK));
+ PLOG_IF(ERROR, fd < 0) << "open lock " << path.value();
+ return base::ScopedFD(fd);
+}
+
+// static
+bool CrashReportDatabaseMac::ReadReportMetadataLocked(
+ const base::FilePath& path, Report* report) {
+ std::string uuid_string;
+ if (ReadXattr(path, XattrName(kXattrUUID),
+ &uuid_string) != XattrStatus::kOK ||
+ !report->uuid.InitializeFromString(uuid_string)) {
+ return false;
+ }
+
+ if (ReadXattrTimeT(path, XattrName(kXattrCreationTime),
+ &report->creation_time) != XattrStatus::kOK) {
+ return false;
+ }
+
+ report->id = std::string();
+ if (ReadXattr(path, XattrName(kXattrCollectorID),
+ &report->id) == XattrStatus::kOtherError) {
+ return false;
+ }
+
+ report->uploaded = false;
+ if (ReadXattrBool(path, XattrName(kXattrIsUploaded),
+ &report->uploaded) == XattrStatus::kOtherError) {
+ return false;
+ }
+
+ report->last_upload_attempt_time = 0;
+ if (ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
+ &report->last_upload_attempt_time) ==
+ XattrStatus::kOtherError) {
+ return false;
+ }
+
+ report->upload_attempts = 0;
+ if (ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
+ &report->upload_attempts) == XattrStatus::kOtherError) {
+ return false;
+ }
+
+ return true;
+}
+
+// static
+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]);
+ base::ScopedFD lock(ObtainReportLock(report_path));
+ if (!lock.is_valid())
+ continue;
+
+ Report report;
+ if (!ReadReportMetadataLocked(report_path, &report)) {
+ LOG(WARNING) << "Failed to read report metadata for "
+ << report_path.value();
+ continue;
+ }
+ reports->push_back(report);
+ }
+
+ return kNoError;
+}
+
+// static
+std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
+ return base::StringPrintf("com.googlecode.crashpad.%s", name.data());
+}
+
+} // namespace
+
+// static
+scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
+ const base::FilePath& path) {
+ scoped_ptr<CrashReportDatabaseMac> database_mac(
+ new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
+ if (!database_mac->Initialize())
+ database_mac.reset();
+
+ return scoped_ptr<CrashReportDatabase>(database_mac.release());
+}
+
+} // namespace crashpad
« no previous file with comments | « client/crash_report_database.cc ('k') | client/crash_report_database_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698