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