diff --git a/src/connection.m b/src/connection.m index 366f985..b7b979a 100644 --- a/src/connection.m +++ b/src/connection.m @@ -964,6 +964,14 @@ error_out: XPC_CLASS(dictionary)* result = nil; mach_msg_header_t* header = NULL; + if (!MACH_PORT_VALID(this->send_port)) { + // Fail fast: the channel was never connected (e.g. the named service + // does not exist, so the bootstrap lookup produced no send right). + // Without this, the message queues behind a dead channel and the + // blocking reply wait below hangs forever (darling-lol M2b). + return XPC_ERROR_CONNECTION_INVALID; + } + @autoreleasepool { XPC_CLASS(serializer)* serializer = [XPC_CLASS(serializer) serializer]; dispatch_mach_msg_t message = NULL; diff --git a/src/event.m b/src/event.m index 0870a6c..e2d4bd6 100644 --- a/src/event.m +++ b/src/event.m @@ -19,9 +19,10 @@ #import #import - -// actually, the event publisher is it's own class -typedef xpc_object_t xpc_event_publisher_t; +#import +#import +#import +#import XPC_EXPORT const char* const _xpc_event_key_name = "XPCEventName"; XPC_EXPORT const char* const _xpc_event_key_stream_name = "XPCEventStreamName"; @@ -30,15 +31,77 @@ XPC_EXPORT const char* const _xpc_event_key_stream_name = "XPCEventStreamName"; // private C API // +/* + * darling-lol: minimal but FUNCTIONAL event publisher. + * + * xpc_event_publisher_* was entirely stubbed (create -> NULL, set_handler + * -> dropped, activate -> no-op). That silently broke any daemon that gates + * its real initialization on the publisher's INITIAL_BARRIER callback. + * + * notifyd is exactly such a daemon: it creates the publisher for + * "com.apple.notifyd.matching" and only calls dispatch_mach_connect() for + * its Mach channels from inside the handler when it sees + * XPC_EVENT_PUBLISHER_ACTION_INITIAL_BARRIER. Because activate() did + * nothing, the barrier never fired, the channels were never connected, and + * the `com.apple.system.notification_center` Mach service registered with + * launchd was left without a serving endpoint. Clients then either hung in + * bootstrap/check-in or failed outright -- e.g. Chromium's + * ThermalStateObserverMac logged "unable to register to power + * notifications. Result: 9" (NOTIFY_STATUS_SERVER_NOT_FOUND). + * + * We do not need a real launchd-backed event stream here; we only need the + * lifecycle the server expects: + * - create() gives back a usable object, + * - set_handler()/set_error_handler() remember the blocks, + * - activate() delivers the INITIAL_BARRIER action once, synchronously + * (Apple delivers it asynchronously on the publisher's queue; sync is + * sufficient for notifyd, which only needs its connects to happen + * before dispatch_main()). + * ADD/REMOVE actions are only ever produced by launchd from registered + * event streams, which Darling does not have; they are not needed to bring + * notifyd's channels up. + */ + +typedef struct _xpc_event_publisher_s { + const char* name; + dispatch_queue_t queue; + void (^handler)(xpc_event_publisher_action_t action, uint64_t event_token, xpc_object_t descriptor); + void (^error_handler)(int error); + bool activated; +} *xpc_event_publisher_t; + XPC_EXPORT void xpc_event_publisher_activate(xpc_event_publisher_t xpub) { - xpc_stub(); + if (xpub == NULL || xpub->activated) + return; + xpub->activated = true; + + void (^handler)(xpc_event_publisher_action_t, uint64_t, xpc_object_t) = xpub->handler; + if (handler == NULL) + // Nothing to deliver; without a handler there is no barrier + // contract to honor. + return; + + // Deliver the barrier synchronously. Apple's real implementation + // delivers it asynchronously on the publisher's queue, but the only + // in-tree consumer (notifyd) just needs dispatch_mach_connect() to run + // before dispatch_main(); synchronous delivery satisfies that without + // depending on the queue being ready to run async blocks yet. + handler(XPC_EVENT_PUBLISHER_ACTION_INITIAL_BARRIER, 0, NULL); }; XPC_EXPORT xpc_object_t xpc_event_publisher_create(const char* name, void* some_parameter) { - xpc_stub(); - return NULL; + xpc_event_publisher_t xpub = calloc(1, sizeof(struct _xpc_event_publisher_s)); + if (xpub == NULL) + return NULL; + xpub->name = name; + // Apple passes the workloop/queue as the second parameter; callers + // (notifyd) pass a dispatch queue. Retained for possible future async + // delivery; the current implementation delivers the barrier + // synchronously in activate(). + xpub->queue = (dispatch_queue_t) some_parameter; + return (xpc_object_t) xpub; }; XPC_EXPORT @@ -76,15 +139,25 @@ au_asid_t xpc_event_publisher_get_subscriber_asid(xpc_event_publisher_t xpub, ui XPC_EXPORT void xpc_event_publisher_set_error_handler(xpc_event_publisher_t xpub, void (^handler)()) { - // no clue what the parameters for the handler are - // (probably includes some sort of error parameter, but i have no clue what that is) - xpc_stub(); + if (xpub == NULL) + return; + // The real signature takes a single `int error` argument. + // NOTE: use Block_copy(), NOT [handler copy]. The latter sends an ObjC + // message (objc_msgSend) which dereferences the block's isa; if the + // caller's blocks runtime leaves isa NULL/unmapped (observed: notifyd + // SEGV addr=0x0 inside [handler copy]), messaging crashes. Block_copy + // operates on the block struct directly and is safe. + xpub->error_handler = (void (^)(int)) Block_copy((const void*)handler); }; XPC_EXPORT void xpc_event_publisher_set_handler(xpc_event_publisher_t xpub, void (^handler)()) { - // likewise, no clue what the parameters to the handler are - xpc_stub(); + if (xpub == NULL) + return; + // The real signature is: + // void (^)(xpc_event_publisher_action_t action, + // uint64_t event_token, xpc_object_t descriptor) + xpub->handler = (void (^)(xpc_event_publisher_action_t, uint64_t, xpc_object_t)) Block_copy((const void*)handler); }; XPC_EXPORT diff --git a/src/pipe.m b/src/pipe.m index d04fb98..f60e6ac 100644 --- a/src/pipe.m +++ b/src/pipe.m @@ -209,7 +209,9 @@ out: goto out; } - *incomingMessage = dict; + // The deserializer autoreleases its result. The C pipe API + // transfers an owned reference to the caller. + *incomingMessage = [dict retain]; status = 0; } else { @@ -249,7 +251,7 @@ out: goto out; } - *incomingMessage = dict; + *incomingMessage = [dict retain]; *replyPort = header->msgh_local_port; status = 0; @@ -581,7 +583,7 @@ retry: goto out; } - *reply = dict; + *reply = [dict retain]; status = 0; } else {