| /* CFXMLPreferencesDomain.c |
| Copyright (c) 1998-2017, Apple Inc. and the Swift project authors |
| |
| Portions Copyright (c) 2014-2017, Apple Inc. and the Swift project authors |
| Licensed under Apache License v2.0 with Runtime Library Exception |
| See http://swift.org/LICENSE.txt for license information |
| See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| Responsibility: David Smith |
| */ |
| |
| |
| #include <CoreFoundation/CFPreferences.h> |
| #include <CoreFoundation/CFURLAccess.h> |
| #include <CoreFoundation/CFPropertyList.h> |
| #include <CoreFoundation/CFNumber.h> |
| #include <CoreFoundation/CFDate.h> |
| #include "CFInternal.h" |
| #include <time.h> |
| #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #endif |
| #if DEPLOYMENT_TARGET_MACOSX |
| #include <mach/mach.h> |
| #include <mach/mach_syscalls.h> |
| #endif |
| |
| Boolean __CFPreferencesShouldWriteXML(void); |
| |
| typedef struct { |
| CFMutableDictionaryRef _domainDict; // Current value of the domain dictionary |
| CFMutableArrayRef _dirtyKeys; // The array of keys which must be synchronized |
| CFAbsoluteTime _lastReadTime; // The last time we synchronized with the disk |
| CFLock_t _lock; // Lock for accessing fields in the domain |
| Boolean _isWorldReadable; // HACK - this is because we have no good way to propogate the kCFPreferencesAnyUser information from the upper level CFPreferences routines REW, 1/13/00 |
| char _padding[3]; |
| } _CFXMLPreferencesDomain; |
| |
| static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context); |
| static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain); |
| static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key); |
| static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value); |
| static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain); |
| static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs); |
| static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *domain); |
| static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable); |
| |
| CF_PRIVATE const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {createXMLDomain, freeXMLDomain, fetchXMLValue, writeXMLValue, synchronizeXMLDomain, getXMLKeysAndValues, copyXMLDomainDictionary, setXMLDomainIsWorldReadable}; |
| |
| // Directly ripped from Foundation.... |
| static void __CFMilliSleep(uint32_t msecs) { |
| #if DEPLOYMENT_TARGET_WINDOWS |
| SleepEx(msecs, false); |
| #elif defined(__svr4__) || defined(__hpux__) |
| sleep((msecs + 900) / 1000); |
| #elif DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX |
| struct timespec input; |
| input.tv_sec = msecs / 1000; |
| input.tv_nsec = (msecs - input.tv_sec * 1000) * 1000000; |
| nanosleep(&input, NULL); |
| #else |
| #error Dont know how to define sleep for this platform |
| #endif |
| } |
| |
| static CFLock_t _propDictLock = CFLockInit; // Annoying that we need this, but otherwise we have a multithreading risk |
| |
| CF_INLINE CFDictionaryRef URLPropertyDictForPOSIXMode(SInt32 mode) { |
| static CFMutableDictionaryRef _propertyDict = NULL; |
| CFNumberRef num = CFNumberCreate(__CFPreferencesAllocator(), kCFNumberSInt32Type, &mode); |
| __CFLock(&_propDictLock); |
| if (!_propertyDict) { |
| _propertyDict = CFDictionaryCreateMutable(__CFPreferencesAllocator(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| } |
| CFDictionarySetValue(_propertyDict, kCFURLFilePOSIXMode, num); |
| CFRelease(num); |
| return _propertyDict; |
| } |
| |
| CF_INLINE void URLPropertyDictRelease(void) { |
| __CFUnlock(&_propDictLock); |
| } |
| |
| // Asssumes caller already knows the directory doesn't exist. |
| static Boolean _createDirectory(CFURLRef dirURL, Boolean worldReadable) { |
| CFAllocatorRef alloc = __CFPreferencesAllocator(); |
| CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(alloc, dirURL); |
| CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
| Boolean parentExists = (val && CFBooleanGetValue(val)); |
| SInt32 mode; |
| Boolean result; |
| if (val) CFRelease(val); |
| if (!parentExists) { |
| CFStringRef path = CFURLCopyPath(parentURL); |
| if (!CFEqual(path, CFSTR("/"))) { |
| _createDirectory(parentURL, worldReadable); |
| val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
| parentExists = (val && CFBooleanGetValue(val)); |
| if (val) CFRelease(val); |
| } |
| CFRelease(path); |
| } |
| if (parentURL) CFRelease(parentURL); |
| if (!parentExists) return false; |
| |
| #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX |
| mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU; |
| #else |
| mode = 0666; |
| #endif |
| |
| result = CFURLWriteDataAndPropertiesToResource(dirURL, (CFDataRef)dirURL, URLPropertyDictForPOSIXMode(mode), NULL); |
| URLPropertyDictRelease(); |
| return result; |
| } |
| |
| |
| /* XML - context is the CFURL where the property list is stored on disk; domain is an _CFXMLPreferencesDomain */ |
| static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain*) CFAllocatorAllocate(allocator, sizeof(_CFXMLPreferencesDomain), 0); |
| domain->_lastReadTime = 0.0; |
| domain->_domainDict = NULL; |
| domain->_dirtyKeys = CFArrayCreateMutable(allocator, 0, & kCFTypeArrayCallBacks); |
| const CFLock_t lock = CFLockInit; |
| domain->_lock = lock; |
| domain->_isWorldReadable = false; |
| return domain; |
| } |
| |
| static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)tDomain; |
| if (domain->_domainDict) CFRelease(domain->_domainDict); |
| if (domain->_dirtyKeys) CFRelease(domain->_dirtyKeys); |
| CFAllocatorDeallocate(allocator, domain); |
| } |
| |
| // Assumes the domain has already been locked |
| static void _loadXMLDomainIfStale(CFURLRef url, _CFXMLPreferencesDomain *domain) { |
| CFAllocatorRef alloc = __CFPreferencesAllocator(); |
| int idx; |
| if (domain->_domainDict) { |
| CFDateRef modDate; |
| CFAbsoluteTime modTime; |
| CFURLRef testURL = url; |
| |
| if (CFDictionaryGetCount(domain->_domainDict) == 0) { |
| // domain never existed; check the parent directory, not the child |
| testURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR(".."), kCFURLPOSIXPathStyle, true, url); |
| } |
| |
| modDate = (CFDateRef )CFURLCreatePropertyFromResource(alloc, testURL, kCFURLFileLastModificationTime, NULL); |
| modTime = modDate ? CFDateGetAbsoluteTime(modDate) : 0.0; |
| |
| // free before possible return. we can test non-NULL of modDate but don't depend on contents after this. |
| if (testURL != url) CFRelease(testURL); |
| if (modDate) CFRelease(modDate); |
| |
| if (modDate != NULL && modTime < domain->_lastReadTime) { // We're up-to-date |
| return; |
| } |
| } |
| |
| |
| // We're out-of-date; destroy domainDict and reload |
| if (domain->_domainDict) { |
| CFRelease(domain->_domainDict); |
| domain->_domainDict = NULL; |
| } |
| |
| // We no longer lock on read; instead, we assume parse failures are because someone else is writing the file, and just try to parse again. If we fail 3 times in a row, we assume the file is corrupted. REW, 7/13/99 |
| |
| for (idx = 0; idx < 3; idx ++) { |
| CFDataRef data; |
| if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &data, NULL, NULL, NULL) || !data) { |
| // Either a file system error (so we can't read the file), or an empty (or perhaps non-existant) file |
| domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| break; |
| } else { |
| CFTypeRef pList = CFPropertyListCreateFromXMLData(alloc, data, kCFPropertyListImmutable, NULL); |
| CFRelease(data); |
| if (pList && CFGetTypeID(pList) == CFDictionaryGetTypeID()) { |
| domain->_domainDict = CFDictionaryCreateMutableCopy(alloc, 0, (CFDictionaryRef)pList); |
| CFRelease(pList); |
| break; |
| } else if (pList) { |
| CFRelease(pList); |
| } |
| // Assume the file is being written; sleep for a short time (to allow the write to complete) then re-read |
| __CFMilliSleep(150); |
| } |
| } |
| if (!domain->_domainDict) { |
| // Failed to ever load |
| domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| } |
| domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); |
| } |
| |
| static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; |
| CFTypeRef result; |
| |
| // Never reload if we've looked at the file system within the last 5 seconds. |
| __CFLock(&domain->_lock); |
| if (domain->_domainDict == NULL) _loadXMLDomainIfStale((CFURLRef )context, domain); |
| result = CFDictionaryGetValue(domain->_domainDict, key); |
| if (result) CFRetain(result); |
| __CFUnlock(&domain->_lock); |
| |
| return result; |
| } |
| |
| |
| #if DEPLOYMENT_TARGET_MACOSX |
| #include <sys/fcntl.h> |
| |
| /* __CFWriteBytesToFileWithAtomicity is a "safe save" facility. Write the bytes using the specified mode on the file to the provided URL. If the atomic flag is true, try to do it in a fashion that will enable a safe save. |
| */ |
| static Boolean __CFWriteBytesToFileWithAtomicity(CFURLRef url, const void *bytes, int length, SInt32 mode, Boolean atomic) { |
| int fd = -1; |
| char auxPath[CFMaxPathSize + 16]; |
| char cpath[CFMaxPathSize]; |
| uid_t owner = getuid(); |
| gid_t group = getgid(); |
| Boolean writingFileAsRoot = ((getuid() != geteuid()) && (geteuid() == 0)); |
| |
| if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)cpath, CFMaxPathSize)) { |
| return false; |
| } |
| |
| if (-1 == mode || writingFileAsRoot) { |
| struct stat statBuf; |
| if (0 == stat(cpath, &statBuf)) { |
| mode = statBuf.st_mode; |
| owner = statBuf.st_uid; |
| group = statBuf.st_gid; |
| } else { |
| mode = 0664; |
| if (writingFileAsRoot && (0 == strncmp(cpath, "/Library/Preferences", 20))) { |
| owner = geteuid(); |
| group = 80; |
| } |
| } |
| } |
| |
| if (atomic) { |
| CFURLRef dir = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); |
| CFURLRef tempFile = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, dir, CFSTR("cf#XXXXX"), false); |
| CFRelease(dir); |
| if (!CFURLGetFileSystemRepresentation(tempFile, true, (uint8_t *)auxPath, CFMaxPathSize)) { |
| CFRelease(tempFile); |
| return false; |
| } |
| CFRelease(tempFile); |
| fd = mkstemp(auxPath); |
| } else { |
| fd = open(cpath, O_WRONLY|O_CREAT|O_TRUNC, mode); |
| } |
| |
| if (fd < 0) return false; |
| |
| if (length && (write(fd, bytes, length) != length || fsync(fd) < 0)) { |
| int saveerr = thread_errno(); |
| close(fd); |
| if (atomic) |
| unlink(auxPath); |
| thread_set_errno(saveerr); |
| return false; |
| } |
| |
| close(fd); |
| |
| if (atomic) { |
| // preserve the mode as passed in originally |
| chmod(auxPath, mode); |
| |
| if (0 != rename(auxPath, cpath)) { |
| unlink(auxPath); |
| return false; |
| } |
| |
| // If the file was renamed successfully and we wrote it as root we need to reset the owner & group as they were. |
| if (writingFileAsRoot) { |
| chown(cpath, owner, group); |
| } |
| } |
| return true; |
| } |
| #endif |
| |
| // domain should already be locked. |
| static Boolean _writeXMLFile(CFURLRef url, CFMutableDictionaryRef dict, Boolean isWorldReadable, Boolean *tryAgain) { |
| Boolean success = false; |
| CFAllocatorRef alloc = __CFPreferencesAllocator(); |
| *tryAgain = false; |
| if (CFDictionaryGetCount(dict) == 0) { |
| // Destroy the file |
| CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); |
| if (val && CFBooleanGetValue(val)) { |
| success = CFURLDestroyResource(url, NULL); |
| } else { |
| success = true; |
| } |
| if (val) CFRelease(val); |
| } else { |
| CFPropertyListFormat desiredFormat = __CFPreferencesShouldWriteXML() ? kCFPropertyListXMLFormat_v1_0 : kCFPropertyListBinaryFormat_v1_0; |
| CFDataRef data = CFPropertyListCreateData(alloc, dict, desiredFormat, 0, NULL); |
| if (data) { |
| SInt32 mode; |
| #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX |
| mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR; |
| #else |
| mode = 0666; |
| #endif |
| #if DEPLOYMENT_TARGET_MACOSX |
| { // Try quick atomic way first, then fallback to slower ways and error cases |
| CFStringRef scheme = CFURLCopyScheme(url); |
| if (!scheme) { |
| *tryAgain = false; |
| CFRelease(data); |
| return false; |
| } else if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) { |
| SInt32 length = CFDataGetLength(data); |
| const void *bytes = (0 == length) ? (const void *)"" : CFDataGetBytePtr(data); |
| Boolean atomicWriteSuccess = __CFWriteBytesToFileWithAtomicity(url, bytes, length, mode, true); |
| if (atomicWriteSuccess) { |
| CFRelease(scheme); |
| *tryAgain = false; |
| CFRelease(data); |
| return true; |
| } |
| if (!atomicWriteSuccess && thread_errno() == ENOSPC) { |
| CFRelease(scheme); |
| *tryAgain = false; |
| CFRelease(data); |
| return false; |
| } |
| } |
| CFRelease(scheme); |
| } |
| #endif |
| success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); |
| URLPropertyDictRelease(); |
| if (success) { |
| CFDataRef readData; |
| if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &readData, NULL, NULL, NULL) || !CFEqual(readData, data)) { |
| success = false; |
| *tryAgain = true; |
| } |
| if (readData) CFRelease(readData); |
| } else { |
| CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); |
| if (!val || !CFBooleanGetValue(val)) { |
| CFURLRef tmpURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("."), kCFURLPOSIXPathStyle, true, url); // Just "." because url is not a directory URL |
| CFURLRef parentURL = tmpURL ? CFURLCopyAbsoluteURL(tmpURL) : NULL; |
| if (tmpURL) CFRelease(tmpURL); |
| if (val) CFRelease(val); |
| val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); |
| if ((!val || !CFBooleanGetValue(val)) && _createDirectory(parentURL, isWorldReadable)) { |
| // parent directory didn't exist; now it does; try again to write |
| success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); |
| URLPropertyDictRelease(); |
| if (success) { |
| CFDataRef rdData; |
| if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &rdData, NULL, NULL, NULL) || !CFEqual(rdData, data)) { |
| success = false; |
| *tryAgain = true; |
| } |
| if (rdData) CFRelease(rdData); |
| } |
| |
| } |
| if (parentURL) CFRelease(parentURL); |
| } |
| if (val) CFRelease(val); |
| } |
| CFRelease(data); |
| } else { |
| // ??? This should never happen |
| CFLog(__kCFLogAssertion, CFSTR("Could not generate XML data for property list")); |
| success = false; |
| } |
| } |
| return success; |
| } |
| |
| static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; |
| const void *existing = NULL; |
| |
| __CFLock(&domain->_lock); |
| if (domain->_domainDict == NULL) { |
| _loadXMLDomainIfStale((CFURLRef )context, domain); |
| } |
| |
| // check to see if the value is the same |
| // if (1) the key is present AND value is !NULL and equal to existing, do nothing, or |
| // if (2) the key is not present AND value is NULL, do nothing |
| // these things are no-ops, and should not dirty the domain |
| if (CFDictionaryGetValueIfPresent(domain->_domainDict, key, &existing)) { |
| if (NULL != value && (existing == value || CFEqual(existing, value))) { |
| __CFUnlock(&domain->_lock); |
| return; |
| } |
| } else { |
| if (NULL == value) { |
| __CFUnlock(&domain->_lock); |
| return; |
| } |
| } |
| |
| // We must append first so key gets another retain (in case we're |
| // about to remove it from the dictionary, and that's the sole reference) |
| // This should be a set not an array. |
| if (!CFArrayContainsValue(domain->_dirtyKeys, CFRangeMake(0, CFArrayGetCount(domain->_dirtyKeys)), key)) { |
| CFArrayAppendValue(domain->_dirtyKeys, key); |
| } |
| if (value) { |
| // Must copy for two reasons - we don't want mutable objects in the cache, and we don't want objects allocated from a different allocator in the cache. |
| CFTypeRef newValue = CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), value, kCFPropertyListImmutable); |
| CFDictionarySetValue(domain->_domainDict, key, newValue); |
| CFRelease(newValue); |
| } else { |
| CFDictionaryRemoveValue(domain->_domainDict, key); |
| } |
| __CFUnlock(&domain->_lock); |
| } |
| |
| static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; |
| CFIndex count; |
| __CFLock(&domain->_lock); |
| if (!domain->_domainDict) { |
| _loadXMLDomainIfStale((CFURLRef )context, domain); |
| } |
| count = CFDictionaryGetCount(domain->_domainDict); |
| if (buf) { |
| void **values; |
| if (count <= *numKeyValuePairs) { |
| values = *buf + count; |
| CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); |
| } else if (alloc != kCFAllocatorNull) { |
| *buf = (void**) CFAllocatorReallocate(alloc, (*buf ? *buf : NULL), count * 2 * sizeof(void *), 0); |
| if (*buf) { |
| values = *buf + count; |
| CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); |
| } |
| } |
| } |
| *numKeyValuePairs = count; |
| __CFUnlock(&domain->_lock); |
| } |
| |
| static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *xmlDomain) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; |
| CFDictionaryRef result; |
| |
| __CFLock(&domain->_lock); |
| if(!domain->_domainDict) { |
| _loadXMLDomainIfStale((CFURLRef)context, domain); |
| } |
| |
| result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), domain->_domainDict, kCFPropertyListImmutable); |
| |
| __CFUnlock(&domain->_lock); |
| return result; |
| } |
| |
| |
| static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable) { |
| ((_CFXMLPreferencesDomain *)domain)->_isWorldReadable = isWorldReadable; |
| } |
| |
| static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain) { |
| _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; |
| CFMutableDictionaryRef cachedDict; |
| CFMutableArrayRef changedKeys; |
| SInt32 idx, count; |
| Boolean success, tryAgain; |
| |
| __CFLock(&domain->_lock); |
| cachedDict = domain->_domainDict; |
| changedKeys = domain->_dirtyKeys; |
| count = CFArrayGetCount(changedKeys); |
| |
| if (count == 0) { |
| // no changes were made to this domain; just remove it from the cache to guarantee it will be taken from disk next access |
| if (cachedDict) { |
| CFRelease(cachedDict); |
| domain->_domainDict = NULL; |
| } |
| __CFUnlock(&domain->_lock); |
| return true; |
| } |
| |
| domain->_domainDict = NULL; // This forces a reload. Note that we now have a retain on cachedDict |
| do { |
| _loadXMLDomainIfStale((CFURLRef )context, domain); |
| // now cachedDict holds our changes; domain->_domainDict has the latest version from the disk |
| for (idx = 0; idx < count; idx ++) { |
| CFStringRef key = (CFStringRef) CFArrayGetValueAtIndex(changedKeys, idx); |
| CFTypeRef value = CFDictionaryGetValue(cachedDict, key); |
| if (value) |
| CFDictionarySetValue(domain->_domainDict, key, value); |
| else |
| CFDictionaryRemoveValue(domain->_domainDict, key); |
| } |
| success = _writeXMLFile((CFURLRef )context, domain->_domainDict, domain->_isWorldReadable, &tryAgain); |
| if (tryAgain) { |
| __CFMilliSleep(50); |
| } |
| } while (tryAgain); |
| CFRelease(cachedDict); |
| if (success) { |
| CFArrayRemoveAllValues(domain->_dirtyKeys); |
| } |
| domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); |
| __CFUnlock(&domain->_lock); |
| return success; |
| } |
| |