]> git.saurik.com Git - apple/configd.git/blobdiff - Plugins/PreferencesMonitor/prefsmon.c
configd-130.tar.gz
[apple/configd.git] / Plugins / PreferencesMonitor / prefsmon.c
diff --git a/Plugins/PreferencesMonitor/prefsmon.c b/Plugins/PreferencesMonitor/prefsmon.c
new file mode 100644 (file)
index 0000000..9071ef7
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * 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 <ajn@apple.com>
+ * - use SCPreference notification APIs
+ *
+ * June 24, 2001               Allan Nathanson <ajn@apple.com>
+ * - update to public SystemConfiguration.framework APIs
+ *
+ * November 10, 2000           Allan Nathanson <ajn@apple.com>
+ * - initial revision
+ */
+
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <SystemConfiguration/SCValidation.h>
+
+
+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