CFXMLPreferencesDomain.c [plain text]
#if !defined(__WIN32__)
#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>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <mach/mach.h>
#include <mach/mach_syscalls.h>
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 defined(__WIN32__)
SleepEx(msecs, false);
#elif defined(__svr4__) || defined(__hpux__)
sleep((msecs + 900) / 1000);
#elif defined(__MACH__)
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 = 0;
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 = 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 = CFURLCreatePropertyFromResource(alloc, parentURL, kCFURLFileExists, NULL);
parentExists = (val && CFBooleanGetValue(val));
if (val) CFRelease(val);
}
CFRelease(path);
}
if (parentURL) CFRelease(parentURL);
if (!parentExists) return false;
mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU;
result = CFURLWriteDataAndPropertiesToResource(dirURL, (CFDataRef)dirURL, URLPropertyDictForPOSIXMode(mode), NULL);
URLPropertyDictRelease();
return result;
}
static void *createXMLDomain(CFAllocatorRef allocator, CFTypeRef context) {
_CFXMLPreferencesDomain *domain = CFAllocatorAllocate(allocator, sizeof(_CFXMLPreferencesDomain), 0);
domain->_lastReadTime = 0.0;
domain->_domainDict = NULL;
domain->_dirtyKeys = CFArrayCreateMutable(allocator, 0, & kCFTypeArrayCallBacks);
domain->_lock = 0;
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 defined(__MACH__)
#include <sys/fcntl.h>
#if 0
static int __CFmkstemp83(char *fn, char *prefix, int mode, int *fd) {
static CFSpinLock_t counter_lock = 0;
static unsigned int extension_counter = 0;
int origlen = strlen(fn);
char idbuf[6], extbuf[6], prebuf[5];
uint16_t pid, origpid, ext, origext;
__CFSpinLock(&counter_lock);
ext = extension_counter++;
if (0xFFF < extension_counter) extension_counter = 0;
__CFSpinUnlock(&counter_lock);
origext = ext;
do {
char *s1 = prebuf;
const char *s2 = prefix;
int n = 0;
for (; (*s1 = *s2) && (n < 4); s1++, s2++, n++);
} while (0);
prebuf[4] = '\0';
if (0 < origlen && fn[origlen - 1] != '/')
fn[origlen++] = '/';
pid = getpid() & 0xFFFF;
origpid = pid;
snprintf(idbuf, 6, "%04x", pid);
snprintf(extbuf, 6, ".%03x", ext);
fn[origlen] = '\0';
strcat(fn, prebuf);
strcat(fn, idbuf);
strcat(fn, extbuf);
for (;;) {
*fd = open(fn, O_CREAT|O_EXCL|O_RDWR, mode);
if (0 <= *fd)
return 0;
if (EEXIST != thread_errno())
return -1;
ext = (ext + 1) & 0xFFF;
if (origext == ext) {
pid = (pid + 1) & 0xFFFF;
if (pid == origpid)
return -1; snprintf(idbuf, 6, "%04x", pid);
}
snprintf(extbuf, 6, ".%03x", ext);
fn[origlen] = '\0';
strcat(fn, prebuf);
strcat(fn, idbuf);
strcat(fn, extbuf);
}
return -1;
}
#endif
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, 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(NULL, url);
CFURLRef tempFile = CFURLCreateCopyAppendingPathComponent(NULL, dir, CFSTR("cf#XXXXX"), false);
CFRelease(dir);
if (!CFURLGetFileSystemRepresentation(tempFile, true, 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 = 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 = CFWriteStreamCopyProperty(binStream, kCFStreamPropertyDataWritten);
CFRelease(binStream);
if (data) {
SInt32 mode;
mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR;
#if 1 && defined(__MACH__)
{ 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 = 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 = 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 = 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 = 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(((__CFReadTSR() & 0xf) + 1) * 50);
}
} while (tryAgain);
CFRelease(cachedDict);
if (success) {
CFArrayRemoveAllValues(domain->_dirtyKeys);
}
domain->_lastReadTime = CFAbsoluteTimeGetCurrent();
__CFSpinUnlock(&domain->_lock);
return success;
}
#endif