// Copyright 2015 The Crashpad Authors // // 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 #include #include #include #include #include #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 writer_; std::unique_ptr reader_; ScopedRemoveFile file_remover_; std::vector> attachment_writers_; std::vector 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 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 reader_; CrashReportDatabase* database_; std::vector> attachment_readers_; std::map 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 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 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* 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 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* 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* 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* 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 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_