mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-29 00:32:35 +08:00
7d5e17cd2e
Rather than accepting the path to the database’s parent directory, this now accepts the path to the database itself. Using the parent directory proved cumbersome in practice. When testing crashpad_handler with a variety of databases, it is useful to be able to specify --database=/tmp/crashpad_database, --database=/tmp/crashpad_database_2, etc. The old interface required that these directories be created as a separate step, and would put the actual database at /tmp/crashpad_database/Crashpad. This was contrary to the operation of most tools and interfaces, which would only require that /tmp exist and would put the database at /tmp/crashpad_database. TEST=crashpad_client_test R=rsesek@chromium.org Review URL: https://codereview.chromium.org/991393002
606 lines
19 KiB
Plaintext
606 lines
19 KiB
Plaintext
// 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 <time.h>
|
|
#include <unistd.h>
|
|
#include <uuid/uuid.h>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/mac/scoped_nsautorelease_pool.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 "client/settings.h"
|
|
#include "util/file/file_io.h"
|
|
#include "util/mac/xattr.h"
|
|
#include "util/misc/initialization_state_dcheck.h"
|
|
|
|
namespace crashpad {
|
|
|
|
namespace {
|
|
|
|
const char kWriteDirectory[] = "new";
|
|
const char kUploadPendingDirectory[] = "pending";
|
|
const char kCompletedDirectory[] = "completed";
|
|
|
|
const char kSettings[] = "settings.dat";
|
|
|
|
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 awaiting 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:
|
|
Settings* GetSettings() override;
|
|
OperationStatus PrepareNewCrashReport(NewReport** report) override;
|
|
OperationStatus FinishedWritingCrashReport(NewReport* report,
|
|
UUID* uuid) override;
|
|
OperationStatus ErrorWritingCrashReport(NewReport* report) override;
|
|
OperationStatus LookUpCrashReport(const UUID& uuid,
|
|
Report* report) override;
|
|
OperationStatus GetPendingReports(std::vector<Report>* reports) override;
|
|
OperationStatus GetCompletedReports(std::vector<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 lock.
|
|
//!
|
|
//! \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<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_;
|
|
Settings settings_;
|
|
InitializationStateDcheck initialized_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
|
|
};
|
|
|
|
CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
|
|
: CrashReportDatabase(),
|
|
base_dir_(path),
|
|
settings_(base_dir_.Append(kSettings)),
|
|
initialized_() {
|
|
}
|
|
|
|
CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
|
|
|
|
bool CrashReportDatabaseMac::Initialize() {
|
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (!settings_.Initialize())
|
|
return false;
|
|
|
|
// Write an xattr as the last step, to ensure the filesystem has support for
|
|
// them. This attribute will never be read.
|
|
if (!WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true))
|
|
return false;
|
|
|
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
|
return true;
|
|
}
|
|
|
|
Settings* CrashReportDatabaseMac::GetSettings() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
return &settings_;
|
|
}
|
|
|
|
CrashReportDatabase::OperationStatus
|
|
CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
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) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
// 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::ErrorWritingCrashReport(NewReport* report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
// 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);
|
|
|
|
// Remove the file that the report would have been written to had no error
|
|
// occurred.
|
|
if (unlink(report->path.value().c_str()) != 0) {
|
|
PLOG(ERROR) << "unlink " << report->path.value();
|
|
return kFileSystemError;
|
|
}
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
CrashReportDatabase::OperationStatus
|
|
CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
|
|
CrashReportDatabase::Report* report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
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<CrashReportDatabase::Report>* reports) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
|
|
}
|
|
|
|
CrashReportDatabase::OperationStatus
|
|
CrashReportDatabaseMac::GetCompletedReports(
|
|
std::vector<CrashReportDatabase::Report>* reports) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
return ReportsInDirectory(base_dir_.Append(kCompletedDirectory), reports);
|
|
}
|
|
|
|
CrashReportDatabase::OperationStatus
|
|
CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid,
|
|
const Report** report) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
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) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
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;
|
|
}
|
|
|
|
time_t now = time(nullptr);
|
|
if (!WriteXattrTimeT(report_path, XattrName(kXattrLastUploadTime), now)) {
|
|
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;
|
|
}
|
|
|
|
if (!settings_.SetLastUploadAttemptTime(now)) {
|
|
return kDatabaseError;
|
|
}
|
|
|
|
return kNoError;
|
|
}
|
|
|
|
CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload(
|
|
const UUID& uuid) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
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_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<CrashReportDatabase::Report>* reports) {
|
|
base::mac::ScopedNSAutoreleasePool pool;
|
|
|
|
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));
|
|
if (!database_mac->Initialize())
|
|
database_mac.reset();
|
|
|
|
return scoped_ptr<CrashReportDatabase>(database_mac.release());
|
|
}
|
|
|
|
} // namespace crashpad
|