crashpad/client/crash_report_database.h
Justin Cohen df86075acc ios: Prevent duplicate uploads and watchdog kills with slow uploads.
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>
2022-07-14 18:47:58 +00:00

466 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 clients 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 thats 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 records 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_