2 * Copyright (c) 2000-2005, 2008-2018 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 acquireNotifyPort(SCDynamicStoreRef store
)
47 mach_port_t oldNotify
;
48 #ifdef HAVE_MACHPORT_GUARDS
49 mach_port_options_t opts
;
50 #endif // HAVE_MACHPORT_GUARDS
53 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
55 /* allocate a mach port for the SCDynamicStore notifications */
59 #ifdef HAVE_MACHPORT_GUARDS
60 memset(&opts
, 0, sizeof(opts
));
61 opts
.flags
= MPO_CONTEXT_AS_GUARD
|MPO_INSERT_SEND_RIGHT
;
63 kr
= mach_port_construct(mach_task_self(), &opts
, (mach_port_context_t
)store
, &port
);
64 #else // HAVE_MACHPORT_GUARDS
65 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &port
);
66 #endif // HAVE_MACHPORT_GUARDS
68 if (kr
!= KERN_SUCCESS
) {
69 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
70 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
74 return MACH_PORT_NULL
;
78 #ifndef HAVE_MACHPORT_GUARDS
79 kr
= mach_port_insert_right(mach_task_self(),
82 MACH_MSG_TYPE_MAKE_SEND
);
83 if (kr
!= KERN_SUCCESS
) {
85 * We can't insert a send right into our own port! This should
86 * only happen if someone stomped on OUR port (so let's leave
89 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
90 return MACH_PORT_NULL
;
92 #endif // HAVE_MACHPORT_GUARDS
94 /* Request a notification when/if the server dies */
95 kr
= mach_port_request_notification(mach_task_self(),
97 MACH_NOTIFY_NO_SENDERS
,
100 MACH_MSG_TYPE_MAKE_SEND_ONCE
,
102 if (kr
!= KERN_SUCCESS
) {
104 * We can't request a notification for our own port! This should
105 * only happen if someone stomped on OUR port (so let's leave
108 SC_log(LOG_NOTICE
, "mach_port_request_notification() failed: %s", mach_error_string(kr
));
109 return MACH_PORT_NULL
;
112 if (oldNotify
!= MACH_PORT_NULL
) {
113 SC_log(LOG_NOTICE
, "oldNotify != MACH_PORT_NULL");
117 SC_log(LOG_DEBUG
, " establish notification request with SCDynamicStore server");
120 #ifdef VERBOSE_ACTIVITY_LOGGING
121 os_activity_scope(storePrivate
->activity
);
122 #endif // VERBOSE_ACTIVITY_LOGGING
126 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule", port
);
127 kr
= notifyviaport(storePrivate
->server
, port
, 0, (int *)&sc_status
);
129 if (__SCDynamicStoreCheckRetryAndHandleError(store
,
132 "rlsSchedule notifyviaport()")) {
136 if (kr
!= KERN_SUCCESS
) {
137 if ((kr
== MACH_SEND_INVALID_DEST
) || (kr
== MIG_SERVER_DIED
)) {
138 /* remove the send right that we tried (but failed) to pass to the server */
139 (void) mach_port_deallocate(mach_task_self(), port
);
142 /* remove our receive right */
143 #ifdef HAVE_MACHPORT_GUARDS
144 (void) mach_port_destruct(mach_task_self(), port
, 0, (mach_port_context_t
)store
);
145 #else // HAVE_MACHPORT_GUARDS
146 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
147 #endif // HAVE_MACHPORT_GUARDS
148 return MACH_PORT_NULL
;
151 if (sc_status
!= kSCStatusOK
) {
152 /* something [else] didn't work, remove our receive right */
153 #ifdef HAVE_MACHPORT_GUARDS
154 (void) mach_port_destruct(mach_task_self(), port
, 0, (mach_port_context_t
)store
);
155 #else // HAVE_MACHPORT_GUARDS
156 (void) mach_port_mod_refs(mach_task_self(), port
, MACH_PORT_RIGHT_RECEIVE
, -1);
157 #endif // HAVE_MACHPORT_GUARDS
158 return MACH_PORT_NULL
;
166 notifyMPCopyDescription(const void *info
)
168 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
170 return CFStringCreateWithFormat(NULL
,
172 CFSTR("<SCDynamicStore notification MP> {store = %p}"),
178 rlsCallback(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
184 mach_no_senders_notification_t
*buf
= msg
;
185 mach_msg_id_t msgid
= buf
->not_header
.msgh_id
;
186 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
187 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
189 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
190 /* the server died, disable additional callbacks */
192 SC_log(LOG_DEBUG
, " notifier port closed");
196 if (port
!= storePrivate
->rlsNotifyPort
) {
197 SC_log(LOG_DEBUG
, "why is port != rlsNotifyPort?");
201 /* re-establish notification and inform the client */
202 (void)__SCDynamicStoreReconnectNotifications(store
);
206 SC_log(LOG_DEBUG
, "mach port callback, signal RLS");
209 /* signal the real runloop source */
210 if (storePrivate
->rls
!= NULL
) {
211 CFRunLoopSourceSignal(storePrivate
->rls
);
219 rlsSchedule(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
221 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
222 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
225 SC_log(LOG_DEBUG
, "schedule notifications for mode %@", mode
);
228 if (storePrivate
->rlsNotifyPort
== NULL
) {
229 CFMachPortContext context
= { 0
233 , notifyMPCopyDescription
238 SC_log(LOG_DEBUG
, " activate callback runloop source");
241 port
= acquireNotifyPort(store
);
242 if (port
== MACH_PORT_NULL
) {
246 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after notifyviaport)", port
);
247 storePrivate
->rlsNotifyPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore",
253 storePrivate
->rlsNotifyRLS
= CFMachPortCreateRunLoopSource(NULL
, storePrivate
->rlsNotifyPort
, 0);
254 storePrivate
->rlList
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
258 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
259 /* set notifier active */
260 storePrivate
->notifyStatus
= Using_NotifierInformViaRunLoop
;
262 if (!_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
264 * if we are not already scheduled with this runLoop / runLoopMode
266 CFRunLoopAddSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
267 __MACH_PORT_DEBUG(TRUE
, "*** rlsSchedule (after CFRunLoopAddSource)", CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
270 _SC_schedule(store
, rl
, mode
, storePrivate
->rlList
);
278 rlsCancel(void *info
, CFRunLoopRef rl
, CFStringRef mode
)
281 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
282 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
285 SC_log(LOG_DEBUG
, "cancel notifications for mode %@", mode
);
288 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
289 if (_SC_unschedule(store
, rl
, mode
, storePrivate
->rlList
, FALSE
)) {
291 * if currently scheduled on this runLoop / runLoopMode
293 n
= CFArrayGetCount(storePrivate
->rlList
);
294 if (n
== 0 || !_SC_isScheduled(store
, rl
, mode
, storePrivate
->rlList
)) {
296 * if we are no longer scheduled to receive notifications for
297 * this runLoop / runLoopMode
299 CFRunLoopRemoveSource(rl
, storePrivate
->rlsNotifyRLS
, mode
);
309 SC_log(LOG_DEBUG
, " cancel callback runloop source");
311 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
313 CFMachPortGetPort(storePrivate
->rlsNotifyPort
));
315 if (storePrivate
->rls
!= NULL
) {
316 // Remove the reference we took on the rls. We do not invalidate
317 // the runloop source and let the client do it when appropriate.
318 CFRelease(storePrivate
->rls
);
319 storePrivate
->rls
= NULL
;
322 if (storePrivate
->rlList
!= NULL
) {
323 CFRelease(storePrivate
->rlList
);
324 storePrivate
->rlList
= NULL
;
327 if (storePrivate
->rlsNotifyRLS
!= NULL
) {
328 /* invalidate & remove the run loop source */
329 CFRunLoopSourceInvalidate(storePrivate
->rlsNotifyRLS
);
330 CFRelease(storePrivate
->rlsNotifyRLS
);
331 storePrivate
->rlsNotifyRLS
= NULL
;
334 if (storePrivate
->rlsNotifyPort
!= NULL
) {
337 mp
= CFMachPortGetPort(storePrivate
->rlsNotifyPort
);
338 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
339 "*** rlsCancel (before invalidating/releasing CFMachPort)",
342 /* invalidate and release port */
343 CFMachPortInvalidate(storePrivate
->rlsNotifyPort
);
344 CFRelease(storePrivate
->rlsNotifyPort
);
345 storePrivate
->rlsNotifyPort
= NULL
;
347 /* and, finally, remove our receive right */
348 #ifdef HAVE_MACHPORT_GUARDS
349 (void) mach_port_destruct(mach_task_self(), mp
, 0, (mach_port_context_t
)store
);
350 #else // HAVE_MACHPORT_GUARDS
351 (void) mach_port_mod_refs(mach_task_self(), mp
, MACH_PORT_RIGHT_RECEIVE
, -1);
352 #endif // HAVE_MACHPORT_GUARDS
356 SC_log(LOG_DEBUG
, " cancel notification request with SCDynamicStore server");
359 #ifdef VERBOSE_ACTIVITY_LOGGING
360 os_activity_scope(storePrivate
->activity
);
361 #endif // VERBOSE_ACTIVITY_LOGGING
363 if (storePrivate
->server
!= MACH_PORT_NULL
) {
364 kr
= notifycancel(storePrivate
->server
, (int *)&sc_status
);
366 (void) __SCDynamicStoreCheckRetryAndHandleError(store
,
369 "rlsCancel notifycancel()");
371 if (kr
!= KERN_SUCCESS
) {
376 /* set notifier inactive */
377 storePrivate
->notifyStatus
= NotifierNotRegistered
;
385 rlsPerform(void *info
)
387 CFArrayRef changedKeys
= NULL
;
389 void (*context_release
)(const void *);
390 SCDynamicStoreCallBack rlsFunction
;
391 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
392 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
395 SC_log(LOG_DEBUG
, " executing notification function");
398 changedKeys
= SCDynamicStoreCopyNotifiedKeys(store
);
399 if (storePrivate
->disconnectForceCallBack
) {
400 storePrivate
->disconnectForceCallBack
= FALSE
;
401 if (changedKeys
== NULL
) {
402 changedKeys
= CFArrayCreate(NULL
, NULL
, 0, &kCFTypeArrayCallBacks
);
404 } else if ((changedKeys
== NULL
) || (CFArrayGetCount(changedKeys
) == 0)) {
405 /* if no changes or something happened to the server */
409 rlsFunction
= storePrivate
->rlsFunction
;
411 if (storePrivate
->rlsContext
.retain
!= NULL
) {
412 context_info
= (void *)storePrivate
->rlsContext
.retain(storePrivate
->rlsContext
.info
);
413 context_release
= storePrivate
->rlsContext
.release
;
415 context_info
= storePrivate
->rlsContext
.info
;
416 context_release
= NULL
;
418 if (rlsFunction
!= NULL
) {
419 SC_log(LOG_DEBUG
, "exec SCDynamicStore callout");
420 (*rlsFunction
)(store
, changedKeys
, context_info
);
422 if (context_release
!= NULL
) {
423 context_release(context_info
);
429 SC_log(LOG_DEBUG
, " done!");
432 if (changedKeys
!= NULL
) {
433 CFRelease(changedKeys
);
441 rlsCopyDescription(const void *info
)
443 CFMutableStringRef result
;
444 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
445 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
447 result
= CFStringCreateMutable(NULL
, 0);
448 CFStringAppendFormat(result
, NULL
, CFSTR("<SCDynamicStore RLS> {"));
449 CFStringAppendFormat(result
, NULL
, CFSTR("store = %p"), store
);
450 if (storePrivate
->notifyStatus
== Using_NotifierInformViaRunLoop
) {
451 CFStringRef description
= NULL
;
453 CFStringAppendFormat(result
, NULL
, CFSTR(", callout = %p"), storePrivate
->rlsFunction
);
455 if ((storePrivate
->rlsContext
.info
!= NULL
) && (storePrivate
->rlsContext
.copyDescription
!= NULL
)) {
456 description
= (*storePrivate
->rlsContext
.copyDescription
)(storePrivate
->rlsContext
.info
);
458 if (description
== NULL
) {
459 description
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SCDynamicStore context %p>"), storePrivate
->rlsContext
.info
);
461 if (description
== NULL
) {
462 description
= CFRetain(CFSTR("<no description>"));
464 CFStringAppendFormat(result
, NULL
, CFSTR(", context = %@"), description
);
465 CFRelease(description
);
467 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
474 SCDynamicStoreCreateRunLoopSource(CFAllocatorRef allocator
,
475 SCDynamicStoreRef store
,
478 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
481 /* sorry, you must provide a session */
482 _SCErrorSet(kSCStatusNoStoreSession
);
486 if (storePrivate
->server
== MACH_PORT_NULL
) {
487 /* sorry, you must have an open session to play */
488 _SCErrorSet(kSCStatusNoStoreServer
);
492 switch (storePrivate
->notifyStatus
) {
493 case NotifierNotRegistered
:
494 case Using_NotifierInformViaRunLoop
:
495 /* OK to enable runloop notification */
498 /* sorry, you can only have one notification registered at once */
499 _SCErrorSet(kSCStatusNotifierActive
);
503 if (storePrivate
->rls
== NULL
) {
504 CFRunLoopSourceContext context
= { 0 // version
505 , (void *)store
// info
507 , CFRelease
// release
508 , rlsCopyDescription
// copyDescription
511 , rlsSchedule
// schedule
512 , rlsCancel
// cancel
513 , rlsPerform
// perform
516 storePrivate
->rls
= CFRunLoopSourceCreate(allocator
, order
, &context
);
517 if (storePrivate
->rls
== NULL
) {
518 _SCErrorSet(kSCStatusFailed
);
522 if (storePrivate
->rls
!= NULL
) {
523 CFRetain(storePrivate
->rls
);
526 return storePrivate
->rls
;
531 SCDynamicStoreSetDispatchQueue(SCDynamicStoreRef store
, dispatch_queue_t queue
)
533 dispatch_group_t drainGroup
= NULL
;
534 dispatch_queue_t drainQueue
= NULL
;
535 dispatch_group_t group
= NULL
;
538 dispatch_source_t source
;
539 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)store
;
542 // sorry, you must provide a session
543 _SCErrorSet(kSCStatusNoStoreSession
);
548 if (storePrivate
->dispatchQueue
== NULL
) {
550 _SCErrorSet(kSCStatusInvalidArgument
);
558 if (storePrivate
->server
== MACH_PORT_NULL
) {
559 // sorry, you must have an open session to play
560 _SCErrorSet(kSCStatusNoStoreServer
);
564 if ((storePrivate
->dispatchQueue
!= NULL
) ||
565 (storePrivate
->rls
!= NULL
) ||
566 (storePrivate
->notifyStatus
!= NotifierNotRegistered
)) {
567 // if already scheduled
568 _SCErrorSet(kSCStatusNotifierActive
);
573 SC_log(LOG_DEBUG
, "schedule notifications for dispatch queue");
577 // mark our using of the SCDynamicStore notifications, create and schedule
578 // the notification source/port (storePrivate->dispatchSource), and a bunch
581 storePrivate
->notifyStatus
= Using_NotifierInformViaDispatch
;
583 mp
= acquireNotifyPort(store
);
584 if (mp
== MACH_PORT_NULL
) {
585 // if we could not schedule the notification
586 _SCErrorSet(kSCStatusFailed
);
590 // retain the dispatch queue
591 storePrivate
->dispatchQueue
= queue
;
592 dispatch_retain(storePrivate
->dispatchQueue
);
595 // We've taken a reference to the callers dispatch_queue and we
596 // want to hold on to that reference until we've processed any/all
597 // notifications. To facilitate this we create a group, dispatch
598 // any notification blocks to via that group, and when the caller
599 // has told us to stop the notifications (unschedule) we wait for
600 // the group to empty and use the group's finalizer to release
601 // our reference to the SCDynamicStore.
603 group
= dispatch_group_create();
604 storePrivate
->dispatchGroup
= group
;
606 dispatch_set_context(storePrivate
->dispatchGroup
, (void *)store
);
607 dispatch_set_finalizer_f(storePrivate
->dispatchGroup
, (dispatch_function_t
)CFRelease
);
609 // create a dispatch source for the mach notifications
610 source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, mp
, 0, queue
);
611 if (source
== NULL
) {
612 SC_log(LOG_NOTICE
, "dispatch_source_create() failed");
614 // remove our receive right
615 #ifdef HAVE_MACHPORT_GUARDS
616 (void) mach_port_destruct(mach_task_self(), mp
, 0, (mach_port_context_t
)store
);
617 #else // HAVE_MACHPORT_GUARDS
618 (void) mach_port_mod_refs(mach_task_self(), mp
, MACH_PORT_RIGHT_RECEIVE
, -1);
619 #endif // HAVE_MACHPORT_GUARDS
621 _SCErrorSet(kSCStatusFailed
);
625 dispatch_source_set_event_handler(source
, ^{
629 u_int8_t buf
[sizeof(mach_msg_empty_t
) + MAX_TRAILER_SIZE
];
630 mach_msg_empty_rcv_t msg
;
631 mach_no_senders_notification_t no_senders
;
634 kr
= mach_msg(¬ify_msg
.msg
.header
, // msg
635 MACH_RCV_MSG
, // options
637 sizeof(notify_msg
), // rcv_size
639 MACH_MSG_TIMEOUT_NONE
, // timeout
640 MACH_PORT_NULL
); // notify
641 if (kr
!= KERN_SUCCESS
) {
642 SC_log(LOG_NOTICE
, "mach_msg() failed, kr=0x%x", kr
);
646 msgid
= notify_msg
.msg
.header
.msgh_id
;
647 mach_msg_destroy(¬ify_msg
.msg
.header
);
650 SC_log(LOG_DEBUG
, "dispatch source callback, queue rlsPerform");
654 dispatch_group_async(group
, queue
, ^{
655 if (msgid
== MACH_NOTIFY_NO_SENDERS
) {
656 // re-establish notification and inform the client
657 (void)__SCDynamicStoreReconnectNotifications(store
);
659 rlsPerform(storePrivate
);
664 dispatch_source_set_cancel_handler(source
, ^{
665 __MACH_PORT_DEBUG((storePrivate
->rlsNotifyPort
!= NULL
),
666 "*** SCDynamicStoreSetDispatchQueue (before releasing source/port)",
669 // remove our receive right
670 #ifdef HAVE_MACHPORT_GUARDS
671 (void) mach_port_destruct(mach_task_self(), mp
, 0, (mach_port_context_t
)store
);
672 #else // HAVE_MACHPORT_GUARDS
673 (void) mach_port_mod_refs(mach_task_self(), mp
, MACH_PORT_RIGHT_RECEIVE
, -1);
674 #endif // HAVE_MACHPORT_GUARDS
677 dispatch_release(source
);
680 storePrivate
->dispatchSource
= source
;
681 dispatch_resume(source
);
688 SC_log(LOG_DEBUG
, "cancel notifications for dispatch queue");
693 if (storePrivate
->dispatchSource
!= NULL
) {
694 dispatch_source_cancel(storePrivate
->dispatchSource
);
695 storePrivate
->dispatchSource
= NULL
;
697 drainGroup
= storePrivate
->dispatchGroup
;
698 storePrivate
->dispatchGroup
= NULL
;
699 drainQueue
= storePrivate
->dispatchQueue
;
700 storePrivate
->dispatchQueue
= NULL
;
702 if ((drainGroup
!= NULL
) && (drainQueue
!= NULL
)) {
703 dispatch_group_notify(drainGroup
, drainQueue
, ^{
704 // release group/queue references
705 dispatch_release(drainQueue
);
706 dispatch_release(drainGroup
); // releases our store reference
710 storePrivate
->notifyStatus
= NotifierNotRegistered
;