/* * Copyright (c) 2000-2005, 2008-2019 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * June 1, 2001 Allan Nathanson * - public API conversion * * March 31, 2000 Allan Nathanson * - initial revision */ #include "SCDynamicStoreInternal.h" #include "config.h" /* MiG generated file */ #include "SCD.h" #if !TARGET_OS_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090)) #define HAVE_MACHPORT_GUARDS #endif static void removeReceiveRight(SCDynamicStoreRef store, mach_port_t port) { /* remove our receive right */ #ifdef HAVE_MACHPORT_GUARDS (void) mach_port_destruct(mach_task_self(), port, 0, (mach_port_context_t)store); #else // HAVE_MACHPORT_GUARDS (void) mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1); #endif // HAVE_MACHPORT_GUARDS return; } #define MAX_INVALID_RIGHT_RETRY 3 static mach_port_t addNotifyPort(SCDynamicStoreRef store) { kern_return_t kr; mach_port_t oldNotify; #ifdef HAVE_MACHPORT_GUARDS mach_port_options_t opts; #endif // HAVE_MACHPORT_GUARDS mach_port_t port; int sc_status; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; int try = 0; /* allocate a mach port for the SCDynamicStore notifications */ retry_allocate : #ifdef HAVE_MACHPORT_GUARDS memset(&opts, 0, sizeof(opts)); opts.flags = MPO_CONTEXT_AS_GUARD|MPO_INSERT_SEND_RIGHT; kr = mach_port_construct(mach_task_self(), &opts, (mach_port_context_t)store, &port); #else // HAVE_MACHPORT_GUARDS kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); #endif // HAVE_MACHPORT_GUARDS if (kr != KERN_SUCCESS) { SC_log(LOG_NOTICE, "could not allocate mach port: %s", mach_error_string(kr)); if ((kr == KERN_NO_SPACE) || (kr == KERN_RESOURCE_SHORTAGE)) { usleep(50 * 1000); // sleep 50ms between attempts goto retry_allocate; } goto fail; } #ifndef HAVE_MACHPORT_GUARDS kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); if (kr != KERN_SUCCESS) { /* * We can't insert a send right into our own port! This should * only happen if someone stomped on OUR port (so let's leave * the port alone). */ SC_log(LOG_NOTICE, "mach_port_insert_right() failed: %s", mach_error_string(kr)); goto fail; } #endif // HAVE_MACHPORT_GUARDS /* Request a notification when/if the server dies */ kr = mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS, 1, port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &oldNotify); if (kr != KERN_SUCCESS) { /* * We can't request a notification for our own port! This should * only happen if someone stomped on OUR port (so let's leave * the port alone). */ SC_log(LOG_NOTICE, "mach_port_request_notification() failed: %s", mach_error_string(kr)); goto fail; } if (oldNotify != MACH_PORT_NULL) { SC_log(LOG_NOTICE, "oldNotify != MACH_PORT_NULL"); } #ifdef DEBUG SC_log(LOG_DEBUG, "+ establish notification request w/port=0x%x (%d) with SCDynamicStore server (%@)", port, port, (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ retry : __MACH_PORT_DEBUG(TRUE, "*** addNotifyPort", port); kr = notifyviaport(storePrivate->server, port, 0, (int *)&sc_status); if (kr == MACH_SEND_INVALID_RIGHT) { /* * the notifyviaport() call was not able to pass/process * the [notify port] send right. This was most likely due * to the [SCDynamicStore] server having restarted. We * handle this issue by removing the remaining receive * right, reallocating the notify port, and retrying * the operation. */ SC_log((try++ < MAX_INVALID_RIGHT_RETRY) ? LOG_INFO : LOG_ERR, "SCDynamicStore callback notifyviaport() w/port 0x%x (%d) failed (try %d): %s", port, port, try, mach_error_string(kr)); removeReceiveRight(store, port); } if (__SCDynamicStoreCheckRetryAndHandleError(store, kr, &sc_status, "SCDynamicStore callback notifyviaport()")) { if (kr == MACH_SEND_INVALID_RIGHT) { if (try <= MAX_INVALID_RIGHT_RETRY) { goto retry_allocate; } goto fail; } goto retry; } if (try > 0) { SC_log(LOG_INFO, "SCDynamicStore callback notifyviaport() succeeded after %d retr%s w/port 0x%x (%d)", try, (try == 1) ? "y" : "ies", port, port); } if (kr != KERN_SUCCESS) { if ((kr == MACH_SEND_INVALID_DEST) || (kr == MIG_SERVER_DIED)) { /* remove the send right that we tried (but failed) to pass to the server */ (void) mach_port_deallocate(mach_task_self(), port); } removeReceiveRight(store, port); goto fail; } if (sc_status != kSCStatusOK) { /* something [else] didn't work */ removeReceiveRight(store, port); kr = sc_status; goto fail; } return port; fail : _SCErrorSet(kr); return MACH_PORT_NULL; } static CFStringRef notifyMPCopyDescription(const void *info) { SCDynamicStoreRef store = (SCDynamicStoreRef)info; return CFStringCreateWithFormat(NULL, NULL, CFSTR(" {store = %p}"), store); } static void rlsCallback(CFMachPortRef port, void *msg, CFIndex size, void *info) { #ifndef DEBUG #pragma unused(port) #endif /* DEBUG */ #pragma unused(size) mach_no_senders_notification_t *buf = msg; mach_msg_id_t msgid = buf->not_header.msgh_id; SCDynamicStoreRef store = (SCDynamicStoreRef)info; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; #ifdef DEBUG SC_log(LOG_DEBUG, "mach port callback, %ssignal RLS(%@)", (msgid == MACH_NOTIFY_NO_SENDERS) ? "reconnect and " : "", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ if (msgid == MACH_NOTIFY_NO_SENDERS) { /* the server died, disable additional callbacks */ #ifdef DEBUG SC_log(LOG_DEBUG, " notifier port closed"); #endif /* DEBUG */ #ifdef DEBUG if (port != storePrivate->rlsNotifyPort) { SC_log(LOG_DEBUG, "why is port != rlsNotifyPort?"); } #endif /* DEBUG */ /* re-establish notification and inform the client */ (void)__SCDynamicStoreReconnectNotifications(store); } /* signal the real runloop source */ if (storePrivate->rls != NULL) { CFRunLoopSourceSignal(storePrivate->rls); } return; } static void rlsSchedule(void *info, CFRunLoopRef rl, CFStringRef mode) { SCDynamicStoreRef store = (SCDynamicStoreRef)info; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; #ifdef DEBUG SC_log(LOG_DEBUG, "schedule notifications for mode %@", mode); #endif /* DEBUG */ if (storePrivate->rlsNotifyPort == NULL) { CFMachPortContext context = { 0 , (void *)store , CFRetain , CFRelease , notifyMPCopyDescription }; mach_port_t port; #ifdef DEBUG SC_log(LOG_DEBUG, " activate callback runloop source"); #endif /* DEBUG */ port = addNotifyPort(store); if (port == MACH_PORT_NULL) { return; } __MACH_PORT_DEBUG(TRUE, "*** rlsSchedule (after addNotifyPort)", port); storePrivate->rlsNotifyPort = _SC_CFMachPortCreateWithPort("SCDynamicStore", port, rlsCallback, &context); if (rl != NULL) { storePrivate->rlsNotifyRLS = CFMachPortCreateRunLoopSource(NULL, storePrivate->rlsNotifyPort, 0); storePrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } } if (storePrivate->rlsNotifyRLS != NULL) { /* set notifier active */ storePrivate->notifyStatus = Using_NotifierInformViaRunLoop; if (!_SC_isScheduled(store, rl, mode, storePrivate->rlList)) { /* * if we are not already scheduled with this runLoop / runLoopMode */ CFRunLoopAddSource(rl, storePrivate->rlsNotifyRLS, mode); __MACH_PORT_DEBUG(TRUE, "*** rlsSchedule (after CFRunLoopAddSource)", CFMachPortGetPort(storePrivate->rlsNotifyPort)); } _SC_schedule(store, rl, mode, storePrivate->rlList); } return; } static void rlsCancel(void *info, CFRunLoopRef rl, CFStringRef mode) { CFIndex n = 0; SCDynamicStoreRef store = (SCDynamicStoreRef)info; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; #ifdef DEBUG SC_log(LOG_DEBUG, "cancel notifications for mode %@", mode); #endif /* DEBUG */ if (storePrivate->rlsNotifyRLS != NULL) { if (_SC_unschedule(store, rl, mode, storePrivate->rlList, FALSE)) { /* * if currently scheduled on this runLoop / runLoopMode */ n = CFArrayGetCount(storePrivate->rlList); if (n == 0 || !_SC_isScheduled(store, rl, mode, storePrivate->rlList)) { /* * if we are no longer scheduled to receive notifications for * this runLoop / runLoopMode */ CFRunLoopRemoveSource(rl, storePrivate->rlsNotifyRLS, mode); } } } if (n == 0) { int sc_status; kern_return_t kr; #ifdef DEBUG SC_log(LOG_DEBUG, " cancel callback runloop source"); #endif /* DEBUG */ __MACH_PORT_DEBUG((storePrivate->rlsNotifyPort != NULL), "*** rlsCancel", CFMachPortGetPort(storePrivate->rlsNotifyPort)); if (storePrivate->rls != NULL) { // Remove the reference we took on the rls. We do not invalidate // the runloop source and let the client do it when appropriate. CFRelease(storePrivate->rls); storePrivate->rls = NULL; } if (storePrivate->rlList != NULL) { CFRelease(storePrivate->rlList); storePrivate->rlList = NULL; } if (storePrivate->rlsNotifyRLS != NULL) { /* invalidate & remove the run loop source */ CFRunLoopSourceInvalidate(storePrivate->rlsNotifyRLS); CFRelease(storePrivate->rlsNotifyRLS); storePrivate->rlsNotifyRLS = NULL; } if (storePrivate->rlsNotifyPort != NULL) { mach_port_t mp; mp = CFMachPortGetPort(storePrivate->rlsNotifyPort); __MACH_PORT_DEBUG((storePrivate->rlsNotifyPort != NULL), "*** rlsCancel (before invalidating/releasing CFMachPort)", mp); /* invalidate and release port */ CFMachPortInvalidate(storePrivate->rlsNotifyPort); CFRelease(storePrivate->rlsNotifyPort); storePrivate->rlsNotifyPort = NULL; /* and, finally, remove our receive right */ removeReceiveRight(store, mp); } #ifdef DEBUG SC_log(LOG_DEBUG, " cancel notification request with SCDynamicStore server"); #endif /* DEBUG */ if (storePrivate->server != MACH_PORT_NULL) { kr = notifycancel(storePrivate->server, (int *)&sc_status); (void) __SCDynamicStoreCheckRetryAndHandleError(store, kr, &sc_status, "rlsCancel notifycancel()"); if (kr != KERN_SUCCESS) { return; } } /* set notifier inactive */ storePrivate->notifyStatus = NotifierNotRegistered; } return; } static void rlsPerform(void *info) { CFArrayRef changedKeys = NULL; void *context_info; void (*context_release)(const void *); SCDynamicStoreCallBack rlsFunction; SCDynamicStoreRef store = (SCDynamicStoreRef)info; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; #ifdef DEBUG SC_log(LOG_DEBUG, "handling SCDynamicStore changes (%@)", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ changedKeys = SCDynamicStoreCopyNotifiedKeys(store); if (storePrivate->disconnectForceCallBack) { storePrivate->disconnectForceCallBack = FALSE; if (changedKeys == NULL) { changedKeys = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); } } else if ((changedKeys == NULL) || (CFArrayGetCount(changedKeys) == 0)) { /* if no changes or something happened to the server */ goto done; } rlsFunction = storePrivate->rlsFunction; if (storePrivate->rlsContext.retain != NULL) { context_info = (void *)storePrivate->rlsContext.retain(storePrivate->rlsContext.info); context_release = storePrivate->rlsContext.release; } else { context_info = storePrivate->rlsContext.info; context_release = NULL; } if (rlsFunction != NULL) { SC_log(LOG_DEBUG, "+ exec SCDynamicStore callout"); (*rlsFunction)(store, changedKeys, context_info); SC_log(LOG_DEBUG, "+ done"); } if (context_release != NULL) { context_release(context_info); } done : #ifdef DEBUG SC_log(LOG_DEBUG, "done"); #endif /* DEBUG */ if (changedKeys != NULL) { CFRelease(changedKeys); } return; } static CFStringRef rlsCopyDescription(const void *info) { CFMutableStringRef result; SCDynamicStoreRef store = (SCDynamicStoreRef)info; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; result = CFStringCreateMutable(NULL, 0); CFStringAppendFormat(result, NULL, CFSTR(" {")); CFStringAppendFormat(result, NULL, CFSTR("store = %p"), store); if (storePrivate->notifyStatus == Using_NotifierInformViaRunLoop) { CFStringRef description = NULL; CFStringAppendFormat(result, NULL, CFSTR(", callout = %p"), storePrivate->rlsFunction); if ((storePrivate->rlsContext.info != NULL) && (storePrivate->rlsContext.copyDescription != NULL)) { description = (*storePrivate->rlsContext.copyDescription)(storePrivate->rlsContext.info); } if (description == NULL) { description = CFStringCreateWithFormat(NULL, NULL, CFSTR(""), storePrivate->rlsContext.info); } if (description == NULL) { description = CFRetain(CFSTR("")); } CFStringAppendFormat(result, NULL, CFSTR(", context = %@"), description); CFRelease(description); } CFStringAppendFormat(result, NULL, CFSTR("}")); return result; } CFRunLoopSourceRef SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator, SCDynamicStoreRef store, CFIndex order) { SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; if (store == NULL) { /* sorry, you must provide a session */ _SCErrorSet(kSCStatusNoStoreSession); return NULL; } if (storePrivate->server == MACH_PORT_NULL) { /* sorry, you must have an open session to play */ _SCErrorSet(kSCStatusNoStoreServer); return NULL; } switch (storePrivate->notifyStatus) { case NotifierNotRegistered : case Using_NotifierInformViaRunLoop : /* OK to enable runloop notification */ break; default : /* sorry, you can only have one notification registered at once */ _SCErrorSet(kSCStatusNotifierActive); return NULL; } if (storePrivate->rls == NULL) { CFRunLoopSourceContext context = { 0 // version , (void *)store // info , CFRetain // retain , CFRelease // release , rlsCopyDescription // copyDescription , CFEqual // equal , CFHash // hash , rlsSchedule // schedule , rlsCancel // cancel , rlsPerform // perform }; storePrivate->rls = CFRunLoopSourceCreate(allocator, order, &context); if (storePrivate->rls == NULL) { _SCErrorSet(kSCStatusFailed); } } if (storePrivate->rls != NULL) { CFRetain(storePrivate->rls); } return storePrivate->rls; } Boolean SCDynamicStoreSetDispatchQueue(SCDynamicStoreRef store, dispatch_queue_t queue) { dispatch_group_t drainGroup = NULL; dispatch_queue_t drainQueue = NULL; dispatch_group_t group = NULL; mach_port_t mp; Boolean ok = FALSE; dispatch_source_t source; SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; if (store == NULL) { // sorry, you must provide a session _SCErrorSet(kSCStatusNoStoreSession); return FALSE; } if (queue == NULL) { if (storePrivate->dispatchQueue == NULL) { // if not scheduled _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } #ifdef DEBUG SC_log(LOG_DEBUG, "unschedule notifications from dispatch queue (%@)", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ ok = TRUE; goto cleanup; } if (storePrivate->server == MACH_PORT_NULL) { // sorry, you must have an open session to play _SCErrorSet(kSCStatusNoStoreServer); return FALSE; } if ((storePrivate->dispatchQueue != NULL) || (storePrivate->rls != NULL) || (storePrivate->notifyStatus != NotifierNotRegistered)) { // if already scheduled _SCErrorSet(kSCStatusNotifierActive); return FALSE; } #ifdef DEBUG SC_log(LOG_DEBUG, "schedule notifications for dispatch queue (%@)", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ // // mark our using of the SCDynamicStore notifications, create and schedule // the notification source/port (storePrivate->dispatchSource), and a bunch // of other "setup" // storePrivate->notifyStatus = Using_NotifierInformViaDispatch; mp = addNotifyPort(store); if (mp == MACH_PORT_NULL) { // if we could not schedule the notification _SCErrorSet(kSCStatusFailed); #ifdef DEBUG SC_log(LOG_DEBUG, "addNotifyPort() failed (%@)", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ goto cleanup; } // retain the dispatch queue storePrivate->dispatchQueue = queue; dispatch_retain(storePrivate->dispatchQueue); // // We've taken a reference to the callers dispatch_queue and we // want to hold on to that reference until we've processed any/all // notifications. To facilitate this we create a group, dispatch // any notification blocks to via that group, and when the caller // has told us to stop the notifications (unschedule) we wait for // the group to empty and use the group's finalizer to release // our reference to the SCDynamicStore. // group = dispatch_group_create(); storePrivate->dispatchGroup = group; CFRetain(store); dispatch_set_context(storePrivate->dispatchGroup, (void *)store); dispatch_set_finalizer_f(storePrivate->dispatchGroup, (dispatch_function_t)CFRelease); // create a dispatch source for the mach notifications source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, mp, 0, queue); if (source == NULL) { SC_log(LOG_NOTICE, "dispatch_source_create() failed"); // remove our receive right removeReceiveRight(store, mp); _SCErrorSet(kSCStatusFailed); goto cleanup; } dispatch_source_set_event_handler(source, ^{ kern_return_t kr; mach_msg_id_t msgid; union { u_int8_t buf1[sizeof(mach_msg_empty_rcv_t) + MAX_TRAILER_SIZE]; u_int8_t buf2[sizeof(mach_no_senders_notification_t) + MAX_TRAILER_SIZE]; mach_msg_empty_rcv_t msg; mach_no_senders_notification_t no_senders; } notify_msg; kr = mach_msg(¬ify_msg.msg.header, // msg MACH_RCV_MSG, // options 0, // send_size sizeof(notify_msg), // rcv_size mp, // rcv_name MACH_MSG_TIMEOUT_NONE, // timeout MACH_PORT_NULL); // notify if (kr != KERN_SUCCESS) { SC_log(LOG_NOTICE, "mach_msg() failed, kr=0x%x", kr); return; } msgid = notify_msg.msg.header.msgh_id; mach_msg_destroy(¬ify_msg.msg.header); #ifdef DEBUG SC_log(LOG_DEBUG, "dispatch source callback, queue rlsPerform (%@)", (storePrivate->name != NULL) ? storePrivate->name : CFSTR("?")); #endif /* DEBUG */ CFRetain(store); dispatch_group_async(group, queue, ^{ if (msgid == MACH_NOTIFY_NO_SENDERS) { // re-establish notification and inform the client (void)__SCDynamicStoreReconnectNotifications(store); } rlsPerform(storePrivate); CFRelease(store); }); }); dispatch_source_set_cancel_handler(source, ^{ __MACH_PORT_DEBUG(TRUE, "*** SCDynamicStoreSetDispatchQueue (before cancel)", mp); // remove our receive right removeReceiveRight(store, mp); // release source dispatch_release(source); }); storePrivate->dispatchSource = source; dispatch_resume(source); return TRUE; cleanup : CFRetain(store); if (storePrivate->dispatchSource != NULL) { dispatch_source_cancel(storePrivate->dispatchSource); storePrivate->dispatchSource = NULL; } drainGroup = storePrivate->dispatchGroup; storePrivate->dispatchGroup = NULL; drainQueue = storePrivate->dispatchQueue; storePrivate->dispatchQueue = NULL; if ((drainGroup != NULL) && (drainQueue != NULL)) { dispatch_group_notify(drainGroup, drainQueue, ^{ // release group/queue references dispatch_release(drainQueue); dispatch_release(drainGroup); // releases our store reference }); } storePrivate->notifyStatus = NotifierNotRegistered; CFRelease(store); return ok; }