/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * * -*- mode: c, c-basic-offset: 4 -*- */ /* * Copyright Likewise Software 2004-2008 * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General * Public License along with this program. If not, see * . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ /* * Copyright (C) Likewise Software. All rights reserved. * * Module Name: * * media-sense-mac.c * * Abstract: * * Likewise Security and Authentication Subsystem (LSASS) * * Media Sense Support for Mac OS X. * * Authors: Danilo Almeida (dalmeida@likewisesoftware.com) */ #include #include #include #include #include #define MEDIA_SENSE_KEY_PATH "State:/Network/Global/IPv4" #define MEDIA_SENSE_RUN_LOOP_TIMEOUT (100.0 * 365.0 * 3600.0) struct _MEDIA_SENSE_HANDLE_DATA { pthread_t* Thread; // Error startup up the thread. int StartError; // Store reference for figuring out initial state. SCDynamicStoreRef StoreRef; // This is the event source that the thread needs to add/remove to/from // its run loop. CFRunLoopSourceRef RunLoopSourceRef; // The thread stores its run loop here so that it can be stopped by // other code. This is necessary because the run loop is per-thread. CFRunLoopRef RunLoopRef; // The mutex and condition are used to communicate the run loop ref // back the the code that started the thread. pthread_mutex_t* Mutex; pthread_cond_t* Condition; // The online/offline transition callback. MEDIA_SENSE_TRANSITION_CALLBACK TransitionCallback; void* TransitionCallbackContext; }; #define GOTO_CLEANUP() \ goto cleanup #define GOTO_CLEANUP_EE(ee) \ do { \ (ee) = __LINE__; \ GOTO_CLEANUP(); \ } while (0) #define GOTO_CLEANUP_ON_ERROR(error) \ do { \ if (error) \ { \ GOTO_CLEANUP(); \ } \ } while (0) #define GOTO_CLEANUP_ON_ERROR_EE(error, ee) \ do { \ if (error) \ { \ GOTO_CLEANUP_EE(ee); \ } \ } while (0) #define _RELEASE_HELPER(Pointer, ReleaseFunction) \ do { \ if (Pointer) \ { \ (ReleaseFunction)(Pointer); \ (Pointer) = NULL; \ } \ } while (0) #define CF_RELEASE(Reference) \ _RELEASE_HELPER(Reference, CFRelease) #define FREE(Pointer) \ _RELEASE_HELPER(Pointer, free) #define LOG(Format, ...) \ LSA_LOG_DEBUG(Format, ## __VA_ARGS__) #define LOG_LEAVE(error, ee) \ do { \ if (error || ee) \ { \ LOG("LEAVE: %s() -> %d (ee = %d)", __FUNCTION__, error, ee); \ } \ } while(0) static int MediaSenseCreateCFString( OUT CFStringRef* ObjectRef, IN const char* Data ) { int error = 0; *ObjectRef = CFStringCreateWithCString(NULL, Data, kCFStringEncodingUTF8); if (!*ObjectRef) { error = ENOMEM; } return error; } static int MediaSenseCreateCFMutableArray( OUT CFMutableArrayRef* ObjectRef ) { int error = 0; *ObjectRef = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!*ObjectRef) { error = ENOMEM; } return error; } static int MediaSenseAppendStrings( IN OUT CFMutableArrayRef ArrayRef, IN size_t Count, IN const char** Strings ) { int error = 0; CFStringRef stringRef = NULL; size_t i; for (i = 0; i < Count; i++) { error = MediaSenseCreateCFString(&stringRef, Strings[i]); GOTO_CLEANUP_ON_ERROR(error); CFArrayAppendValue(ArrayRef, stringRef); CF_RELEASE(stringRef); } cleanup: CF_RELEASE(stringRef); return error; } static CFStringRef MediaSenseDescribeContextInfo( IN const void* Info ) { return CFSTR("N/A"); } static int MediaSenseAllocateStringFromCFStringRef( OUT char** String, IN CFStringRef StringRef ) { int error = 0; char* result = NULL; CFIndex size = 0; CFIndex length = 0; length = CFStringGetLength(StringRef); if (length <= 0) { GOTO_CLEANUP(); } size = length + 1; if (size < length) { // overflow error = ERANGE; GOTO_CLEANUP(); } result = malloc(size); if (!result) { error = ENOMEM; GOTO_CLEANUP(); } if (!CFStringGetCString(StringRef, result, size, kCFStringEncodingUTF8)) { error = EINVAL; GOTO_CLEANUP(); } cleanup: if (error) { FREE(result); } *String = result; return error; } static int MediaSenseDetermineOfflineState( IN SCDynamicStoreRef StoreRef, OUT bool* IsOffline, OUT OPTIONAL const char** ErrorString ) { int error = 0; int ee = 0; bool isOffline = false; CFStringRef keyRef = NULL; CFPropertyListRef propListRef = NULL; const char* errorString = NULL; // Do not neet to release a CFSTR-based CFStringRef as it points to static // text. keyRef = CFSTR(MEDIA_SENSE_KEY_PATH); if (!keyRef) { errorString = "failed to allocate static string reference"; error = ENOMEM; GOTO_CLEANUP_ON_ERROR_EE(error, ee); } propListRef = SCDynamicStoreCopyValue(StoreRef, keyRef); if (!propListRef) { int scErrorCode = SCError(); switch (scErrorCode) { case kSCStatusNoKey: isOffline = true; break; case kSCStatusOK: case kSCStatusFailed: case kSCStatusInvalidArgument: case kSCStatusAccessError: case kSCStatusKeyExists: case kSCStatusLocked: case kSCStatusNeedLock: case kSCStatusNoStoreSession: case kSCStatusNoStoreServer: case kSCStatusNotifierActive: case kSCStatusNoPrefsSession: case kSCStatusPrefsBusy: case kSCStatusNoConfigFile: case kSCStatusNoLink: case kSCStatusStale: case kSCStatusMaxLink: case kSCStatusReachabilityUnknown: default: errorString = SCErrorString(scErrorCode); if (!errorString) { errorString = "*unknown*"; } error = scErrorCode; if (!error) { error = ENOMEM; } break; } } else { isOffline = false; } cleanup: CF_RELEASE(propListRef); *IsOffline = isOffline; if (ErrorString) { *ErrorString = errorString; } LOG_LEAVE(error, ee); return error; } static void MediaSenseDynamicStoreCallback( IN SCDynamicStoreRef StoreRef, IN CFArrayRef ChangedKeysRef, IN OPTIONAL void* Context ) { CFIndex count = 0; CFIndex i = 0; char* key = NULL; MEDIA_SENSE_HANDLE context = (MEDIA_SENSE_HANDLE) Context; count = CFArrayGetCount(ChangedKeysRef); for (i = 0; i < count; ++i) { int error = 0; CFStringRef changedKeyRef = NULL; const char* errorString = NULL; bool isOffline = false; FREE(key); // Note that "get" does not reference. changedKeyRef = CFArrayGetValueAtIndex(ChangedKeysRef, i); if (!changedKeyRef) { LOG("Missing key reference at index %d", i); continue; } error = MediaSenseAllocateStringFromCFStringRef(&key, changedKeyRef); if (error) { LOG("Error allocating string at index %d", i); continue; } if (!key) { LOG("Missing key string at index %d", i); continue; } if (strcmp(key, MEDIA_SENSE_KEY_PATH)) { LOG("Changed key '%s'", key); continue; } error = MediaSenseDetermineOfflineState(StoreRef, &isOffline, &errorString); if (error) { LOG("Changed key '%s' - error determining state: %s", key, errorString); } else { LOG("Changed key '%s' - %s", key, isOffline ? "offline" : "online"); if (context && context->TransitionCallback) { context->TransitionCallback(context->TransitionCallbackContext, isOffline); } } } FREE(key); } static int MediaSenseCreateSCDynamicStore( OUT SCDynamicStoreRef* ObjectRef, IN CFStringRef NameRef, IN OPTIONAL SCDynamicStoreCallBack Callback, IN OPTIONAL void* Context ) { int error = 0; SCDynamicStoreContext context = { 0 }; if (Context) { context.version = 0; context.info = Context; // From the spec, it looks like we need this. context.copyDescription = MediaSenseDescribeContextInfo; } *ObjectRef = SCDynamicStoreCreate(NULL, NameRef, Callback, Context ? &context : NULL); if (!*ObjectRef) { LOG("SCDynamicStoreCreate failed with '%s'", SCErrorString(SCError())); error = ENOMEM; } return error; } static int MediaSenseCreateDynamicStoreCFRunLoopSource( OUT CFRunLoopSourceRef* ObjectRef, IN SCDynamicStoreRef StoreRef ) { int error = 0; *ObjectRef = SCDynamicStoreCreateRunLoopSource(NULL, StoreRef, 0); if (!*ObjectRef) { LOG("SCDynamicStoreCreateRunLoopSource failed with '%s'", SCErrorString(SCError())); error = ENOMEM; } return error; } #define CASE_TO_STR(x) case x: return #x static const char* MediaSenseRunLoopResultToString( IN SInt32 RunLoopResultCode ) { switch (RunLoopResultCode) { CASE_TO_STR(kCFRunLoopRunFinished); CASE_TO_STR(kCFRunLoopRunStopped); CASE_TO_STR(kCFRunLoopRunTimedOut); CASE_TO_STR(kCFRunLoopRunHandledSource); default: return NULL; } } static int MediaSenseDetermineInitialState( IN MEDIA_SENSE_HANDLE Context ) { int error = 0; bool isOffline = false; const char* errorString = NULL; error = MediaSenseDetermineOfflineState(Context->StoreRef, &isOffline, &errorString); if (error) { LOG("got error %d - %s", error, errorString); } else { LOG("Starting out %s", isOffline ? "offline" : "online"); if (Context->TransitionCallback) { Context->TransitionCallback(Context->TransitionCallbackContext, isOffline); } } return error; } static void* MediaSenseThreadRoutine( IN void* Context ) { int error = 0; int ee = 0; MEDIA_SENSE_HANDLE context = (MEDIA_SENSE_HANDLE)Context; CFRunLoopRef runLoopRef = CFRunLoopGetCurrent(); CFStringRef runLoopModeRef = kCFRunLoopDefaultMode; // Add source to thread's run loop. CFRunLoopAddSource(runLoopRef, context->RunLoopSourceRef, runLoopModeRef); // Determine initial state after registering for events. error = MediaSenseDetermineInitialState(context); if (error) { // Signal thread state back to thread that started this thread // that we failed to start up. pthread_mutex_lock(context->Mutex); context->StartError = error; pthread_mutex_unlock(context->Mutex); pthread_cond_signal(context->Condition); GOTO_CLEANUP_ON_ERROR_EE(error, ee); } LOG("Starting media sense loop"); // Signal thread state back to thread that started this thread // that we started up ok. pthread_mutex_lock(context->Mutex); context->RunLoopRef = runLoopRef; CFRetain(context->RunLoopRef); pthread_mutex_unlock(context->Mutex); pthread_cond_signal(context->Condition); for (;;) { SInt32 runCode = 0; const char* runCodeString = NULL; runCode = CFRunLoopRunInMode(runLoopModeRef, MEDIA_SENSE_RUN_LOOP_TIMEOUT, true); runCodeString = MediaSenseRunLoopResultToString(runCode); if (runCodeString) { LOG("run loop result = %s", runCodeString); } else { LOG("run loop result = %d", runCode); } if (kCFRunLoopRunStopped == runCode) { break; } } LOG("Exiting media sense loop"); cleanup: // Remove source from the run loop. CFRunLoopRemoveSource(runLoopRef, context->RunLoopSourceRef, runLoopModeRef); return NULL; } static int MediaSenseAllocate( OUT void** Pointer, IN size_t Size ) { int error = 0; void* pointer = NULL; pointer = malloc(Size); if (!pointer) { error = ENOMEM; } else { memset(pointer, 0, Size); } *Pointer = pointer; return error; } static int MediaSenseCreateMutex( OUT pthread_mutex_t** Mutex ) { int error = 0; int ee = 0; pthread_mutex_t* mutex = NULL; error = MediaSenseAllocate((void**)&mutex, sizeof(*mutex)); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = pthread_mutex_init(mutex, NULL); GOTO_CLEANUP_ON_ERROR_EE(error, ee); cleanup: if (error) { FREE(mutex); } *Mutex = mutex; return error; } static int MediaSenseCreateThread( OUT pthread_t** Thread, IN void* (*ThreadRoutine)(void*), IN void* ThreadContext ) { int error = 0; int ee = 0; pthread_t* thread = NULL; error = MediaSenseAllocate((void**)&thread, sizeof(*thread)); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = pthread_create(thread, NULL, ThreadRoutine, ThreadContext); GOTO_CLEANUP_ON_ERROR_EE(error, ee); cleanup: if (error) { FREE(thread); } *Thread = thread; return error; } static int MediaSenseCreateCondition( OUT pthread_cond_t** Condition ) { int error = 0; int ee = 0; pthread_cond_t* condition = NULL; error = MediaSenseAllocate((void**)&condition, sizeof(*condition)); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = pthread_cond_init(condition, NULL); GOTO_CLEANUP_ON_ERROR_EE(error, ee); cleanup: if (error) { FREE(condition); } *Condition = condition; return error; } static void MediaSenseDestroyContext( IN OUT MEDIA_SENSE_HANDLE* Context ) { MEDIA_SENSE_HANDLE context = *Context; if (context) { LOG("Destroying"); // Signal thread to stop. if (context->RunLoopRef) { CFRunLoopStop(context->RunLoopRef); CF_RELEASE(context->RunLoopRef); } // Join the thread. if (context->Thread) { void* result = NULL; pthread_join(*context->Thread, &result); free(context->Thread); } CF_RELEASE(context->RunLoopSourceRef); CF_RELEASE(context->StoreRef); if (context->Mutex) { pthread_mutex_destroy(context->Mutex); free(context->Mutex); } if (context->Condition) { pthread_cond_destroy(context->Condition); free(context->Condition); } free(context); *Context = NULL; } } static int MediaSenseCreateContext( OUT MEDIA_SENSE_HANDLE* Context ) { int error = 0; int ee = 0; MEDIA_SENSE_HANDLE context = NULL; error = MediaSenseAllocate((void**)&context, sizeof(*context)); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateMutex(&context->Mutex); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateCondition(&context->Condition); GOTO_CLEANUP_ON_ERROR_EE(error, ee); cleanup: if (error) { MediaSenseDestroyContext(&context); } *Context = context; LOG_LEAVE(error, ee); return error; } static int MediaSenseStartEx( OUT MEDIA_SENSE_HANDLE* Context, IN OPTIONAL MEDIA_SENSE_TRANSITION_CALLBACK TransitionCallback, IN OPTIONAL void* TransitionCallbackContext, IN const char* Name, IN size_t Count, IN const char** Strings ) { int error = 0; int ee = 0; CFStringRef nameRef = NULL; CFMutableArrayRef notificationKeysRef = NULL; SCDynamicStoreRef dynamicStoreRef = NULL; CFRunLoopSourceRef dynamicStoreSourceRef = NULL; MEDIA_SENSE_HANDLE context = NULL; bool isAcquired = false; error = MediaSenseCreateCFString(&nameRef, Name); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateCFMutableArray(¬ificationKeysRef); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseAppendStrings(notificationKeysRef, Count, Strings); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateContext(&context); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateSCDynamicStore(&dynamicStoreRef, nameRef, MediaSenseDynamicStoreCallback, context); GOTO_CLEANUP_ON_ERROR_EE(error, ee); error = MediaSenseCreateDynamicStoreCFRunLoopSource(&dynamicStoreSourceRef, dynamicStoreRef); GOTO_CLEANUP_ON_ERROR_EE(error, ee); // Now tell the store to start notifications. if (!SCDynamicStoreSetNotificationKeys(dynamicStoreRef, NULL, notificationKeysRef)) { LOG("SCDynamicStoreCreate failed with '%s'", SCErrorString(SCError())); // ISSUE-2008/09/11-dalmeida -- Need better error... error = ENOMEM; GOTO_CLEANUP_EE(ee); } context->TransitionCallback = TransitionCallback; context->TransitionCallbackContext = TransitionCallbackContext; context->StoreRef = dynamicStoreRef; CFRetain(context->StoreRef); context->RunLoopSourceRef = dynamicStoreSourceRef; CFRetain(context->RunLoopSourceRef); pthread_mutex_lock(context->Mutex); isAcquired = true; error = MediaSenseCreateThread(&context->Thread, MediaSenseThreadRoutine, context); GOTO_CLEANUP_ON_ERROR_EE(error, ee); // Wait for start error or run loop reference to be returned. pthread_cond_wait(context->Condition, context->Mutex); pthread_mutex_unlock(context->Mutex); error = context->StartError; cleanup: if (isAcquired) { pthread_mutex_unlock(context->Mutex); } CF_RELEASE(dynamicStoreSourceRef); CF_RELEASE(dynamicStoreRef); CF_RELEASE(notificationKeysRef); CF_RELEASE(nameRef); if (error) { MediaSenseDestroyContext(&context); } *Context = context; LOG_LEAVE(error, ee); return error; } static int MediaSenseStart_Mac( OUT MEDIA_SENSE_HANDLE* MediaSenseContext, IN OPTIONAL MEDIA_SENSE_TRANSITION_CALLBACK TransitionCallback, IN OPTIONAL void* TransitionCallbackContext ) { const char* Monitor[] = { MEDIA_SENSE_KEY_PATH }; return MediaSenseStartEx(MediaSenseContext, TransitionCallback, TransitionCallbackContext, "lsassd", 1, Monitor); } static void MediaSenseStop_Mac( IN OUT MEDIA_SENSE_HANDLE* MediaSenseContext ) { MediaSenseDestroyContext(MediaSenseContext); }