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