From 6bfdc78f233099497dbf9f59f917328cf799822d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 14 Apr 2026 19:13:44 +0200 Subject: [PATCH 1/2] feat: add UploadsPaused setting to retain reports in pending Adds a new Settings flag that, when set, causes the upload thread to leave pending reports in the pending state instead of uploading or skipping them. Intended for consumers that transiently revoke upload consent and want reports preserved for later upload once the pause is cleared. The flag is independent of UploadsEnabled and is checked at the top of ProcessPendingReports. While paused, the known-pending queue is left untouched and no database scan is performed, so reports are retried on the next pass once the pause is cleared. Co-Authored-By: Claude Opus 4.6 (1M context) --- client/settings.cc | 28 +++++++++++++++++++++++++++ client/settings.h | 24 +++++++++++++++++++++++ handler/crash_report_upload_thread.cc | 8 ++++++++ 3 files changed, 60 insertions(+) diff --git a/client/settings.cc b/client/settings.cc index 38da08b8b..dabed1f3e 100644 --- a/client/settings.cc +++ b/client/settings.cc @@ -126,6 +126,7 @@ struct SettingsReader::Data { enum Options : uint32_t { kUploadsEnabled = 1 << 0, + kUploadsPaused = 1 << 1, }; Data() : magic(kSettingsMagic), @@ -174,6 +175,17 @@ bool SettingsReader::GetUploadsEnabled(bool* enabled) { return true; } +bool SettingsReader::GetUploadsPaused(bool* paused) { + DCHECK(initialized().is_valid()); + + Data settings; + if (!OpenAndReadSettings(&settings)) + return false; + + *paused = (settings.options & Data::Options::kUploadsPaused) != 0; + return true; +} + bool SettingsReader::GetLastUploadAttemptTime(time_t* time) { DCHECK(initialized().is_valid()); @@ -268,6 +280,22 @@ bool Settings::SetUploadsEnabled(bool enabled) { return WriteSettings(handle.get(), settings); } +bool Settings::SetUploadsPaused(bool paused) { + DCHECK(initialized().is_valid()); + + Data settings; + ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); + if (!handle.is_valid()) + return false; + + if (paused) + settings.options |= Data::Options::kUploadsPaused; + else + settings.options &= ~Data::Options::kUploadsPaused; + + return WriteSettings(handle.get(), settings); +} + bool Settings::SetLastUploadAttemptTime(time_t time) { DCHECK(initialized().is_valid()); diff --git a/client/settings.h b/client/settings.h index 7da179de2..9ca78d89c 100644 --- a/client/settings.h +++ b/client/settings.h @@ -100,6 +100,20 @@ class SettingsReader { //! error logged. bool GetUploadsEnabled(bool* enabled); + //! \brief Retrieves whether uploads are paused. + //! + //! When paused, pending reports are retained in the `pending` state rather + //! than being uploaded or skipped. Reports accumulate in `pending` until the + //! flag is cleared, at which point normal processing resumes. + //! + //! The default value is `false`. + //! + //! \param[out] paused Whether uploads are paused. + //! + //! \return On success, returns `true`, otherwise returns `false` with an + //! error logged. + bool GetUploadsPaused(bool* paused); + //! \brief Retrieves the last time at which a report was attempted to be //! uploaded. //! @@ -172,6 +186,16 @@ class Settings final : public SettingsReader { //! error logged. bool SetUploadsEnabled(bool enabled); + //! \brief Sets whether uploads are paused. + //! + //! See GetUploadsPaused() for the semantics of this flag. + //! + //! \param[in] paused Whether uploads should be paused. + //! + //! \return On success, returns `true`, otherwise returns `false` with an + //! error logged. + bool SetUploadsPaused(bool paused); + //! \brief Sets the last time at which a report was attempted to be uploaded. //! //! This is only meant to be used internally by the CrashReportDatabase. diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index c4eeaf46f..5785b8da8 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -140,6 +140,14 @@ void CrashReportUploadThread::ProcessPendingReports() { // uploads complete (regardless of whether or not that succeeded). ScopedFunctionInvoker scoped_function_invoker(callback_); + bool uploads_paused; + if (database_->GetSettings()->GetUploadsPaused(&uploads_paused) && + uploads_paused) { + // Leave known pending report UUIDs in the queue so they are retried once + // the pause is lifted, and skip scanning for new pending reports. + return; + } + std::vector known_report_uuids = known_pending_report_uuids_.Drain(); for (const UUID& report_uuid : known_report_uuids) { CrashReportDatabase::Report report; From 207af6331feebd8846ca2a6e6dbd130554b1350c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 14 Apr 2026 21:03:47 +0200 Subject: [PATCH 2/2] feat: add CrashpadClient::RequestRetry() to wake the handler's upload thread Allows clients to request an immediate retry of pending report uploads rather than waiting for the handler's periodic scan. Useful after state changes (for example, clearing UploadsPaused) that should take effect without delay. Adds a new client -> handler IPC message on all three out-of-process platforms, piggybacking on each platform's existing channel: * Windows: new kRequestRetry type in ClientToServerMessage, sent via SendToCrashHandlerServer() on the existing named pipe. * Linux: new kTypeRequestRetry type in ExceptionHandlerProtocol::ClientToServerMessage, sent via UnixCredentialSocket over the existing handler socket. * macOS: new kRequestRetry type in ClientToServerMessage, sent via SendClientToServerMessage() to the existing exception Mach port and dispatched by a dedicated MachMessageServer::Interface registered alongside the exception and no-senders handlers. The struct, enum, and send/receive helpers live in a new util/mach/exception_handler_protocol.{h,cc} shared by client and handler, mirroring the Linux and Windows layouts. On the handler side, each platform's Delegate gains a RequestRetry / ExceptionHandlerServerRetryRequested / HandleClientToServerMessage entry point that forwards to CrashReportUploadThread::RetryPending(), a new method that wakes the worker thread without enqueuing a specific UUID. Co-Authored-By: Claude Opus 4.6 (1M context) --- client/crashpad_client.h | 8 ++ client/crashpad_client_linux.cc | 10 ++ client/crashpad_client_mac.cc | 6 ++ client/crashpad_client_win.cc | 7 ++ handler/crash_report_upload_thread.cc | 5 + handler/crash_report_upload_thread.h | 9 ++ .../linux/crash_report_exception_handler.cc | 6 ++ .../linux/crash_report_exception_handler.h | 1 + handler/linux/exception_handler_server.cc | 4 + handler/linux/exception_handler_server.h | 4 + handler/mac/crash_report_exception_handler.cc | 6 ++ handler/mac/crash_report_exception_handler.h | 7 +- handler/mac/exception_handler_server.cc | 98 ++++++++++++++----- handler/mac/exception_handler_server.h | 18 +++- handler/win/crash_report_exception_handler.cc | 6 ++ handler/win/crash_report_exception_handler.h | 1 + util/CMakeLists.txt | 2 + util/linux/exception_handler_client.cc | 7 ++ util/linux/exception_handler_client.h | 3 + util/linux/exception_handler_protocol.h | 3 + util/mach/exception_handler_protocol.cc | 79 +++++++++++++++ util/mach/exception_handler_protocol.h | 80 +++++++++++++++ util/win/exception_handler_server.cc | 8 ++ util/win/exception_handler_server.h | 4 + util/win/registration_protocol_win_structs.h | 4 + 25 files changed, 357 insertions(+), 29 deletions(-) create mode 100644 util/mach/exception_handler_protocol.cc create mode 100644 util/mach/exception_handler_protocol.h diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 82e8400d5..6f8351872 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -872,6 +872,14 @@ class CrashpadClient { void RemoveAttachment(const base::FilePath& attachment); #endif + //! \brief Requests that the handler retry pending report uploads. + //! + //! Wakes the handler's upload thread to immediately process pending reports + //! rather than waiting for its next periodic scan. Useful after state + //! changes (such as clearing UploadsPaused) that should take effect without + //! delay. + void RequestRetry(); + private: #if BUILDFLAG(IS_WIN) || DOXYGEN //! \brief Registers process handlers for the client. diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc index df72a25cc..43844df03 100644 --- a/client/crashpad_client_linux.cc +++ b/client/crashpad_client_linux.cc @@ -445,6 +445,11 @@ class RequestCrashDumpHandler : public SignalHandler { client.RemoveAttachment(attachment); } + void RequestRetry() { + ExceptionHandlerClient client(sock_to_handler_.get(), true); + client.RequestRetry(); + } + private: RequestCrashDumpHandler() = default; @@ -832,4 +837,9 @@ void CrashpadClient::RemoveAttachment(const base::FilePath& attachment) { signal_handler->RemoveAttachment(attachment); } +void CrashpadClient::RequestRetry() { + auto signal_handler = RequestCrashDumpHandler::Get(); + signal_handler->RequestRetry(); +} + } // namespace crashpad diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index 8e339c606..f6cb8f87d 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -31,6 +31,7 @@ #include "util/mac/mac_util.h" #include "util/mach/bootstrap.h" #include "util/mach/child_port_handshake.h" +#include "util/mach/exception_handler_protocol.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" @@ -612,6 +613,11 @@ base::apple::ScopedMachSendRight CrashpadClient::GetHandlerMachPort() const { return base::apple::ScopedMachSendRight(exception_port_.get()); } +void CrashpadClient::RequestRetry() { + SendClientToServerMessage(exception_port_.get(), + ClientToServerMessage::kRequestRetry); +} + // static void CrashpadClient::UseSystemDefaultHandler() { base::apple::ScopedMachSendRight system_crash_reporter_handler( diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index b81f4256c..15cbe93dd 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -1231,4 +1231,11 @@ void CrashpadClient::RemoveAttachment(const base::FilePath& attachment) { &response); } +void CrashpadClient::RequestRetry() { + ClientToServerMessage message = {}; + message.type = ClientToServerMessage::kRequestRetry; + ServerToClientMessage response = {}; + SendToCrashHandlerServer(ipc_pipe_, message, &response); +} + } // namespace crashpad diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index 5785b8da8..271ae542b 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -122,6 +122,11 @@ void CrashReportUploadThread::ReportPendingSync(const UUID& report_uuid) { DoWork(nullptr); } +void CrashReportUploadThread::RetryPending() { + if (thread_.is_running()) + thread_.DoWorkNow(); +} + void CrashReportUploadThread::Start() { thread_.Start( options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait); diff --git a/handler/crash_report_upload_thread.h b/handler/crash_report_upload_thread.h index 239103105..a12bb8946 100644 --- a/handler/crash_report_upload_thread.h +++ b/handler/crash_report_upload_thread.h @@ -114,6 +114,15 @@ class CrashReportUploadThread : public WorkerThread::Delegate, //! This method will run on the pool thread executing the OnCrashDumpEvent void ReportPendingSync(const UUID& report_uuid); + //! \brief Wakes the upload thread to immediately process pending reports. + //! + //! Unlike ReportPending(), no specific report UUID is enqueued; the next + //! pass relies on the thread's pending-report scan (requires + //! \a watch_pending_reports). + //! + //! This method may be called from any thread. + void RetryPending(); + // Stoppable: //! \brief Starts a dedicated upload thread, which executes ThreadMain(). diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc index 13bc234ea..11f0f4717 100644 --- a/handler/linux/crash_report_exception_handler.cc +++ b/handler/linux/crash_report_exception_handler.cc @@ -236,6 +236,12 @@ void CrashReportExceptionHandler::RemoveAttachment( attachments_.erase(it); } +void CrashReportExceptionHandler::RequestRetry() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + bool CrashReportExceptionHandler::WriteMinidumpToDatabase( ProcessSnapshotLinux* process_snapshot, ProcessSnapshotSanitized* sanitized_snapshot, diff --git a/handler/linux/crash_report_exception_handler.h b/handler/linux/crash_report_exception_handler.h index d38055dc3..269e00c4c 100644 --- a/handler/linux/crash_report_exception_handler.h +++ b/handler/linux/crash_report_exception_handler.h @@ -100,6 +100,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { void AddAttachment(const base::FilePath& attachment) override; void RemoveAttachment(const base::FilePath& attachment) override; + void RequestRetry() override; private: bool HandleExceptionWithConnection( diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc index 69c3c319a..05e74342a 100644 --- a/handler/linux/exception_handler_server.cc +++ b/handler/linux/exception_handler_server.cc @@ -439,6 +439,10 @@ bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { case ExceptionHandlerProtocol::ClientToServerMessage::kTypeRemoveAttachment: delegate_->RemoveAttachment(base::FilePath(message.attachment_info.path)); return true; + + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeRequestRetry: + delegate_->RequestRetry(); + return true; } DCHECK(false); diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h index df9749ef3..677d8a4f9 100644 --- a/handler/linux/exception_handler_server.h +++ b/handler/linux/exception_handler_server.h @@ -117,6 +117,10 @@ class ExceptionHandlerServer { //! \brief Called to remove an attachment from the crash report. virtual void RemoveAttachment(const base::FilePath& attachment) = 0; + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void RequestRetry() = 0; + virtual ~Delegate() {} }; diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 9a31a2ba6..9612ca4d5 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -308,4 +308,10 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( return KERN_SUCCESS; } +void CrashReportExceptionHandler::RequestRetry() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + } // namespace crashpad diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h index 326393535..5893a0679 100644 --- a/handler/mac/crash_report_exception_handler.h +++ b/handler/mac/crash_report_exception_handler.h @@ -22,6 +22,7 @@ #include "client/crash_report_database.h" #include "handler/crash_report_upload_thread.h" +#include "handler/mac/exception_handler_server.h" #include "handler/user_stream_data_source.h" #include "util/misc/uuid.h" #include "util/mach/exc_server_variants.h" @@ -31,7 +32,7 @@ namespace crashpad { //! \brief An exception handler that writes crash reports for exception messages //! to a CrashReportDatabase. class CrashReportExceptionHandler final - : public UniversalMachExcServer::Interface { + : public ExceptionHandlerServer::Delegate { public: //! \brief Creates a new object that will store crash reports in \a database. //! @@ -91,7 +92,11 @@ class CrashReportExceptionHandler final const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override; + // ExceptionHandlerServer::Delegate: + void RequestRetry() override; + private: + CrashReportDatabase* database_; // weak CrashReportUploadThread* upload_thread_; // weak const std::map* process_annotations_; // weak diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc index 5c1814c8c..b538c5388 100644 --- a/handler/mac/exception_handler_server.cc +++ b/handler/mac/exception_handler_server.cc @@ -14,12 +14,16 @@ #include "handler/mac/exception_handler_server.h" +#include + +#include #include #include "base/apple/mach_logging.h" #include "base/check.h" #include "base/logging.h" #include "util/mach/composite_mach_message_server.h" +#include "util/mach/exception_handler_protocol.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" @@ -29,26 +33,72 @@ namespace crashpad { namespace { +// Dispatches ClientToServerMessage Mach messages to the delegate by message +// type. +class ClientToServerMessageServer : public MachMessageServer::Interface { + public: + explicit ClientToServerMessageServer(ExceptionHandlerServer::Delegate* delegate) + : delegate_(delegate) {} + + ClientToServerMessageServer(const ClientToServerMessageServer&) = delete; + ClientToServerMessageServer& operator=(const ClientToServerMessageServer&) = + delete; + + bool MachMessageServerFunction(const mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override { + const ClientToServerMessage* message = + ReceiveClientToServerMessage(in_header, out_header); + if (!message) { + return false; + } + *destroy_complex_request = true; + switch (message->type) { + case ClientToServerMessage::kRequestRetry: + delegate_->RequestRetry(); + break; + } + return true; + } + + std::set MachMessageServerRequestIDs() override { + return {kClientToServerMessageID}; + } + + mach_msg_size_t MachMessageServerRequestSize() override { + return sizeof(ClientToServerMessage); + } + + mach_msg_size_t MachMessageServerReplySize() override { + return sizeof(mig_reply_error_t); + } + + private: + ExceptionHandlerServer::Delegate* delegate_; // weak +}; + class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, public NotifyServer::DefaultInterface { public: - ExceptionHandlerServerRun( - mach_port_t exception_port, - mach_port_t notify_port, - bool launchd, - UniversalMachExcServer::Interface* exception_interface) + ExceptionHandlerServerRun(mach_port_t exception_port, + mach_port_t notify_port, + bool launchd, + ExceptionHandlerServer::Delegate* delegate) : UniversalMachExcServer::Interface(), NotifyServer::DefaultInterface(), mach_exc_server_(this), notify_server_(this), + client_to_server_message_server_(delegate), composite_mach_message_server_(), - exception_interface_(exception_interface), + delegate_(delegate), exception_port_(exception_port), notify_port_(notify_port), running_(true), launchd_(launchd) { composite_mach_message_server_.AddHandler(&mach_exc_server_); composite_mach_message_server_.AddHandler(¬ify_server_); + composite_mach_message_server_.AddHandler( + &client_to_server_message_server_); } ExceptionHandlerServerRun(const ExceptionHandlerServerRun&) = delete; @@ -142,20 +192,20 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, return KERN_FAILURE; } - return exception_interface_->CatchMachException(behavior, - exception_port, - thread, - task, - exception, - code, - code_count, - flavor, - old_state, - old_state_count, - new_state, - new_state_count, - trailer, - destroy_complex_request); + return delegate_->CatchMachException(behavior, + exception_port, + thread, + task, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + trailer, + destroy_complex_request); } // NotifyServer::DefaultInterface: @@ -182,8 +232,9 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, private: UniversalMachExcServer mach_exc_server_; NotifyServer notify_server_; + ClientToServerMessageServer client_to_server_message_server_; CompositeMachMessageServer composite_mach_message_server_; - UniversalMachExcServer::Interface* exception_interface_; // weak + ExceptionHandlerServer::Delegate* delegate_; // weak mach_port_t exception_port_; // weak mach_port_t notify_port_; // weak bool running_; @@ -205,10 +256,9 @@ ExceptionHandlerServer::ExceptionHandlerServer( ExceptionHandlerServer::~ExceptionHandlerServer() { } -void ExceptionHandlerServer::Run( - UniversalMachExcServer::Interface* exception_interface) { +void ExceptionHandlerServer::Run(Delegate* delegate) { ExceptionHandlerServerRun run( - receive_port_.get(), notify_port_.get(), launchd_, exception_interface); + receive_port_.get(), notify_port_.get(), launchd_, delegate); run.Run(); } diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index 6d63a290d..c83d98f27 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -26,6 +26,17 @@ namespace crashpad { //! process. class ExceptionHandlerServer { public: + //! \brief Interface for an object that receives exceptions and + //! client-to-server control messages dispatched by the server. + class Delegate : public UniversalMachExcServer::Interface { + public: + ~Delegate() = default; + + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void RequestRetry() = 0; + }; + //! \brief Constructs an ExceptionHandlerServer object. //! //! \param[in] receive_port The port that exception messages and no-senders @@ -44,7 +55,8 @@ class ExceptionHandlerServer { //! \brief Runs the exception-handling server. //! - //! \param[in] exception_interface An object to send exception messages to. + //! \param[in] delegate An object to send exception messages and + //! client-to-server control messages to. //! //! This method monitors the receive port for exception messages and, if //! not being run by launchd, no-senders notifications. It continues running @@ -54,15 +66,13 @@ class ExceptionHandlerServer { //! queued by `mach_msg()` to be sent to a client) prior to calling this //! method, or it will detect that it is sender-less and return immediately. //! - //! All exception messages will be passed to \a exception_interface. - //! //! This method must only be called once on an ExceptionHandlerServer object. //! //! If an unexpected condition that prevents this method from functioning is //! encountered, it will log a message and terminate execution. Receipt of an //! invalid message on the receive port will cause a message to be logged, but //! this method will continue running normally. - void Run(UniversalMachExcServer::Interface* exception_interface); + void Run(Delegate* delegate); //! \brief Stops a running exception-handling server. //! diff --git a/handler/win/crash_report_exception_handler.cc b/handler/win/crash_report_exception_handler.cc index 9761542cf..8e06556b0 100644 --- a/handler/win/crash_report_exception_handler.cc +++ b/handler/win/crash_report_exception_handler.cc @@ -217,4 +217,10 @@ void CrashReportExceptionHandler::ExceptionHandlerServerAttachmentRemoved( attachments_.erase(it); } +void CrashReportExceptionHandler::ExceptionHandlerServerRetryRequested() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + } // namespace crashpad diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h index 57f50eff3..9fe36b00d 100644 --- a/handler/win/crash_report_exception_handler.h +++ b/handler/win/crash_report_exception_handler.h @@ -87,6 +87,7 @@ class CrashReportExceptionHandler final const base::FilePath& attachment) override; void ExceptionHandlerServerAttachmentRemoved( const base::FilePath& attachment) override; + void ExceptionHandlerServerRetryRequested() override; private: CrashReportDatabase* database_; // weak diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 3ab946aaa..7d6db7ce0 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -155,6 +155,8 @@ if(APPLE) mach/exc_server_variants.h mach/exception_behaviors.cc mach/exception_behaviors.h + mach/exception_handler_protocol.cc + mach/exception_handler_protocol.h mach/exception_ports.cc mach/exception_ports.h mach/mach_extensions.cc diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc index 60d72257d..306a932e3 100644 --- a/util/linux/exception_handler_client.cc +++ b/util/linux/exception_handler_client.cc @@ -244,4 +244,11 @@ void ExceptionHandlerClient::RemoveAttachment( UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); } +void ExceptionHandlerClient::RequestRetry() { + ExceptionHandlerProtocol::ClientToServerMessage message; + message.type = + ExceptionHandlerProtocol::ClientToServerMessage::kTypeRequestRetry; + UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); +} + } // namespace crashpad diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h index 30c706a0c..a7e4c66df 100644 --- a/util/linux/exception_handler_client.h +++ b/util/linux/exception_handler_client.h @@ -74,6 +74,9 @@ class ExceptionHandlerClient { //! \brief Removes an attachment from the crash report. void RemoveAttachment(const base::FilePath& attachment); + //! \brief Requests that the handler retry pending report uploads. + void RequestRetry(); + private: int SendCrashDumpRequest( const ExceptionHandlerProtocol::ClientInformation& info, diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h index 07773a001..0b6a642f0 100644 --- a/util/linux/exception_handler_protocol.h +++ b/util/linux/exception_handler_protocol.h @@ -93,6 +93,9 @@ class ExceptionHandlerProtocol { //! \brief Request that the server remove an attachment. kTypeRemoveAttachment, + + //! \brief Request that the server retry pending report uploads. + kTypeRequestRetry, }; Type type; diff --git a/util/mach/exception_handler_protocol.cc b/util/mach/exception_handler_protocol.cc new file mode 100644 index 000000000..e9cc6c49d --- /dev/null +++ b/util/mach/exception_handler_protocol.cc @@ -0,0 +1,79 @@ +// Copyright 2025 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. + +#include "util/mach/exception_handler_protocol.h" + +#include + +#include "base/apple/mach_logging.h" +#include "base/logging.h" +#include "util/mach/mach_message.h" + +namespace crashpad { + +bool SendClientToServerMessage(mach_port_t port, + ClientToServerMessage::Type type, + const std::string& payload) { + if (port == MACH_PORT_NULL) { + DLOG(ERROR) << "cannot send client-to-server message: invalid port"; + return false; + } + + ClientToServerMessage message = {}; + message.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; + message.header.msgh_size = sizeof(message); + message.header.msgh_remote_port = port; + message.header.msgh_local_port = MACH_PORT_NULL; + message.header.msgh_id = kClientToServerMessageID; + + message.body.msgh_descriptor_count = 1; + + message.payload.address = const_cast(payload.c_str()); + message.payload.size = static_cast(payload.size() + 1); + message.payload.deallocate = FALSE; + message.payload.copy = MACH_MSG_VIRTUAL_COPY; + message.payload.type = MACH_MSG_OOL_DESCRIPTOR; + + message.ndr = NDR_record; + message.type = type; + + kern_return_t kr = mach_msg(&message.header, + MACH_SEND_MSG, + sizeof(message), + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_msg"; + return false; + } + + return true; +} + +const ClientToServerMessage* ReceiveClientToServerMessage( + const mach_msg_header_t* in_header, + mach_msg_header_t* out_header) { + if (in_header->msgh_id != kClientToServerMessageID) { + return nullptr; + } + + PrepareMIGReplyFromRequest(in_header, out_header); + SetMIGReplyError(out_header, MIG_NO_REPLY); + return reinterpret_cast(in_header); +} + +} // namespace crashpad diff --git a/util/mach/exception_handler_protocol.h b/util/mach/exception_handler_protocol.h new file mode 100644 index 000000000..145f1550a --- /dev/null +++ b/util/mach/exception_handler_protocol.h @@ -0,0 +1,80 @@ +// Copyright 2025 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_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ +#define CRASHPAD_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ + +#include +#include + +#include + +namespace crashpad { + +//! \brief Mach message ID used by client-to-server control messages sent to +//! the crashpad handler's exception port. +//! +//! FourCC `'CPad'`, chosen far from Apple's Mach MIG subsystem bases (which +//! cluster between 64 and ~6000) so it cannot collide with a kernel-originated +//! message delivered to the handler's exception port. +constexpr mach_msg_id_t kClientToServerMessageID = 0x43506164; // 'CPad' + +//! \brief A control message sent by a Crashpad client to the handler. +//! +//! The message carries a typed command and an optional out-of-line payload. +//! The payload is always transferred as an OOL descriptor even when empty, +//! so the struct layout is uniform for all message types. +struct ClientToServerMessage { + //! \brief Command types understood by the handler. + enum Type : uint32_t { + //! \brief Ask the handler to retry pending report uploads. Carries no + //! meaningful payload. + kRequestRetry = 1, + }; + + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_ool_descriptor_t payload; + NDR_record_t ndr; + Type type; +}; + +//! \brief Sends a ClientToServerMessage to the handler. +//! +//! \param[in] port The handler's exception port. +//! \param[in] type The command type. +//! \param[in] payload An optional string payload. The null terminator is +//! included in the transferred OOL region, so `payload.size` is always +//! at least 1. +//! +//! \return `true` on success, `false` otherwise (logs the error). +bool SendClientToServerMessage(mach_port_t port, + ClientToServerMessage::Type type, + const std::string& payload = ""); + +//! \brief Validates and unwraps a received ClientToServerMessage. +//! +//! \param[in] in_header The incoming Mach message header. +//! \param[out] out_header The reply header to populate. +//! +//! \return A pointer to the received ClientToServerMessage on success (aliases +//! \a in_header). `nullptr` if the message is not a ClientToServerMessage +//! or is malformed. +const ClientToServerMessage* ReceiveClientToServerMessage( + const mach_msg_header_t* in_header, + mach_msg_header_t* out_header); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 8b2e65acc..554cfd768 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -554,6 +554,14 @@ bool ExceptionHandlerServer::ServiceClientConnection( return false; } + case ClientToServerMessage::kRequestRetry: { + ServerToClientMessage response = {}; + service_context.delegate()->ExceptionHandlerServerRetryRequested(); + LoggingWriteFile( + service_context.pipe(), &response, sizeof(response)); + return false; + } + default: LOG(ERROR) << "unhandled message type: " << message.type; return false; diff --git a/util/win/exception_handler_server.h b/util/win/exception_handler_server.h index df1ce87d1..d693cc3c7 100644 --- a/util/win/exception_handler_server.h +++ b/util/win/exception_handler_server.h @@ -72,6 +72,10 @@ class ExceptionHandlerServer { virtual void ExceptionHandlerServerAttachmentRemoved( const base::FilePath& attachment) = 0; + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void ExceptionHandlerServerRetryRequested() = 0; + protected: ~Delegate(); }; diff --git a/util/win/registration_protocol_win_structs.h b/util/win/registration_protocol_win_structs.h index dd6a894fa..61f95f7b4 100644 --- a/util/win/registration_protocol_win_structs.h +++ b/util/win/registration_protocol_win_structs.h @@ -163,6 +163,10 @@ struct ClientToServerMessage { //! \brief For AttachmentRequestV2 (variable-length paths). kRemoveAttachmentV2, + + //! \brief Requests that the server retry pending report uploads. No + //! additional payload. + kRequestRetry, } type; union {