/*
- * 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 <ajn@apple.com>
+ * - public API conversion
+ *
+ * March 31, 2000 Allan Nathanson <ajn@apple.com>
+ * - initial revision
+ */
+
+#include <Availability.h>
+#include <TargetConditionals.h>
+#include <sys/cdefs.h>
+#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
-#include <SystemConfiguration/SCD.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#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("<SCDynamicStore notification MP> {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("<SCDynamicStore RLS> {"));
+ 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("<SCDynamicStore context %p>"), storePrivate->rlsContext.info);
+ }
+ if (description == NULL) {
+ description = CFRetain(CFSTR("<no description>"));
+ }
+ 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;
}