1/* 2 * Copyright (c) 2014 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* CFXMLPreferencesDomain.c 25 Copyright (c) 1998-2013, Apple Inc. All rights reserved. 26 Responsibility: David Smith 27*/ 28 29 30#include <CoreFoundation/CFPreferences.h> 31#include <CoreFoundation/CFURLAccess.h> 32#include <CoreFoundation/CFPropertyList.h> 33#include <CoreFoundation/CFNumber.h> 34#include <CoreFoundation/CFDate.h> 35#include "CFInternal.h" 36#include <time.h> 37#if DEPLOYMENT_TARGET_MACOSX 38#include <unistd.h> 39#include <stdio.h> 40#include <sys/stat.h> 41#include <mach/mach.h> 42#include <mach/mach_syscalls.h> 43#endif 44 45Boolean __CFPreferencesShouldWriteXML(void); 46 47typedef struct { 48 CFMutableDictionaryRef _domainDict; // Current value of the domain dictionary 49 CFMutableArrayRef _dirtyKeys; // The array of keys which must be synchronized 50 CFAbsoluteTime _lastReadTime; // The last time we synchronized with the disk 51 CFSpinLock_t _lock; // Lock for accessing fields in the domain 52 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 53 char _padding[3]; 54} _CFXMLPreferencesDomain; 55 56static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context); 57static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain); 58static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key); 59static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value); 60static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain); 61static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs); 62static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *domain); 63static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable); 64 65CF_PRIVATE const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {createXMLDomain, freeXMLDomain, fetchXMLValue, writeXMLValue, synchronizeXMLDomain, getXMLKeysAndValues, copyXMLDomainDictionary, setXMLDomainIsWorldReadable}; 66 67// Directly ripped from Foundation.... 68static void __CFMilliSleep(uint32_t msecs) { 69#if DEPLOYMENT_TARGET_WINDOWS 70 SleepEx(msecs, false); 71#elif defined(__svr4__) || defined(__hpux__) 72 sleep((msecs + 900) / 1000); 73#elif DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX 74 struct timespec input; 75 input.tv_sec = msecs / 1000; 76 input.tv_nsec = (msecs - input.tv_sec * 1000) * 1000000; 77 nanosleep(&input, NULL); 78#else 79#error Dont know how to define sleep for this platform 80#endif 81} 82 83static CFSpinLock_t _propDictLock = CFSpinLockInit; // Annoying that we need this, but otherwise we have a multithreading risk 84 85CF_INLINE CFDictionaryRef URLPropertyDictForPOSIXMode(SInt32 mode) { 86 static CFMutableDictionaryRef _propertyDict = NULL; 87 CFNumberRef num = CFNumberCreate(__CFPreferencesAllocator(), kCFNumberSInt32Type, &mode); 88 __CFSpinLock(&_propDictLock); 89 if (!_propertyDict) { 90 _propertyDict = CFDictionaryCreateMutable(__CFPreferencesAllocator(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 91 } 92 CFDictionarySetValue(_propertyDict, kCFURLFilePOSIXMode, num); 93 CFRelease(num); 94 return _propertyDict; 95} 96 97CF_INLINE void URLPropertyDictRelease(void) { 98 __CFSpinUnlock(&_propDictLock); 99} 100 101// Asssumes caller already knows the directory doesn't exist. 102static Boolean _createDirectory(CFURLRef dirURL, Boolean worldReadable) { 103 CFAllocatorRef alloc = __CFPreferencesAllocator(); 104 CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(alloc, dirURL); 105 CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); 106 Boolean parentExists = (val && CFBooleanGetValue(val)); 107 SInt32 mode; 108 Boolean result; 109 if (val) CFRelease(val); 110 if (!parentExists) { 111 CFStringRef path = CFURLCopyPath(parentURL); 112 if (!CFEqual(path, CFSTR("/"))) { 113 _createDirectory(parentURL, worldReadable); 114 val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); 115 parentExists = (val && CFBooleanGetValue(val)); 116 if (val) CFRelease(val); 117 } 118 CFRelease(path); 119 } 120 if (parentURL) CFRelease(parentURL); 121 if (!parentExists) return false; 122 123#if DEPLOYMENT_TARGET_MACOSX 124 mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU; 125#else 126 mode = 0666; 127#endif 128 129 result = CFURLWriteDataAndPropertiesToResource(dirURL, (CFDataRef)dirURL, URLPropertyDictForPOSIXMode(mode), NULL); 130 URLPropertyDictRelease(); 131 return result; 132} 133 134 135/* XML - context is the CFURL where the property list is stored on disk; domain is an _CFXMLPreferencesDomain */ 136static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context) { 137 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain*) CFAllocatorAllocate(allocator, sizeof(_CFXMLPreferencesDomain), 0); 138 domain->_lastReadTime = 0.0; 139 domain->_domainDict = NULL; 140 domain->_dirtyKeys = CFArrayCreateMutable(allocator, 0, & kCFTypeArrayCallBacks); 141 const CFSpinLock_t lock = CFSpinLockInit; 142 domain->_lock = lock; 143 domain->_isWorldReadable = false; 144 return domain; 145} 146 147static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain) { 148 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)tDomain; 149 if (domain->_domainDict) CFRelease(domain->_domainDict); 150 if (domain->_dirtyKeys) CFRelease(domain->_dirtyKeys); 151 CFAllocatorDeallocate(allocator, domain); 152} 153 154// Assumes the domain has already been locked 155static void _loadXMLDomainIfStale(CFURLRef url, _CFXMLPreferencesDomain *domain) { 156 CFAllocatorRef alloc = __CFPreferencesAllocator(); 157 int idx; 158 if (domain->_domainDict) { 159 CFDateRef modDate; 160 CFAbsoluteTime modTime; 161 CFURLRef testURL = url; 162 163 if (CFDictionaryGetCount(domain->_domainDict) == 0) { 164 // domain never existed; check the parent directory, not the child 165 testURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR(".."), kCFURLPOSIXPathStyle, true, url); 166 } 167 168 modDate = (CFDateRef )CFURLCreatePropertyFromResource(alloc, testURL, kCFURLFileLastModificationTime, NULL); 169 modTime = modDate ? CFDateGetAbsoluteTime(modDate) : 0.0; 170 171 // free before possible return. we can test non-NULL of modDate but don't depend on contents after this. 172 if (testURL != url) CFRelease(testURL); 173 if (modDate) CFRelease(modDate); 174 175 if (modDate != NULL && modTime < domain->_lastReadTime) { // We're up-to-date 176 return; 177 } 178 } 179 180 181 // We're out-of-date; destroy domainDict and reload 182 if (domain->_domainDict) { 183 CFRelease(domain->_domainDict); 184 domain->_domainDict = NULL; 185 } 186 187 // 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 188 189 for (idx = 0; idx < 3; idx ++) { 190 CFDataRef data; 191 if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &data, NULL, NULL, NULL) || !data) { 192 // Either a file system error (so we can't read the file), or an empty (or perhaps non-existant) file 193 domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 194 break; 195 } else { 196 CFTypeRef pList = CFPropertyListCreateFromXMLData(alloc, data, kCFPropertyListImmutable, NULL); 197 CFRelease(data); 198 if (pList && CFGetTypeID(pList) == CFDictionaryGetTypeID()) { 199 domain->_domainDict = CFDictionaryCreateMutableCopy(alloc, 0, (CFDictionaryRef)pList); 200 CFRelease(pList); 201 break; 202 } else if (pList) { 203 CFRelease(pList); 204 } 205 // Assume the file is being written; sleep for a short time (to allow the write to complete) then re-read 206 __CFMilliSleep(150); 207 } 208 } 209 if (!domain->_domainDict) { 210 // Failed to ever load 211 domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 212 } 213 domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); 214} 215 216static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key) { 217 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; 218 CFTypeRef result; 219 220 // Never reload if we've looked at the file system within the last 5 seconds. 221 __CFSpinLock(&domain->_lock); 222 if (domain->_domainDict == NULL) _loadXMLDomainIfStale((CFURLRef )context, domain); 223 result = CFDictionaryGetValue(domain->_domainDict, key); 224 if (result) CFRetain(result); 225 __CFSpinUnlock(&domain->_lock); 226 227 return result; 228} 229 230 231#if DEPLOYMENT_TARGET_MACOSX 232#include <sys/fcntl.h> 233 234/* __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. 235 */ 236static Boolean __CFWriteBytesToFileWithAtomicity(CFURLRef url, const void *bytes, int length, SInt32 mode, Boolean atomic) { 237 int fd = -1; 238 char auxPath[CFMaxPathSize + 16]; 239 char cpath[CFMaxPathSize]; 240 uid_t owner = getuid(); 241 gid_t group = getgid(); 242 Boolean writingFileAsRoot = ((getuid() != geteuid()) && (geteuid() == 0)); 243 244 if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)cpath, CFMaxPathSize)) { 245 return false; 246 } 247 248 if (-1 == mode || writingFileAsRoot) { 249 struct stat statBuf; 250 if (0 == stat(cpath, &statBuf)) { 251 mode = statBuf.st_mode; 252 owner = statBuf.st_uid; 253 group = statBuf.st_gid; 254 } else { 255 mode = 0664; 256 if (writingFileAsRoot && (0 == strncmp(cpath, "/Library/Preferences", 20))) { 257 owner = geteuid(); 258 group = 80; 259 } 260 } 261 } 262 263 if (atomic) { 264 CFURLRef dir = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); 265 CFURLRef tempFile = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, dir, CFSTR("cf#XXXXX"), false); 266 CFRelease(dir); 267 if (!CFURLGetFileSystemRepresentation(tempFile, true, (uint8_t *)auxPath, CFMaxPathSize)) { 268 CFRelease(tempFile); 269 return false; 270 } 271 CFRelease(tempFile); 272 fd = mkstemp(auxPath); 273 } else { 274 fd = open(cpath, O_WRONLY|O_CREAT|O_TRUNC, mode); 275 } 276 277 if (fd < 0) return false; 278 279 if (length && (write(fd, bytes, length) != length || fsync(fd) < 0)) { 280 int saveerr = thread_errno(); 281 close(fd); 282 if (atomic) 283 unlink(auxPath); 284 thread_set_errno(saveerr); 285 return false; 286 } 287 288 close(fd); 289 290 if (atomic) { 291 // preserve the mode as passed in originally 292 chmod(auxPath, mode); 293 294 if (0 != rename(auxPath, cpath)) { 295 unlink(auxPath); 296 return false; 297 } 298 299 // If the file was renamed successfully and we wrote it as root we need to reset the owner & group as they were. 300 if (writingFileAsRoot) { 301 chown(cpath, owner, group); 302 } 303 } 304 return true; 305} 306#endif 307 308// domain should already be locked. 309static Boolean _writeXMLFile(CFURLRef url, CFMutableDictionaryRef dict, Boolean isWorldReadable, Boolean *tryAgain) { 310 Boolean success = false; 311 CFAllocatorRef alloc = __CFPreferencesAllocator(); 312 *tryAgain = false; 313 if (CFDictionaryGetCount(dict) == 0) { 314 // Destroy the file 315 CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); 316 if (val && CFBooleanGetValue(val)) { 317 success = CFURLDestroyResource(url, NULL); 318 } else { 319 success = true; 320 } 321 if (val) CFRelease(val); 322 } else { 323 CFPropertyListFormat desiredFormat = __CFPreferencesShouldWriteXML() ? kCFPropertyListXMLFormat_v1_0 : kCFPropertyListBinaryFormat_v1_0; 324 CFDataRef data = CFPropertyListCreateData(alloc, dict, desiredFormat, 0, NULL); 325 if (data) { 326 SInt32 mode; 327#if DEPLOYMENT_TARGET_MACOSX 328 mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR; 329#else 330 mode = 0666; 331#endif 332#if DEPLOYMENT_TARGET_MACOSX 333 { // Try quick atomic way first, then fallback to slower ways and error cases 334 CFStringRef scheme = CFURLCopyScheme(url); 335 if (!scheme) { 336 *tryAgain = false; 337 CFRelease(data); 338 return false; 339 } else if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) { 340 SInt32 length = CFDataGetLength(data); 341 const void *bytes = (0 == length) ? (const void *)"" : CFDataGetBytePtr(data); 342 Boolean atomicWriteSuccess = __CFWriteBytesToFileWithAtomicity(url, bytes, length, mode, true); 343 if (atomicWriteSuccess) { 344 CFRelease(scheme); 345 *tryAgain = false; 346 CFRelease(data); 347 return true; 348 } 349 if (!atomicWriteSuccess && thread_errno() == ENOSPC) { 350 CFRelease(scheme); 351 *tryAgain = false; 352 CFRelease(data); 353 return false; 354 } 355 } 356 CFRelease(scheme); 357 } 358#endif 359 success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); 360 URLPropertyDictRelease(); 361 if (success) { 362 CFDataRef readData; 363 if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &readData, NULL, NULL, NULL) || !CFEqual(readData, data)) { 364 success = false; 365 *tryAgain = true; 366 } 367 if (readData) CFRelease(readData); 368 } else { 369 CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL); 370 if (!val || !CFBooleanGetValue(val)) { 371 CFURLRef tmpURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("."), kCFURLPOSIXPathStyle, true, url); // Just "." because url is not a directory URL 372 CFURLRef parentURL = tmpURL ? CFURLCopyAbsoluteURL(tmpURL) : NULL; 373 if (tmpURL) CFRelease(tmpURL); 374 if (val) CFRelease(val); 375 val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL); 376 if ((!val || !CFBooleanGetValue(val)) && _createDirectory(parentURL, isWorldReadable)) { 377 // parent directory didn't exist; now it does; try again to write 378 success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL); 379 URLPropertyDictRelease(); 380 if (success) { 381 CFDataRef rdData; 382 if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &rdData, NULL, NULL, NULL) || !CFEqual(rdData, data)) { 383 success = false; 384 *tryAgain = true; 385 } 386 if (rdData) CFRelease(rdData); 387 } 388 389 } 390 if (parentURL) CFRelease(parentURL); 391 } 392 if (val) CFRelease(val); 393 } 394 CFRelease(data); 395 } else { 396 // ??? This should never happen 397 CFLog(__kCFLogAssertion, CFSTR("Could not generate XML data for property list")); 398 success = false; 399 } 400 } 401 return success; 402} 403 404static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value) { 405 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; 406 const void *existing = NULL; 407 408 __CFSpinLock(&domain->_lock); 409 if (domain->_domainDict == NULL) { 410 _loadXMLDomainIfStale((CFURLRef )context, domain); 411 } 412 413 // check to see if the value is the same 414 // if (1) the key is present AND value is !NULL and equal to existing, do nothing, or 415 // if (2) the key is not present AND value is NULL, do nothing 416 // these things are no-ops, and should not dirty the domain 417 if (CFDictionaryGetValueIfPresent(domain->_domainDict, key, &existing)) { 418 if (NULL != value && (existing == value || CFEqual(existing, value))) { 419 __CFSpinUnlock(&domain->_lock); 420 return; 421 } 422 } else { 423 if (NULL == value) { 424 __CFSpinUnlock(&domain->_lock); 425 return; 426 } 427 } 428 429 // We must append first so key gets another retain (in case we're 430 // about to remove it from the dictionary, and that's the sole reference) 431 // This should be a set not an array. 432 if (!CFArrayContainsValue(domain->_dirtyKeys, CFRangeMake(0, CFArrayGetCount(domain->_dirtyKeys)), key)) { 433 CFArrayAppendValue(domain->_dirtyKeys, key); 434 } 435 if (value) { 436 // 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. 437 CFTypeRef newValue = CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), value, kCFPropertyListImmutable); 438 CFDictionarySetValue(domain->_domainDict, key, newValue); 439 CFRelease(newValue); 440 } else { 441 CFDictionaryRemoveValue(domain->_domainDict, key); 442 } 443 __CFSpinUnlock(&domain->_lock); 444} 445 446static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs) { 447 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; 448 CFIndex count; 449 __CFSpinLock(&domain->_lock); 450 if (!domain->_domainDict) { 451 _loadXMLDomainIfStale((CFURLRef )context, domain); 452 } 453 count = CFDictionaryGetCount(domain->_domainDict); 454 if (buf) { 455 void **values; 456 if (count <= *numKeyValuePairs) { 457 values = *buf + count; 458 CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); 459 } else if (alloc != kCFAllocatorNull) { 460 *buf = (void**) CFAllocatorReallocate(alloc, (*buf ? *buf : NULL), count * 2 * sizeof(void *), 0); 461 if (*buf) { 462 values = *buf + count; 463 CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values); 464 } 465 } 466 } 467 *numKeyValuePairs = count; 468 __CFSpinUnlock(&domain->_lock); 469} 470 471static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *xmlDomain) { 472 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; 473 CFDictionaryRef result; 474 475 __CFSpinLock(&domain->_lock); 476 if(!domain->_domainDict) { 477 _loadXMLDomainIfStale((CFURLRef)context, domain); 478 } 479 480 result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), domain->_domainDict, kCFPropertyListImmutable); 481 482 __CFSpinUnlock(&domain->_lock); 483 return result; 484} 485 486 487static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable) { 488 ((_CFXMLPreferencesDomain *)domain)->_isWorldReadable = isWorldReadable; 489} 490 491static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain) { 492 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain; 493 CFMutableDictionaryRef cachedDict; 494 CFMutableArrayRef changedKeys; 495 SInt32 idx, count; 496 Boolean success, tryAgain; 497 498 __CFSpinLock(&domain->_lock); 499 cachedDict = domain->_domainDict; 500 changedKeys = domain->_dirtyKeys; 501 count = CFArrayGetCount(changedKeys); 502 503 if (count == 0) { 504 // no changes were made to this domain; just remove it from the cache to guarantee it will be taken from disk next access 505 if (cachedDict) { 506 CFRelease(cachedDict); 507 domain->_domainDict = NULL; 508 } 509 __CFSpinUnlock(&domain->_lock); 510 return true; 511 } 512 513 domain->_domainDict = NULL; // This forces a reload. Note that we now have a retain on cachedDict 514 do { 515 _loadXMLDomainIfStale((CFURLRef )context, domain); 516 // now cachedDict holds our changes; domain->_domainDict has the latest version from the disk 517 for (idx = 0; idx < count; idx ++) { 518 CFStringRef key = (CFStringRef) CFArrayGetValueAtIndex(changedKeys, idx); 519 CFTypeRef value = CFDictionaryGetValue(cachedDict, key); 520 if (value) 521 CFDictionarySetValue(domain->_domainDict, key, value); 522 else 523 CFDictionaryRemoveValue(domain->_domainDict, key); 524 } 525 success = _writeXMLFile((CFURLRef )context, domain->_domainDict, domain->_isWorldReadable, &tryAgain); 526 if (tryAgain) { 527 __CFMilliSleep(50); 528 } 529 } while (tryAgain); 530 CFRelease(cachedDict); 531 if (success) { 532 CFArrayRemoveAllValues(domain->_dirtyKeys); 533 } 534 domain->_lastReadTime = CFAbsoluteTimeGetCurrent(); 535 __CFSpinUnlock(&domain->_lock); 536 return success; 537} 538 539