mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
df86075acc
On iOS, holding a lock during a slow upload can lead to watchdog kills if the app is suspended mid-upload. Instead, if the client can obtain the lock, the database sets a lock-time file attribute and releases the flock. The file attribute is cleared when the upload is completed. The lock-time attribute can be used to prevent file access from other processes, or to discard reports that likely were terminated mid-upload. Bug:chromium:1342051 Change-Id: Ib878f6ade8eae467ee39acb52288296759c84582 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3739019 Reviewed-by: Robert Sesek <rsesek@chromium.org> Commit-Queue: Justin Cohen <justincohen@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
466 lines
18 KiB
C++
466 lines
18 KiB
C++
// 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.
|
||
|
||
#ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
|
||
#define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
|
||
|
||
#include <stdint.h>
|
||
#include <time.h>
|
||
|
||
#include <map>
|
||
#include <memory>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "base/files/file_path.h"
|
||
#include "util/file/file_io.h"
|
||
#include "util/file/file_reader.h"
|
||
#include "util/file/file_writer.h"
|
||
#include "util/file/scoped_remove_file.h"
|
||
#include "util/misc/metrics.h"
|
||
#include "util/misc/uuid.h"
|
||
|
||
namespace crashpad {
|
||
|
||
class Settings;
|
||
|
||
//! \brief An interface for managing a collection of crash report files and
|
||
//! metadata associated with the crash reports.
|
||
//!
|
||
//! All Report objects that are returned by this class are logically const.
|
||
//! They are snapshots of the database at the time the query was run, and the
|
||
//! data returned is liable to change after the query is executed.
|
||
//!
|
||
//! The lifecycle of a crash report has three stages:
|
||
//!
|
||
//! 1. New: A crash report is created with PrepareNewCrashReport(), the
|
||
//! the client then writes the report, and then calls
|
||
//! FinishedWritingCrashReport() to make the report Pending.
|
||
//! 2. Pending: The report has been written but has not been locally
|
||
//! processed, or it was has been brought back from 'Completed' state by
|
||
//! user request.
|
||
//! 3. Completed: The report has been locally processed, either by uploading
|
||
//! it to a collection server and calling RecordUploadComplete(), or by
|
||
//! calling SkipReportUpload().
|
||
class CrashReportDatabase {
|
||
public:
|
||
//! \brief A crash report record.
|
||
//!
|
||
//! This represents the metadata for a crash report, as well as the location
|
||
//! of the report itself. A CrashReportDatabase maintains at least this
|
||
//! information.
|
||
struct Report {
|
||
Report();
|
||
|
||
//! A unique identifier by which this report will always be known to the
|
||
//! database.
|
||
UUID uuid;
|
||
|
||
//! The current location of the crash report on the client’s filesystem.
|
||
//! The location of a crash report may change over time, so the UUID should
|
||
//! be used as the canonical identifier.
|
||
base::FilePath file_path;
|
||
|
||
//! An identifier issued to this crash report by a collection server.
|
||
std::string id;
|
||
|
||
//! The time at which the report was generated.
|
||
time_t creation_time;
|
||
|
||
//! Whether this crash report was successfully uploaded to a collection
|
||
//! server.
|
||
bool uploaded;
|
||
|
||
//! The last timestamp at which an attempt was made to submit this crash
|
||
//! report to a collection server. If this is zero, then the report has
|
||
//! never been uploaded. If #uploaded is true, then this timestamp is the
|
||
//! time at which the report was uploaded, and no other attempts to upload
|
||
//! this report will be made.
|
||
time_t last_upload_attempt_time;
|
||
|
||
//! The number of times an attempt was made to submit this report to
|
||
//! a collection server. If this is more than zero, then
|
||
//! #last_upload_attempt_time will be set to the timestamp of the most
|
||
//! recent attempt.
|
||
int upload_attempts;
|
||
|
||
//! Whether this crash report was explicitly requested by user to be
|
||
//! uploaded. This can be true only if report is in the 'pending' state.
|
||
bool upload_explicitly_requested;
|
||
|
||
//! The total size in bytes taken by the report, including any potential
|
||
//! attachments.
|
||
uint64_t total_size;
|
||
};
|
||
|
||
//! \brief A crash report that is in the process of being written.
|
||
//!
|
||
//! An instance of this class should be created via PrepareNewCrashReport().
|
||
class NewReport {
|
||
public:
|
||
NewReport();
|
||
|
||
NewReport(const NewReport&) = delete;
|
||
NewReport& operator=(const NewReport&) = delete;
|
||
|
||
~NewReport();
|
||
|
||
//! \brief An open FileWriter with which to write the report.
|
||
FileWriter* Writer() const { return writer_.get(); }
|
||
|
||
//! \brief Returns a FileReaderInterface to the report, or `nullptr` with a
|
||
//! message logged.
|
||
FileReaderInterface* Reader();
|
||
|
||
//! A unique identifier by which this report will always be known to the
|
||
//! database.
|
||
const UUID& ReportID() const { return uuid_; }
|
||
|
||
//! \brief Adds an attachment to the report.
|
||
//!
|
||
//! \param[in] name The key and name for the attachment, which will be
|
||
//! included in the http upload. The attachment will not appear in the
|
||
//! minidump report. \a name should only use characters from the set
|
||
//! `[a-zA-Z0-9._-]`.
|
||
//! \return A FileWriter that the caller should use to write the contents of
|
||
//! the attachment, or `nullptr` on failure with an error logged.
|
||
FileWriter* AddAttachment(const std::string& name);
|
||
|
||
private:
|
||
friend class CrashReportDatabaseGeneric;
|
||
friend class CrashReportDatabaseMac;
|
||
friend class CrashReportDatabaseWin;
|
||
|
||
bool Initialize(CrashReportDatabase* database,
|
||
const base::FilePath& directory,
|
||
const base::FilePath::StringType& extension);
|
||
|
||
std::unique_ptr<FileWriter> writer_;
|
||
std::unique_ptr<FileReader> reader_;
|
||
ScopedRemoveFile file_remover_;
|
||
std::vector<std::unique_ptr<FileWriter>> attachment_writers_;
|
||
std::vector<ScopedRemoveFile> attachment_removers_;
|
||
UUID uuid_;
|
||
CrashReportDatabase* database_;
|
||
};
|
||
|
||
//! \brief A crash report that is in the process of being uploaded.
|
||
//!
|
||
//! An instance of this class should be created via GetReportForUploading().
|
||
class UploadReport : public Report {
|
||
public:
|
||
UploadReport();
|
||
|
||
UploadReport(const UploadReport&) = delete;
|
||
UploadReport& operator=(const UploadReport&) = delete;
|
||
|
||
virtual ~UploadReport();
|
||
|
||
//! \brief An open FileReader with which to read the report.
|
||
FileReader* Reader() const { return reader_.get(); }
|
||
|
||
//! \brief Obtains a mapping of names to file readers for any attachments
|
||
//! for the report.
|
||
std::map<std::string, FileReader*> GetAttachments() const {
|
||
return attachment_map_;
|
||
}
|
||
|
||
private:
|
||
friend class CrashReportDatabase;
|
||
friend class CrashReportDatabaseGeneric;
|
||
friend class CrashReportDatabaseMac;
|
||
friend class CrashReportDatabaseWin;
|
||
|
||
bool Initialize(const base::FilePath& path, CrashReportDatabase* database);
|
||
void InitializeAttachments();
|
||
|
||
std::unique_ptr<FileReader> reader_;
|
||
CrashReportDatabase* database_;
|
||
std::vector<std::unique_ptr<FileReader>> attachment_readers_;
|
||
std::map<std::string, FileReader*> attachment_map_;
|
||
bool report_metrics_;
|
||
};
|
||
|
||
//! \brief The result code for operations performed on a database.
|
||
enum OperationStatus {
|
||
//! \brief No error occurred.
|
||
kNoError = 0,
|
||
|
||
//! \brief The report that was requested could not be located.
|
||
//!
|
||
//! This may occur when the report is present in the database but not in a
|
||
//! state appropriate for the requested operation, for example, if
|
||
//! GetReportForUploading() is called to obtain report that’s already in the
|
||
//! completed state.
|
||
kReportNotFound,
|
||
|
||
//! \brief An error occured while performing a file operation on a crash
|
||
//! report.
|
||
//!
|
||
//! A database is responsible for managing both the metadata about a report
|
||
//! and the actual crash report itself. This error is returned when an
|
||
//! error occurred when managing the report file. Additional information
|
||
//! will be logged.
|
||
kFileSystemError,
|
||
|
||
//! \brief An error occured while recording metadata for a crash report or
|
||
//! database-wide settings.
|
||
//!
|
||
//! A database is responsible for managing both the metadata about a report
|
||
//! and the actual crash report itself. This error is returned when an
|
||
//! error occurred when managing the metadata about a crash report or
|
||
//! database-wide settings. Additional information will be logged.
|
||
kDatabaseError,
|
||
|
||
//! \brief The operation could not be completed because a concurrent
|
||
//! operation affecting the report is occurring.
|
||
kBusyError,
|
||
|
||
//! \brief The report cannot be uploaded by user request as it has already
|
||
//! been uploaded.
|
||
kCannotRequestUpload,
|
||
};
|
||
|
||
CrashReportDatabase(const CrashReportDatabase&) = delete;
|
||
CrashReportDatabase& operator=(const CrashReportDatabase&) = delete;
|
||
|
||
virtual ~CrashReportDatabase() {}
|
||
|
||
//! \brief Opens a database of crash reports, possibly creating it.
|
||
//!
|
||
//! \param[in] path A path to the database to be created or opened. If the
|
||
//! database does not yet exist, it will be created if possible. Note that
|
||
//! for databases implemented as directory structures, existence refers
|
||
//! solely to the outermost directory.
|
||
//!
|
||
//! \return A database object on success, `nullptr` on failure with an error
|
||
//! logged.
|
||
//!
|
||
//! \sa InitializeWithoutCreating
|
||
static std::unique_ptr<CrashReportDatabase> Initialize(
|
||
const base::FilePath& path);
|
||
|
||
//! \brief Opens an existing database of crash reports.
|
||
//!
|
||
//! \param[in] path A path to the database to be opened. If the database does
|
||
//! not yet exist, it will not be created. Note that for databases
|
||
//! implemented as directory structures, existence refers solely to the
|
||
//! outermost directory. On such databases, as long as the outermost
|
||
//! directory is present, this method will create the inner structure.
|
||
//!
|
||
//! \return A database object on success, `nullptr` on failure with an error
|
||
//! logged.
|
||
//!
|
||
//! \sa Initialize
|
||
static std::unique_ptr<CrashReportDatabase> InitializeWithoutCreating(
|
||
const base::FilePath& path);
|
||
|
||
//! \brief Returns the Settings object for this database.
|
||
//!
|
||
//! \return A weak pointer to the Settings object, which is owned by the
|
||
//! database.
|
||
virtual Settings* GetSettings() = 0;
|
||
|
||
//! \brief Creates a record of a new crash report.
|
||
//!
|
||
//! Callers should write the crash report using the FileWriter provided.
|
||
//! Callers should then call FinishedWritingCrashReport() to complete report
|
||
//! creation. If an error is encountered while writing the crash report, no
|
||
//! special action needs to be taken. If FinishedWritingCrashReport() is not
|
||
//! called, the report will be removed from the database when \a report is
|
||
//! destroyed.
|
||
//!
|
||
//! \param[out] report A NewReport object containing a FileWriter with which
|
||
//! to write the report data. Only valid if this returns #kNoError.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus PrepareNewCrashReport(
|
||
std::unique_ptr<NewReport>* report) = 0;
|
||
|
||
//! \brief Informs the database that a crash report has been successfully
|
||
//! written.
|
||
//!
|
||
//! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The
|
||
//! NewReport object will be invalidated as part of this call.
|
||
//! \param[out] uuid The UUID of this crash report.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus FinishedWritingCrashReport(
|
||
std::unique_ptr<NewReport> report,
|
||
UUID* uuid) = 0;
|
||
|
||
//! \brief Returns the crash report record for the unique identifier.
|
||
//!
|
||
//! \param[in] uuid The crash report record unique identifier.
|
||
//! \param[out] report A crash report record. Only valid if this returns
|
||
//! #kNoError.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus LookUpCrashReport(const UUID& uuid,
|
||
Report* report) = 0;
|
||
|
||
//! \brief Returns a list of crash report records that have not been uploaded.
|
||
//!
|
||
//! \param[out] reports A list of crash report record objects. This must be
|
||
//! empty on entry. Only valid if this returns #kNoError.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus GetPendingReports(std::vector<Report>* reports) = 0;
|
||
|
||
//! \brief Returns a list of crash report records that have been completed,
|
||
//! either by being uploaded or by skipping upload.
|
||
//!
|
||
//! \param[out] reports A list of crash report record objects. This must be
|
||
//! empty on entry. Only valid if this returns #kNoError.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus GetCompletedReports(std::vector<Report>* reports) = 0;
|
||
|
||
//! \brief Obtains and locks a report object for uploading to a collection
|
||
//! server. On iOS the file lock is released and mutual-exclusion is kept
|
||
//! via a file attribute.
|
||
//!
|
||
//! Callers should upload the crash report using the FileReader provided.
|
||
//! Callers should then call RecordUploadComplete() to record a successful
|
||
//! upload. If RecordUploadComplete() is not called, the upload attempt will
|
||
//! be recorded as unsuccessful and the report lock released when \a report is
|
||
//! destroyed.
|
||
//!
|
||
//! On iOS, holding a lock during a slow upload can lead to watchdog kills if
|
||
//! the app is suspended mid-upload. Instead, if the client can obtain the
|
||
//! lock, the database sets a lock-time file attribute and releases the lock.
|
||
//! The attribute is cleared when the upload is completed. The lock-time
|
||
//! attribute can be used to prevent file access from other processes, or to
|
||
//! discard reports that likely were terminated mid-upload.
|
||
//!
|
||
//! \param[in] uuid The unique identifier for the crash report record.
|
||
//! \param[out] report A crash report record for the report to be uploaded.
|
||
//! Only valid if this returns #kNoError.
|
||
//! \param[in] report_metrics If `false`, metrics will not be recorded for
|
||
//! this upload attempt when RecordUploadComplete() is called or \a report
|
||
//! is destroyed. Metadata for the upload attempt will still be recorded
|
||
//! in the database.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus GetReportForUploading(
|
||
const UUID& uuid,
|
||
std::unique_ptr<const UploadReport>* report,
|
||
bool report_metrics = true) = 0;
|
||
|
||
//! \brief Records a successful upload for a report and updates the last
|
||
//! upload attempt time as returned by
|
||
//! Settings::GetLastUploadAttemptTime().
|
||
//!
|
||
//! \param[in] report A UploadReport object obtained from
|
||
//! GetReportForUploading(). The UploadReport object will be invalidated
|
||
//! and the report unlocked as part of this call.
|
||
//! \param[in] id The possibly empty identifier assigned to this crash report
|
||
//! by the collection server.
|
||
//!
|
||
//! \return The operation status code.
|
||
OperationStatus RecordUploadComplete(
|
||
std::unique_ptr<const UploadReport> report,
|
||
const std::string& id);
|
||
|
||
//! \brief Moves a report from the pending state to the completed state, but
|
||
//! without the report being uploaded.
|
||
//!
|
||
//! This can be used if the user has disabled crash report collection, but
|
||
//! crash generation is still enabled in the product.
|
||
//!
|
||
//! \param[in] uuid The unique identifier for the crash report record.
|
||
//! \param[in] reason The reason the report upload is being skipped for
|
||
//! metrics tracking purposes.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus SkipReportUpload(
|
||
const UUID& uuid,
|
||
Metrics::CrashSkippedReason reason) = 0;
|
||
|
||
//! \brief Deletes a crash report file and its associated metadata.
|
||
//!
|
||
//! \param[in] uuid The UUID of the report to delete.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus DeleteReport(const UUID& uuid) = 0;
|
||
|
||
//! \brief Marks a crash report as explicitly requested to be uploaded by the
|
||
//! user and moves it to 'pending' state.
|
||
//!
|
||
//! \param[in] uuid The unique identifier for the crash report record.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus RequestUpload(const UUID& uuid) = 0;
|
||
|
||
//! \brief Cleans the database of expired lockfiles, metadata without report
|
||
//! files, report files without metadata, and attachments without report
|
||
//! files.
|
||
//!
|
||
//! As the macOS implementation does not use lock or metadata files, the
|
||
//! cleaning is limited to attachments without report files.
|
||
//!
|
||
//! \param[in] lockfile_ttl The number of seconds at which lockfiles or new
|
||
//! report files are considered expired.
|
||
//! \return The number of reports cleaned.
|
||
virtual int CleanDatabase(time_t lockfile_ttl) { return 0; }
|
||
|
||
protected:
|
||
CrashReportDatabase() {}
|
||
|
||
//! \brief The path to the database passed to Initialize.
|
||
//!
|
||
//! \return The filepath of the database;
|
||
virtual base::FilePath DatabasePath() = 0;
|
||
|
||
//! \brief Build a filepath for the root attachments directory.
|
||
//!
|
||
//! \return The filepath to the attachments directory.
|
||
base::FilePath AttachmentsRootPath();
|
||
|
||
//! \brief Build a filepath for the directory for the report to hold
|
||
//! attachments.
|
||
//!
|
||
//! \param[in] uuid The unique identifier for the crash report record.
|
||
//!
|
||
//! \return The filepath to the report attachments directory.
|
||
base::FilePath AttachmentsPath(const UUID& uuid);
|
||
|
||
//! \brief Attempts to remove any attachments associated with the given
|
||
//! report UUID. There may not be any, so failing is not an error.
|
||
//!
|
||
//! \param[in] uuid The unique identifier for the crash report record.
|
||
void RemoveAttachmentsByUUID(const UUID& uuid);
|
||
|
||
private:
|
||
//! \brief Adjusts a crash report record’s metadata to account for an upload
|
||
//! attempt, and updates the last upload attempt time as returned by
|
||
//! Settings::GetLastUploadAttemptTime().
|
||
//!
|
||
//! \param[in] report The report object obtained from
|
||
//! GetReportForUploading().
|
||
//! \param[in] successful Whether the upload attempt was successful.
|
||
//! \param[in] id The identifier assigned to this crash report by the
|
||
//! collection server. Must be empty if \a successful is `false`; may be
|
||
//! empty if it is `true`.
|
||
//!
|
||
//! \return The operation status code.
|
||
virtual OperationStatus RecordUploadAttempt(UploadReport* report,
|
||
bool successful,
|
||
const std::string& id) = 0;
|
||
};
|
||
|
||
} // namespace crashpad
|
||
|
||
#endif // CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
|