]> git.saurik.com Git - apple/configd.git/blobdiff - SCMonitor/monitor.c
configd-210.tar.gz
[apple/configd.git] / SCMonitor / monitor.c
diff --git a/SCMonitor/monitor.c b/SCMonitor/monitor.c
new file mode 100644 (file)
index 0000000..6ff25d6
--- /dev/null
@@ -0,0 +1,601 @@
+/*
+ * Copyright (c) 2007 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
+ *
+ * October 24, 2007            Allan Nathanson <ajn@apple.com>
+ * - initial revision
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include "UserEventAgentInterface.h"
+
+#define MY_BUNDLE_ID    CFSTR("com.apple.SystemConfiguration.SCMonitor")
+#define        MY_ICON_PATH    "/System/Library/PreferencePanes/Network.prefPane/Contents/Resources/Network.icns"
+
+#define        NETWORK_PREF_APP        "/System/Library/PreferencePanes/Network.prefPane"
+#define        NETWORK_PREF_CMD        "New Interface"
+
+typedef struct {
+       UserEventAgentInterfaceStruct   *_UserEventAgentInterface;
+       CFUUIDRef                       _factoryID;
+       UInt32                          _refCount;
+
+       CFRunLoopSourceRef              monitorRls;
+
+       CFMutableSetRef                 knownInterfaces;
+
+       CFMutableArrayRef               userInterfaces;
+       CFUserNotificationRef           userNotification;
+       CFRunLoopSourceRef              userRls;
+} MyType;
+
+static CFMutableDictionaryRef  notify_to_instance      = NULL;
+
+
+#pragma mark -
+#pragma mark Watch for new [network] interfaces
+
+
+static void
+open_NetworkPrefPane(void)
+{
+       AEDesc          aeDesc  = { typeNull, NULL };
+       CFArrayRef      prefArray;
+       CFURLRef        prefURL;
+       LSLaunchURLSpec prefSpec;
+       OSStatus        status;
+
+       prefURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+                                               CFSTR(NETWORK_PREF_APP),
+                                               kCFURLPOSIXPathStyle,
+                                               FALSE);
+       prefArray = CFArrayCreate(NULL, (const void **)&prefURL, 1, &kCFTypeArrayCallBacks);
+       CFRelease(prefURL);
+
+       status = AECreateDesc('ptru',
+                             (const void *)NETWORK_PREF_CMD,
+                             strlen(NETWORK_PREF_CMD),
+                             &aeDesc);
+       if (status != noErr) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: AECreateDesc() failed: %d"), status);
+       }
+
+       prefSpec.appURL         = NULL;
+       prefSpec.itemURLs       = prefArray;
+       prefSpec.passThruParams = &aeDesc;
+       prefSpec.launchFlags    = kLSLaunchAsync | kLSLaunchDontAddToRecents;
+       prefSpec.asyncRefCon    = NULL;
+
+       status = LSOpenFromURLSpec(&prefSpec, NULL);
+       if (status != noErr) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: LSOpenFromURLSpec() failed: %d"), status);
+       }
+
+       CFRelease(prefArray);
+       if (aeDesc.descriptorType != typeNull) AEDisposeDesc(&aeDesc);
+       return;
+}
+
+
+static void
+notify_remove(MyType *myInstance, Boolean cancel)
+{
+       if (myInstance->userInterfaces != NULL) {
+               CFRelease(myInstance->userInterfaces);
+               myInstance->userInterfaces = NULL;
+       }
+
+       if (myInstance->userRls != NULL) {
+               CFRunLoopSourceInvalidate(myInstance->userRls);
+               CFRelease(myInstance->userRls);
+               myInstance->userRls = NULL;
+       }
+
+       if (myInstance->userNotification != NULL) {
+               if (cancel) {
+                       SInt32  status;
+
+                       status = CFUserNotificationCancel(myInstance->userNotification);
+                       if (status != 0) {
+                               SCLog(TRUE, LOG_ERR,
+                                     CFSTR("SCMonitor: CFUserNotificationCancel() failed, status=%d"),
+                                     status);
+                       }
+               }
+               CFRelease(myInstance->userNotification);
+               myInstance->userNotification = NULL;
+       }
+
+       return;
+}
+
+
+static void
+notify_reply(CFUserNotificationRef userNotification, CFOptionFlags response_flags)
+{
+       MyType  *myInstance     = NULL;
+
+       // get instance for notification
+       if (notify_to_instance != NULL) {
+               myInstance = (MyType *)CFDictionaryGetValue(notify_to_instance, userNotification);
+               if (myInstance != NULL) {
+                       CFDictionaryRemoveValue(notify_to_instance, userNotification);
+                       if (CFDictionaryGetCount(notify_to_instance) == 0) {
+                               CFRelease(notify_to_instance);
+                               notify_to_instance = NULL;
+                       }
+               }
+       }
+       if (myInstance == NULL) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: can't find user notification"));
+               return;
+       }
+
+       // process response
+       switch (response_flags & 0x3) {
+               case kCFUserNotificationDefaultResponse:
+                       // user asked to configure interface
+                       open_NetworkPrefPane();
+                       break;
+               default:
+                       // user cancelled
+                       break;
+       }
+
+       notify_remove(myInstance, FALSE);
+       return;
+}
+
+static void
+notify_add(MyType *myInstance)
+{
+       CFBundleRef             bundle;
+       CFMutableDictionaryRef  dict    = NULL;
+       SInt32                  error   = 0;
+       CFIndex                 i;
+       CFMutableArrayRef       message;
+       CFIndex                 n       = CFArrayGetCount(myInstance->userInterfaces);
+       CFURLRef                url     = NULL;
+
+       if (myInstance->userNotification != NULL) {
+               CFMutableArrayRef       save    = NULL;
+
+               if (n > 0) {
+                       CFRetain(myInstance->userInterfaces);
+                       save = myInstance->userInterfaces;
+               }
+               notify_remove(myInstance, TRUE);
+               myInstance->userInterfaces = save;
+               if (n == 0) {
+                       return;
+               }
+       }
+
+       dict = CFDictionaryCreateMutable(NULL,
+                                        0,
+                                        &kCFTypeDictionaryKeyCallBacks,
+                                        &kCFTypeDictionaryValueCallBacks);
+
+       // set localization URL
+       bundle = CFBundleGetBundleWithIdentifier(MY_BUNDLE_ID);
+       if (bundle != NULL) {
+               url = CFBundleCopyBundleURL(bundle);
+       }
+       if (url != NULL) {
+               // set URL
+               CFDictionarySetValue(dict, kCFUserNotificationLocalizationURLKey, url);
+               CFRelease(url);
+       } else {
+               SCLog(TRUE, LOG_NOTICE, CFSTR("SCMonitor: can't find bundle"));
+               goto done;
+       }
+
+       // set icon URL
+       url = CFURLCreateFromFileSystemRepresentation(NULL,
+                                                     (const UInt8 *)MY_ICON_PATH,
+                                                     strlen(MY_ICON_PATH),
+                                                     FALSE);
+       if (url != NULL) {
+               CFDictionarySetValue(dict, kCFUserNotificationIconURLKey, url);
+               CFRelease(url);
+       }
+
+       // header
+       CFDictionarySetValue(dict,
+                            kCFUserNotificationAlertHeaderKey,
+                            (n == 1) ? CFSTR("HEADER_1") : CFSTR("HEADER_N"));
+
+       // message
+       message = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       CFArrayAppendValue(message,
+                          (n == 1) ? CFSTR("MESSAGE_S1") : CFSTR("MESSAGE_SN"));
+       for (i = 0; i < n; i++) {
+               SCNetworkInterfaceRef   interface;
+               CFStringRef             name;
+
+               interface = CFArrayGetValueAtIndex(myInstance->userInterfaces, i);
+               name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
+               if (n == 1) {
+                       CFArrayAppendValue(message, name);
+               } else {
+                       CFStringRef     str;
+
+                       str = CFStringCreateWithFormat(NULL, NULL, CFSTR("\r\t%@"), name);
+                       CFArrayAppendValue(message, str);
+                       CFRelease(str);
+               }
+       }
+       CFArrayAppendValue(message,
+                          (n == 1) ? CFSTR("MESSAGE_E1") : CFSTR("MESSAGE_EN"));
+       CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message);
+       CFRelease(message);
+
+       // button titles
+       CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey,   CFSTR("OPEN_NP"));
+       CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFSTR("CANCEL"));
+
+       // create and post notification
+       myInstance->userNotification = CFUserNotificationCreate(NULL,
+                                                               0,
+                                                               kCFUserNotificationNoteAlertLevel,
+                                                               &error,
+                                                               dict);
+       if (myInstance->userNotification == NULL) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: CFUserNotificationCreate() failed, %d"), error);
+               goto done;
+       }
+
+       // establish callback
+       myInstance->userRls = CFUserNotificationCreateRunLoopSource(NULL,
+                                                                   myInstance->userNotification,
+                                                                   notify_reply,
+                                                                   0);
+       if (myInstance->userRls == NULL) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: CFUserNotificationCreateRunLoopSource() failed"));
+               CFRelease(myInstance->userNotification);
+               myInstance->userNotification = NULL;
+               goto done;
+       }
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), myInstance->userRls,  kCFRunLoopDefaultMode);
+
+       // add instance for notification
+       if (notify_to_instance == NULL) {
+               notify_to_instance = CFDictionaryCreateMutable(NULL,
+                                                              0,
+                                                              &kCFTypeDictionaryKeyCallBacks,
+                                                              NULL);   // no retain/release/... for values
+       }
+       CFDictionarySetValue(notify_to_instance, myInstance->userNotification, myInstance);
+
+    done :
+
+       if (dict != NULL) CFRelease(dict);
+       return;
+}
+
+
+static void
+updateInterfaceList(SCDynamicStoreRef store, CFArrayRef changes, void * arg)
+{
+       CFIndex                 i;
+       CFArrayRef              interfaces;
+       MyType                  *myInstance     = (MyType *)arg;
+       CFIndex                 n;
+       SCPreferencesRef        prefs;
+       CFMutableSetRef         previouslyKnown = NULL;
+       SCNetworkSetRef         set             = NULL;
+
+       prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL);
+       if (prefs == NULL) {
+               return;
+       }
+
+       set = SCNetworkSetCopyCurrent(prefs);
+       if (set == NULL) {
+               set = SCNetworkSetCreate(prefs);
+               if (set == NULL) {
+                       goto done;
+               }
+       }
+
+       previouslyKnown = CFSetCreateMutableCopy(NULL, 0, myInstance->knownInterfaces);
+
+       interfaces = SCNetworkInterfaceCopyAll();
+       if (interfaces != NULL) {
+
+               n = CFArrayGetCount(interfaces);
+               for (i = 0; i < n; i++) {
+                       CFStringRef             bsdName;
+                       SCNetworkInterfaceRef   interface;
+                       Boolean                 ok;
+
+                       interface = CFArrayGetValueAtIndex(interfaces, i);
+                       bsdName = SCNetworkInterfaceGetBSDName(interface);
+                       if (bsdName == NULL) {
+                               // if no BSD name
+                               continue;
+                       }
+
+                       CFSetRemoveValue(previouslyKnown, bsdName);
+
+                       if (CFSetContainsValue(myInstance->knownInterfaces, bsdName)) {
+                               // if known interface
+                               continue;
+                       }
+
+                       CFSetAddValue(myInstance->knownInterfaces, bsdName);
+
+                       ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface);
+                       if (ok) {
+                               // this is a *new* interface
+                               if (myInstance->userInterfaces == NULL) {
+                                       myInstance->userInterfaces = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+                               }
+                               CFArrayAppendValue(myInstance->userInterfaces, interface);
+                       }
+               }
+
+               CFRelease(interfaces);
+       }
+
+       n = CFSetGetCount(previouslyKnown);
+       if (n > 0) {
+               const void *    names_q[32];
+               const void **   names                 = names_q;
+
+               if (n > (CFIndex)(sizeof(names_q) / sizeof(CFTypeRef)))
+                       names = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
+               CFSetGetValues(previouslyKnown, names);
+               for (i = 0; i < n; i++) {
+                       if (myInstance->userInterfaces != NULL) {
+                               CFIndex j;
+
+                               j = CFArrayGetCount(myInstance->userInterfaces);
+                               while (--j >= 0) {
+                                       CFStringRef             bsdName;
+                                       SCNetworkInterfaceRef   interface;
+
+                                       interface = CFArrayGetValueAtIndex(myInstance->userInterfaces, j);
+                                       bsdName = SCNetworkInterfaceGetBSDName(interface);
+                                       if (CFEqual(bsdName, names[i])) {
+                                               // if we have previously posted a notification
+                                               // for this no-longer-present interface
+                                               CFArrayRemoveValueAtIndex(myInstance->userInterfaces, j);
+                                       }
+                               }
+                       }
+
+                       CFSetRemoveValue(myInstance->knownInterfaces, names[i]);
+               }
+               if (names != names_q)       CFAllocatorDeallocate(NULL, names);
+       }
+
+    done :
+
+       // post notification
+       if (myInstance->userInterfaces != NULL) {
+               notify_add(myInstance);
+       }
+
+       if (set != NULL) CFRelease(set);
+       CFRelease(prefs);
+       return;
+}
+
+
+static void
+watcher_remove(MyType *myInstance)
+{
+       if (myInstance->monitorRls != NULL) {
+               CFRunLoopSourceInvalidate(myInstance->monitorRls);
+               CFRelease(myInstance->monitorRls);
+               myInstance->monitorRls = NULL;
+       }
+
+       if (myInstance->knownInterfaces != NULL) {
+               CFRelease(myInstance->knownInterfaces);
+               myInstance->knownInterfaces = NULL;
+       }
+
+       return;
+}
+
+
+static void
+watcher_add(MyType *myInstance)
+{
+       SCDynamicStoreContext   context = { 0, (void *)myInstance, NULL, NULL, NULL };
+       CFDictionaryRef         dict;
+       CFStringRef             key;
+       CFArrayRef              keys;
+       SCDynamicStoreRef       store;
+
+       store = SCDynamicStoreCreate(NULL, CFSTR("SCMonitor"), updateInterfaceList, &context);
+       if (store == NULL) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCMonitor: SCDynamicStoreCreate() failed: %s"),
+                     SCErrorString(SCError()));
+               return;
+       }
+
+       key = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState);
+
+       // watch for changes to the list of network interfaces
+       keys = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks);
+       SCDynamicStoreSetNotificationKeys(store, NULL, keys);
+       CFRelease(keys);
+       myInstance->monitorRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
+       CFRunLoopAddSource(CFRunLoopGetCurrent(),
+                          myInstance->monitorRls,
+                          kCFRunLoopDefaultMode);
+
+       // initialize the list of known interfaces
+       myInstance->knownInterfaces = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
+       dict = SCDynamicStoreCopyValue(store, key);
+       if (dict != NULL) {
+               if (isA_CFDictionary(dict)) {
+                       CFIndex         i;
+                       CFArrayRef      interfaces;
+                       CFIndex         n;
+
+                       interfaces = CFDictionaryGetValue(dict, kSCPropNetInterfaces);
+                       n = isA_CFArray(interfaces) ? CFArrayGetCount(interfaces) : 0;
+                       for (i = 0; i < n; i++) {
+                               CFStringRef     bsdName;
+
+                               bsdName = CFArrayGetValueAtIndex(interfaces, i);
+                               if (isA_CFString(bsdName)) {
+                                       CFSetAddValue(myInstance->knownInterfaces, bsdName);
+                               }
+                       }
+               }
+
+               CFRelease(dict);
+       }
+
+       CFRelease(key);
+       CFRelease(store);
+       return;
+}
+
+
+#pragma mark -
+#pragma mark UserEventAgent stubs
+
+
+static HRESULT
+myQueryInterface(void *myInstance, REFIID iid, LPVOID *ppv)
+{
+       CFUUIDRef       interfaceID     = CFUUIDCreateFromUUIDBytes(NULL, iid);
+
+       // Test the requested ID against the valid interfaces.
+       if (CFEqual(interfaceID, kUserEventAgentInterfaceID)) {
+               ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
+               *ppv = myInstance;
+               CFRelease(interfaceID);
+               return S_OK;
+       }
+
+       if (CFEqual(interfaceID, IUnknownUUID)) {
+               ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
+               *ppv = myInstance;
+               CFRelease(interfaceID);
+               return S_OK;
+       }
+
+       // Requested interface unknown, bail with error.
+       *ppv = NULL;
+       CFRelease(interfaceID);
+       return E_NOINTERFACE;
+}
+
+
+static ULONG
+myAddRef(void *myInstance)
+{
+       ((MyType *) myInstance)->_refCount++;
+       return ((MyType *) myInstance)->_refCount;
+}
+
+
+static ULONG
+myRelease(void *myInstance)
+{
+       ((MyType *) myInstance)->_refCount--;
+       if (((MyType *) myInstance)->_refCount == 0) {
+               CFUUIDRef       factoryID       = ((MyType *) myInstance)->_factoryID;
+
+               if (factoryID != NULL) {
+                       CFPlugInRemoveInstanceForFactory(factoryID);
+                       CFRelease(factoryID);
+
+                       watcher_remove((MyType *)myInstance);
+                       notify_remove((MyType *)myInstance, TRUE);
+               }
+               free(myInstance);
+               return 0;
+       }
+
+       return ((MyType *) myInstance)->_refCount;
+}
+
+
+static void
+myInstall(void *myInstance)
+{
+       watcher_add((MyType *)myInstance);
+       return;
+}
+
+
+static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
+       NULL,                   // Required padding for COM
+       myQueryInterface,       // These three are the required COM functions
+       myAddRef,
+       myRelease,
+       myInstall               // Interface implementation
+};
+
+
+void *
+UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
+{
+       MyType  *newOne = NULL;
+
+       if (CFEqual(typeID, kUserEventAgentTypeID)) {
+               newOne  = (MyType *)malloc(sizeof(MyType));
+               bzero(newOne, sizeof(*newOne));
+               newOne->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
+               newOne->_factoryID = (CFUUIDRef)CFRetain(kUserEventAgentFactoryID);
+               CFPlugInAddInstanceForFactory(kUserEventAgentFactoryID);
+               newOne->_refCount = 1;
+       }
+
+       return newOne;
+}
+
+
+#ifdef MAIN
+int
+main(int argc, char **argv)
+{
+       MyType *newOne = (MyType *)malloc(sizeof(MyType));
+
+       _sc_log     = FALSE;
+       _sc_verbose = (argc > 1) ? TRUE : FALSE;
+
+       bzero(newOne, sizeof(*newOne));
+       myInstall(newOne);
+       CFRunLoopRun();
+       exit(0);
+       return (0);
+}
+#endif // MAIN