]> git.saurik.com Git - apple/cf.git/blob - CFXMLPreferencesDomain.c
CF-1152.14.tar.gz
[apple/cf.git] / CFXMLPreferencesDomain.c
1 /*
2 * Copyright (c) 2015 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-2014, 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
45 Boolean __CFPreferencesShouldWriteXML(void);
46
47 typedef 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 CFLock_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
56 static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context);
57 static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain);
58 static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key);
59 static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value);
60 static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain);
61 static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs);
62 static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *domain);
63 static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable);
64
65 CF_PRIVATE const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {createXMLDomain, freeXMLDomain, fetchXMLValue, writeXMLValue, synchronizeXMLDomain, getXMLKeysAndValues, copyXMLDomainDictionary, setXMLDomainIsWorldReadable};
66
67 // Directly ripped from Foundation....
68 static 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
83 static CFLock_t _propDictLock = CFLockInit; // Annoying that we need this, but otherwise we have a multithreading risk
84
85 CF_INLINE CFDictionaryRef URLPropertyDictForPOSIXMode(SInt32 mode) {
86 static CFMutableDictionaryRef _propertyDict = NULL;
87 CFNumberRef num = CFNumberCreate(__CFPreferencesAllocator(), kCFNumberSInt32Type, &mode);
88 __CFLock(&_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
97 CF_INLINE void URLPropertyDictRelease(void) {
98 __CFUnlock(&_propDictLock);
99 }
100
101 // Asssumes caller already knows the directory doesn't exist.
102 static 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 */
136 static 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 CFLock_t lock = CFLockInit;
142 domain->_lock = lock;
143 domain->_isWorldReadable = false;
144 return domain;
145 }
146
147 static 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
155 static 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
216 static 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 __CFLock(&domain->_lock);
222 if (domain->_domainDict == NULL) _loadXMLDomainIfStale((CFURLRef )context, domain);
223 result = CFDictionaryGetValue(domain->_domainDict, key);
224 if (result) CFRetain(result);
225 __CFUnlock(&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 */
236 static 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.
309 static 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
404 static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value) {
405 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
406 const void *existing = NULL;
407
408 __CFLock(&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 __CFUnlock(&domain->_lock);
420 return;
421 }
422 } else {
423 if (NULL == value) {
424 __CFUnlock(&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 __CFUnlock(&domain->_lock);
444 }
445
446 static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs) {
447 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
448 CFIndex count;
449 __CFLock(&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 __CFUnlock(&domain->_lock);
469 }
470
471 static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *xmlDomain) {
472 _CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
473 CFDictionaryRef result;
474
475 __CFLock(&domain->_lock);
476 if(!domain->_domainDict) {
477 _loadXMLDomainIfStale((CFURLRef)context, domain);
478 }
479
480 result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), domain->_domainDict, kCFPropertyListImmutable);
481
482 __CFUnlock(&domain->_lock);
483 return result;
484 }
485
486
487 static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable) {
488 ((_CFXMLPreferencesDomain *)domain)->_isWorldReadable = isWorldReadable;
489 }
490
491 static 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 __CFLock(&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 __CFUnlock(&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 __CFUnlock(&domain->_lock);
536 return success;
537 }
538