CFXMLPreferencesDomain.c [plain text]
#include <CoreFoundation/CFPreferences.h>
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFDate.h>
#include "CFInternal.h"
#include <time.h>
#if DEPLOYMENT_TARGET_MACOSX
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <mach/mach.h>
#include <mach/mach_syscalls.h>
#endif
Boolean __CFPreferencesShouldWriteXML(void);
typedef struct {
CFMutableDictionaryRef _domainDict; CFMutableArrayRef _dirtyKeys; CFAbsoluteTime _lastReadTime; CFSpinLock_t _lock; Boolean _isWorldReadable; char _padding[3];
} _CFXMLPreferencesDomain;
static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context);
static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain);
static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key);
static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value);
static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain);
static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs);
static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *domain);
static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable);
__private_extern__ const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {createXMLDomain, freeXMLDomain, fetchXMLValue, writeXMLValue, synchronizeXMLDomain, getXMLKeysAndValues, copyXMLDomainDictionary, setXMLDomainIsWorldReadable};
static void __CFMilliSleep(uint32_t msecs) {
#if DEPLOYMENT_TARGET_WINDOWS
SleepEx(msecs, false);
#elif defined(__svr4__) || defined(__hpux__)
sleep((msecs + 900) / 1000);
#elif DEPLOYMENT_TARGET_MACOSX
struct timespec input;
input.tv_sec = msecs / 1000;
input.tv_nsec = (msecs - input.tv_sec * 1000) * 1000000;
nanosleep(&input, NULL);
#else
#error Dont know how to define sleep for this platform
#endif
}
static CFSpinLock_t _propDictLock = CFSpinLockInit;
CF_INLINE CFDictionaryRef URLPropertyDictForPOSIXMode(SInt32 mode) {
static CFMutableDictionaryRef _propertyDict = NULL;
CFNumberRef num = CFNumberCreate(__CFPreferencesAllocator(), kCFNumberSInt32Type, &mode);
__CFSpinLock(&_propDictLock);
if (!_propertyDict) {
_propertyDict = CFDictionaryCreateMutable(__CFPreferencesAllocator(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
CFDictionarySetValue(_propertyDict, kCFURLFilePOSIXMode, num);
CFRelease(num);
return _propertyDict;
}
CF_INLINE void URLPropertyDictRelease(void) {
__CFSpinUnlock(&_propDictLock);
}
static Boolean _createDirectory(CFURLRef dirURL, Boolean worldReadable) {
CFAllocatorRef alloc = __CFPreferencesAllocator();
CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(alloc, dirURL);
CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL);
Boolean parentExists = (val && CFBooleanGetValue(val));
SInt32 mode;
Boolean result;
if (val) CFRelease(val);
if (!parentExists) {
CFStringRef path = CFURLCopyPath(parentURL);
if (!CFEqual(path, CFSTR("/"))) {
_createDirectory(parentURL, worldReadable);
val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL);
parentExists = (val && CFBooleanGetValue(val));
if (val) CFRelease(val);
}
CFRelease(path);
}
if (parentURL) CFRelease(parentURL);
if (!parentExists) return false;
#if DEPLOYMENT_TARGET_MACOSX
mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU;
#else
mode = 0666;
#endif
result = CFURLWriteDataAndPropertiesToResource(dirURL, (CFDataRef)dirURL, URLPropertyDictForPOSIXMode(mode), NULL);
URLPropertyDictRelease();
return result;
}
static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain*) CFAllocatorAllocate(allocator, sizeof(_CFXMLPreferencesDomain), 0);
domain->_lastReadTime = 0.0;
domain->_domainDict = NULL;
domain->_dirtyKeys = CFArrayCreateMutable(allocator, 0, & kCFTypeArrayCallBacks);
const CFSpinLock_t lock = CFSpinLockInit;
domain->_lock = lock;
domain->_isWorldReadable = false;
return domain;
}
static void freeXMLDomain(CFAllocatorRef allocator, CFTypeRef context, void *tDomain) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)tDomain;
if (domain->_domainDict) CFRelease(domain->_domainDict);
if (domain->_dirtyKeys) CFRelease(domain->_dirtyKeys);
CFAllocatorDeallocate(allocator, domain);
}
static void _loadXMLDomainIfStale(CFURLRef url, _CFXMLPreferencesDomain *domain) {
CFAllocatorRef alloc = __CFPreferencesAllocator();
int idx;
if (domain->_domainDict) {
CFDateRef modDate;
CFAbsoluteTime modTime;
CFURLRef testURL = url;
if (CFDictionaryGetCount(domain->_domainDict) == 0) {
testURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR(".."), kCFURLPOSIXPathStyle, true, url);
}
modDate = (CFDateRef )CFURLCreatePropertyFromResource(alloc, testURL, kCFURLFileLastModificationTime, NULL);
modTime = modDate ? CFDateGetAbsoluteTime(modDate) : 0.0;
if (testURL != url) CFRelease(testURL);
if (modDate) CFRelease(modDate);
if (modDate != NULL && modTime < domain->_lastReadTime) { return;
}
}
if (domain->_domainDict) {
CFRelease(domain->_domainDict);
domain->_domainDict = NULL;
}
for (idx = 0; idx < 3; idx ++) {
CFDataRef data;
if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &data, NULL, NULL, NULL) || !data) {
domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
break;
} else {
CFTypeRef pList = CFPropertyListCreateFromXMLData(alloc, data, kCFPropertyListImmutable, NULL);
CFRelease(data);
if (pList && CFGetTypeID(pList) == CFDictionaryGetTypeID()) {
domain->_domainDict = CFDictionaryCreateMutableCopy(alloc, 0, (CFDictionaryRef)pList);
CFRelease(pList);
break;
} else if (pList) {
CFRelease(pList);
}
__CFMilliSleep(150);
}
}
if (!domain->_domainDict) {
domain->_domainDict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
domain->_lastReadTime = CFAbsoluteTimeGetCurrent();
}
static CFTypeRef fetchXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
CFTypeRef result;
__CFSpinLock(&domain->_lock);
if (domain->_domainDict == NULL) _loadXMLDomainIfStale((CFURLRef )context, domain);
result = CFDictionaryGetValue(domain->_domainDict, key);
if (result) CFRetain(result);
__CFSpinUnlock(&domain->_lock);
return result;
}
#if DEPLOYMENT_TARGET_MACOSX
#include <sys/fcntl.h>
static Boolean __CFWriteBytesToFileWithAtomicity(CFURLRef url, const void *bytes, int length, SInt32 mode, Boolean atomic) {
int fd = -1;
char auxPath[CFMaxPathSize + 16];
char cpath[CFMaxPathSize];
uid_t owner = getuid();
gid_t group = getgid();
Boolean writingFileAsRoot = ((getuid() != geteuid()) && (geteuid() == 0));
if (!CFURLGetFileSystemRepresentation(url, true, (uint8_t *)cpath, CFMaxPathSize)) {
return false;
}
if (-1 == mode || writingFileAsRoot) {
struct stat statBuf;
if (0 == stat(cpath, &statBuf)) {
mode = statBuf.st_mode;
owner = statBuf.st_uid;
group = statBuf.st_gid;
} else {
mode = 0664;
if (writingFileAsRoot && (0 == strncmp(cpath, "/Library/Preferences", 20))) {
owner = geteuid();
group = 80;
}
}
}
if (atomic) {
CFURLRef dir = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url);
CFURLRef tempFile = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, dir, CFSTR("cf#XXXXX"), false);
CFRelease(dir);
if (!CFURLGetFileSystemRepresentation(tempFile, true, (uint8_t *)auxPath, CFMaxPathSize)) {
CFRelease(tempFile);
return false;
}
CFRelease(tempFile);
fd = mkstemp(auxPath);
} else {
fd = open(cpath, O_WRONLY|O_CREAT|O_TRUNC, mode);
}
if (fd < 0) return false;
if (length && (write(fd, bytes, length) != length || fsync(fd) < 0)) {
int saveerr = thread_errno();
close(fd);
if (atomic)
unlink(auxPath);
thread_set_errno(saveerr);
return false;
}
close(fd);
if (atomic) {
chmod(auxPath, mode);
if (0 != rename(auxPath, cpath)) {
unlink(auxPath);
return false;
}
if (writingFileAsRoot) {
chown(cpath, owner, group);
}
}
return true;
}
#endif
static Boolean _writeXMLFile(CFURLRef url, CFMutableDictionaryRef dict, Boolean isWorldReadable, Boolean *tryAgain) {
Boolean success = false;
CFAllocatorRef alloc = __CFPreferencesAllocator();
*tryAgain = false;
if (CFDictionaryGetCount(dict) == 0) {
CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL);
if (val && CFBooleanGetValue(val)) {
success = CFURLDestroyResource(url, NULL);
} else {
success = true;
}
if (val) CFRelease(val);
} else {
CFPropertyListFormat desiredFormat = __CFPreferencesShouldWriteXML() ? kCFPropertyListXMLFormat_v1_0 : kCFPropertyListBinaryFormat_v1_0;
CFWriteStreamRef binStream = CFWriteStreamCreateWithAllocatedBuffers(alloc, alloc);
CFWriteStreamOpen(binStream);
CFPropertyListWriteToStream(dict, binStream, desiredFormat, NULL);
CFWriteStreamClose(binStream);
CFDataRef data = (CFDataRef) CFWriteStreamCopyProperty(binStream, kCFStreamPropertyDataWritten);
CFRelease(binStream);
if (data) {
SInt32 mode;
#if DEPLOYMENT_TARGET_MACOSX
mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR;
#else
mode = 0666;
#endif
#if DEPLOYMENT_TARGET_MACOSX
{ CFStringRef scheme = CFURLCopyScheme(url);
if (!scheme) {
*tryAgain = false;
CFRelease(data);
return false;
} else if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) {
SInt32 length = CFDataGetLength(data);
const void *bytes = (0 == length) ? (const void *)"" : CFDataGetBytePtr(data);
Boolean atomicWriteSuccess = __CFWriteBytesToFileWithAtomicity(url, bytes, length, mode, true);
if (atomicWriteSuccess) {
CFRelease(scheme);
*tryAgain = false;
CFRelease(data);
return true;
}
if (!atomicWriteSuccess && thread_errno() == ENOSPC) {
CFRelease(scheme);
*tryAgain = false;
CFRelease(data);
return false;
}
}
CFRelease(scheme);
}
#endif
success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL);
URLPropertyDictRelease();
if (success) {
CFDataRef readData;
if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &readData, NULL, NULL, NULL) || !CFEqual(readData, data)) {
success = false;
*tryAgain = true;
}
if (readData) CFRelease(readData);
} else {
CFBooleanRef val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, url, kCFURLFileExists, NULL);
if (!val || !CFBooleanGetValue(val)) {
CFURLRef tmpURL = CFURLCreateWithFileSystemPathRelativeToBase(alloc, CFSTR("."), kCFURLPOSIXPathStyle, true, url); CFURLRef parentURL = tmpURL ? CFURLCopyAbsoluteURL(tmpURL) : NULL;
if (tmpURL) CFRelease(tmpURL);
if (val) CFRelease(val);
val = (CFBooleanRef) CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL);
if ((!val || !CFBooleanGetValue(val)) && _createDirectory(parentURL, isWorldReadable)) {
success = CFURLWriteDataAndPropertiesToResource(url, data, URLPropertyDictForPOSIXMode(mode), NULL);
URLPropertyDictRelease();
if (success) {
CFDataRef rdData;
if (!CFURLCreateDataAndPropertiesFromResource(alloc, url, &rdData, NULL, NULL, NULL) || !CFEqual(rdData, data)) {
success = false;
*tryAgain = true;
}
if (rdData) CFRelease(rdData);
}
}
if (parentURL) CFRelease(parentURL);
}
if (val) CFRelease(val);
}
CFRelease(data);
} else {
CFLog(__kCFLogAssertion, CFSTR("Could not generate XML data for property list"));
success = false;
}
}
return success;
}
static void writeXMLValue(CFTypeRef context, void *xmlDomain, CFStringRef key, CFTypeRef value) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
const void *existing = NULL;
__CFSpinLock(&domain->_lock);
if (domain->_domainDict == NULL) {
_loadXMLDomainIfStale((CFURLRef )context, domain);
}
if (CFDictionaryGetValueIfPresent(domain->_domainDict, key, &existing)) {
if (NULL != value && (existing == value || CFEqual(existing, value))) {
__CFSpinUnlock(&domain->_lock);
return;
}
} else {
if (NULL == value) {
__CFSpinUnlock(&domain->_lock);
return;
}
}
if (!CFArrayContainsValue(domain->_dirtyKeys, CFRangeMake(0, CFArrayGetCount(domain->_dirtyKeys)), key)) {
CFArrayAppendValue(domain->_dirtyKeys, key);
}
if (value) {
CFTypeRef newValue = CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), value, kCFPropertyListImmutable);
CFDictionarySetValue(domain->_domainDict, key, newValue);
CFRelease(newValue);
} else {
CFDictionaryRemoveValue(domain->_domainDict, key);
}
__CFSpinUnlock(&domain->_lock);
}
static void getXMLKeysAndValues(CFAllocatorRef alloc, CFTypeRef context, void *xmlDomain, void **buf[], CFIndex *numKeyValuePairs) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
CFIndex count;
__CFSpinLock(&domain->_lock);
if (!domain->_domainDict) {
_loadXMLDomainIfStale((CFURLRef )context, domain);
}
count = CFDictionaryGetCount(domain->_domainDict);
if (buf) {
void **values;
if (count <= *numKeyValuePairs) {
values = *buf + count;
CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values);
} else if (alloc != kCFAllocatorNull) {
*buf = (void**) CFAllocatorReallocate(alloc, (*buf ? *buf : NULL), count * 2 * sizeof(void *), 0);
if (*buf) {
values = *buf + count;
CFDictionaryGetKeysAndValues(domain->_domainDict, (const void **)*buf, (const void **)values);
}
}
}
*numKeyValuePairs = count;
__CFSpinUnlock(&domain->_lock);
}
static CFDictionaryRef copyXMLDomainDictionary(CFTypeRef context, void *xmlDomain) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
CFDictionaryRef result;
__CFSpinLock(&domain->_lock);
if(!domain->_domainDict) {
_loadXMLDomainIfStale((CFURLRef)context, domain);
}
result = (CFDictionaryRef)CFPropertyListCreateDeepCopy(__CFPreferencesAllocator(), domain->_domainDict, kCFPropertyListImmutable);
__CFSpinUnlock(&domain->_lock);
return result;
}
static void setXMLDomainIsWorldReadable(CFTypeRef context, void *domain, Boolean isWorldReadable) {
((_CFXMLPreferencesDomain *)domain)->_isWorldReadable = isWorldReadable;
}
static Boolean synchronizeXMLDomain(CFTypeRef context, void *xmlDomain) {
_CFXMLPreferencesDomain *domain = (_CFXMLPreferencesDomain *)xmlDomain;
CFMutableDictionaryRef cachedDict;
CFMutableArrayRef changedKeys;
SInt32 idx, count;
Boolean success, tryAgain;
__CFSpinLock(&domain->_lock);
cachedDict = domain->_domainDict;
changedKeys = domain->_dirtyKeys;
count = CFArrayGetCount(changedKeys);
if (count == 0) {
if (cachedDict) {
CFRelease(cachedDict);
domain->_domainDict = NULL;
}
__CFSpinUnlock(&domain->_lock);
return true;
}
domain->_domainDict = NULL; do {
_loadXMLDomainIfStale((CFURLRef )context, domain);
for (idx = 0; idx < count; idx ++) {
CFStringRef key = (CFStringRef) CFArrayGetValueAtIndex(changedKeys, idx);
CFTypeRef value = CFDictionaryGetValue(cachedDict, key);
if (value)
CFDictionarySetValue(domain->_domainDict, key, value);
else
CFDictionaryRemoveValue(domain->_domainDict, key);
}
success = _writeXMLFile((CFURLRef )context, domain->_domainDict, domain->_isWorldReadable, &tryAgain);
if (tryAgain) {
__CFMilliSleep((((uint32_t)__CFReadTSR() & 0xf) + 1) * 50);
}
} while (tryAgain);
CFRelease(cachedDict);
if (success) {
CFArrayRemoveAllValues(domain->_dirtyKeys);
}
domain->_lastReadTime = CFAbsoluteTimeGetCurrent();
__CFSpinUnlock(&domain->_lock);
return success;
}