2 * Copyright (c) 2000-2005, 2008-2016 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
30 * March 31, 2000 Allan Nathanson <ajn@apple.com>
34 #include "SCDynamicStoreInternal.h"
35 #include "config.h" /* MiG generated file */
38 #if !TARGET_OS_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090))
39 #define HAVE_MACHPORT_GUARDS
44 notifyMPCopyDescription(const void *info
)
46 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
48 return CFStringCreateWithFormat(NULL
,
50 CFSTR("<SCDynamicStore notification MP> {store = %p}"),
56 rlsCallback(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
58 mach_no_senders_notification_t
*buf
= msg
;
59 mach_msg_id_t msgid
= buf
->not_header
.msgh_id
;
60 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
61 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
63 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
64 /* the server died, disable additional callbacks */
66 SC_log(LOG_DEBUG
, " notifier port closed");
70 if (port
!= storePrivate
->rlsNotifyPort
) {
71 SC_log(LOG_DEBUG
, "why is port != rlsNotifyPort?");
75 /* re-establish notification and inform the client */
76 (void)__SCDynamicStoreReconnectNotifications(store
);
80 SC_log(LOG_DEBUG
, "mach port callback, signal RLS");
83 /* signal the real runloop source */
84 if (storePrivate
->rls
!= NULL
) {
85 CFRunLoopSourceSignal(storePrivate
->rls
);
93 rlsSchedule(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
95 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
96 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
99 SC_log(LOG_DEBUG
, "schedule notifications for mode %@",
100 (rl
!= NULL
) ? mode
: CFSTR("libdispatch"));
103 if (storePrivate
->rlsNotifyPort
== NULL
) {
104 CFMachPortContext context
= { 0
108 , notifyMPCopyDescription
111 mach_port_t oldNotify
;
112 #ifdef HAVE_MACHPORT_GUARDS
113 mach_port_options_t opts
;
114 #endif // HAVE_MACHPORT_GUARDS
119 SC_log(LOG_DEBUG
, " activate callback runloop source");
122 /* allocate a mach port for the SCDynamicStore notifications */
126 #ifdef HAVE_MACHPORT_GUARDS
127 bzero(&opts
, sizeof(opts
));
128 opts
.flags
= MPO_CONTEXT_AS_GUARD
|MPO_INSERT_SEND_RIGHT
;
130 kr
= mach_port_construct(mach_task_self(), &opts
, store
, &port
);
131 #else // HAVE_MACHPORT_GUARDS
132 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &port
);
133 #endif // HAVE_MACHPORT_GUARDS
135 if (kr
!= KERN_SUCCESS
) {
136 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
137 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
145 #ifndef HAVE_MACHPORT_GUARDS
146 kr
= mach_port_insert_right(mach_task_self(),
149 MACH_MSG_TYPE_MAKE_SEND
);
150 if (kr
!= KERN_SUCCESS
) {
152 * We can't insert a send right into our own port! This should
153 * only happen if someone stomped on OUR port (so let's leave
156 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
159 #endif // HAVE_MACHPORT_GUARDS
161 /* Request a notification when/if the server dies */
162 kr
= mach_port_request_notification(mach_task_self(),
164 MACH_NOTIFY_NO_SENDERS
,
167 MACH_MSG_TYPE_MAKE_SEND_ONCE
,
169 if (kr
!= KERN_SUCCESS
) {
171 * We can't request a notification for our own port! This should
172 * only happen if someone stomped on OUR port (so let's leave
175 SC_log(LOG_NOTICE
, "mach_port_request_notification() failed: %s", mach_error_string(kr
));
179 if (oldNotify
!= MACH_PORT_NULL
) {
180 SC_log(LOG_NOTICE
, "oldNotify != MACH_PORT_NULL");
184 SC_log(LOG_DEBUG
, " establish notification request with SCDynamicStore server");
187 os_activity_scope(storePrivate
->activity
);
191 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule", port
);
192 kr
= notifyviaport(storePrivate
->server
, port
, 0, (int *)&sc_status
);
194 if (__SCDynamicStoreCheckRetryAndHandleError(store
,
197 "rlsSchedule notifyviaport()")) {
201 if (kr
!= KERN_SUCCESS
) {
202 if ((kr
== MACH_SEND_INVALID_DEST
) || (kr
== MIG_SERVER_DIED
)) {
203 /* remove the send right that we tried (but failed) to pass to the server */
204 (void) mach_port_deallocate(mach_task_self(), port
);
207 /* remove our receive right */
208 #ifdef HAVE_MACHPORT_GUARDS
209 (void) mach_port_destruct(mach_task_self(), port
, 0, store
);
210 #else // HAVE_MACHPORT_GUARDS
211 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
212 #endif // HAVE_MACHPORT_GUARDS
216 if (sc_status
!= kSCStatusOK
) {
217 /* something [else] didn't work, remove our receive right */
218 #ifdef HAVE_MACHPORT_GUARDS
219 (void) mach_port_destruct(mach_task_self(), port
, 0, store
);
220 #else // HAVE_MACHPORT_GUARDS
221 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
222 #endif // HAVE_MACHPORT_GUARDS
226 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after notifyviaport)", port
);
227 storePrivate
->rlsNotifyPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore",
233 storePrivate
->rlsNotifyRLS
= CFMachPortCreateRunLoopSource(NULL
, storePrivate
->rlsNotifyPort
, 0);
234 storePrivate
->rlList
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
238 if ((rl
!= NULL
) && (storePrivate
->rlsNotifyRLS
!= NULL
)) {
239 /* set notifier active */
240 storePrivate
->notifyStatus
= Using_NotifierInformViaRunLoop
;
242 if (!_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
244 * if we are not already scheduled with this runLoop / runLoopMode
246 CFRunLoopAddSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
247 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after CFRunLoopAddSource)", CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
250 _SC_schedule(store
, rl
, mode
, storePrivate
->rlList
);
258 rlsCancel(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
261 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
262 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
265 SC_log(LOG_DEBUG
, "cancel notifications for mode %@",
266 (rl
!= NULL
) ? mode
: CFSTR("libdispatch"));
269 if ((rl
!= NULL
) && (storePrivate
->rlsNotifyRLS
!= NULL
)) {
270 if (_SC_unschedule(store
, rl
, mode
, storePrivate
->rlList
, FALSE
)) {
272 * if currently scheduled on this runLoop / runLoopMode
274 n
= CFArrayGetCount(storePrivate
->rlList
);
275 if (n
== 0 || !_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
277 * if we are no longer scheduled to receive notifications for
278 * this runLoop / runLoopMode
280 CFRunLoopRemoveSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
290 SC_log(LOG_DEBUG
, " cancel callback runloop source");
292 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
294 CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
296 if (storePrivate
->rls
!= NULL
) {
297 // Remove the reference we took on the rls. We do not invalidate
298 // the runloop source and let the client do it when appropriate.
299 CFRelease(storePrivate
->rls
);
300 storePrivate
->rls
= NULL
;
303 if (storePrivate
->rlList
!= NULL
) {
304 CFRelease(storePrivate
->rlList
);
305 storePrivate
->rlList
= NULL
;
308 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
309 /* invalidate & remove the run loop source */
310 CFRunLoopSourceInvalidate(storePrivate
->rlsNotifyRLS
);
311 CFRelease(storePrivate
->rlsNotifyRLS
);
312 storePrivate
->rlsNotifyRLS
= NULL
;
315 if (storePrivate
->rlsNotifyPort
!= NULL
) {
318 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
319 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
320 "*** rlsCancel (before invalidating/releasing CFMachPort)",
323 /* invalidate and release port */
324 CFMachPortInvalidate(storePrivate
->rlsNotifyPort
);
325 CFRelease(storePrivate
->rlsNotifyPort
);
326 storePrivate
->rlsNotifyPort
= NULL
;
328 /* and, finally, remove our receive right */
329 #ifdef HAVE_MACHPORT_GUARDS
330 (void) mach_port_destruct(mach_task_self(), mp
, 0, store
);
331 #else // HAVE_MACHPORT_GUARDS
332 (void) mach_port_mod_refs(mach_task_self(), mp
, MACH_PORT_RIGHT_RECEIVE
, -1);
333 #endif // HAVE_MACHPORT_GUARDS
337 SC_log(LOG_DEBUG
, " cancel notification request with SCDynamicStore server");
340 os_activity_scope(storePrivate
->activity
);
342 if (storePrivate
->server
!= MACH_PORT_NULL
) {
343 kr
= notifycancel(storePrivate
->server
, (int *)&sc_status
);
345 (void) __SCDynamicStoreCheckRetryAndHandleError(store
,
348 "rlsCancel notifycancel()");
350 if (kr
!= KERN_SUCCESS
) {
355 /* set notifier inactive */
356 storePrivate
->notifyStatus
= NotifierNotRegistered
;
364 rlsPerform(void *info
)
366 CFArrayRef changedKeys
= NULL
;
368 void (*context_release
)(const void *);
369 SCDynamicStoreCallBack rlsFunction
;
370 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
371 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
374 SC_log(LOG_DEBUG
, " executing notification function");
377 changedKeys
= SCDynamicStoreCopyNotifiedKeys(store
);
378 if (storePrivate
->disconnectForceCallBack
) {
379 storePrivate
->disconnectForceCallBack
= FALSE
;
380 if (changedKeys
== NULL
) {
381 changedKeys
= CFArrayCreate(NULL
, NULL
, 0, &kCFTypeArrayCallBacks
);
383 } else if ((changedKeys
== NULL
) || (CFArrayGetCount(changedKeys
) == 0)) {
384 /* if no changes or something happened to the server */
388 rlsFunction
= storePrivate
->rlsFunction
;
390 if (storePrivate
->rlsContext
.retain
!= NULL
) {
391 context_info
= (void *)storePrivate
->rlsContext
.retain(storePrivate
->rlsContext
.info
);
392 context_release
= storePrivate
->rlsContext
.release
;
394 context_info
= storePrivate
->rlsContext
.info
;
395 context_release
= NULL
;
397 if (rlsFunction
!= NULL
) {
398 SC_log(LOG_DEBUG
, "exec SCDynamicStore callout");
399 (*rlsFunction
)(store
, changedKeys
, context_info
);
401 if (context_release
!= NULL
) {
402 context_release(context_info
);
408 SC_log(LOG_DEBUG
, " done!");
411 if (changedKeys
!= NULL
) {
412 CFRelease(changedKeys
);
420 rlsCopyDescription(const void *info
)
422 CFMutableStringRef result
;
423 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
424 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
426 result
= CFStringCreateMutable(NULL
, 0);
427 CFStringAppendFormat(result
, NULL
, CFSTR("<SCDynamicStore RLS> {"));
428 CFStringAppendFormat(result
, NULL
, CFSTR("store = %p"), store
);
429 if (storePrivate
->notifyStatus
== Using_NotifierInformViaRunLoop
) {
430 CFStringRef description
= NULL
;
432 CFStringAppendFormat(result
, NULL
, CFSTR(", callout = %p"), storePrivate
->rlsFunction
);
434 if ((storePrivate
->rlsContext
.info
!= NULL
) && (storePrivate
->rlsContext
.copyDescription
!= NULL
)) {
435 description
= (*storePrivate
->rlsContext
.copyDescription
)(storePrivate
->rlsContext
.info
);
437 if (description
== NULL
) {
438 description
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SCDynamicStore context %p>"), storePrivate
->rlsContext
.info
);
440 if (description
== NULL
) {
441 description
= CFRetain(CFSTR("<no description>"));
443 CFStringAppendFormat(result
, NULL
, CFSTR(", context = %@"), description
);
444 CFRelease(description
);
446 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
453 SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator
,
454 SCDynamicStoreRef store
,
457 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
460 /* sorry, you must provide a session */
461 _SCErrorSet(kSCStatusNoStoreSession
);
465 if (storePrivate
->server
== MACH_PORT_NULL
) {
466 /* sorry, you must have an open session to play */
467 _SCErrorSet(kSCStatusNoStoreServer
);
471 switch (storePrivate
->notifyStatus
) {
472 case NotifierNotRegistered
:
473 case Using_NotifierInformViaRunLoop
:
474 /* OK to enable runloop notification */
477 /* sorry, you can only have one notification registered at once */
478 _SCErrorSet(kSCStatusNotifierActive
);
482 if (storePrivate
->rls
== NULL
) {
483 CFRunLoopSourceContext context
= { 0 // version
484 , (void *)store
// info
486 , CFRelease
// release
487 , rlsCopyDescription
// copyDescription
490 , rlsSchedule
// schedule
491 , rlsCancel
// cancel
492 , rlsPerform
// perform
495 storePrivate
->rls
= CFRunLoopSourceCreate(allocator
, order
, &context
);
496 if (storePrivate
->rls
== NULL
) {
497 _SCErrorSet(kSCStatusFailed
);
501 if (storePrivate
->rls
!= NULL
) {
502 CFRetain(storePrivate
->rls
);
505 return storePrivate
->rls
;
510 SCDynamicStoreSetDispatchQueue(SCDynamicStoreRef store
, dispatch_queue_t queue
)
512 dispatch_group_t drainGroup
= NULL
;
513 dispatch_queue_t drainQueue
= NULL
;
514 dispatch_group_t group
= NULL
;
517 dispatch_source_t source
;
518 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
521 // sorry, you must provide a session
522 _SCErrorSet(kSCStatusNoStoreSession
);
527 if (storePrivate
->dispatchQueue
== NULL
) {
528 _SCErrorSet(kSCStatusInvalidArgument
);
536 if (storePrivate
->server
== MACH_PORT_NULL
) {
537 // sorry, you must have an open session to play
538 _SCErrorSet(kSCStatusNoStoreServer
);
542 if ((storePrivate
->dispatchQueue
!= NULL
) || (storePrivate
->rls
!= NULL
)) {
543 _SCErrorSet(kSCStatusInvalidArgument
);
547 if (storePrivate
->notifyStatus
!= NotifierNotRegistered
) {
548 // sorry, you can only have one notification registered at once...
549 _SCErrorSet(kSCStatusNotifierActive
);
554 * mark our using of the SCDynamicStore notifications, create and schedule
555 * the notification port (storePrivate->rlsNotifyPort), and a bunch of other
558 storePrivate
->notifyStatus
= Using_NotifierInformViaDispatch
;
559 rlsSchedule((void*)store
, NULL
, NULL
);
560 if (storePrivate
->rlsNotifyPort
== NULL
) {
561 /* if we could not schedule the notification */
562 _SCErrorSet(kSCStatusFailed
);
566 // retain the dispatch queue
567 storePrivate
->dispatchQueue
= queue
;
568 dispatch_retain(storePrivate
->dispatchQueue
);
571 // We've taken a reference to the callers dispatch_queue and we
572 // want to hold on to that reference until we've processed any/all
573 // notifications. To facilitate this we create a group, dispatch
574 // any notification blocks to via that group, and when the caller
575 // has told us to stop the notifications (unschedule) we wait for
576 // the group to empty and use the group's finalizer to release
577 // our reference to the SCDynamicStore.
579 group
= dispatch_group_create();
580 storePrivate
->dispatchGroup
= group
;
582 dispatch_set_context(storePrivate
->dispatchGroup
, (void *)store
);
583 dispatch_set_finalizer_f(storePrivate
->dispatchGroup
, (dispatch_function_t
)CFRelease
);
585 // create a dispatch source for the mach notifications
586 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
587 source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, mp
, 0, queue
);
588 if (source
== NULL
) {
589 SC_log(LOG_NOTICE
, "dispatch_source_create() failed");
590 _SCErrorSet(kSCStatusFailed
);
594 dispatch_source_set_event_handler(source
, ^{
598 u_int8_t buf
[sizeof(mach_msg_empty_t
) + MAX_TRAILER_SIZE
];
599 mach_msg_empty_rcv_t msg
;
600 mach_no_senders_notification_t no_senders
;
603 kr
= mach_msg(¬ify_msg
.msg
.header
, // msg
604 MACH_RCV_MSG
, // options
606 sizeof(notify_msg
), // rcv_size
608 MACH_MSG_TIMEOUT_NONE
, // timeout
609 MACH_PORT_NULL
); // notify
610 if (kr
!= KERN_SUCCESS
) {
611 SC_log(LOG_NOTICE
, "mach_msg() failed, kr=0x%x", kr
);
615 msgid
= notify_msg
.msg
.header
.msgh_id
;
618 SC_log(LOG_DEBUG
, "dispatch source callback, queue rlsPerform");
622 dispatch_group_async(group
, queue
, ^{
623 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
624 // re-establish notification and inform the client
625 (void)__SCDynamicStoreReconnectNotifications(store
);
627 rlsPerform(storePrivate
);
632 dispatch_source_set_cancel_handler(source
, ^{
633 dispatch_release(source
);
636 storePrivate
->dispatchSource
= source
;
637 dispatch_resume(source
);
645 if (storePrivate
->dispatchSource
!= NULL
) {
646 dispatch_source_cancel(storePrivate
->dispatchSource
);
647 storePrivate
->dispatchSource
= NULL
;
649 drainGroup
= storePrivate
->dispatchGroup
;
650 storePrivate
->dispatchGroup
= NULL
;
651 drainQueue
= storePrivate
->dispatchQueue
;
652 storePrivate
->dispatchQueue
= NULL
;
654 rlsCancel((void*)store
, NULL
, NULL
);
656 if ((drainGroup
!= NULL
) && (drainQueue
!= NULL
)) {
657 dispatch_group_notify(drainGroup
, drainQueue
, ^{
658 // release group/queue references
659 dispatch_release(drainQueue
);
660 dispatch_release(drainGroup
); // releases our store reference
664 storePrivate
->notifyStatus
= NotifierNotRegistered
;