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