2 * Copyright (c) 2000-2005, 2008-2017 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
)
59 mach_no_senders_notification_t
*buf
= msg
;
60 mach_msg_id_t msgid
= buf
->not_header
.msgh_id
;
61 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
62 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
64 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
65 /* the server died, disable additional callbacks */
67 SC_log(LOG_DEBUG
, " notifier port closed");
71 if (port
!= storePrivate
->rlsNotifyPort
) {
72 SC_log(LOG_DEBUG
, "why is port != rlsNotifyPort?");
76 /* re-establish notification and inform the client */
77 (void)__SCDynamicStoreReconnectNotifications(store
);
81 SC_log(LOG_DEBUG
, "mach port callback, signal RLS");
84 /* signal the real runloop source */
85 if (storePrivate
->rls
!= NULL
) {
86 CFRunLoopSourceSignal(storePrivate
->rls
);
94 rlsSchedule(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
96 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
97 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
100 SC_log(LOG_DEBUG
, "schedule notifications for mode %@",
101 (rl
!= NULL
) ? mode
: CFSTR("libdispatch"));
104 if (storePrivate
->rlsNotifyPort
== NULL
) {
105 CFMachPortContext context
= { 0
109 , notifyMPCopyDescription
112 mach_port_t oldNotify
;
113 #ifdef HAVE_MACHPORT_GUARDS
114 mach_port_options_t opts
;
115 #endif // HAVE_MACHPORT_GUARDS
120 SC_log(LOG_DEBUG
, " activate callback runloop source");
123 /* allocate a mach port for the SCDynamicStore notifications */
127 #ifdef HAVE_MACHPORT_GUARDS
128 bzero(&opts
, sizeof(opts
));
129 opts
.flags
= MPO_CONTEXT_AS_GUARD
|MPO_INSERT_SEND_RIGHT
;
131 kr
= mach_port_construct(mach_task_self(), &opts
, (mach_port_context_t
)store
, &port
);
132 #else // HAVE_MACHPORT_GUARDS
133 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &port
);
134 #endif // HAVE_MACHPORT_GUARDS
136 if (kr
!= KERN_SUCCESS
) {
137 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
138 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
146 #ifndef HAVE_MACHPORT_GUARDS
147 kr
= mach_port_insert_right(mach_task_self(),
150 MACH_MSG_TYPE_MAKE_SEND
);
151 if (kr
!= KERN_SUCCESS
) {
153 * We can't insert a send right into our own port! This should
154 * only happen if someone stomped on OUR port (so let's leave
157 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
160 #endif // HAVE_MACHPORT_GUARDS
162 /* Request a notification when/if the server dies */
163 kr
= mach_port_request_notification(mach_task_self(),
165 MACH_NOTIFY_NO_SENDERS
,
168 MACH_MSG_TYPE_MAKE_SEND_ONCE
,
170 if (kr
!= KERN_SUCCESS
) {
172 * We can't request a notification for our own port! This should
173 * only happen if someone stomped on OUR port (so let's leave
176 SC_log(LOG_NOTICE
, "mach_port_request_notification() failed: %s", mach_error_string(kr
));
180 if (oldNotify
!= MACH_PORT_NULL
) {
181 SC_log(LOG_NOTICE
, "oldNotify != MACH_PORT_NULL");
185 SC_log(LOG_DEBUG
, " establish notification request with SCDynamicStore server");
188 #ifdef VERBOSE_ACTIVITY_LOGGING
189 os_activity_scope(storePrivate
->activity
);
190 #endif // VERBOSE_ACTIVITY_LOGGING
194 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule", port
);
195 kr
= notifyviaport(storePrivate
->server
, port
, 0, (int *)&sc_status
);
197 if (__SCDynamicStoreCheckRetryAndHandleError(store
,
200 "rlsSchedule notifyviaport()")) {
204 if (kr
!= KERN_SUCCESS
) {
205 if ((kr
== MACH_SEND_INVALID_DEST
) || (kr
== MIG_SERVER_DIED
)) {
206 /* remove the send right that we tried (but failed) to pass to the server */
207 (void) mach_port_deallocate(mach_task_self(), port
);
210 /* remove our receive right */
211 #ifdef HAVE_MACHPORT_GUARDS
212 (void) mach_port_destruct(mach_task_self(), port
, 0, (mach_port_context_t
)store
);
213 #else // HAVE_MACHPORT_GUARDS
214 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
215 #endif // HAVE_MACHPORT_GUARDS
219 if (sc_status
!= kSCStatusOK
) {
220 /* something [else] didn't work, remove our receive right */
221 #ifdef HAVE_MACHPORT_GUARDS
222 (void) mach_port_destruct(mach_task_self(), port
, 0, (mach_port_context_t
)store
);
223 #else // HAVE_MACHPORT_GUARDS
224 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
225 #endif // HAVE_MACHPORT_GUARDS
229 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after notifyviaport)", port
);
230 storePrivate
->rlsNotifyPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore",
236 storePrivate
->rlsNotifyRLS
= CFMachPortCreateRunLoopSource(NULL
, storePrivate
->rlsNotifyPort
, 0);
237 storePrivate
->rlList
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
241 if ((rl
!= NULL
) && (storePrivate
->rlsNotifyRLS
!= NULL
)) {
242 /* set notifier active */
243 storePrivate
->notifyStatus
= Using_NotifierInformViaRunLoop
;
245 if (!_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
247 * if we are not already scheduled with this runLoop / runLoopMode
249 CFRunLoopAddSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
250 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after CFRunLoopAddSource)", CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
253 _SC_schedule(store
, rl
, mode
, storePrivate
->rlList
);
261 rlsCancel(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
264 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
265 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
268 SC_log(LOG_DEBUG
, "cancel notifications for mode %@",
269 (rl
!= NULL
) ? mode
: CFSTR("libdispatch"));
272 if ((rl
!= NULL
) && (storePrivate
->rlsNotifyRLS
!= NULL
)) {
273 if (_SC_unschedule(store
, rl
, mode
, storePrivate
->rlList
, FALSE
)) {
275 * if currently scheduled on this runLoop / runLoopMode
277 n
= CFArrayGetCount(storePrivate
->rlList
);
278 if (n
== 0 || !_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
280 * if we are no longer scheduled to receive notifications for
281 * this runLoop / runLoopMode
283 CFRunLoopRemoveSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
293 SC_log(LOG_DEBUG
, " cancel callback runloop source");
295 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
297 CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
299 if (storePrivate
->rls
!= NULL
) {
300 // Remove the reference we took on the rls. We do not invalidate
301 // the runloop source and let the client do it when appropriate.
302 CFRelease(storePrivate
->rls
);
303 storePrivate
->rls
= NULL
;
306 if (storePrivate
->rlList
!= NULL
) {
307 CFRelease(storePrivate
->rlList
);
308 storePrivate
->rlList
= NULL
;
311 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
312 /* invalidate & remove the run loop source */
313 CFRunLoopSourceInvalidate(storePrivate
->rlsNotifyRLS
);
314 CFRelease(storePrivate
->rlsNotifyRLS
);
315 storePrivate
->rlsNotifyRLS
= NULL
;
318 if (storePrivate
->rlsNotifyPort
!= NULL
) {
321 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
322 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
323 "*** rlsCancel (before invalidating/releasing CFMachPort)",
326 /* invalidate and release port */
327 CFMachPortInvalidate(storePrivate
->rlsNotifyPort
);
328 CFRelease(storePrivate
->rlsNotifyPort
);
329 storePrivate
->rlsNotifyPort
= NULL
;
331 /* and, finally, remove our receive right */
332 #ifdef HAVE_MACHPORT_GUARDS
333 (void) mach_port_destruct(mach_task_self(), mp
, 0, (mach_port_context_t
)store
);
334 #else // HAVE_MACHPORT_GUARDS
335 (void) mach_port_mod_refs(mach_task_self(), mp
, MACH_PORT_RIGHT_RECEIVE
, -1);
336 #endif // HAVE_MACHPORT_GUARDS
340 SC_log(LOG_DEBUG
, " cancel notification request with SCDynamicStore server");
343 #ifdef VERBOSE_ACTIVITY_LOGGING
344 os_activity_scope(storePrivate
->activity
);
345 #endif // VERBOSE_ACTIVITY_LOGGING
347 if (storePrivate
->server
!= MACH_PORT_NULL
) {
348 kr
= notifycancel(storePrivate
->server
, (int *)&sc_status
);
350 (void) __SCDynamicStoreCheckRetryAndHandleError(store
,
353 "rlsCancel notifycancel()");
355 if (kr
!= KERN_SUCCESS
) {
360 /* set notifier inactive */
361 storePrivate
->notifyStatus
= NotifierNotRegistered
;
369 rlsPerform(void *info
)
371 CFArrayRef changedKeys
= NULL
;
373 void (*context_release
)(const void *);
374 SCDynamicStoreCallBack rlsFunction
;
375 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
376 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
379 SC_log(LOG_DEBUG
, " executing notification function");
382 changedKeys
= SCDynamicStoreCopyNotifiedKeys(store
);
383 if (storePrivate
->disconnectForceCallBack
) {
384 storePrivate
->disconnectForceCallBack
= FALSE
;
385 if (changedKeys
== NULL
) {
386 changedKeys
= CFArrayCreate(NULL
, NULL
, 0, &kCFTypeArrayCallBacks
);
388 } else if ((changedKeys
== NULL
) || (CFArrayGetCount(changedKeys
) == 0)) {
389 /* if no changes or something happened to the server */
393 rlsFunction
= storePrivate
->rlsFunction
;
395 if (storePrivate
->rlsContext
.retain
!= NULL
) {
396 context_info
= (void *)storePrivate
->rlsContext
.retain(storePrivate
->rlsContext
.info
);
397 context_release
= storePrivate
->rlsContext
.release
;
399 context_info
= storePrivate
->rlsContext
.info
;
400 context_release
= NULL
;
402 if (rlsFunction
!= NULL
) {
403 SC_log(LOG_DEBUG
, "exec SCDynamicStore callout");
404 (*rlsFunction
)(store
, changedKeys
, context_info
);
406 if (context_release
!= NULL
) {
407 context_release(context_info
);
413 SC_log(LOG_DEBUG
, " done!");
416 if (changedKeys
!= NULL
) {
417 CFRelease(changedKeys
);
425 rlsCopyDescription(const void *info
)
427 CFMutableStringRef result
;
428 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
429 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
431 result
= CFStringCreateMutable(NULL
, 0);
432 CFStringAppendFormat(result
, NULL
, CFSTR("<SCDynamicStore RLS> {"));
433 CFStringAppendFormat(result
, NULL
, CFSTR("store = %p"), store
);
434 if (storePrivate
->notifyStatus
== Using_NotifierInformViaRunLoop
) {
435 CFStringRef description
= NULL
;
437 CFStringAppendFormat(result
, NULL
, CFSTR(", callout = %p"), storePrivate
->rlsFunction
);
439 if ((storePrivate
->rlsContext
.info
!= NULL
) && (storePrivate
->rlsContext
.copyDescription
!= NULL
)) {
440 description
= (*storePrivate
->rlsContext
.copyDescription
)(storePrivate
->rlsContext
.info
);
442 if (description
== NULL
) {
443 description
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SCDynamicStore context %p>"), storePrivate
->rlsContext
.info
);
445 if (description
== NULL
) {
446 description
= CFRetain(CFSTR("<no description>"));
448 CFStringAppendFormat(result
, NULL
, CFSTR(", context = %@"), description
);
449 CFRelease(description
);
451 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
458 SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator
,
459 SCDynamicStoreRef store
,
462 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
465 /* sorry, you must provide a session */
466 _SCErrorSet(kSCStatusNoStoreSession
);
470 if (storePrivate
->server
== MACH_PORT_NULL
) {
471 /* sorry, you must have an open session to play */
472 _SCErrorSet(kSCStatusNoStoreServer
);
476 switch (storePrivate
->notifyStatus
) {
477 case NotifierNotRegistered
:
478 case Using_NotifierInformViaRunLoop
:
479 /* OK to enable runloop notification */
482 /* sorry, you can only have one notification registered at once */
483 _SCErrorSet(kSCStatusNotifierActive
);
487 if (storePrivate
->rls
== NULL
) {
488 CFRunLoopSourceContext context
= { 0 // version
489 , (void *)store
// info
491 , CFRelease
// release
492 , rlsCopyDescription
// copyDescription
495 , rlsSchedule
// schedule
496 , rlsCancel
// cancel
497 , rlsPerform
// perform
500 storePrivate
->rls
= CFRunLoopSourceCreate(allocator
, order
, &context
);
501 if (storePrivate
->rls
== NULL
) {
502 _SCErrorSet(kSCStatusFailed
);
506 if (storePrivate
->rls
!= NULL
) {
507 CFRetain(storePrivate
->rls
);
510 return storePrivate
->rls
;
515 SCDynamicStoreSetDispatchQueue(SCDynamicStoreRef store
, dispatch_queue_t queue
)
517 dispatch_group_t drainGroup
= NULL
;
518 dispatch_queue_t drainQueue
= NULL
;
519 dispatch_group_t group
= NULL
;
522 dispatch_source_t source
;
523 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
526 // sorry, you must provide a session
527 _SCErrorSet(kSCStatusNoStoreSession
);
532 if (storePrivate
->dispatchQueue
== NULL
) {
533 _SCErrorSet(kSCStatusInvalidArgument
);
541 if (storePrivate
->server
== MACH_PORT_NULL
) {
542 // sorry, you must have an open session to play
543 _SCErrorSet(kSCStatusNoStoreServer
);
547 if ((storePrivate
->dispatchQueue
!= NULL
) || (storePrivate
->rls
!= NULL
)) {
548 _SCErrorSet(kSCStatusInvalidArgument
);
552 if (storePrivate
->notifyStatus
!= NotifierNotRegistered
) {
553 // sorry, you can only have one notification registered at once...
554 _SCErrorSet(kSCStatusNotifierActive
);
559 * mark our using of the SCDynamicStore notifications, create and schedule
560 * the notification port (storePrivate->rlsNotifyPort), and a bunch of other
563 storePrivate
->notifyStatus
= Using_NotifierInformViaDispatch
;
564 rlsSchedule((void*)store
, NULL
, NULL
);
565 if (storePrivate
->rlsNotifyPort
== NULL
) {
566 /* if we could not schedule the notification */
567 _SCErrorSet(kSCStatusFailed
);
571 // retain the dispatch queue
572 storePrivate
->dispatchQueue
= queue
;
573 dispatch_retain(storePrivate
->dispatchQueue
);
576 // We've taken a reference to the callers dispatch_queue and we
577 // want to hold on to that reference until we've processed any/all
578 // notifications. To facilitate this we create a group, dispatch
579 // any notification blocks to via that group, and when the caller
580 // has told us to stop the notifications (unschedule) we wait for
581 // the group to empty and use the group's finalizer to release
582 // our reference to the SCDynamicStore.
584 group
= dispatch_group_create();
585 storePrivate
->dispatchGroup
= group
;
587 dispatch_set_context(storePrivate
->dispatchGroup
, (void *)store
);
588 dispatch_set_finalizer_f(storePrivate
->dispatchGroup
, (dispatch_function_t
)CFRelease
);
590 // create a dispatch source for the mach notifications
591 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
592 source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, mp
, 0, queue
);
593 if (source
== NULL
) {
594 SC_log(LOG_NOTICE
, "dispatch_source_create() failed");
595 _SCErrorSet(kSCStatusFailed
);
599 dispatch_source_set_event_handler(source
, ^{
603 u_int8_t buf
[sizeof(mach_msg_empty_t
) + MAX_TRAILER_SIZE
];
604 mach_msg_empty_rcv_t msg
;
605 mach_no_senders_notification_t no_senders
;
608 kr
= mach_msg(¬ify_msg
.msg
.header
, // msg
609 MACH_RCV_MSG
, // options
611 sizeof(notify_msg
), // rcv_size
613 MACH_MSG_TIMEOUT_NONE
, // timeout
614 MACH_PORT_NULL
); // notify
615 if (kr
!= KERN_SUCCESS
) {
616 SC_log(LOG_NOTICE
, "mach_msg() failed, kr=0x%x", kr
);
620 msgid
= notify_msg
.msg
.header
.msgh_id
;
623 SC_log(LOG_DEBUG
, "dispatch source callback, queue rlsPerform");
627 dispatch_group_async(group
, queue
, ^{
628 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
629 // re-establish notification and inform the client
630 (void)__SCDynamicStoreReconnectNotifications(store
);
632 rlsPerform(storePrivate
);
637 dispatch_source_set_cancel_handler(source
, ^{
638 dispatch_release(source
);
641 storePrivate
->dispatchSource
= source
;
642 dispatch_resume(source
);
650 if (storePrivate
->dispatchSource
!= NULL
) {
651 dispatch_source_cancel(storePrivate
->dispatchSource
);
652 storePrivate
->dispatchSource
= NULL
;
654 drainGroup
= storePrivate
->dispatchGroup
;
655 storePrivate
->dispatchGroup
= NULL
;
656 drainQueue
= storePrivate
->dispatchQueue
;
657 storePrivate
->dispatchQueue
= NULL
;
659 rlsCancel((void*)store
, NULL
, NULL
);
661 if ((drainGroup
!= NULL
) && (drainQueue
!= NULL
)) {
662 dispatch_group_notify(drainGroup
, drainQueue
, ^{
663 // release group/queue references
664 dispatch_release(drainQueue
);
665 dispatch_release(drainGroup
); // releases our store reference
669 storePrivate
->notifyStatus
= NotifierNotRegistered
;