#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define SYSTEMCONFIGURATION_NEW_API
#include <SystemConfiguration/SystemConfiguration.h>
#define USE_FLAT_FILES "UseFlatFiles"
SCDSessionRef session = NULL;
CFMutableSetRef curCacheKeys = NULL;
CFMutableSetRef newCacheKeys = NULL;
CFIndex keyCnt;
static void
updateCache(const void *key, const void *value, void *context)
{
CFStringRef configKey = (CFStringRef)key;
CFPropertyListRef configData = (CFDictionaryRef)value;
SCDStatus scd_status;
SCDHandleRef handle;
CFPropertyListRef cacheData;
scd_status = SCDGet(session, configKey, &handle);
switch (scd_status) {
case SCD_OK :
cacheData = SCDHandleGetData(handle);
if (!CFEqual(cacheData, configData)) {
SCDHandleSetData(handle, configData);
scd_status = SCDSet(session, configKey, handle);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDSet(): %s"), SCDError(scd_status));
}
}
SCDHandleRelease(handle);
break;
case SCD_NOKEY :
handle = SCDHandleInit();
SCDHandleSetData(handle, configData);
scd_status = SCDAdd(session, configKey, handle);
SCDHandleRelease(handle);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDSet(): %s"), SCDError(scd_status));
}
break;
default :
SCDLog(LOG_ERR, CFSTR("SCDGet(): %s"), SCDError(scd_status));
break;
}
CFSetRemoveValue(curCacheKeys, configKey);
CFSetAddValue (newCacheKeys, configKey);
return;
}
void
removeCacheKey(const void *value, void *context)
{
SCDStatus scd_status;
CFStringRef configKey = (CFStringRef)value;
scd_status = SCDRemove(session, configKey);
if ((scd_status != SCD_OK) && (scd_status != SCD_NOKEY)) {
SCDLog(LOG_ERR, CFSTR("SCDRemove() failed: %s"), SCDError(scd_status));
}
return;
}
static void
flatten(SCPSessionRef pSession,
CFStringRef key,
CFDictionaryRef base,
CFMutableDictionaryRef newPreferences)
{
CFDictionaryRef subset;
CFStringRef link;
CFMutableDictionaryRef myDict;
CFStringRef myKey;
CFIndex i;
CFIndex nKeys;
void **keys;
void **vals;
if (!CFDictionaryGetValueIfPresent(base, kSCResvLink, (void **)&link)) {
subset = base;
} else {
SCPStatus scp_status;
scp_status = SCPPathGetValue(pSession, link, &subset);
if (scp_status != SCP_OK) {
SCDLog(LOG_ERR,
CFSTR("SCPPathGetValue(,%@,) failed: %s"),
link,
SCPError(scp_status));
return;
}
}
if (CFDictionaryContainsKey(subset, kSCResvInactive)) {
return;
}
myKey = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("%@%@"),
kSCCacheDomainSetup,
key);
myDict = (CFMutableDictionaryRef)CFDictionaryGetValue(newPreferences, myKey);
if (myDict == NULL) {
myDict = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
} else {
myDict = CFDictionaryCreateMutableCopy(NULL,
0,
(CFDictionaryRef)myDict);
}
nKeys = CFDictionaryGetCount(subset);
keys = CFAllocatorAllocate(NULL, nKeys * sizeof(CFStringRef) , 0);
vals = CFAllocatorAllocate(NULL, nKeys * sizeof(CFPropertyListRef), 0);
CFDictionaryGetKeysAndValues(subset, keys, vals);
for (i=0; i<nKeys; i++) {
if (CFGetTypeID((CFTypeRef)vals[i]) != CFDictionaryGetTypeID()) {
CFDictionarySetValue(myDict, keys[i], vals[i]);
} else {
CFStringRef subKey;
subKey = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("%@%s%@"),
key,
CFEqual(key, CFSTR("/")) ? "" : "/",
keys[i]);
flatten(pSession, subKey, vals[i], newPreferences);
CFRelease(subKey);
}
}
CFAllocatorDeallocate(NULL, keys);
CFAllocatorDeallocate(NULL, vals);
if (CFDictionaryGetCount(myDict) > 0) {
CFDictionarySetValue(newPreferences, myKey, myDict);
}
CFRelease(myDict);
CFRelease(myKey);
return;
}
static boolean_t
updateConfiguration(SCDSessionRef session, void *arg)
{
CFArrayRef changedKeys;
boolean_t cleanupKeys = TRUE;
CFStringRef current = NULL;
CFArrayRef currentKeys;
CFDateRef date;
CFMutableDictionaryRef dict;
CFDictionaryRef global = NULL;
boolean_t haveLock = FALSE;
CFIndex i;
CFMutableDictionaryRef newPreferences = NULL;
SCPSessionRef pSession = NULL;
SCPStatus scp_status;
CFDictionaryRef set = NULL;
SCDStatus scd_status;
SCDLog(LOG_DEBUG, CFSTR("updating configuration"));
scd_status = SCDNotifierGetChanges(session, &changedKeys);
if (scd_status == SCD_OK) {
CFRelease(changedKeys);
} else {
SCDLog(LOG_ERR, CFSTR("SCDNotifierGetChanges() failed: %s"), SCDError(scd_status));
}
curCacheKeys = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
newCacheKeys = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
scd_status = SCDList(session, kSCCacheDomainSetup, 0, ¤tKeys);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDList() failed: %s"), SCDError(scd_status));
goto error;
}
for (i=0; i<CFArrayGetCount(currentKeys); i++) {
CFSetAddValue(curCacheKeys, CFArrayGetValueAtIndex(currentKeys, i));
}
CFRelease(currentKeys);
newPreferences = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
date = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
scp_status = SCPOpen(&pSession, CFSTR("PreferencesMonitor.bundle"), NULL, 0);
if (scp_status != SCP_OK) {
CFStringRef key;
SCDLog(LOG_NOTICE, CFSTR("updateConfiguration(): no preferences."));
key = SCDKeyCreate(CFSTR("%@" USE_FLAT_FILES), kSCCacheDomainSetup);
CFDictionarySetValue(newPreferences, key, date);
CFRelease(key);
cleanupKeys = FALSE;
goto done;
}
scp_status = SCPGet(pSession, kSCPrefSystem, (CFPropertyListRef *)&global);
switch (scp_status) {
case SCP_OK :
break;
case SCP_NOKEY :
goto getSet;
default :
SCDLog(LOG_NOTICE,
CFSTR("SCPGet(,%@,) failed: %s"),
kSCPrefSystem,
SCPError(scp_status));
goto error;
}
if ((global == NULL) || (CFGetTypeID(global) != CFDictionaryGetTypeID())) {
SCDLog(LOG_NOTICE,
CFSTR("updateConfiguration(): %@ is not a dictionary."),
kSCPrefSystem);
goto done;
}
flatten(pSession, CFSTR("/"), global, newPreferences);
getSet :
scp_status = SCPGet(pSession, kSCPrefCurrentSet, (CFPropertyListRef *)¤t);
switch (scp_status) {
case SCP_OK :
break;
case SCP_NOKEY :
goto done;
default :
SCDLog(LOG_NOTICE,
CFSTR("SCPGet(,%@,) failed: %s"),
kSCPrefCurrentSet,
SCPError(scp_status));
goto error;
}
if ((current == NULL) || (CFGetTypeID(current) != CFStringGetTypeID())) {
SCDLog(LOG_NOTICE,
CFSTR("updateConfiguration(): %@ is not a string."),
kSCPrefCurrentSet);
goto done;
}
scp_status = SCPPathGetValue(pSession, current, &set);
switch (scp_status) {
case SCP_OK :
break;
case SCP_NOKEY :
SCDLog(LOG_ERR,
CFSTR("%@ value (%@) not valid"),
kSCPrefCurrentSet,
current);
goto done;
default :
SCDLog(LOG_ERR,
CFSTR("SCPPathGetValue(,%@,) failed: %s"),
current,
SCPError(scp_status));
goto error;
}
if ((set == NULL) || (CFGetTypeID(set) != CFDictionaryGetTypeID())) {
goto done;
}
flatten(pSession, CFSTR("/"), set, newPreferences);
CFDictionarySetValue(dict, kSCCachePropSetupCurrentSet, current);
done :
CFDictionarySetValue(dict, kSCCachePropSetupLastUpdated, date);
CFRelease(date);
CFDictionarySetValue(newPreferences, kSCCacheDomainSetup, dict);
CFRelease(dict);
scd_status = SCDLock(session);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDLock() failed: %s"), SCDError(scd_status));
goto error;
}
haveLock = TRUE;
CFDictionaryApplyFunction(newPreferences, updateCache, NULL);
if (cleanupKeys) {
CFSetApplyFunction(curCacheKeys, removeCacheKey, NULL);
}
error :
if (haveLock) {
scd_status = SCDUnlock(session);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDUnlock() failed: %s"), SCDError(scd_status));
}
}
CFRelease(curCacheKeys);
CFRelease(newCacheKeys);
if (pSession) (void)SCPClose(&pSession);
if (newPreferences) CFRelease(newPreferences);
return TRUE;
}
void
prime()
{
SCDLog(LOG_DEBUG, CFSTR("prime() called"));
updateConfiguration(session, NULL);
return;
}
void
start(const char *bundleName, const char *bundleDir)
{
SCDStatus scd_status;
CFStringRef notifyKey;
SCDLog(LOG_DEBUG, CFSTR("start() called"));
SCDLog(LOG_DEBUG, CFSTR(" bundle name = %s"), bundleName);
SCDLog(LOG_DEBUG, CFSTR(" bundle directory = %s"), bundleDir);
scd_status = SCDOpen(&session, CFSTR("Configuraton Preferences Monitor plug-in"));
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDOpen() failed: %s"), SCDError(scd_status));
goto error;
}
notifyKey = SCPNotificationKeyCreate(NULL, kSCPKeyApply);
scd_status = SCDNotifierAdd(session, notifyKey, 0);
CFRelease(notifyKey);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDNotifierAdd() failed: %s"), SCDError(scd_status));
goto error;
}
scd_status = SCDNotifierInformViaCallback(session, updateConfiguration, NULL);
if (scd_status != SCD_OK) {
SCDLog(LOG_ERR, CFSTR("SCDNotifierInformViaCallback() failed: %s"), SCDError(scd_status));
goto error;
}
return;
error :
if (session) (void) SCDClose(&session);
return;
}