X-Git-Url: https://git.saurik.com/apple/configd.git/blobdiff_plain/5958d7c06f2795b9ec773eb750b8259460acf8cb..085a2e6a11110e1303f99ce18f80f98e067cb876:/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c diff --git a/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c b/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c index 481d5b1..02e7d48 100644 --- a/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c +++ b/SystemConfiguration.fproj/SCDNotifierInformViaCallback.c @@ -1,275 +1,652 @@ /* - * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2005, 2008-2013 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ - * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. - * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * + * 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 OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. - * + * 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 +#include +#include +#include #include #include -#include +#include +#include +#include "SCDynamicStoreInternal.h" #include "config.h" /* MiG generated file */ -#include "SCDPrivate.h" + + +static CFStringRef +notifyMPCopyDescription(const void *info) +{ + SCDynamicStoreRef store = (SCDynamicStoreRef)info; + + return CFStringCreateWithFormat(NULL, + NULL, + CFSTR(" {store = %p}"), + store); +} static void -informCallback(CFMachPortRef port, void *msg, CFIndex size, void *info) +rlsCallback(CFMachPortRef port, void *msg, CFIndex size, void *info) { - SCDSessionRef session = (SCDSessionRef)info; - SCDSessionPrivateRef sessionPrivate = (SCDSessionPrivateRef)session; - mach_msg_empty_rcv_t *buf = msg; - mach_msg_id_t msgid = buf->header.msgh_id; - SCDCallbackRoutine_t cbFunc = sessionPrivate->callbackFunction; - void *cbArg = sessionPrivate->callbackArgument; + 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; if (msgid == MACH_NOTIFY_NO_SENDERS) { /* the server died, disable additional callbacks */ - SCDLog(LOG_DEBUG, CFSTR(" notifier port closed, disabling notifier")); - } else if (cbFunc == NULL) { - /* there is no (longer) a callback function, disable additional callbacks */ - SCDLog(LOG_DEBUG, CFSTR(" no callback function, disabling notifier")); - } else { - SCDLog(LOG_DEBUG, CFSTR(" executing notifiction function")); - if ((*cbFunc)(session, cbArg)) { +#ifdef DEBUG + SCLog(_sc_verbose, LOG_INFO, CFSTR(" rlsCallback(), notifier port closed")); +#endif /* DEBUG */ + +#ifdef DEBUG + if (port != storePrivate->rlsNotifyPort) { + SCLog(_sc_verbose, LOG_DEBUG, CFSTR("rlsCallback(), 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 + SCLog(_sc_verbose, LOG_DEBUG, + CFSTR("schedule notifications for mode %@"), + (rl != NULL) ? mode : CFSTR("libdispatch")); +#endif /* DEBUG */ + + if (storePrivate->rlList == NULL) { + CFMachPortContext context = { 0 + , (void *)store + , CFRetain + , CFRelease + , notifyMPCopyDescription + }; + mach_port_t oldNotify; + mach_port_t port; + int sc_status; + kern_return_t status; + +#ifdef DEBUG + SCLog(_sc_verbose, LOG_DEBUG, CFSTR(" activate callback runloop source")); +#endif /* DEBUG */ + + /* Allocating port (for server response) */ + status = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); + if (status != KERN_SUCCESS) { + SCLog(TRUE, LOG_ERR, CFSTR("rlsSchedule mach_port_allocate(): %s"), mach_error_string(status)); + return; + } + + status = mach_port_insert_right(mach_task_self(), + port, + port, + MACH_MSG_TYPE_MAKE_SEND); + if (status != KERN_SUCCESS) { /* - * callback function returned 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). */ + SCLog(TRUE, LOG_ERR, CFSTR("rlsSchedule mach_port_insert_right(): %s"), mach_error_string(status)); return; - } else { - SCDLog(LOG_DEBUG, CFSTR(" callback returned error, disabling notifier")); } + + /* Request a notification when/if the server dies */ + status = mach_port_request_notification(mach_task_self(), + port, + MACH_NOTIFY_NO_SENDERS, + 1, + port, + MACH_MSG_TYPE_MAKE_SEND_ONCE, + &oldNotify); + if (status != 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). + */ + SCLog(TRUE, LOG_ERR, CFSTR("rlsSchedule mach_port_request_notification(): %s"), mach_error_string(status)); + return; + } + + if (oldNotify != MACH_PORT_NULL) { + SCLog(TRUE, LOG_ERR, CFSTR("rlsSchedule(): oldNotify != MACH_PORT_NULL")); + } + + retry : + + __MACH_PORT_DEBUG(TRUE, "*** rlsSchedule", port); + status = notifyviaport(storePrivate->server, port, 0, (int *)&sc_status); + + if (__SCDynamicStoreCheckRetryAndHandleError(store, + status, + &sc_status, + "rlsSchedule notifyviaport()")) { + goto retry; + } + + if (status != KERN_SUCCESS) { + if ((status == MACH_SEND_INVALID_DEST) || (status == 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); + } + + /* remove our receive right */ + (void) mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1); + return; + } + + if (sc_status != kSCStatusOK) { + /* something [else] didn't work, remove our receive right */ + (void) mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1); + return; + } + + __MACH_PORT_DEBUG(TRUE, "*** rlsSchedule (after notifyviaport)", port); + storePrivate->rlsNotifyPort = _SC_CFMachPortCreateWithPort("SCDynamicStore", + port, + rlsCallback, + &context); + storePrivate->rlsNotifyRLS = CFMachPortCreateRunLoopSource(NULL, storePrivate->rlsNotifyPort, 0); + + storePrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + + if ((rl != NULL) && (storePrivate->rlsNotifyRLS != NULL)) { + 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 - if (port != sessionPrivate->callbackPort) { - SCDLog(LOG_DEBUG, CFSTR("informCallback, why is port != callbackPort?")); + SCLog(_sc_verbose, LOG_DEBUG, + CFSTR("cancel notifications for mode %@"), + (rl != NULL) ? mode : CFSTR("libdispatch")); +#endif /* DEBUG */ + + if ((rl != NULL) && (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 status; + +#ifdef DEBUG + SCLog(_sc_verbose, LOG_DEBUG, CFSTR(" cancel callback runloop source")); #endif /* DEBUG */ + __MACH_PORT_DEBUG((storePrivate->rlsNotifyPort != NULL), + "*** rlsCancel", + CFMachPortGetPort(storePrivate->rlsNotifyPort)); - /* we have encountered some type of error, disable additional callbacks */ + 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; + } - /* XXX invalidating the port is not sufficient, remove the run loop source */ - CFRunLoopRemoveSource(CFRunLoopGetCurrent(), - sessionPrivate->callbackRunLoopSource, - kCFRunLoopDefaultMode); - CFRelease(sessionPrivate->callbackRunLoopSource); + if (storePrivate->rlsNotifyPort != NULL) { + mach_port_t mp; - /* invalidate port */ - CFMachPortInvalidate(port); - CFRelease(port); + mp = CFMachPortGetPort(storePrivate->rlsNotifyPort); + __MACH_PORT_DEBUG((storePrivate->rlsNotifyPort != NULL), + "*** rlsCancel (before invalidating/releasing CFMachPort)", + mp); - sessionPrivate->notifyStatus = NotifierNotRegistered; - sessionPrivate->callbackFunction = NULL; - sessionPrivate->callbackArgument = NULL; - sessionPrivate->callbackPort = NULL; - sessionPrivate->callbackRunLoopSource = NULL; /* XXX */ + /* invalidate and release port */ + CFMachPortInvalidate(storePrivate->rlsNotifyPort); + CFRelease(storePrivate->rlsNotifyPort); + storePrivate->rlsNotifyPort = NULL; + + /* and, finally, remove our receive right */ + (void)mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_RECEIVE, -1); + } + + if (storePrivate->server != MACH_PORT_NULL) { + status = notifycancel(storePrivate->server, (int *)&sc_status); + + (void) __SCDynamicStoreCheckRetryAndHandleError(store, + status, + &sc_status, + "rlsCancel notifycancel()"); + + if (status != KERN_SUCCESS) { + return; + } + } + } return; } static void -cleanupMachPort(void *ptr) +rlsPerform(void *info) { - mach_port_t *port = (mach_port_t *)ptr; + CFArrayRef changedKeys; + void *context_info; + void (*context_release)(const void *); + SCDynamicStoreCallBack rlsFunction; + SCDynamicStoreRef store = (SCDynamicStoreRef)info; + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; + +#ifdef DEBUG + SCLog(_sc_verbose, LOG_DEBUG, CFSTR(" executing notification function")); +#endif /* DEBUG */ - SCDLog(LOG_DEBUG, CFSTR(" cleaning up notification port %d"), *port); - if (*port != MACH_PORT_NULL) { - (void) mach_port_destroy(mach_task_self(), *port); - free(port); + changedKeys = SCDynamicStoreCopyNotifiedKeys(store); + if (storePrivate->disconnectForceCallBack) { + storePrivate->disconnectForceCallBack = FALSE; + if (changedKeys == NULL) { + changedKeys = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); + } + } else { + if (changedKeys == NULL) { + /* if no changes or something happened to the server */ + return; + } else if (CFArrayGetCount(changedKeys) == 0) { + 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) { + (*rlsFunction)(store, changedKeys, context_info); + } + if (context_release != NULL) { + context_release(context_info); + } + + done : + + CFRelease(changedKeys); return; } -static void * -watcherThread(void *arg) +static CFTypeRef +rlsRetain(CFTypeRef cf) { - SCDSessionRef session = (SCDSessionRef)arg; - SCDSessionPrivateRef sessionPrivate = (SCDSessionPrivateRef)session; - SCDCallbackRoutine_t cbFunc = sessionPrivate->callbackFunction; - void *cbArg = sessionPrivate->callbackArgument; - mach_port_t *port = malloc(sizeof(mach_port_t)); + SCDynamicStoreRef store = (SCDynamicStoreRef)cf; + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; + + switch (storePrivate->notifyStatus) { + case NotifierNotRegistered : + /* mark RLS active */ + storePrivate->notifyStatus = Using_NotifierInformViaRunLoop; + /* keep a reference to the store */ + CFRetain(store); + break; + case Using_NotifierInformViaRunLoop : + break; + default : + SCLog(TRUE, LOG_ERR, CFSTR("rlsRetain() error: notifyStatus=%d"), storePrivate->notifyStatus); + break; + } - *port = CFMachPortGetPort(sessionPrivate->callbackPort); - pthread_cleanup_push(cleanupMachPort, (void *)port); + return cf; +} - while (TRUE) { - mach_msg_id_t msgid; - SCDLog(LOG_DEBUG, CFSTR("Callback thread waiting, port=%d, tid=0x%08x"), - *port, pthread_self()); +static void +rlsRelease(CFTypeRef cf) +{ + SCDynamicStoreRef store = (SCDynamicStoreRef)cf; + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; - msgid = _waitForMachMessage(*port); + switch (storePrivate->notifyStatus) { + case NotifierNotRegistered : + break; + case Using_NotifierInformViaRunLoop : + /* mark RLS inactive */ + storePrivate->notifyStatus = NotifierNotRegistered; + storePrivate->rls = NULL; - if (msgid == MACH_NOTIFY_NO_SENDERS) { - /* the server closed the notifier port, disable additional callbacks */ - SCDLog(LOG_DEBUG, CFSTR(" notifier port closed, disabling notifier")); + /* release our reference to the store */ + CFRelease(store); break; - } + default : + SCLog(TRUE, LOG_ERR, CFSTR("rlsRelease() error: notifyStatus=%d"), storePrivate->notifyStatus); + break; + } - if (msgid == -1) { - mach_port_type_t pt; + return; +} - /* an error was detected, disable additional callbacks */ - SCDLog(LOG_DEBUG, CFSTR(" server failure, disabling notifier")); - /* check if the server connection is not valid, close if necessary */ - if ((mach_port_type(mach_task_self(), sessionPrivate->server, &pt) == KERN_SUCCESS) && - (pt & MACH_PORT_TYPE_DEAD_NAME)) { - SCDLog(LOG_DEBUG, CFSTR(" server process died, destroying (dead) port")); - (void) mach_port_destroy(mach_task_self(), sessionPrivate->server); - sessionPrivate->server = MACH_PORT_NULL; - } - break; - } +static CFStringRef +rlsCopyDescription(const void *info) +{ + CFMutableStringRef result; + SCDynamicStoreRef store = (SCDynamicStoreRef)info; + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; - if (cbFunc == NULL) { - /* there is no (longer) a callback function, disable additional callbacks */ - SCDLog(LOG_DEBUG, CFSTR(" no callback function, disabling notifier")); - break; - } + result = CFStringCreateMutable(NULL, 0); + CFStringAppendFormat(result, NULL, CFSTR(" {")); + CFStringAppendFormat(result, NULL, CFSTR("store = %p"), store); + if (storePrivate->notifyStatus == Using_NotifierInformViaRunLoop) { + CFStringRef description = NULL; - SCDLog(LOG_DEBUG, CFSTR(" executing notifiction function")); + CFStringAppendFormat(result, NULL, CFSTR(", callout = %p"), storePrivate->rlsFunction); - if (!(*cbFunc)(session, cbArg)) { - /* - * callback function returned an error, exit the thread - */ - break; + 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("}")); - /* - * pop the cleanup routine for the "port" mach port. We end up calling - * mach_port_destroy() in the process. - */ - pthread_cleanup_pop(1); - - pthread_exit (NULL); - return NULL; + return result; } -SCDStatus -SCDNotifierInformViaCallback(SCDSessionRef session, SCDCallbackRoutine_t func, void *arg) +CFRunLoopSourceRef +SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator, + SCDynamicStoreRef store, + CFIndex order) { - SCDSessionPrivateRef sessionPrivate = (SCDSessionPrivateRef)session; - kern_return_t status; - mach_port_t port; - mach_port_t oldNotify; - SCDStatus scd_status; - CFMachPortContext context = { 0, (void *)session, NULL, NULL, NULL }; + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store; - SCDLog(LOG_DEBUG, CFSTR("SCDNotifierInformViaCallback:")); + if (store == NULL) { + /* sorry, you must provide a session */ + _SCErrorSet(kSCStatusNoStoreSession); + return NULL; + } - if ((session == NULL) || (sessionPrivate->server == MACH_PORT_NULL)) { - return SCD_NOSESSION; /* you must have an open session to play */ + if (storePrivate->server == MACH_PORT_NULL) { + /* sorry, you must have an open session to play */ + _SCErrorSet(kSCStatusNoStoreServer); + return NULL; } - if (sessionPrivate->notifyStatus != NotifierNotRegistered) { - /* sorry, you can only have one notification registered at once */ - return SCD_NOTIFIERACTIVE; + 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 (func == NULL) { - /* sorry, you must specify a callback function */ - return SCD_INVALIDARGUMENT; + if (storePrivate->rls != NULL) { + CFRetain(storePrivate->rls); + } else { + CFRunLoopSourceContext context = { 0 // version + , (void *)store // info + , rlsRetain // retain + , rlsRelease // 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); + } } - /* Allocating port (for server response) */ - sessionPrivate->callbackPort = CFMachPortCreate(NULL, - informCallback, - &context, - NULL); + return storePrivate->rls; +} - /* Request a notification when/if the server dies */ - port = CFMachPortGetPort(sessionPrivate->callbackPort); - status = mach_port_request_notification(mach_task_self(), - port, - MACH_NOTIFY_NO_SENDERS, - 1, - port, - MACH_MSG_TYPE_MAKE_SEND_ONCE, - &oldNotify); - if (status != KERN_SUCCESS) { - SCDLog(LOG_DEBUG, CFSTR("mach_port_request_notification(): %s"), mach_error_string(status)); - CFMachPortInvalidate(sessionPrivate->callbackPort); - CFRelease(sessionPrivate->callbackPort); - return SCD_FAILED; + +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; } -#ifdef DEBUG - if (oldNotify != MACH_PORT_NULL) { - SCDLog(LOG_DEBUG, CFSTR("SCDNotifierInformViaCallback(): why is oldNotify != MACH_PORT_NULL?")); + if (queue == NULL) { + if (storePrivate->dispatchQueue == NULL) { + _SCErrorSet(kSCStatusInvalidArgument); + return FALSE; + } + + ok = TRUE; + goto cleanup; } -#endif /* DEBUG */ - /* Requesting notification via mach port */ - status = notifyviaport(sessionPrivate->server, - port, - 0, - (int *)&scd_status); - - if (status != KERN_SUCCESS) { - if (status != MACH_SEND_INVALID_DEST) - SCDLog(LOG_DEBUG, CFSTR("notifyviaport(): %s"), mach_error_string(status)); - CFMachPortInvalidate(sessionPrivate->callbackPort); - CFRelease(sessionPrivate->callbackPort); - (void) mach_port_destroy(mach_task_self(), sessionPrivate->server); - sessionPrivate->server = MACH_PORT_NULL; - return SCD_NOSERVER; + if (storePrivate->server == MACH_PORT_NULL) { + // sorry, you must have an open session to play + _SCErrorSet(kSCStatusNoStoreServer); + return FALSE; } - if (scd_status != SCD_OK) { - return scd_status; + if ((storePrivate->dispatchQueue != NULL) || (storePrivate->rls != NULL)) { + _SCErrorSet(kSCStatusInvalidArgument); + return FALSE; } - /* set notifier active */ - sessionPrivate->notifyStatus = Using_NotifierInformViaCallback; - sessionPrivate->callbackFunction = func; - sessionPrivate->callbackArgument = arg; - - if (SCDOptionGet(session, kSCDOptionUseCFRunLoop)) { - /* Creating/adding a run loop source for the port */ - sessionPrivate->callbackRunLoopSource = - CFMachPortCreateRunLoopSource(NULL, sessionPrivate->callbackPort, 0); - CFRunLoopAddSource(CFRunLoopGetCurrent(), - sessionPrivate->callbackRunLoopSource, - kCFRunLoopDefaultMode); - } else { - pthread_attr_t tattr; - - SCDLog(LOG_DEBUG, CFSTR("Starting background thread to watch for notifications...")); - pthread_attr_init(&tattr); - pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM); - pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); - pthread_attr_setstacksize(&tattr, 96 * 1024); // each thread gets a 96K stack - pthread_create(&sessionPrivate->callbackHelper, - &tattr, - watcherThread, - (void *)session); - pthread_attr_destroy(&tattr); - SCDLog(LOG_DEBUG, CFSTR(" thread id=0x%08x"), sessionPrivate->callbackHelper); + if (storePrivate->notifyStatus != NotifierNotRegistered) { + // sorry, you can only have one notification registered at once... + _SCErrorSet(kSCStatusNotifierActive); + return FALSE; + } + + /* + * mark our using of the SCDynamicStore notifications, create and schedule + * the notification port (storePrivate->rlsNotifyPort), and a bunch of other + * "setup" + */ + storePrivate->notifyStatus = Using_NotifierInformViaDispatch; + rlsSchedule((void*)store, NULL, NULL); + if (storePrivate->rlsNotifyPort == NULL) { + /* if we could not schedule the notification */ + _SCErrorSet(kSCStatusFailed); + 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 + mp = CFMachPortGetPort(storePrivate->rlsNotifyPort); + source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, mp, 0, queue); + if (source == NULL) { + SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStore dispatch_source_create() failed")); + _SCErrorSet(kSCStatusFailed); + goto cleanup; + } + + dispatch_source_set_event_handler(source, ^{ + kern_return_t kr; + mach_msg_id_t msgid; + union { + u_int8_t buf[sizeof(mach_msg_empty_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) { + SCLog(TRUE, LOG_ERR, + CFSTR("SCDynamicStore notification handler, kr=0x%x"), + kr); + return; + } + + msgid = notify_msg.msg.header.msgh_id; + + 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, ^{ + 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; + + rlsCancel((void*)store, NULL, NULL); + + if (drainGroup != NULL) { + dispatch_group_notify(drainGroup, drainQueue, ^{ + // release group/queue references + dispatch_release(drainQueue); + dispatch_release(drainGroup); // releases our store reference + }); } - return SCD_OK; + storePrivate->notifyStatus = NotifierNotRegistered; + + CFRelease(store); + + return ok; }