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/*	CFPreferences.c
25	Copyright (c) 1998-2013, Apple Inc. All rights reserved.
26	Responsibility: David Smith
27*/
28
29#include <CoreFoundation/CFPreferences.h>
30#include <CoreFoundation/CFURLAccess.h>
31#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
32#include <CoreFoundation/CFUserNotification.h>
33#endif
34#include <CoreFoundation/CFPropertyList.h>
35#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS
36#include <CoreFoundation/CFBundle.h>
37#endif
38#include <CoreFoundation/CFNumber.h>
39#include <CoreFoundation/CFPriv.h>
40#include "CFInternal.h"
41#include <sys/stat.h>
42#if DEPLOYMENT_TARGET_MACOSX
43#include <unistd.h>
44#include <CoreFoundation/CFUUID.h>
45#endif
46
47#if DEBUG_PREFERENCES_MEMORY
48#include "../Tests/CFCountingAllocator.c"
49#endif
50
51static CFURLRef _CFPreferencesURLForStandardDomainWithSafetyLevel(CFStringRef domainName, CFStringRef userName, CFStringRef hostName, unsigned long safeLevel);
52
53struct __CFPreferencesDomain {
54    CFRuntimeBase _base;
55    /* WARNING - not copying the callbacks; we know they are always static structs */
56    const _CFPreferencesDomainCallBacks *_callBacks;
57    CFTypeRef _context;
58    void *_domain;
59};
60
61CONST_STRING_DECL(kCFPreferencesAnyApplication, "kCFPreferencesAnyApplication")
62CONST_STRING_DECL(kCFPreferencesAnyHost, "kCFPreferencesAnyHost")
63CONST_STRING_DECL(kCFPreferencesAnyUser, "kCFPreferencesAnyUser")
64CONST_STRING_DECL(kCFPreferencesCurrentApplication, "kCFPreferencesCurrentApplication")
65CONST_STRING_DECL(kCFPreferencesCurrentHost, "kCFPreferencesCurrentHost")
66CONST_STRING_DECL(kCFPreferencesCurrentUser, "kCFPreferencesCurrentUser")
67
68
69static CFAllocatorRef _preferencesAllocator = NULL;
70CF_PRIVATE CFAllocatorRef __CFPreferencesAllocator(void) {
71    if (!_preferencesAllocator) {
72#if DEBUG_PREFERENCES_MEMORY
73        _preferencesAllocator = CFCountingAllocatorCreate(NULL);
74#else
75        _preferencesAllocator = __CFGetDefaultAllocator();
76        CFRetain(_preferencesAllocator);
77#endif
78    }
79    return _preferencesAllocator;
80}
81
82// declaration for telling the
83void _CFApplicationPreferencesDomainHasChanged(CFPreferencesDomainRef);
84
85#if DEBUG_PREFERENCES_MEMORY
86#warning Preferences debugging on
87CF_EXPORT void CFPreferencesDumpMem(void) {
88    if (_preferencesAllocator) {
89//        CFCountingAllocatorPrintSummary(_preferencesAllocator);
90        CFCountingAllocatorPrintPointers(_preferencesAllocator);
91    }
92//    CFCountingAllocatorReset(_preferencesAllocator);
93}
94#endif
95
96#if DEPLOYMENT_TARGET_MACOSX
97#pragma mark -
98#pragma mark Determining host UUID
99#endif
100
101#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
102// The entry point is in libSystem.B.dylib, but not actually declared
103// If this becomes available in a header (<rdar://problem/4943036>), I need to pull this out
104int gethostuuid(unsigned char *uuid_buf, const struct timespec *timeoutp);
105
106CF_PRIVATE CFStringRef _CFGetHostUUIDString(void) {
107    static CFStringRef __hostUUIDString = NULL;
108
109    if (!__hostUUIDString) {
110        CFUUIDBytes uuidBytes;
111        int getuuidErr = 0;
112        struct timespec timeout = {0, 0};   // Infinite timeout for gethostuuid()
113
114        getuuidErr = gethostuuid((unsigned char *)&uuidBytes, &timeout);
115        if (getuuidErr == -1) {
116            // An error has occurred trying to get the host UUID string. There's nothing we can do here, so we should just return NULL.
117            CFLog(kCFLogLevelWarning, CFSTR("_CFGetHostUUIDString: unable to determine UUID for host. Error: %d"), errno);
118            return NULL;
119        }
120
121        CFUUIDRef uuidRef = CFUUIDCreateFromUUIDBytes(kCFAllocatorSystemDefault, uuidBytes);
122        CFStringRef uuidAsString = CFUUIDCreateString(kCFAllocatorSystemDefault, uuidRef);
123
124        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)uuidAsString, (void *)&__hostUUIDString)) {
125            CFRelease(uuidAsString);    // someone else made the assignment, so just release the extra string.
126        }
127
128        CFRelease(uuidRef);
129    }
130
131    return __hostUUIDString;
132}
133
134CF_PRIVATE CFStringRef _CFPreferencesGetByHostIdentifierString(void) {
135    static CFStringRef __byHostIdentifierString = NULL;
136
137    if (!__byHostIdentifierString) {
138        CFStringRef hostID = _CFGetHostUUIDString();
139        if (hostID) {
140            if (CFStringHasPrefix(hostID, CFSTR("00000000-0000-1000-8000-"))) {
141                // If the host UUID is prefixed by "00000000-0000-1000-8000-" then the UUID returned is the "compatible" type. The last field of the string will be the MAC address of the primary ethernet interface of the computer. We use this for compatibility with existing by-host preferences.
142                CFStringRef lastField = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, hostID, CFRangeMake(24, 12));
143                CFMutableStringRef tmpstr = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, lastField);
144                CFStringLowercase(tmpstr, NULL);
145                CFStringRef downcasedField = CFStringCreateCopy(kCFAllocatorSystemDefault, tmpstr);
146
147                if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)downcasedField, (void *)&__byHostIdentifierString)) {
148                    CFRelease(downcasedField);
149                }
150
151                CFRelease(tmpstr);
152                CFRelease(lastField);
153            } else {
154                // The host UUID is a full UUID, and we should just use that. This doesn't involve any additional string creation, so we should just be able to do the assignment.
155                __byHostIdentifierString = hostID;
156            }
157        } else {
158            __byHostIdentifierString = CFSTR("UnknownHostID");
159        }
160    }
161
162    return __byHostIdentifierString;
163}
164
165#else
166
167CF_PRIVATE CFStringRef _CFPreferencesGetByHostIdentifierString(void) {
168    return CFSTR("");
169}
170
171#endif
172
173
174static unsigned long __CFSafeLaunchLevel = 0;
175
176#if DEPLOYMENT_TARGET_WINDOWS
177#include <shfolder.h>
178
179#endif
180
181static CFURLRef _preferencesDirectoryForUserHostSafetyLevel(CFStringRef userName, CFStringRef hostName, unsigned long safeLevel) {
182    CFAllocatorRef alloc = __CFPreferencesAllocator();
183#if DEPLOYMENT_TARGET_WINDOWS
184
185	CFURLRef url = NULL;
186
187	CFMutableStringRef completePath = _CFCreateApplicationRepositoryPath(alloc, CSIDL_APPDATA);
188 	if (completePath) {
189	    // append "Preferences\" and make the CFURL
190	    CFStringAppend(completePath, CFSTR("Preferences\\"));
191		url = CFURLCreateWithFileSystemPath(alloc, completePath, kCFURLWindowsPathStyle, true);
192		CFRelease(completePath);
193	}
194
195
196	// Can't find a better place?  Home directory then?
197	if (url == NULL)
198		url = CFCopyHomeDirectoryURLForUser((userName == kCFPreferencesCurrentUser) ? NULL : userName);
199
200	return url;
201
202#else
203    CFURLRef  home = NULL;
204    CFURLRef  url;
205    int levels = 0;
206    //    if (hostName != kCFPreferencesCurrentHost && hostName != kCFPreferencesAnyHost) return NULL; // Arbitrary host access not permitted
207    if (userName == kCFPreferencesAnyUser) {
208        if (!home) home = CFURLCreateWithFileSystemPath(alloc, CFSTR("/Library/Preferences/"), kCFURLPOSIXPathStyle, true);
209        levels = 1;
210        if (hostName == kCFPreferencesCurrentHost) url = home;
211        else {
212            url = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("Network/"), kCFURLPOSIXPathStyle, true, home);
213            levels ++;
214            CFRelease(home);
215        }
216    } else {
217        home = CFCopyHomeDirectoryURLForUser((userName == kCFPreferencesCurrentUser) ? NULL : userName);
218        if (home) {
219            url = (safeLevel > 0) ? CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("Library/Safe Preferences/"), kCFURLPOSIXPathStyle, true, home) :
220            CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("Library/Preferences/"), kCFURLPOSIXPathStyle, true, home);
221            levels = 2;
222            CFRelease(home);
223            if (hostName != kCFPreferencesAnyHost) {
224                home = url;
225                url = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("ByHost/"), kCFURLPOSIXPathStyle, true, home);
226                levels ++;
227                CFRelease(home);
228            }
229        } else {
230            url = NULL;
231        }
232    }
233    return url;
234#endif
235}
236
237static CFURLRef  _preferencesDirectoryForUserHost(CFStringRef  userName, CFStringRef  hostName) {
238    return _preferencesDirectoryForUserHostSafetyLevel(userName, hostName, __CFSafeLaunchLevel);
239}
240
241static Boolean __CFPreferencesWritesXML = true;
242
243Boolean __CFPreferencesShouldWriteXML(void) {
244    return __CFPreferencesWritesXML;
245}
246
247static CFSpinLock_t domainCacheLock = CFSpinLockInit;
248static CFMutableDictionaryRef  domainCache = NULL; // mutable
249
250// Public API
251
252CFTypeRef  CFPreferencesCopyValue(CFStringRef  key, CFStringRef  appName, CFStringRef  user, CFStringRef  host) {
253    CFPreferencesDomainRef domain;
254    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
255    CFAssert1(key != NULL, __kCFLogAssertion, "%s(): Cannot access preferences with a NULL key", __PRETTY_FUNCTION__);
256
257    domain = _CFPreferencesStandardDomain(appName, user, host);
258    if (domain) {
259        return _CFPreferencesDomainCreateValueForKey(domain, key);
260    } else {
261        return NULL;
262    }
263}
264
265CFDictionaryRef CFPreferencesCopyMultiple(CFArrayRef keysToFetch, CFStringRef appName, CFStringRef user, CFStringRef host) {
266    CFPreferencesDomainRef domain;
267    CFMutableDictionaryRef result;
268    CFIndex idx, count;
269
270    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
271    __CFGenericValidateType(appName, CFStringGetTypeID());
272    __CFGenericValidateType(user, CFStringGetTypeID());
273    __CFGenericValidateType(host, CFStringGetTypeID());
274
275    domain = _CFPreferencesStandardDomain(appName, user, host);
276    if (!domain) return NULL;
277    if (!keysToFetch) {
278        return _CFPreferencesDomainDeepCopyDictionary(domain);
279    } else {
280        __CFGenericValidateType(keysToFetch, CFArrayGetTypeID());
281        count = CFArrayGetCount(keysToFetch);
282        result = CFDictionaryCreateMutable(CFGetAllocator(domain), count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
283        if (!result) return NULL;
284        for (idx = 0; idx < count; idx ++) {
285            CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keysToFetch, idx);
286            CFPropertyListRef value;
287            __CFGenericValidateType(key, CFStringGetTypeID());
288            value = _CFPreferencesDomainCreateValueForKey(domain, key);
289            if (value) {
290                CFDictionarySetValue(result, key, value);
291                CFRelease(value);
292            }
293        }
294    }
295    return result;
296}
297
298void CFPreferencesSetValue(CFStringRef  key, CFTypeRef  value, CFStringRef  appName, CFStringRef  user, CFStringRef  host) {
299    CFPreferencesDomainRef domain;
300    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
301    CFAssert1(key != NULL, __kCFLogAssertion, "%s(): Cannot access preferences with a NULL key", __PRETTY_FUNCTION__);
302
303    domain = _CFPreferencesStandardDomain(appName, user, host);
304    if (domain) {
305        _CFPreferencesDomainSet(domain, key, value);
306        _CFApplicationPreferencesDomainHasChanged(domain);
307    }
308}
309
310
311void CFPreferencesSetMultiple(CFDictionaryRef keysToSet, CFArrayRef keysToRemove, CFStringRef appName, CFStringRef user, CFStringRef host) {
312    CFPreferencesDomainRef domain;
313    CFIndex idx, count;
314    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
315    if (keysToSet) __CFGenericValidateType(keysToSet, CFDictionaryGetTypeID());
316    if (keysToRemove) __CFGenericValidateType(keysToRemove, CFArrayGetTypeID());
317    __CFGenericValidateType(appName, CFStringGetTypeID());
318    __CFGenericValidateType(user, CFStringGetTypeID());
319    __CFGenericValidateType(host, CFStringGetTypeID());
320
321    CFTypeRef *keys = NULL;
322    CFTypeRef *values;
323    CFIndex numOfKeysToSet = 0;
324
325    domain = _CFPreferencesStandardDomain(appName, user, host);
326    if (!domain) return;
327
328    CFAllocatorRef alloc = CFGetAllocator(domain);
329
330    if (keysToSet && (count = CFDictionaryGetCount(keysToSet))) {
331        numOfKeysToSet = count;
332        keys = (CFTypeRef *)CFAllocatorAllocate(alloc, 2*count*sizeof(CFTypeRef), 0);
333        if (keys) {
334            values = &(keys[count]);
335            CFDictionaryGetKeysAndValues(keysToSet, keys, values);
336            for (idx = 0; idx < count; idx ++) {
337                _CFPreferencesDomainSet(domain, (CFStringRef)keys[idx], values[idx]);
338            }
339        }
340    }
341    if (keysToRemove && (count = CFArrayGetCount(keysToRemove))) {
342        for (idx = 0; idx < count; idx ++) {
343            CFStringRef removedKey = (CFStringRef)CFArrayGetValueAtIndex(keysToRemove, idx);
344            _CFPreferencesDomainSet(domain, removedKey, NULL);
345        }
346    }
347
348
349    _CFApplicationPreferencesDomainHasChanged(domain);
350
351    if(keys) CFAllocatorDeallocate(alloc, keys);
352}
353
354Boolean CFPreferencesSynchronize(CFStringRef  appName, CFStringRef  user, CFStringRef  host) {
355    CFPreferencesDomainRef domain;
356    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
357
358    domain = _CFPreferencesStandardDomain(appName, user, host);
359    if(domain) _CFApplicationPreferencesDomainHasChanged(domain);
360
361    return domain ? _CFPreferencesDomainSynchronize(domain) : false;
362}
363
364CFArrayRef  CFPreferencesCopyApplicationList(CFStringRef  user, CFStringRef  host) {
365    CFArrayRef  array;
366    CFAssert1(user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL user or host", __PRETTY_FUNCTION__);
367    array = _CFPreferencesCreateDomainList(user, host);
368    return array;
369}
370
371CFArrayRef  CFPreferencesCopyKeyList(CFStringRef  appName, CFStringRef  user, CFStringRef  host) {
372    CFPreferencesDomainRef domain;
373    CFAssert1(appName != NULL && user != NULL && host != NULL, __kCFLogAssertion, "%s(): Cannot access preferences for a NULL application name, user, or host", __PRETTY_FUNCTION__);
374
375    domain = _CFPreferencesStandardDomain(appName, user, host);
376    if (!domain) {
377        return NULL;
378    } else {
379        CFArrayRef  result;
380
381	CFAllocatorRef alloc = __CFPreferencesAllocator();
382	CFDictionaryRef d = _CFPreferencesDomainDeepCopyDictionary(domain);
383	CFIndex count = d ? CFDictionaryGetCount(d) : 0;
384	CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(alloc, count * sizeof(CFTypeRef), 0);
385	if (d) CFDictionaryGetKeysAndValues(d, keys, NULL);
386        if (count == 0) {
387            result = NULL;
388        } else {
389            result = CFArrayCreate(alloc, keys, count, &kCFTypeArrayCallBacks);
390        }
391	CFAllocatorDeallocate(alloc, keys);
392	if (d) CFRelease(d);
393        return result;
394    }
395}
396
397
398/****************************/
399/*  CFPreferencesDomain     */
400/****************************/
401
402static CFStringRef __CFPreferencesDomainCopyDescription(CFTypeRef cf) {
403    return CFStringCreateWithFormat(__CFPreferencesAllocator(), NULL, CFSTR("<Private CFType %p>\n"), cf);
404}
405
406static void __CFPreferencesDomainDeallocate(CFTypeRef cf) {
407    const struct __CFPreferencesDomain *domain = (struct __CFPreferencesDomain *)cf;
408    CFAllocatorRef alloc = __CFPreferencesAllocator();
409    domain->_callBacks->freeDomain(alloc, domain->_context, domain->_domain);
410    if (domain->_context) CFRelease(domain->_context);
411}
412
413static CFTypeID __kCFPreferencesDomainTypeID = _kCFRuntimeNotATypeID;
414
415static const CFRuntimeClass __CFPreferencesDomainClass = {
416    0,
417    "CFPreferencesDomain",
418    NULL,      // init
419    NULL,      // copy
420    __CFPreferencesDomainDeallocate,
421    NULL,
422    NULL,
423    NULL,      //
424    __CFPreferencesDomainCopyDescription
425};
426
427/* This is called once at CFInitialize() time. */
428CF_PRIVATE void __CFPreferencesDomainInitialize(void) {
429    __kCFPreferencesDomainTypeID = _CFRuntimeRegisterClass(&__CFPreferencesDomainClass);
430}
431
432/* We spend a lot of time constructing these prefixes; we should cache.  REW, 7/19/99 */
433static CFStringRef  _CFPreferencesCachePrefixForUserHost(CFStringRef  userName, CFStringRef  hostName) {
434    if (userName == kCFPreferencesAnyUser && hostName == kCFPreferencesAnyHost) {
435        return (CFStringRef)CFRetain(CFSTR("*/*/"));
436    }
437    CFMutableStringRef result = CFStringCreateMutable(__CFPreferencesAllocator(), 0);
438    if (userName == kCFPreferencesCurrentUser) {
439        userName = CFCopyUserName();
440        CFStringAppend(result, userName);
441        CFRelease(userName);
442        CFStringAppend(result, CFSTR("/"));
443    } else if (userName == kCFPreferencesAnyUser) {
444        CFStringAppend(result, CFSTR("*/"));
445    }
446    if (hostName == kCFPreferencesCurrentHost) {
447        CFStringRef hostID = _CFPreferencesGetByHostIdentifierString();
448        CFStringAppend(result, hostID);
449        CFStringAppend(result, CFSTR("/"));
450    } else if (hostName == kCFPreferencesAnyHost) {
451        CFStringAppend(result, CFSTR("*/"));
452    }
453    return result;
454}
455
456// It would be nice if we could remember the key for "well-known" combinations, so we're not constantly allocing more strings....  - REW 2/3/99
457static CFStringRef  _CFPreferencesStandardDomainCacheKey(CFStringRef  domainName, CFStringRef  userName, CFStringRef  hostName) {
458    CFStringRef  prefix = _CFPreferencesCachePrefixForUserHost(userName, hostName);
459    CFStringRef  result = NULL;
460
461    if (prefix) {
462        result = CFStringCreateWithFormat(__CFPreferencesAllocator(), NULL, CFSTR("%@%@"), prefix, domainName);
463        CFRelease(prefix);
464    }
465    return result;
466}
467
468static CFURLRef _CFPreferencesURLForStandardDomainWithSafetyLevel(CFStringRef domainName, CFStringRef userName, CFStringRef hostName, unsigned long safeLevel) {
469    CFURLRef theURL = NULL;
470    CFAllocatorRef prefAlloc = __CFPreferencesAllocator();
471#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
472    CFURLRef prefDir = _preferencesDirectoryForUserHostSafetyLevel(userName, hostName, safeLevel);
473    CFStringRef  appName;
474    CFStringRef  fileName;
475    Boolean mustFreeAppName = false;
476
477    if (!prefDir) return NULL;
478    if (domainName == kCFPreferencesAnyApplication) {
479        appName = CFSTR(".GlobalPreferences");
480    } else if (domainName == kCFPreferencesCurrentApplication) {
481        CFBundleRef mainBundle = CFBundleGetMainBundle();
482        appName = mainBundle ? CFBundleGetIdentifier(mainBundle) : NULL;
483        if (!appName || CFStringGetLength(appName) == 0) {
484            appName = _CFProcessNameString();
485        }
486    } else {
487        appName = domainName;
488    }
489    if (userName != kCFPreferencesAnyUser) {
490        if (hostName == kCFPreferencesAnyHost) {
491            fileName = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR("%@.plist"), appName);
492        } else if (hostName == kCFPreferencesCurrentHost) {
493            CFStringRef hostID = _CFPreferencesGetByHostIdentifierString();
494            fileName = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR("%@.%@.plist"), appName, hostID);
495        } else {
496            fileName = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR("%@.%@.plist"), appName, hostName);      // sketchy - this allows someone to set an arbitrary hostname.
497        }
498    } else {
499        fileName = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR("%@.plist"), appName);
500    }
501    if (mustFreeAppName) {
502	CFRelease(appName);
503    }
504    if (fileName) {
505#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
506        theURL = CFURLCreateWithFileSystemPathRelativeToBase(prefAlloc, fileName, kCFURLPOSIXPathStyle, false, prefDir);
507#elif DEPLOYMENT_TARGET_WINDOWS
508		theURL = CFURLCreateWithFileSystemPathRelativeToBase(prefAlloc, fileName, kCFURLWindowsPathStyle, false, prefDir);
509#endif
510        if (prefDir) CFRelease(prefDir);
511        CFRelease(fileName);
512    }
513#else
514//#error Do not know where to store NSUserDefaults on this platform
515#endif
516    return theURL;
517}
518
519static CFURLRef _CFPreferencesURLForStandardDomain(CFStringRef domainName, CFStringRef userName, CFStringRef hostName) {
520    return _CFPreferencesURLForStandardDomainWithSafetyLevel(domainName, userName, hostName, __CFSafeLaunchLevel);
521}
522
523CFPreferencesDomainRef _CFPreferencesStandardDomain(CFStringRef  domainName, CFStringRef  userName, CFStringRef  hostName) {
524    CFPreferencesDomainRef domain;
525    CFStringRef  domainKey;
526    Boolean shouldReleaseDomain = true;
527     domainKey = _CFPreferencesStandardDomainCacheKey(domainName, userName, hostName);
528    __CFSpinLock(&domainCacheLock);
529    if (!domainCache) {
530        CFAllocatorRef alloc = __CFPreferencesAllocator();
531        domainCache = CFDictionaryCreateMutable(alloc, 0, & kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
532    }
533    domain = (CFPreferencesDomainRef)CFDictionaryGetValue(domainCache, domainKey);
534    __CFSpinUnlock(&domainCacheLock);
535    if (!domain) {
536        // Domain's not in the cache; load from permanent storage
537		CFURLRef  theURL = _CFPreferencesURLForStandardDomain(domainName, userName, hostName);
538        if (theURL) {
539			domain = _CFPreferencesDomainCreate(theURL, &__kCFXMLPropertyListDomainCallBacks);
540
541            if (userName == kCFPreferencesAnyUser) {
542                _CFPreferencesDomainSetIsWorldReadable(domain, true);
543            }
544            CFRelease(theURL);
545        }
546	__CFSpinLock(&domainCacheLock);
547        if (domain && domainCache) {
548            // We've just synthesized a domain & we're about to throw it in the domain cache. The problem is that someone else might have gotten in here behind our backs, so we can't just blindly set the domain (3021920). We'll need to check to see if this happened, and compensate.
549            CFPreferencesDomainRef checkDomain = (CFPreferencesDomainRef)CFDictionaryGetValue(domainCache, domainKey);
550            if(checkDomain) {
551                // Someone got in here ahead of us, so we shouldn't smash the domain we're given. checkDomain is the current version, we should use that.
552                // checkDomain was retrieved with a Get, so we don't want to over-release.
553                shouldReleaseDomain = false;
554                CFRelease(domain);	// release the domain we synthesized earlier.
555                domain = checkDomain;	// repoint it at the domain picked up out of the cache.
556            } else {
557                // We must not have found the domain in the cache, so it's ok for us to put this in.
558                CFDictionarySetValue(domainCache, domainKey, domain);
559            }
560            if(shouldReleaseDomain) CFRelease(domain);
561        }
562	__CFSpinUnlock(&domainCacheLock);
563    }
564    CFRelease(domainKey);
565    return domain;
566}
567
568static void __CFPreferencesPerformSynchronize(const void *key, const void *value, void *context) {
569    CFPreferencesDomainRef domain = (CFPreferencesDomainRef)value;
570    Boolean *cumulativeResult = (Boolean *)context;
571    if (!_CFPreferencesDomainSynchronize(domain)) *cumulativeResult = false;
572}
573
574CF_PRIVATE Boolean _CFSynchronizeDomainCache(void) {
575    Boolean result = true;
576    __CFSpinLock(&domainCacheLock);
577    if (domainCache) {
578        CFDictionaryApplyFunction(domainCache, __CFPreferencesPerformSynchronize, &result);
579    }
580    __CFSpinUnlock(&domainCacheLock);
581    return result;
582}
583
584CF_PRIVATE void _CFPreferencesPurgeDomainCache(void) {
585    _CFSynchronizeDomainCache();
586    __CFSpinLock(&domainCacheLock);
587    if (domainCache) {
588        CFRelease(domainCache);
589        domainCache = NULL;
590    }
591    __CFSpinUnlock(&domainCacheLock);
592}
593
594CF_PRIVATE CFArrayRef  _CFPreferencesCreateDomainList(CFStringRef  userName, CFStringRef  hostName) {
595    CFAllocatorRef prefAlloc = __CFPreferencesAllocator();
596    CFArrayRef  domains;
597    CFMutableArrayRef  marray;
598    CFStringRef  *cachedDomainKeys;
599    CFPreferencesDomainRef *cachedDomains;
600    SInt32 idx, cnt;
601    CFStringRef  suffix;
602    UInt32 suffixLen;
603    CFURLRef prefDir = _preferencesDirectoryForUserHost(userName, hostName);
604
605    if (!prefDir) {
606        return NULL;
607    }
608    if (hostName == kCFPreferencesAnyHost) {
609        suffix = CFStringCreateWithCString(prefAlloc, ".plist", kCFStringEncodingASCII);
610    } else if (hostName == kCFPreferencesCurrentHost) {
611        CFStringRef hostID = _CFPreferencesGetByHostIdentifierString();
612        suffix = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR(".%@.plist"), hostID);
613    } else {
614        suffix = CFStringCreateWithFormat(prefAlloc, NULL, CFSTR(".%@.plist"), hostName);   // sketchy - this allows someone to create a domain list for an arbitrary hostname.
615    }
616    suffixLen = CFStringGetLength(suffix);
617
618    domains = (CFArrayRef)CFURLCreatePropertyFromResource(prefAlloc, prefDir, kCFURLFileDirectoryContents, NULL);
619    CFRelease(prefDir);
620    if (domains){
621        marray = CFArrayCreateMutableCopy(prefAlloc, 0, domains);
622        CFRelease(domains);
623    } else {
624        marray = CFArrayCreateMutable(prefAlloc, 0, & kCFTypeArrayCallBacks);
625    }
626    for (idx = CFArrayGetCount(marray)-1; idx >= 0; idx --) {
627        CFURLRef  url = (CFURLRef)CFArrayGetValueAtIndex(marray, idx);
628        CFStringRef string = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
629        if (!CFStringHasSuffix(string, suffix)) {
630            CFArrayRemoveValueAtIndex(marray, idx);
631        } else {
632            CFStringRef  dom = CFStringCreateWithSubstring(prefAlloc, string, CFRangeMake(0, CFStringGetLength(string) - suffixLen));
633            if (CFEqual(dom, CFSTR(".GlobalPreferences"))) {
634                CFArraySetValueAtIndex(marray, idx, kCFPreferencesAnyApplication);
635            } else {
636                CFArraySetValueAtIndex(marray, idx, dom);
637            }
638            CFRelease(dom);
639        }
640        CFRelease(string);
641    }
642    CFRelease(suffix);
643
644    // Now add any domains added in the cache; delete any that have been deleted in the cache
645    __CFSpinLock(&domainCacheLock);
646    if (!domainCache) {
647        __CFSpinUnlock(&domainCacheLock);
648        return marray;
649    }
650    cnt = CFDictionaryGetCount(domainCache);
651    cachedDomainKeys = (CFStringRef *)CFAllocatorAllocate(prefAlloc, 2 * cnt * sizeof(CFStringRef), 0);
652    cachedDomains = (CFPreferencesDomainRef *)(cachedDomainKeys + cnt);
653    CFDictionaryGetKeysAndValues(domainCache, (const void **)cachedDomainKeys, (const void **)cachedDomains);
654    __CFSpinUnlock(&domainCacheLock);
655    suffix = _CFPreferencesCachePrefixForUserHost(userName, hostName);
656    suffixLen = CFStringGetLength(suffix);
657
658    for (idx = 0; idx < cnt; idx ++) {
659        CFStringRef  domainKey = cachedDomainKeys[idx];
660        CFPreferencesDomainRef domain = cachedDomains[idx];
661        CFStringRef  domainName;
662        CFIndex keyCount = 0;
663
664        if (!CFStringHasPrefix(domainKey, suffix)) continue;
665        domainName = CFStringCreateWithSubstring(prefAlloc, domainKey, CFRangeMake(suffixLen, CFStringGetLength(domainKey) - suffixLen));
666        if (CFEqual(domainName, CFSTR("*"))) {
667            CFRelease(domainName);
668            domainName = (CFStringRef)CFRetain(kCFPreferencesAnyApplication);
669        } else if (CFEqual(domainName, kCFPreferencesCurrentApplication)) {
670            CFRelease(domainName);
671            domainName = (CFStringRef)CFRetain(_CFProcessNameString());
672        }
673        CFDictionaryRef d = _CFPreferencesDomainDeepCopyDictionary(domain);
674        keyCount = d ? CFDictionaryGetCount(d) : 0;
675        if (keyCount) CFRelease(d);
676        if (keyCount == 0) {
677            // Domain was deleted
678            SInt32 firstIndexOfValue = CFArrayGetFirstIndexOfValue(marray, CFRangeMake(0, CFArrayGetCount(marray)), domainName);
679            if (0 <= firstIndexOfValue) {
680                CFArrayRemoveValueAtIndex(marray, firstIndexOfValue);
681            }
682        } else if (!CFArrayContainsValue(marray, CFRangeMake(0, CFArrayGetCount(marray)), domainName)) {
683            CFArrayAppendValue(marray, domainName);
684        }
685        CFRelease(domainName);
686    }
687    CFRelease(suffix);
688    CFAllocatorDeallocate(prefAlloc, cachedDomainKeys);
689    return marray;
690}
691
692//
693// CFPreferencesDomain functions
694//
695
696CFPreferencesDomainRef _CFPreferencesDomainCreate(CFTypeRef  context, const _CFPreferencesDomainCallBacks *callBacks) {
697    CFAllocatorRef alloc = __CFPreferencesAllocator();
698    CFPreferencesDomainRef newDomain;
699    CFAssert(callBacks != NULL && callBacks->createDomain != NULL && callBacks->freeDomain != NULL && callBacks->fetchValue != NULL && callBacks->writeValue != NULL, __kCFLogAssertion, "Cannot create a domain with NULL callbacks");
700    newDomain = (CFPreferencesDomainRef)_CFRuntimeCreateInstance(alloc, __kCFPreferencesDomainTypeID, sizeof(struct __CFPreferencesDomain) - sizeof(CFRuntimeBase), NULL);
701    if (newDomain) {
702        newDomain->_callBacks = callBacks;
703        if (context) CFRetain(context);
704        newDomain->_context = context;
705        newDomain->_domain = callBacks->createDomain(alloc, context);
706    }
707    return newDomain;
708}
709
710CFTypeRef  _CFPreferencesDomainCreateValueForKey(CFPreferencesDomainRef domain, CFStringRef key) {
711    return domain->_callBacks->fetchValue(domain->_context, domain->_domain, key);
712}
713
714void _CFPreferencesDomainSet(CFPreferencesDomainRef domain, CFStringRef  key, CFTypeRef  value) {
715    domain->_callBacks->writeValue(domain->_context, domain->_domain, key, value);
716}
717
718CF_PRIVATE Boolean _CFPreferencesDomainSynchronize(CFPreferencesDomainRef domain) {
719    return domain->_callBacks->synchronize(domain->_context, domain->_domain);
720}
721
722CF_PRIVATE void _CFPreferencesDomainSetIsWorldReadable(CFPreferencesDomainRef domain, Boolean isWorldReadable) {
723    if (domain->_callBacks->setIsWorldReadable) {
724        domain->_callBacks->setIsWorldReadable(domain->_context, domain->_domain, isWorldReadable);
725    }
726}
727
728CF_PRIVATE void *_CFPreferencesDomainCopyDictFunc(CFPreferencesDomainRef domain) {
729    return domain->_callBacks->copyDomainDictionary;
730}
731
732void _CFPreferencesDomainSetDictionary(CFPreferencesDomainRef domain, CFDictionaryRef dict) {
733    CFAllocatorRef alloc = __CFPreferencesAllocator();
734    CFDictionaryRef d = _CFPreferencesDomainDeepCopyDictionary(domain);
735    CFIndex idx, count = d ? CFDictionaryGetCount(d) : 0;
736
737    CFTypeRef *keys = (CFTypeRef *)CFAllocatorAllocate(alloc, count * sizeof(CFTypeRef), 0);
738    if (d) CFDictionaryGetKeysAndValues(d, keys, NULL);
739    for (idx = 0; idx < count; idx ++) {
740        _CFPreferencesDomainSet(domain, (CFStringRef)keys[idx], NULL);
741    }
742    CFAllocatorDeallocate(alloc, keys);
743    if (d) CFRelease(d);
744
745    if (dict && (count = CFDictionaryGetCount(dict)) != 0) {
746        CFStringRef *newKeys = (CFStringRef *)CFAllocatorAllocate(alloc, count * sizeof(CFStringRef), 0);
747        CFDictionaryGetKeysAndValues(dict, (const void **)newKeys, NULL);
748        for (idx = 0; idx < count; idx ++) {
749            CFStringRef key = newKeys[idx];
750            _CFPreferencesDomainSet(domain, key, (CFTypeRef)CFDictionaryGetValue(dict, key));
751        }
752            CFAllocatorDeallocate(alloc, newKeys);
753    }
754}
755
756CFDictionaryRef _CFPreferencesDomainDeepCopyDictionary(CFPreferencesDomainRef domain) {
757    CFDictionaryRef result = domain->_callBacks->copyDomainDictionary(domain->_context, domain->_domain);
758    if(result && CFDictionaryGetCount(result) == 0) {
759        CFRelease(result);
760        result = NULL;
761    }
762    return result;
763}
764
765Boolean _CFPreferencesDomainExists(CFStringRef domainName, CFStringRef userName, CFStringRef hostName) {
766    CFPreferencesDomainRef domain;
767    domain = _CFPreferencesStandardDomain(domainName, userName, hostName);
768    if (domain) {
769		CFDictionaryRef d = _CFPreferencesDomainDeepCopyDictionary(domain);
770		if (d) CFRelease(d);
771		return d != NULL;
772    } else {
773        return false;
774    }
775}
776
777/* Volatile domains - context is ignored; domain is a CFDictionary (mutable) */
778static void *createVolatileDomain(CFAllocatorRef allocator, CFTypeRef  context) {
779    return CFDictionaryCreateMutable(allocator, 0, & kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
780}
781
782static void freeVolatileDomain(CFAllocatorRef allocator, CFTypeRef  context, void *domain) {
783    CFRelease((CFTypeRef)domain);
784}
785
786static CFTypeRef  fetchVolatileValue(CFTypeRef  context, void *domain, CFStringRef  key) {
787    CFTypeRef  result = CFDictionaryGetValue((CFMutableDictionaryRef  )domain, key);
788    if (result) CFRetain(result);
789    return result;
790}
791
792static void writeVolatileValue(CFTypeRef  context, void *domain, CFStringRef  key, CFTypeRef  value) {
793    if (value)
794        CFDictionarySetValue((CFMutableDictionaryRef  )domain, key, value);
795    else
796        CFDictionaryRemoveValue((CFMutableDictionaryRef  )domain, key);
797}
798
799static Boolean synchronizeVolatileDomain(CFTypeRef  context, void *domain) {
800    return true;
801}
802
803static void getVolatileKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *domain, void **buf[], CFIndex *numKeyValuePairs) {
804    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)domain;
805    CFIndex count = CFDictionaryGetCount(dict);
806
807    if (buf) {
808        void **values;
809        if ( count < *numKeyValuePairs ) {
810            values = *buf + count;
811            CFDictionaryGetKeysAndValues(dict, (const void **)*buf, (const void **)values);
812        } else if (alloc != kCFAllocatorNull) {
813            if (*buf) {
814                *buf = (void **)CFAllocatorReallocate(alloc, *buf, count * 2 * sizeof(void *), 0);
815            } else {
816                *buf = (void **)CFAllocatorAllocate(alloc, count*2*sizeof(void *), 0);
817            }
818            if (*buf) {
819                values = *buf + count;
820                CFDictionaryGetKeysAndValues(dict, (const void **)*buf, (const void **)values);
821            }
822        }
823    }
824    *numKeyValuePairs = count;
825}
826
827static CFDictionaryRef copyVolatileDomainDictionary(CFTypeRef context, void *volatileDomain) {
828    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)volatileDomain;
829
830    CFDictionaryRef result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), dict, kCFPropertyListImmutable);
831    return result;
832}
833
834const _CFPreferencesDomainCallBacks __kCFVolatileDomainCallBacks = {createVolatileDomain, freeVolatileDomain, fetchVolatileValue, writeVolatileValue, synchronizeVolatileDomain, getVolatileKeysAndValues, copyVolatileDomainDictionary, NULL};
835