Index: handler/mac/exception_handler_server.cc |
diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc |
index 3757c454fbf9f209c594d94049f87ed828f4f8bf..19038293bf104be39b48310cfa139efe57eaaadc 100644 |
--- a/handler/mac/exception_handler_server.cc |
+++ b/handler/mac/exception_handler_server.cc |
@@ -14,7 +14,12 @@ |
#include "handler/mac/exception_handler_server.h" |
+#include <signal.h> |
+ |
+#include "base/auto_reset.h" |
#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/scoped_generic.h" |
#include "base/mac/mach_logging.h" |
#include "util/mach/composite_mach_message_server.h" |
#include "util/mach/mach_extensions.h" |
@@ -26,11 +31,59 @@ namespace crashpad { |
namespace { |
+struct ResetSIGTERMTraits { |
+ static struct sigaction* InvalidValue() { |
+ return nullptr; |
+ } |
+ |
+ static void Free(struct sigaction* sa) { |
+ int rv = sigaction(SIGTERM, sa, nullptr); |
+ PLOG_IF(ERROR, rv != 0) << "sigaction"; |
+ } |
+}; |
+using ScopedResetSIGTERM = |
+ base::ScopedGeneric<struct sigaction*, ResetSIGTERMTraits>; |
+ |
+mach_port_t g_signal_notify_port; |
+ |
+// This signal handler is only operative when being run from launchd. It causes |
+// the exception handler server to stop running by sending it a synthesized |
+// no-senders notification. |
+void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) { |
+ DCHECK(g_signal_notify_port); |
+ |
+ // mach_no_senders_notification_t defines the receive side of this structure, |
+ // with a trailer element that’s undesirable for the send side. |
+ struct { |
+ mach_msg_header_t header; |
+ NDR_record_t ndr; |
+ mach_msg_type_number_t mscount; |
+ } no_senders_notification = {}; |
+ no_senders_notification.header.msgh_bits = |
+ MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0); |
+ no_senders_notification.header.msgh_size = sizeof(no_senders_notification); |
+ no_senders_notification.header.msgh_remote_port = g_signal_notify_port; |
+ no_senders_notification.header.msgh_local_port = MACH_PORT_NULL; |
+ no_senders_notification.header.msgh_id = MACH_NOTIFY_NO_SENDERS; |
+ no_senders_notification.ndr = NDR_record; |
+ no_senders_notification.mscount = 0; |
+ |
+ kern_return_t kr = mach_msg(&no_senders_notification.header, |
+ MACH_SEND_MSG, |
+ sizeof(no_senders_notification), |
+ 0, |
+ MACH_PORT_NULL, |
+ MACH_MSG_TIMEOUT_NONE, |
+ MACH_PORT_NULL); |
+ MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg"; |
+} |
+ |
class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
public NotifyServer::DefaultInterface { |
public: |
ExceptionHandlerServerRun( |
mach_port_t exception_port, |
+ bool launchd, |
UniversalMachExcServer::Interface* exception_interface) |
: UniversalMachExcServer::Interface(), |
NotifyServer::DefaultInterface(), |
@@ -40,7 +93,8 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
exception_interface_(exception_interface), |
exception_port_(exception_port), |
notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)), |
- running_(true) { |
+ running_(true), |
+ launchd_(launchd) { |
CHECK(notify_port_.is_valid()); |
composite_mach_message_server_.AddHandler(&mach_exc_server_); |
@@ -53,22 +107,45 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
void Run() { |
DCHECK(running_); |
- // Request that a no-senders notification for exception_port_ be sent to |
- // notify_port_. |
- mach_port_t previous; |
- kern_return_t kr = |
- mach_port_request_notification(mach_task_self(), |
- exception_port_, |
- MACH_NOTIFY_NO_SENDERS, |
- 0, |
- notify_port_.get(), |
- MACH_MSG_TYPE_MAKE_SEND_ONCE, |
- &previous); |
- MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification"; |
- |
- if (previous != MACH_PORT_NULL) { |
- kr = mach_port_deallocate(mach_task_self(), previous); |
- MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_deallocate"; |
+ kern_return_t kr; |
+ scoped_ptr<base::AutoReset<mach_port_t>> reset_signal_notify_port; |
+ struct sigaction old_sa; |
+ ScopedResetSIGTERM reset_sigterm; |
+ if (!launchd_) { |
+ // Request that a no-senders notification for exception_port_ be sent to |
+ // notify_port_. |
+ mach_port_t previous; |
+ kr = mach_port_request_notification(mach_task_self(), |
+ exception_port_, |
+ MACH_NOTIFY_NO_SENDERS, |
+ 0, |
+ notify_port_.get(), |
+ MACH_MSG_TYPE_MAKE_SEND_ONCE, |
+ &previous); |
+ MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification"; |
+ |
+ if (previous != MACH_PORT_NULL) { |
+ kr = mach_port_deallocate(mach_task_self(), previous); |
+ MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_deallocate"; |
+ } |
+ } else { |
+ // A real no-senders notification would never be triggered, because |
+ // launchd maintains a send right to the service. When launchd wants the |
+ // job to exit, it will send a SIGTERM. See launchd.plist(5). |
+ // |
+ // Set up a SIGTERM handler that will cause Run() to return (incidentally, |
+ // by sending a synthetic no-senders notification). |
+ struct sigaction sa = {}; |
+ sigemptyset(&sa.sa_mask); |
+ sa.sa_flags = SA_SIGINFO; |
+ sa.sa_sigaction = HandleSIGTERM; |
+ int rv = sigaction(SIGTERM, &sa, &old_sa); |
+ PCHECK(rv == 0) << "sigaction"; |
+ reset_sigterm.reset(&old_sa); |
+ |
+ DCHECK(!g_signal_notify_port); |
+ reset_signal_notify_port.reset(new base::AutoReset<mach_port_t>( |
+ &g_signal_notify_port, notify_port_.get())); |
} |
// A single CompositeMachMessageServer will dispatch both exception messages |
@@ -81,6 +158,7 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
// the proper send right. |
base::mac::ScopedMachPortSet server_port_set( |
NewMachPort(MACH_PORT_RIGHT_PORT_SET)); |
+ CHECK(server_port_set.is_valid()); |
kr = mach_port_insert_member( |
mach_task_self(), exception_port_, server_port_set.get()); |
@@ -173,6 +251,7 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
mach_port_t exception_port_; // weak |
base::mac::ScopedMachReceiveRight notify_port_; |
bool running_; |
+ bool launchd_; |
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerRun); |
}; |
@@ -180,8 +259,10 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, |
} // namespace |
ExceptionHandlerServer::ExceptionHandlerServer( |
- base::mac::ScopedMachReceiveRight receive_port) |
- : receive_port_(receive_port.Pass()) { |
+ base::mac::ScopedMachReceiveRight receive_port, |
+ bool launchd) |
+ : receive_port_(receive_port.Pass()), |
+ launchd_(launchd) { |
CHECK(receive_port_.is_valid()); |
} |
@@ -190,7 +271,8 @@ ExceptionHandlerServer::~ExceptionHandlerServer() { |
void ExceptionHandlerServer::Run( |
UniversalMachExcServer::Interface* exception_interface) { |
- ExceptionHandlerServerRun run(receive_port_.get(), exception_interface); |
+ ExceptionHandlerServerRun run( |
+ receive_port_.get(), launchd_, exception_interface); |
run.Run(); |
} |