| 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
|
|
|