/* * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * April 2, 2004 Allan Nathanson * - use SCPreference notification APIs * * June 24, 2001 Allan Nathanson * - update to public SystemConfiguration.framework APIs * * November 10, 2000 Allan Nathanson * - initial revision */ #include #include #include #include #include #include #include static SCPreferencesRef prefs = NULL; static SCDynamicStoreRef store = NULL; static CFMutableDictionaryRef currentPrefs; /* current prefs */ static CFMutableDictionaryRef newPrefs; /* new prefs */ static CFMutableArrayRef unchangedPrefsKeys; /* new prefs keys which match current */ static CFMutableArrayRef removedPrefsKeys; /* old prefs keys to be removed */ static Boolean _verbose = FALSE; static void updateCache(const void *key, const void *value, void *context) { CFStringRef configKey = (CFStringRef)key; CFPropertyListRef configData = (CFPropertyListRef)value; CFPropertyListRef cacheData; CFIndex i; cacheData = CFDictionaryGetValue(currentPrefs, configKey); if (cacheData) { /* key exists */ if (CFEqual(cacheData, configData)) { /* * if the old & new property list values have * not changed then we don't need to update * the preference. */ CFArrayAppendValue(unchangedPrefsKeys, configKey); } } /* in any case, this key should not be removed */ i = CFArrayGetFirstIndexOfValue(removedPrefsKeys, CFRangeMake(0, CFArrayGetCount(removedPrefsKeys)), configKey); if (i != kCFNotFound) { CFArrayRemoveValueAtIndex(removedPrefsKeys, i); } return; } static void flatten(SCPreferencesRef prefs, CFStringRef key, CFDictionaryRef base) { CFDictionaryRef subset; CFStringRef link; CFMutableDictionaryRef myDict; CFStringRef myKey; CFIndex i; CFIndex nKeys; const void **keys; const void **vals; if (!CFDictionaryGetValueIfPresent(base, kSCResvLink, (const void **)&link)) { /* if this dictionary is not linked */ subset = base; } else { /* if __LINK__ key is present */ subset = SCPreferencesPathGetValue(prefs, link); if (!subset) { /* if error with link */ SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesPathGetValue(,%@,) failed: %s"), link, SCErrorString(SCError())); return; } } if (CFDictionaryContainsKey(subset, kSCResvInactive)) { /* if __INACTIVE__ key is present */ return; } myKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"), kSCDynamicStoreDomainSetup, key); myDict = (CFMutableDictionaryRef)CFDictionaryGetValue(newPrefs, myKey); if (myDict) { myDict = CFDictionaryCreateMutableCopy(NULL, 0, (CFDictionaryRef)myDict); } else { myDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } nKeys = CFDictionaryGetCount(subset); if (nKeys > 0) { 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()) { /* add this key/value to the current dictionary */ CFDictionarySetValue(myDict, keys[i], vals[i]); } else { CFStringRef subKey; /* flatten [sub]dictionaries */ subKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%s%@"), key, CFEqual(key, CFSTR("/")) ? "" : "/", keys[i]); flatten(prefs, subKey, vals[i]); CFRelease(subKey); } } CFAllocatorDeallocate(NULL, keys); CFAllocatorDeallocate(NULL, vals); } if (CFDictionaryGetCount(myDict) > 0) { /* add this dictionary to the new preferences */ CFDictionarySetValue(newPrefs, myKey, myDict); } CFRelease(myDict); CFRelease(myKey); return; } static void updateConfiguration(SCPreferencesRef prefs, SCPreferencesNotification notificationType, void *info) { CFStringRef current = NULL; CFDateRef date = NULL; CFMutableDictionaryRef dict = NULL; CFDictionaryRef global = NULL; CFIndex i; CFArrayRef keys; CFIndex n; CFStringRef pattern; CFMutableArrayRef patterns; CFDictionaryRef set = NULL; if ((notificationType & kSCPreferencesNotificationApply) != kSCPreferencesNotificationApply) { return; } SCLog(_verbose, LOG_DEBUG, CFSTR("updating configuration")); /* * initialize old preferences, new preferences, an array * of keys which have not changed, and an array of keys * to be removed (cleaned up). */ patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); pattern = CFStringCreateWithFormat(NULL, NULL, CFSTR("^%@.*"), kSCDynamicStoreDomainSetup); CFArrayAppendValue(patterns, pattern); dict = (CFMutableDictionaryRef)SCDynamicStoreCopyMultiple(store, NULL, patterns); CFRelease(patterns); CFRelease(pattern); if (dict) { currentPrefs = CFDictionaryCreateMutableCopy(NULL, 0, dict); CFRelease(dict); } else { currentPrefs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } unchangedPrefsKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); i = CFDictionaryGetCount(currentPrefs); if (i > 0) { const void **currentKeys; CFArrayRef array; currentKeys = CFAllocatorAllocate(NULL, i * sizeof(CFStringRef), 0); CFDictionaryGetKeysAndValues(currentPrefs, currentKeys, NULL); array = CFArrayCreate(NULL, currentKeys, i, &kCFTypeArrayCallBacks); removedPrefsKeys = CFArrayCreateMutableCopy(NULL, 0, array); CFRelease(array); CFAllocatorDeallocate(NULL, currentKeys); } else { removedPrefsKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } /* * The "newPrefs" dictionary will contain the new / updated * configuration which will be written to the configuration cache. */ newPrefs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); /* * create status dictionary associated with current configuration * information including: * - current set "name" to cache * - time stamp indicating when the cache preferences were * last updated. */ dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); date = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent()); /* * load preferences */ keys = SCPreferencesCopyKeyList(prefs); if ((keys == NULL) || (CFArrayGetCount(keys) == 0)) { SCLog(TRUE, LOG_NOTICE, CFSTR("updateConfiguration(): no preferences.")); goto done; } /* * get "global" system preferences */ (CFPropertyListRef)global = SCPreferencesGetValue(prefs, kSCPrefSystem); if (!global) { /* if no global preferences are defined */ goto getSet; } if (!isA_CFDictionary(global)) { SCLog(TRUE, LOG_ERR, CFSTR("updateConfiguration(): %@ is not a dictionary."), kSCPrefSystem); goto done; } /* flatten property list */ flatten(prefs, CFSTR("/"), global); getSet : /* * get current set name */ (CFPropertyListRef)current = SCPreferencesGetValue(prefs, kSCPrefCurrentSet); if (!current) { /* if current set not defined */ goto done; } if (!isA_CFString(current)) { SCLog(TRUE, LOG_ERR, CFSTR("updateConfiguration(): %@ is not a string."), kSCPrefCurrentSet); goto done; } /* * get current set */ (CFPropertyListRef)set = SCPreferencesPathGetValue(prefs, current); if (!set) { /* if error with path */ SCLog(TRUE, LOG_ERR, CFSTR("%@ value (%@) not valid"), kSCPrefCurrentSet, current); goto done; } if (!isA_CFDictionary(set)) { SCLog(TRUE, LOG_ERR, CFSTR("updateConfiguration(): %@ is not a dictionary."), current); goto done; } /* flatten property list */ flatten(prefs, CFSTR("/"), set); CFDictionarySetValue(dict, kSCDynamicStorePropSetupCurrentSet, current); done : /* add last updated time stamp */ CFDictionarySetValue(dict, kSCDynamicStorePropSetupLastUpdated, date); /* add Setup: key */ CFDictionarySetValue(newPrefs, kSCDynamicStoreDomainSetup, dict); /* compare current and new preferences */ CFDictionaryApplyFunction(newPrefs, updateCache, NULL); /* remove those keys which have not changed from the update */ n = CFArrayGetCount(unchangedPrefsKeys); for (i = 0; i < n; i++) { CFStringRef key; key = CFArrayGetValueAtIndex(unchangedPrefsKeys, i); CFDictionaryRemoveValue(newPrefs, key); } /* Update the dynamic store */ if (!SCDynamicStoreSetMultiple(store, newPrefs, removedPrefsKeys, NULL)) { SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreSetMultiple() failed: %s"), SCErrorString(SCError())); } /* finished with current prefs, wait for changes */ SCPreferencesSynchronize(prefs); CFRelease(currentPrefs); CFRelease(newPrefs); CFRelease(unchangedPrefsKeys); CFRelease(removedPrefsKeys); if (dict) CFRelease(dict); if (date) CFRelease(date); if (keys) CFRelease(keys); return; } __private_extern__ void stop_PreferencesMonitor(CFRunLoopSourceRef stopRls) { // cleanup if (prefs != NULL) { if (!SCPreferencesUnscheduleFromRunLoop(prefs, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesUnscheduleFromRunLoop() failed: %s"), SCErrorString(SCError())); } CFRelease(prefs); prefs = NULL; } if (store != NULL) { CFRelease(store); store = NULL; } CFRunLoopSourceSignal(stopRls); return; } __private_extern__ void prime_PreferencesMonitor() { SCLog(_verbose, LOG_DEBUG, CFSTR("prime() called")); /* load the initial configuration from the database */ updateConfiguration(prefs, kSCPreferencesNotificationApply, (void *)store); return; } __private_extern__ void load_PreferencesMonitor(CFBundleRef bundle, Boolean bundleVerbose) { if (bundleVerbose) { _verbose = TRUE; } SCLog(_verbose, LOG_DEBUG, CFSTR("load() called")); SCLog(_verbose, LOG_DEBUG, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle)); /* open a SCDynamicStore session to allow cache updates */ store = SCDynamicStoreCreate(NULL, CFSTR("PreferencesMonitor.bundle"), NULL, NULL); if (store == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreCreate() failed: %s"), SCErrorString(SCError())); goto error; } /* open a SCPreferences session */ prefs = SCPreferencesCreate(NULL, CFSTR("PreferencesMonitor.bundle"), NULL); if (prefs == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesCreate() failed: %s"), SCErrorString(SCError())); goto error; } if (!SCPreferencesSetCallback(prefs, updateConfiguration, NULL)) { SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesSetCallBack() failed: %s"), SCErrorString(SCError())); goto error; } /* * register for change notifications. */ if (!SCPreferencesScheduleWithRunLoop(prefs, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesScheduleWithRunLoop() failed: %s"), SCErrorString(SCError())); goto error; } return; error : if (store) CFRelease(store); if (prefs) CFRelease(prefs); return; } #ifdef MAIN int main(int argc, char **argv) { _sc_log = FALSE; _sc_verbose = (argc > 1) ? TRUE : FALSE; load_PreferencesMonitor(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE); prime_PreferencesMonitor(); CFRunLoopRun(); /* not reached */ exit(0); return 0; } #endif