SCDNotifierInformViaCallback.c [plain text]
#include "SCDynamicStoreInternal.h"
#include "config.h"
#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)
{
#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;
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); 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) {
SC_log(LOG_NOTICE, "mach_port_insert_right() failed: %s", mach_error_string(kr));
goto fail;
}
#endif // HAVE_MACHPORT_GUARDS
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) {
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
retry :
__MACH_PORT_DEBUG(TRUE, "*** addNotifyPort", port);
kr = notifyviaport(storePrivate->server, port, 0, (int *)&sc_status);
if (kr == MACH_SEND_INVALID_RIGHT) {
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)) {
(void) mach_port_deallocate(mach_task_self(), port);
}
removeReceiveRight(store, port);
goto fail;
}
if (sc_status != kSCStatusOK) {
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("<SCDynamicStore notification MP> {store = %p}"),
store);
}
static void
rlsCallback(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
#ifndef DEBUG
#pragma unused(port)
#endif
#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
if (msgid == MACH_NOTIFY_NO_SENDERS) {
#ifdef DEBUG
SC_log(LOG_DEBUG, " notifier port closed");
#endif
#ifdef DEBUG
if (port != storePrivate->rlsNotifyPort) {
SC_log(LOG_DEBUG, "why is port != rlsNotifyPort?");
}
#endif
(void)__SCDynamicStoreReconnectNotifications(store);
}
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
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
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) {
storePrivate->notifyStatus = Using_NotifierInformViaRunLoop;
if (!_SC_isScheduled(store, rl, mode, storePrivate->rlList)) {
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
if (storePrivate->rlsNotifyRLS != NULL) {
if (_SC_unschedule(store, rl, mode, storePrivate->rlList, FALSE)) {
n = CFArrayGetCount(storePrivate->rlList);
if (n == 0 || !_SC_isScheduled(store, rl, mode, storePrivate->rlList)) {
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
__MACH_PORT_DEBUG((storePrivate->rlsNotifyPort != NULL),
"*** rlsCancel",
CFMachPortGetPort(storePrivate->rlsNotifyPort));
if (storePrivate->rls != NULL) {
CFRelease(storePrivate->rls);
storePrivate->rls = NULL;
}
if (storePrivate->rlList != NULL) {
CFRelease(storePrivate->rlList);
storePrivate->rlList = NULL;
}
if (storePrivate->rlsNotifyRLS != NULL) {
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);
CFMachPortInvalidate(storePrivate->rlsNotifyPort);
CFRelease(storePrivate->rlsNotifyPort);
storePrivate->rlsNotifyPort = NULL;
removeReceiveRight(store, mp);
}
#ifdef DEBUG
SC_log(LOG_DEBUG, " cancel notification request with SCDynamicStore server");
#endif
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;
}
}
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
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)) {
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
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("<SCDynamicStore RLS> {"));
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("<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("}"));
return result;
}
CFRunLoopSourceRef
SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator,
SCDynamicStoreRef store,
CFIndex order)
{
SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)store;
if (store == NULL) {
_SCErrorSet(kSCStatusNoStoreSession);
return NULL;
}
if (storePrivate->server == MACH_PORT_NULL) {
_SCErrorSet(kSCStatusNoStoreServer);
return NULL;
}
switch (storePrivate->notifyStatus) {
case NotifierNotRegistered :
case Using_NotifierInformViaRunLoop :
break;
default :
_SCErrorSet(kSCStatusNotifierActive);
return NULL;
}
if (storePrivate->rls == NULL) {
CFRunLoopSourceContext context = { 0 , (void *)store , CFRetain , CFRelease , rlsCopyDescription , CFEqual , CFHash , rlsSchedule , rlsCancel , rlsPerform };
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) {
_SCErrorSet(kSCStatusNoStoreSession);
return FALSE;
}
if (queue == NULL) {
if (storePrivate->dispatchQueue == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
#ifdef DEBUG
SC_log(LOG_DEBUG, "unschedule notifications from dispatch queue (%@)",
(storePrivate->name != NULL) ? storePrivate->name : CFSTR("?"));
#endif
ok = TRUE;
goto cleanup;
}
if (storePrivate->server == MACH_PORT_NULL) {
_SCErrorSet(kSCStatusNoStoreServer);
return FALSE;
}
if ((storePrivate->dispatchQueue != NULL) ||
(storePrivate->rls != NULL) ||
(storePrivate->notifyStatus != NotifierNotRegistered)) {
_SCErrorSet(kSCStatusNotifierActive);
return FALSE;
}
#ifdef DEBUG
SC_log(LOG_DEBUG, "schedule notifications for dispatch queue (%@)",
(storePrivate->name != NULL) ? storePrivate->name : CFSTR("?"));
#endif
storePrivate->notifyStatus = Using_NotifierInformViaDispatch;
mp = addNotifyPort(store);
if (mp == MACH_PORT_NULL) {
_SCErrorSet(kSCStatusFailed);
#ifdef DEBUG
SC_log(LOG_DEBUG, "addNotifyPort() failed (%@)",
(storePrivate->name != NULL) ? storePrivate->name : CFSTR("?"));
#endif
goto cleanup;
}
storePrivate->dispatchQueue = queue;
dispatch_retain(storePrivate->dispatchQueue);
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);
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, mp, 0, queue);
if (source == NULL) {
SC_log(LOG_NOTICE, "dispatch_source_create() failed");
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, MACH_RCV_MSG, 0, sizeof(notify_msg), mp, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 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
CFRetain(store);
dispatch_group_async(group, queue, ^{
if (msgid == MACH_NOTIFY_NO_SENDERS) {
(void)__SCDynamicStoreReconnectNotifications(store);
}
rlsPerform(storePrivate);
CFRelease(store);
});
});
dispatch_source_set_cancel_handler(source, ^{
__MACH_PORT_DEBUG(TRUE,
"*** SCDynamicStoreSetDispatchQueue (before cancel)",
mp);
removeReceiveRight(store, mp);
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, ^{
dispatch_release(drainQueue);
dispatch_release(drainGroup); });
}
storePrivate->notifyStatus = NotifierNotRegistered;
CFRelease(store);
return ok;
}