/ CFXMLPreferencesDomain.c
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