2 * Copyright (c) 2000-2005, 2008-2019 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 removeReceiveRight(SCDynamicStoreRef store
, mach_port_t port
)
46 /* remove our receive right */
47 #ifdef HAVE_MACHPORT_GUARDS
48 (void) mach_port_destruct(mach_task_self(), port
, 0, (mach_port_context_t
)store
);
49 #else // HAVE_MACHPORT_GUARDS
50 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
51 #endif // HAVE_MACHPORT_GUARDS
56 #define MAX_INVALID_RIGHT_RETRY 3
59 addNotifyPort(SCDynamicStoreRef store
)
62 mach_port_t oldNotify
;
63 #ifdef HAVE_MACHPORT_GUARDS
64 mach_port_options_t opts
;
65 #endif // HAVE_MACHPORT_GUARDS
68 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
71 /* allocate a mach port for the SCDynamicStore notifications */
75 #ifdef HAVE_MACHPORT_GUARDS
76 memset(&opts
, 0, sizeof(opts
));
77 opts
.flags
= MPO_CONTEXT_AS_GUARD
|MPO_INSERT_SEND_RIGHT
;
79 kr
= mach_port_construct(mach_task_self(), &opts
, (mach_port_context_t
)store
, &port
);
80 #else // HAVE_MACHPORT_GUARDS
81 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &port
);
82 #endif // HAVE_MACHPORT_GUARDS
84 if (kr
!= KERN_SUCCESS
) {
85 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
86 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
87 usleep(50 * 1000); // sleep 50ms between attempts
93 #ifndef HAVE_MACHPORT_GUARDS
94 kr
= mach_port_insert_right(mach_task_self(),
97 MACH_MSG_TYPE_MAKE_SEND
);
98 if (kr
!= KERN_SUCCESS
) {
100 * We can't insert a send right into our own port! This should
101 * only happen if someone stomped on OUR port (so let's leave
104 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
107 #endif // HAVE_MACHPORT_GUARDS
109 /* Request a notification when/if the server dies */
110 kr
= mach_port_request_notification(mach_task_self(),
112 MACH_NOTIFY_NO_SENDERS
,
115 MACH_MSG_TYPE_MAKE_SEND_ONCE
,
117 if (kr
!= KERN_SUCCESS
) {
119 * We can't request a notification for our own port! This should
120 * only happen if someone stomped on OUR port (so let's leave
123 SC_log(LOG_NOTICE
, "mach_port_request_notification() failed: %s", mach_error_string(kr
));
127 if (oldNotify
!= MACH_PORT_NULL
) {
128 SC_log(LOG_NOTICE
, "oldNotify != MACH_PORT_NULL");
132 SC_log(LOG_DEBUG
, "+ establish notification request w/port=0x%x (%d) with SCDynamicStore server (%@)",
134 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
139 __MACH_PORT_DEBUG(TRUE
, "*** addNotifyPort", port
);
140 kr
= notifyviaport(storePrivate
->server
, port
, 0, (int *)&sc_status
);
142 if (kr
== MACH_SEND_INVALID_RIGHT
) {
144 * the notifyviaport() call was not able to pass/process
145 * the [notify port] send right. This was most likely due
146 * to the [SCDynamicStore] server having restarted. We
147 * handle this issue by removing the remaining receive
148 * right, reallocating the notify port, and retrying
151 SC_log((try++ < MAX_INVALID_RIGHT_RETRY
) ? LOG_INFO
: LOG_ERR
,
152 "SCDynamicStore callback notifyviaport() w/port 0x%x (%d) failed (try %d): %s",
155 mach_error_string(kr
));
157 removeReceiveRight(store
, port
);
160 if (__SCDynamicStoreCheckRetryAndHandleError(store
,
163 "SCDynamicStore callback notifyviaport()")) {
164 if (kr
== MACH_SEND_INVALID_RIGHT
) {
165 if (try <= MAX_INVALID_RIGHT_RETRY
) {
175 "SCDynamicStore callback notifyviaport() succeeded after %d retr%s w/port 0x%x (%d)",
177 (try == 1) ? "y" : "ies",
181 if (kr
!= KERN_SUCCESS
) {
182 if ((kr
== MACH_SEND_INVALID_DEST
) || (kr
== MIG_SERVER_DIED
)) {
183 /* remove the send right that we tried (but failed) to pass to the server */
184 (void) mach_port_deallocate(mach_task_self(), port
);
186 removeReceiveRight(store
, port
);
190 if (sc_status
!= kSCStatusOK
) {
191 /* something [else] didn't work */
192 removeReceiveRight(store
, port
);
202 return MACH_PORT_NULL
;
207 notifyMPCopyDescription(const void *info
)
209 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
211 return CFStringCreateWithFormat(NULL
,
213 CFSTR("<SCDynamicStore notification MP> {store = %p}"),
219 rlsCallback(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
225 mach_no_senders_notification_t
*buf
= msg
;
226 mach_msg_id_t msgid
= buf
->not_header
.msgh_id
;
227 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
228 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
231 SC_log(LOG_DEBUG
, "mach port callback, %ssignal RLS(%@)",
232 (msgid
== MACH_NOTIFY_NO_SENDERS
) ? "reconnect and " : "",
233 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
236 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
237 /* the server died, disable additional callbacks */
239 SC_log(LOG_DEBUG
, " notifier port closed");
243 if (port
!= storePrivate
->rlsNotifyPort
) {
244 SC_log(LOG_DEBUG
, "why is port != rlsNotifyPort?");
248 /* re-establish notification and inform the client */
249 (void)__SCDynamicStoreReconnectNotifications(store
);
252 /* signal the real runloop source */
253 if (storePrivate
->rls
!= NULL
) {
254 CFRunLoopSourceSignal(storePrivate
->rls
);
262 rlsSchedule(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
264 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
265 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
268 SC_log(LOG_DEBUG
, "schedule notifications for mode %@", mode
);
271 if (storePrivate
->rlsNotifyPort
== NULL
) {
272 CFMachPortContext context
= { 0
276 , notifyMPCopyDescription
281 SC_log(LOG_DEBUG
, " activate callback runloop source");
284 port
= addNotifyPort(store
);
285 if (port
== MACH_PORT_NULL
) {
289 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after addNotifyPort)", port
);
290 storePrivate
->rlsNotifyPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore",
296 storePrivate
->rlsNotifyRLS
= CFMachPortCreateRunLoopSource(NULL
, storePrivate
->rlsNotifyPort
, 0);
297 storePrivate
->rlList
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
301 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
302 /* set notifier active */
303 storePrivate
->notifyStatus
= Using_NotifierInformViaRunLoop
;
305 if (!_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
307 * if we are not already scheduled with this runLoop / runLoopMode
309 CFRunLoopAddSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
310 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after CFRunLoopAddSource)", CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
313 _SC_schedule(store
, rl
, mode
, storePrivate
->rlList
);
321 rlsCancel(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
324 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
325 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
328 SC_log(LOG_DEBUG
, "cancel notifications for mode %@", mode
);
331 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
332 if (_SC_unschedule(store
, rl
, mode
, storePrivate
->rlList
, FALSE
)) {
334 * if currently scheduled on this runLoop / runLoopMode
336 n
= CFArrayGetCount(storePrivate
->rlList
);
337 if (n
== 0 || !_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
339 * if we are no longer scheduled to receive notifications for
340 * this runLoop / runLoopMode
342 CFRunLoopRemoveSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
352 SC_log(LOG_DEBUG
, " cancel callback runloop source");
354 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
356 CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
358 if (storePrivate
->rls
!= NULL
) {
359 // Remove the reference we took on the rls. We do not invalidate
360 // the runloop source and let the client do it when appropriate.
361 CFRelease(storePrivate
->rls
);
362 storePrivate
->rls
= NULL
;
365 if (storePrivate
->rlList
!= NULL
) {
366 CFRelease(storePrivate
->rlList
);
367 storePrivate
->rlList
= NULL
;
370 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
371 /* invalidate & remove the run loop source */
372 CFRunLoopSourceInvalidate(storePrivate
->rlsNotifyRLS
);
373 CFRelease(storePrivate
->rlsNotifyRLS
);
374 storePrivate
->rlsNotifyRLS
= NULL
;
377 if (storePrivate
->rlsNotifyPort
!= NULL
) {
380 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
381 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
382 "*** rlsCancel (before invalidating/releasing CFMachPort)",
385 /* invalidate and release port */
386 CFMachPortInvalidate(storePrivate
->rlsNotifyPort
);
387 CFRelease(storePrivate
->rlsNotifyPort
);
388 storePrivate
->rlsNotifyPort
= NULL
;
390 /* and, finally, remove our receive right */
391 removeReceiveRight(store
, mp
);
395 SC_log(LOG_DEBUG
, " cancel notification request with SCDynamicStore server");
398 if (storePrivate
->server
!= MACH_PORT_NULL
) {
399 kr
= notifycancel(storePrivate
->server
, (int *)&sc_status
);
401 (void) __SCDynamicStoreCheckRetryAndHandleError(store
,
404 "rlsCancel notifycancel()");
406 if (kr
!= KERN_SUCCESS
) {
411 /* set notifier inactive */
412 storePrivate
->notifyStatus
= NotifierNotRegistered
;
420 rlsPerform(void *info
)
422 CFArrayRef changedKeys
= NULL
;
424 void (*context_release
)(const void *);
425 SCDynamicStoreCallBack rlsFunction
;
426 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
427 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
430 SC_log(LOG_DEBUG
, "handling SCDynamicStore changes (%@)",
431 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
434 changedKeys
= SCDynamicStoreCopyNotifiedKeys(store
);
435 if (storePrivate
->disconnectForceCallBack
) {
436 storePrivate
->disconnectForceCallBack
= FALSE
;
437 if (changedKeys
== NULL
) {
438 changedKeys
= CFArrayCreate(NULL
, NULL
, 0, &kCFTypeArrayCallBacks
);
440 } else if ((changedKeys
== NULL
) || (CFArrayGetCount(changedKeys
) == 0)) {
441 /* if no changes or something happened to the server */
445 rlsFunction
= storePrivate
->rlsFunction
;
447 if (storePrivate
->rlsContext
.retain
!= NULL
) {
448 context_info
= (void *)storePrivate
->rlsContext
.retain(storePrivate
->rlsContext
.info
);
449 context_release
= storePrivate
->rlsContext
.release
;
451 context_info
= storePrivate
->rlsContext
.info
;
452 context_release
= NULL
;
454 if (rlsFunction
!= NULL
) {
455 SC_log(LOG_DEBUG
, "+ exec SCDynamicStore callout");
456 (*rlsFunction
)(store
, changedKeys
, context_info
);
457 SC_log(LOG_DEBUG
, "+ done");
459 if (context_release
!= NULL
) {
460 context_release(context_info
);
466 SC_log(LOG_DEBUG
, "done");
469 if (changedKeys
!= NULL
) {
470 CFRelease(changedKeys
);
478 rlsCopyDescription(const void *info
)
480 CFMutableStringRef result
;
481 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
482 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
484 result
= CFStringCreateMutable(NULL
, 0);
485 CFStringAppendFormat(result
, NULL
, CFSTR("<SCDynamicStore RLS> {"));
486 CFStringAppendFormat(result
, NULL
, CFSTR("store = %p"), store
);
487 if (storePrivate
->notifyStatus
== Using_NotifierInformViaRunLoop
) {
488 CFStringRef description
= NULL
;
490 CFStringAppendFormat(result
, NULL
, CFSTR(", callout = %p"), storePrivate
->rlsFunction
);
492 if ((storePrivate
->rlsContext
.info
!= NULL
) && (storePrivate
->rlsContext
.copyDescription
!= NULL
)) {
493 description
= (*storePrivate
->rlsContext
.copyDescription
)(storePrivate
->rlsContext
.info
);
495 if (description
== NULL
) {
496 description
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SCDynamicStore context %p>"), storePrivate
->rlsContext
.info
);
498 if (description
== NULL
) {
499 description
= CFRetain(CFSTR("<no description>"));
501 CFStringAppendFormat(result
, NULL
, CFSTR(", context = %@"), description
);
502 CFRelease(description
);
504 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
511 SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator
,
512 SCDynamicStoreRef store
,
515 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
518 /* sorry, you must provide a session */
519 _SCErrorSet(kSCStatusNoStoreSession
);
523 if (storePrivate
->server
== MACH_PORT_NULL
) {
524 /* sorry, you must have an open session to play */
525 _SCErrorSet(kSCStatusNoStoreServer
);
529 switch (storePrivate
->notifyStatus
) {
530 case NotifierNotRegistered
:
531 case Using_NotifierInformViaRunLoop
:
532 /* OK to enable runloop notification */
535 /* sorry, you can only have one notification registered at once */
536 _SCErrorSet(kSCStatusNotifierActive
);
540 if (storePrivate
->rls
== NULL
) {
541 CFRunLoopSourceContext context
= { 0 // version
542 , (void *)store
// info
544 , CFRelease
// release
545 , rlsCopyDescription
// copyDescription
548 , rlsSchedule
// schedule
549 , rlsCancel
// cancel
550 , rlsPerform
// perform
553 storePrivate
->rls
= CFRunLoopSourceCreate(allocator
, order
, &context
);
554 if (storePrivate
->rls
== NULL
) {
555 _SCErrorSet(kSCStatusFailed
);
559 if (storePrivate
->rls
!= NULL
) {
560 CFRetain(storePrivate
->rls
);
563 return storePrivate
->rls
;
568 SCDynamicStoreSetDispatchQueue(SCDynamicStoreRef store
, dispatch_queue_t queue
)
570 dispatch_group_t drainGroup
= NULL
;
571 dispatch_queue_t drainQueue
= NULL
;
572 dispatch_group_t group
= NULL
;
575 dispatch_source_t source
;
576 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
579 // sorry, you must provide a session
580 _SCErrorSet(kSCStatusNoStoreSession
);
585 if (storePrivate
->dispatchQueue
== NULL
) {
587 _SCErrorSet(kSCStatusInvalidArgument
);
592 SC_log(LOG_DEBUG
, "unschedule notifications from dispatch queue (%@)",
593 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
600 if (storePrivate
->server
== MACH_PORT_NULL
) {
601 // sorry, you must have an open session to play
602 _SCErrorSet(kSCStatusNoStoreServer
);
606 if ((storePrivate
->dispatchQueue
!= NULL
) ||
607 (storePrivate
->rls
!= NULL
) ||
608 (storePrivate
->notifyStatus
!= NotifierNotRegistered
)) {
609 // if already scheduled
610 _SCErrorSet(kSCStatusNotifierActive
);
615 SC_log(LOG_DEBUG
, "schedule notifications for dispatch queue (%@)",
616 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
620 // mark our using of the SCDynamicStore notifications, create and schedule
621 // the notification source/port (storePrivate->dispatchSource), and a bunch
624 storePrivate
->notifyStatus
= Using_NotifierInformViaDispatch
;
626 mp
= addNotifyPort(store
);
627 if (mp
== MACH_PORT_NULL
) {
628 // if we could not schedule the notification
629 _SCErrorSet(kSCStatusFailed
);
631 SC_log(LOG_DEBUG
, "addNotifyPort() failed (%@)",
632 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
637 // retain the dispatch queue
638 storePrivate
->dispatchQueue
= queue
;
639 dispatch_retain(storePrivate
->dispatchQueue
);
642 // We've taken a reference to the callers dispatch_queue and we
643 // want to hold on to that reference until we've processed any/all
644 // notifications. To facilitate this we create a group, dispatch
645 // any notification blocks to via that group, and when the caller
646 // has told us to stop the notifications (unschedule) we wait for
647 // the group to empty and use the group's finalizer to release
648 // our reference to the SCDynamicStore.
650 group
= dispatch_group_create();
651 storePrivate
->dispatchGroup
= group
;
653 dispatch_set_context(storePrivate
->dispatchGroup
, (void *)store
);
654 dispatch_set_finalizer_f(storePrivate
->dispatchGroup
, (dispatch_function_t
)CFRelease
);
656 // create a dispatch source for the mach notifications
657 source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, mp
, 0, queue
);
658 if (source
== NULL
) {
659 SC_log(LOG_NOTICE
, "dispatch_source_create() failed");
661 // remove our receive right
662 removeReceiveRight(store
, mp
);
664 _SCErrorSet(kSCStatusFailed
);
668 dispatch_source_set_event_handler(source
, ^{
672 u_int8_t buf1
[sizeof(mach_msg_empty_rcv_t
) + MAX_TRAILER_SIZE
];
673 u_int8_t buf2
[sizeof(mach_no_senders_notification_t
) + MAX_TRAILER_SIZE
];
674 mach_msg_empty_rcv_t msg
;
675 mach_no_senders_notification_t no_senders
;
678 kr
= mach_msg(¬ify_msg
.msg
.header
, // msg
679 MACH_RCV_MSG
, // options
681 sizeof(notify_msg
), // rcv_size
683 MACH_MSG_TIMEOUT_NONE
, // timeout
684 MACH_PORT_NULL
); // notify
685 if (kr
!= KERN_SUCCESS
) {
686 SC_log(LOG_NOTICE
, "mach_msg() failed, kr=0x%x", kr
);
690 msgid
= notify_msg
.msg
.header
.msgh_id
;
691 mach_msg_destroy(¬ify_msg
.msg
.header
);
694 SC_log(LOG_DEBUG
, "dispatch source callback, queue rlsPerform (%@)",
695 (storePrivate
->name
!= NULL
) ? storePrivate
->name
: CFSTR("?"));
699 dispatch_group_async(group
, queue
, ^{
700 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
701 // re-establish notification and inform the client
702 (void)__SCDynamicStoreReconnectNotifications(store
);
704 rlsPerform(storePrivate
);
709 dispatch_source_set_cancel_handler(source
, ^{
710 __MACH_PORT_DEBUG(TRUE
,
711 "*** SCDynamicStoreSetDispatchQueue (before cancel)",
714 // remove our receive right
715 removeReceiveRight(store
, mp
);
718 dispatch_release(source
);
721 storePrivate
->dispatchSource
= source
;
722 dispatch_resume(source
);
730 if (storePrivate
->dispatchSource
!= NULL
) {
731 dispatch_source_cancel(storePrivate
->dispatchSource
);
732 storePrivate
->dispatchSource
= NULL
;
734 drainGroup
= storePrivate
->dispatchGroup
;
735 storePrivate
->dispatchGroup
= NULL
;
736 drainQueue
= storePrivate
->dispatchQueue
;
737 storePrivate
->dispatchQueue
= NULL
;
739 if ((drainGroup
!= NULL
) && (drainQueue
!= NULL
)) {
740 dispatch_group_notify(drainGroup
, drainQueue
, ^{
741 // release group/queue references
742 dispatch_release(drainQueue
);
743 dispatch_release(drainGroup
); // releases our store reference
747 storePrivate
->notifyStatus
= NotifierNotRegistered
;