Add an observer for both AF_INET (IPv4) and AF_INET6 (IPv6) DNS records for connection hosts. Fixes #12632.
When we're determining reachability, we consult all entries for a host, instead of the one that changed. We only add one observer for each record type.
2 // AIHostReachabilityMonitor.m
3 // AIUtilities.framework
5 // Created by Mac-arena the Bored Zo on 2005-02-11.
8 #import "AIHostReachabilityMonitor.h"
9 #import "AISleepNotification.h"
10 #import <SystemConfiguration/SystemConfiguration.h>
13 #import <netinet/in.h>
15 #define CONNECTIVITY_DEBUG FALSE
17 static AIHostReachabilityMonitor *singleton = nil;
19 @interface AIHostReachabilityMonitor ()
20 - (void)scheduleReachabilityMonitoringForHost:(NSString *)nodename observer:(id)observer;
21 - (void)gotReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef forHost:(NSString *)host observer:(id)observer;
23 - (void)addUnconfiguredHost:(NSString *)host observer:(id)observer;
24 - (void)removeUnconfiguredHost:(NSString *)host observer:(id)observer;
25 - (void)queryUnconfiguredHosts;
27 - (void)beginMonitorngIPChanges;
28 - (void)stopMonitoringIPChanges;
31 @implementation AIHostReachabilityMonitor
33 #pragma mark Shared instance management
36 * @brief Returns a shared instance, usable for most purposes.
37 * @return A shared AIHostReachabilityMonitor instance.
42 singleton = [[AIHostReachabilityMonitor alloc] init];
49 #pragma mark Birth and death
56 if ((self = [super init])) {
57 hostAndObserverListLock = [[NSLock alloc] init];
58 [hostAndObserverListLock setName:@"HostAndObserverListLock"];
60 [hostAndObserverListLock lock];
61 hosts = [[NSMutableArray alloc] init];
62 observers = [[NSMutableArray alloc] init];
63 reachabilities = [[NSMutableArray alloc] init];
65 unconfiguredHostsAndObservers = [[NSMutableSet alloc] init];
66 ipChangesRunLoopSourceRef = nil;
68 [hostAndObserverListLock unlock];
70 //Monitor system sleep so we can accurately report connectivity changes when the system wakes
71 [[NSNotificationCenter defaultCenter] addObserver:self
72 selector:@selector(systemDidWake:)
73 name:AISystemDidWake_Notification
84 [hostAndObserverListLock lock];
85 [hosts release]; hosts = nil;
86 [observers release]; observers = nil;
87 [reachabilities release]; reachabilities = nil;
89 [unconfiguredHostsAndObservers release]; unconfiguredHostsAndObservers = nil;
90 [hostAndObserverListLock unlock];
92 [hostAndObserverListLock release];
94 [[NSNotificationCenter defaultCenter] removeObserver:self];
100 #pragma mark Observer management
103 * @brief Begins observing a host's reachability for an object.
105 - (void)addObserver:(id <AIHostReachabilityObserver>)newObserver forHost:(NSString *)host
107 NSParameterAssert(host != nil);
108 NSParameterAssert(newObserver != nil);
110 NSString *hostCopy = [host copy];
111 [self scheduleReachabilityMonitoringForHost:hostCopy
112 observer:newObserver];
117 * @brief Stops an object's observation of a host's reachability.
119 * When host is non-nil, stops observing that host's reachability for the given observer.
120 * When host is nil, stops observing all hosts' reachability for the given observer.
123 - (void)removeObserver:(id <AIHostReachabilityObserver>)newObserver forHost:(NSString *)host
125 //nil cannot observe, so it must not be in the list.
126 if (!newObserver) return;
128 [hostAndObserverListLock lock];
130 unsigned numObservers = [observers count];
131 for (unsigned i = 0; i < numObservers; ) {
133 if (newObserver == [observers objectAtIndex:i]) {
134 if ((!host) || ([host isEqualToString:[hosts objectAtIndex:i]])) {
135 [hosts removeObjectAtIndex:i];
136 [observers removeObjectAtIndex:i];
137 SCNetworkReachabilityScheduleWithRunLoop((SCNetworkReachabilityRef)[reachabilities objectAtIndex:i],
138 CFRunLoopGetCurrent(),
139 kCFRunLoopDefaultMode);
140 [reachabilities removeObjectAtIndex:i];
142 [self removeUnconfiguredHost:host
143 observer:newObserver];
152 [hostAndObserverListLock unlock];
156 * @brief Is an observer currently observing a host?
158 * @result YES if so, NO if not
160 - (BOOL)observer:(id)observer isObservingHost:(NSString *)host
162 BOOL isObserving = NO;
164 [hostAndObserverListLock lock];
166 if (host && observer) {
167 unsigned numObservers = [observers count];
168 for (unsigned i = 0; i < numObservers; i++) {
169 if ((observer == [observers objectAtIndex:i]) &&
170 ([host isEqualToString:[hosts objectAtIndex:i]])) {
176 if (!isObserving && [unconfiguredHostsAndObservers count]) {
177 NSDictionary *unconfiguredHostObserverDict = [NSDictionary dictionaryWithObjectsAndKeys:
178 observer, @"observer",
181 if ([unconfiguredHostsAndObservers containsObject:unconfiguredHostObserverDict]) {
187 [hostAndObserverListLock unlock];
193 #pragma mark Reachability monitoring
196 * @brief A host's reachability changed
198 * @param reachability The SCNetworkReachabilityRef for the host which changed
199 * @param isReachable YES if the host is now reachable; NO if it is not reachable
201 - (void)reachabilityChanged:(SCNetworkReachabilityRef)reachability
203 [hostAndObserverListLock lock];
205 NSUInteger i = [reachabilities indexOfObjectIdenticalTo:(id)reachability];
206 if (i != NSNotFound) {
207 NSString *host = [hosts objectAtIndex:i];
208 id <AIHostReachabilityObserver> observer = [observers objectAtIndex:i];
210 BOOL anyHostsReachable = NO;
212 // If we have multiple host <-> IP links (AAAA record and an A record), we need to check agreement.
213 for (NSUInteger index = 0; index < hosts.count; index++) {
214 if (![host isEqualToString:[hosts objectAtIndex:index]])
217 SCNetworkReachabilityRef otherReachability = (SCNetworkReachabilityRef)[reachabilities objectAtIndex:index];
218 SCNetworkConnectionFlags flags;
220 if (SCNetworkReachabilityGetFlags(otherReachability, &flags)
221 && (flags & kSCNetworkFlagsReachable)
222 && !(flags & kSCNetworkFlagsConnectionRequired)) {
223 anyHostsReachable = YES;
228 // Return reachability based on any reachability response.
229 if (anyHostsReachable) {
230 [observer hostReachabilityMonitor:self hostIsReachable:host];
232 [observer hostReachabilityMonitor:self hostIsNotReachable:host];
236 [hostAndObserverListLock unlock];
240 * @brief Callback for changes in a host's reachability (SCNetworkReachability)
242 * @param info The AIHostReachabilityMonitor which requested host reachability monitoring
244 static void hostReachabilityChangedCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *info)
246 #if CONNECTIVITY_DEBUG
247 NSLog(@"*** hostReachabilityChangedCallback got flags: %c%c%c%c%c%c%c \n",
248 (flags & kSCNetworkFlagsTransientConnection) ? 't' : '-',
249 (flags & kSCNetworkFlagsReachable) ? 'r' : '-',
250 (flags & kSCNetworkFlagsConnectionRequired) ? 'c' : '-',
251 (flags & kSCNetworkFlagsConnectionAutomatic) ? 'C' : '-',
252 (flags & kSCNetworkFlagsInterventionRequired) ? 'i' : '-',
253 (flags & kSCNetworkFlagsIsLocalAddress) ? 'l' : '-',
254 (flags & kSCNetworkFlagsIsDirect) ? 'd' : '-');
257 AIHostReachabilityMonitor *self = info;
258 [self reachabilityChanged:target];
262 * @brief Callbacak for resolution of a host's name to an IP (CFHost)
264 * @param info An NSDictionary with the keys @"self", @"observer", and @"host"
266 static void hostResolvedCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
268 NSDictionary *infoDict = info;
269 AIHostReachabilityMonitor *self = [infoDict objectForKey:@"self"];
270 id observer = [infoDict objectForKey:@"observer"];
271 NSString *host = [infoDict objectForKey:@"host"];
273 if (typeInfo == kCFHostAddresses) {
274 //CFHostGetAddressing returns a CFArrayRef of CFDataRefs which wrap struct sockaddr
275 CFArrayRef addresses = CFHostGetAddressing(theHost, NULL);
277 if (!CFArrayGetCount(addresses)) {
278 /* We were not able to resolve the host name to an IP address. This is most likely because we have no
279 * Internet connection or because the user is attempting to connect to MSN.
281 * Add to unconfiguredHostsAndObservers so we can try configuring again later.
283 [self addUnconfiguredHost:host
287 // Only add 1 observer for IPv6 and one for IPv4.
288 BOOL addedIPv4 = NO, addedIPv6 = NO;
290 for (NSData *saData in (NSArray *)addresses) {
291 struct sockaddr *remoteAddr = (struct sockaddr *)saData.bytes;
293 if ((remoteAddr->sa_family == AF_INET && addedIPv4) || (remoteAddr->sa_family == AF_INET6 && addedIPv6)) {
297 if (remoteAddr->sa_family == AF_INET)
299 if (remoteAddr->sa_family == AF_INET6)
302 SCNetworkReachabilityRef reachabilityRef;
303 SCNetworkReachabilityContext reachabilityContext = {
307 .release = CFRelease,
308 .copyDescription = CFCopyDescription,
310 SCNetworkConnectionFlags flags;
311 struct sockaddr_in localAddr;
313 /* Create a reachability reference pair with localhost and the remote host */
315 //Our local address is 127.0.0.1
316 bzero(&localAddr, sizeof(localAddr));
317 localAddr.sin_len = sizeof(localAddr);
318 localAddr.sin_family = AF_INET;
319 inet_aton("127.0.0.1", &localAddr.sin_addr);
322 reachabilityRef = SCNetworkReachabilityCreateWithAddressPair(NULL,
323 (struct sockaddr *)&localAddr,
326 //Configure our callback
327 SCNetworkReachabilitySetCallback(reachabilityRef,
328 hostReachabilityChangedCallback,
329 &reachabilityContext);
331 //Add it to the run loop so we will receive the notifications
332 SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef,
333 CFRunLoopGetCurrent(),
334 kCFRunLoopDefaultMode);
336 //Note that we succesfully configured for reachability notifications
337 [self gotReachabilityRef:(SCNetworkReachabilityRef)[(NSObject *)reachabilityRef autorelease]
341 if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
342 //We already have valid flags for the reachabilityRef
343 #if CONNECTIVITY_DEBUG
344 NSLog(@"Immediate reachability info for %@", reachabilityRef);
346 hostReachabilityChangedCallback(reachabilityRef,
351 /* Perform an immediate reachability check, since we've just scheduled checks for future changes
352 * and won't be notified immediately. We update the hostContext to include our reachabilityRef before
353 * scheduling the info resolution (it's still in our run loop from when we requested the IP address).
355 CFHostClientContext hostContext = {
357 .info = [NSDictionary dictionaryWithObjectsAndKeys:
360 observer, @"observer",
361 reachabilityRef, @"reachabilityRef",
364 .release = CFRelease,
365 .copyDescription = CFCopyDescription,
367 CFHostSetClient(theHost,
368 hostResolvedCallback,
370 CFHostStartInfoResolution(theHost,
377 } else if (typeInfo == kCFHostReachability) {
378 /* Asynchronous host reachability notification from CFHost(), triggered by CFHostStartInfoResolution() above. */
379 SCNetworkConnectionFlags flags;
380 CFDataRef flagsData = CFHostGetReachability(theHost,
382 CFDataGetBytes(flagsData,
383 CFRangeMake(0, CFDataGetLength(flagsData)),
386 //Call the reachability changed callback directly
387 hostReachabilityChangedCallback((SCNetworkReachabilityRef)[infoDict objectForKey:@"reachabilityRef"],
391 //No further need for this CFHost to be in our run loop
392 CFHostUnscheduleFromRunLoop(theHost,
393 CFRunLoopGetCurrent(),
394 kCFRunLoopDefaultMode);
399 * @brief We obtained an SCNetworkReachabilityRef for a host/observer pair
401 * We can now effectively monitor connectivity between us and the host.
403 * Add these three objects to our hosts, observers, and reachabilities arrays respectively so we can determine the
404 * host and observer given the reachabilityRef in hostReachabilityChangedCallback() above.
406 * Remove the host/observer pair from the unconfigured dictionary.
408 - (void)gotReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef forHost:(NSString *)host observer:(id)observer
410 //Add to our arrays for tracking
411 [hostAndObserverListLock lock];
413 [hosts addObject:host];
414 [observers addObject:observer];
415 [reachabilities addObject:(id)reachabilityRef];
417 //Remove from our unconfigured array
418 [self removeUnconfiguredHost:host
421 [hostAndObserverListLock unlock];
423 #if CONNECTIVITY_DEBUG
424 NSLog(@"Obtained reachability ref %@ for %@ (%@).",reachabilityRef, host, observer);
429 * @brief Schedule a reachability check for a host, with an observer
431 * This method begins the process of scheduling the reachability check. It actually creates a CFHost to schedules
432 * an asynchronous IP lookup for nodename. hostResolvedCallback() will be called when it succeeds or fails.
434 * @param nodename The name such as "www.adiumxtras.com"
435 * @param observer The observer which will be notified when the reachability changes
437 - (void)scheduleReachabilityMonitoringForHost:(NSString *)nodename observer:(id)observer
439 //Resolve the remote host domain name to an IP asynchronously
440 CFHostClientContext hostContext = {
442 .info = [NSDictionary dictionaryWithObjectsAndKeys:
445 observer, @"observer",
448 .release = CFRelease,
449 .copyDescription = CFCopyDescription,
451 CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault,
452 (CFStringRef)nodename);
453 CFHostSetClient(host,
454 hostResolvedCallback,
456 CFHostScheduleWithRunLoop(host,
457 CFRunLoopGetCurrent(),
458 kCFRunLoopDefaultMode);
459 CFHostStartInfoResolution(host,
462 #if CONNECTIVITY_DEBUG
463 NSLog(@"Scheduled reachability check for %@",nodename);
470 #pragma mark Unconfigured hosts
472 * @brief Add an unconfigured host and observer to unconfiguredHostsAndObservers
474 * We have to resolve a host to an IP address before we can properly observe reachability. unconfiguredHostsAndObservers
475 * holds information on host/observer pairs which we haven't been able to resolve yet. When the IP configuration changes,
476 * we will try again, hoping to get an IP address this time.
478 - (void)addUnconfiguredHost:(NSString *)host observer:(id)observer
480 NSDictionary *unconfiguredHostDict = [NSDictionary dictionaryWithObjectsAndKeys:
481 observer, @"observer",
484 BOOL addedNewUnconfiguredHost = NO;
485 [hostAndObserverListLock lock];
486 if (![unconfiguredHostsAndObservers containsObject:unconfiguredHostDict]) {
487 addedNewUnconfiguredHost = YES;
488 [unconfiguredHostsAndObservers addObject:unconfiguredHostDict];
490 [hostAndObserverListLock unlock];
492 if (addedNewUnconfiguredHost) {
493 /* If this is the first unconfigured host, begin monitoring IP changes so we can try to set it (and any others)
494 * up at the earliest possible time.
496 if ([unconfiguredHostsAndObservers count] == 1) {
497 [self beginMonitorngIPChanges];
500 #if CONNECTIVITY_DEBUG
501 NSLog(@"Unable to resolve %@. Now monitoring IP changes for %@",host,unconfiguredHostsAndObservers);
505 * There are various ways we can get here when we already have an IP, such as when other network conditions
506 * need to be negotiated at the router-level before we're actually connected. In such a case, IPs aren't going to
507 * change... so we'll check one more time, 10 seconds from the last time we get an unconfigured host call,
510 [NSObject cancelPreviousPerformRequestsWithTarget:self
511 selector:@selector(queryUnconfiguredHosts)
513 [self performSelector:@selector(queryUnconfiguredHosts)
520 * @brief Remove an unconfigured host and observer from unconfiguredHostsAndObservers
522 * Must be called with the hostAndObserverListLock already obtained.
524 - (void)removeUnconfiguredHost:(NSString *)host observer:(id)observer
526 [unconfiguredHostsAndObservers removeObject:[NSDictionary dictionaryWithObjectsAndKeys:
527 observer, @"observer",
531 if ([unconfiguredHostsAndObservers count] == 0) {
532 [self stopMonitoringIPChanges];
537 * @brief Attempt to set up reachability monitoring for all currently unconfigured hosts
539 * Called by localIPsChangedCallback() in response to a change in the local IP list.
541 * Also called 5 seconds after one or more unconfigured hosts are added to verify they are actually unreachable.
543 * If we are able to schedule reachability monitoring for a given host, its dictionary in unconfiguredHostsAndObservers
546 - (void)queryUnconfiguredHosts
548 if ([unconfiguredHostsAndObservers count]) {
549 NSEnumerator *enumerator;
550 NSDictionary *unconfiguredDict;
552 [hostAndObserverListLock lock];
553 enumerator = [unconfiguredHostsAndObservers objectEnumerator];
554 while ((unconfiguredDict = [enumerator nextObject])) {
555 [self scheduleReachabilityMonitoringForHost:[unconfiguredDict objectForKey:@"host"]
556 observer:[unconfiguredDict objectForKey:@"observer"]];
558 [hostAndObserverListLock unlock];
563 * @brief The local IP list changed (SCDynamicStore)
565 * @param info The AIHostReachabilityMonitor which set up the callback
567 static void localIPsChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
569 AIHostReachabilityMonitor *self = info;
571 /* Wait one second after receiving the callback, as it seems to be sent in some cases the middle of the change
572 * rather than after it is complete.
574 [self performSelector:@selector(queryUnconfiguredHosts)
579 static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback, void *contextPtr,
580 SCDynamicStoreRef *storeRef, CFRunLoopSourceRef *sourceRef);
583 * @brief Monitor when our local IP list changes, which generally indicates a possible change in network connectivity
585 - (void)beginMonitorngIPChanges
587 if (!ipChangesRunLoopSourceRef) {
588 SCDynamicStoreRef storeRef = nil;
591 //Create the CFRunLoopSourceRef we will want to add to our run loop to have
592 //localIPsChangedCallback() called when the IP list changes
593 status = CreateIPAddressListChangeCallbackSCF(localIPsChangedCallback,
596 &ipChangesRunLoopSourceRef);
598 //Add it to the run loop so we will receive the notifications
599 if((status == noErr) && ipChangesRunLoopSourceRef){
600 CFRunLoopAddSource(CFRunLoopGetCurrent(),
601 ipChangesRunLoopSourceRef,
602 kCFRunLoopDefaultMode);
610 * @brief Stop monitoring changes to our local IP list
612 - (void)stopMonitoringIPChanges
614 if (ipChangesRunLoopSourceRef) {
615 CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
616 ipChangesRunLoopSourceRef,
617 kCFRunLoopDefaultMode);
618 CFRelease(ipChangesRunLoopSourceRef);
619 ipChangesRunLoopSourceRef = nil;
624 #pragma mark Sleep and Wake
627 * @brief System is waking from sleep
629 * When the system wakes, manually reconfigure reachability checking as not all network configurations will report a change.
631 - (void)systemDidWake:(NSNotification *)notification
633 [hostAndObserverListLock lock];
635 NSArray *oldHosts = [hosts copy];
636 NSArray *oldObservers = [observers copy];
638 NSEnumerator *enumerator;
639 SCNetworkReachabilityRef reachabilityRef;
640 enumerator = [reachabilities objectEnumerator];
641 while ((reachabilityRef = (SCNetworkReachabilityRef)[enumerator nextObject])) {
642 SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef,
643 CFRunLoopGetCurrent(),
644 kCFRunLoopDefaultMode);
647 [hosts removeAllObjects];
648 [observers removeAllObjects];
649 [reachabilities removeAllObjects];
651 [hostAndObserverListLock unlock];
653 unsigned numObservers = [oldObservers count];
654 for (unsigned i = 0; i < numObservers; i++) {
655 NSString *host = [oldHosts objectAtIndex:i];
656 id<AIHostReachabilityObserver> observer = [oldObservers objectAtIndex:i];
658 [self addObserver:observer
663 [oldObservers release];
667 #pragma mark CreateIPAddressListChangeCallbackSCF
669 /*CreateIPAddressListChangeCallbackSCF() and its supporting functions are from
670 * Apple's "Living in a Dynamic TCP/IP Environment, available at
671 * http://developer.apple.com/technotes/tn/tn1145.html
674 //Error Handling ------------------------------------------------------------------------------------------------------
677 // SCF returns errors in two ways:
679 // o The function result is usually set to something
680 // generic (like NULL or false) to indicate an error.
682 // o There is a call, SCError, that returns the error
683 // code for the most recent function. These error codes
684 // are not in the OSStatus domain.
686 // We deal with this using two functions, MoreSCError
687 // and MoreSCErrorBoolean. Both of these take a generic
688 // failure indicator (a pointer or a Boolean) and, if
689 // that indicates an error, they call SCError to get the
690 // real error code. They also act as a bottleneck for
691 // mapping SC errors into the OSStatus domain, although
692 // I don't do that in this simple implementation.
694 // Note that I could have eliminated the failure indicator
695 // parameter and just called SCError but I'm worried
696 // about SCF returning an error indicator without setting
697 // the SCError. There's no justification for this worry
698 // other than general paranoia (I know of no examples where
701 static OSStatus MoreSCErrorBoolean(Boolean success)
709 if (scErr == kSCStatusOK) {
710 scErr = kSCStatusFailed;
712 // Return an SCF error directly as an OSStatus.
713 // That's a little cheesy. In a real program
714 // you might want to do some mapping from SCF
715 // errors to a range within the OSStatus range.
721 static OSStatus MoreSCError(const void *value)
723 return MoreSCErrorBoolean(value != NULL);
726 static OSStatus CFQError(CFTypeRef cf)
727 // Maps Core Foundation error indications (such as they
728 // are) to the OSStatus domain.
734 err = coreFoundationUnknownErr;
739 //CreateIPAddressListChangeCallbackSCF ----------------------------------------------------------------------------------
741 static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback,
743 SCDynamicStoreRef *storeRef,
744 CFRunLoopSourceRef *sourceRef)
745 // Create a SCF dynamic store reference and a
746 // corresponding CFRunLoop source. If you add the
747 // run loop source to your run loop then the supplied
748 // callback function will be called when local IP
749 // address list changes.
752 SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL};
753 SCDynamicStoreRef ref;
755 CFArrayRef patternList;
756 CFRunLoopSourceRef rls;
758 assert(callback != NULL);
759 assert( storeRef != NULL);
760 assert(*storeRef == NULL);
761 assert( sourceRef != NULL);
762 assert(*sourceRef == NULL);
769 // Create a connection to the dynamic store, then create
770 // a search pattern that finds all IPv4 entities.
771 // The pattern is "State:/Network/Service/[^/]+/IPv4".
773 context.info = contextPtr;
774 ref = SCDynamicStoreCreate( NULL,
775 CFSTR("AddIPAddressListChangeCallbackSCF"),
778 err = MoreSCError(ref);
780 pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(
782 kSCDynamicStoreDomainState,
785 err = MoreSCError(pattern);
788 // Create a pattern list containing just one pattern,
789 // then tell SCF that we want to watch changes in keys
790 // that match that pattern list, then create our run loop
794 patternList = CFArrayCreate(NULL,
795 (const void **) &pattern, 1,
796 &kCFTypeArrayCallBacks);
797 err = CFQError(patternList);
800 err = MoreSCErrorBoolean(
801 SCDynamicStoreSetNotificationKeys(
808 rls = SCDynamicStoreCreateRunLoopSource(NULL, ref, 0);
809 err = MoreSCError(rls);
814 if (pattern) CFRelease(pattern);
815 if (patternList) CFRelease(patternList);
825 assert( (err == noErr) == (*storeRef != NULL) );
826 assert( (err == noErr) == (*sourceRef != NULL) );