#include <pthread.h>
#include <libc.h>
#include <CoreFoundation/CFRuntime.h>
#include <zlib.h>
#include <errno.h>

#include "KXKextRepository.h"
#include "KXKextRepository_private.h"
#include "paths.h"

/*******************************************************************************
* The basic data structure for a kext repository (a directory containing kexts
* that can be scanned and rescanned).
*******************************************************************************/

typedef struct __KXKextRepository {

    CFRuntimeBase  cfBase;   // base CFType information

    KXKextManagerRef manager;

    CFStringRef  repositoryPath;    // canonicalized to absolute path
    Boolean   useCache;             // use/update cache files when possible
    Boolean   scansForKexts;        // scan whole directory or just kexts known

    Boolean hasAuthenticated;

   /* Kexts whose loadable status has yet to be determined or which may
    * change (disabled/enabled, dependencies arriving/departing).
    */
    CFMutableArrayRef  candidateKexts;

   /* Kexts with hard failures. These kexts aren't currently loadable,
    * but may become so if they fixed and rescanned.
    */
    CFMutableArrayRef  badKexts;

} __KXKextRepository, * __KXKextRepositoryRef;

/*******************************************************************************
* Private function declarations. Definitions at bottom of this file.
*******************************************************************************/
void __KXKextRepositoryInitialize(void);
__KXKextRepositoryRef __KXKextRepositoryCreatePrivate(
    CFAllocatorRef       allocator,
    CFAllocatorContext * context);
static CFStringRef __KXKextRepositoryCopyDebugDescription(CFTypeRef cf);
void __KXKextRepositoryReleaseContents(CFTypeRef aRepository);
KXKextManagerError __KXKextRepositoryScanDirectory(
    KXKextRepositoryRef aRepository);
static void __KXKextRepositoryAuthenticateKextArray(
    __KXKextRepositoryRef aRepository,
    CFMutableArrayRef kexts,
    CFMutableArrayRef badKexts);
static void __KXKextRepositoryCheckIntegrityOfKextArray(
    __KXKextRepositoryRef aRepository,
    CFMutableArrayRef kexts,
    CFMutableArrayRef badKexts,
    CFMutableArrayRef bomArray);


/*******************************************************************************
* Core Foundation Class Definition Stuff
*
* Private functions are at the bottom of this file with other module-internal
* code.
*******************************************************************************/

/* This gets set by __KXKextRepositoryInitialize().
 */
static CFTypeID __kKXKextRepositoryTypeID = _kCFRuntimeNotATypeID;

CFTypeID KXKextGetRepositoryTypeID(void) {
    return __kKXKextRepositoryTypeID;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerRef KXKextRepositoryGetManager(KXKextRepositoryRef aRepository)
{
    return aRepository->manager;
}

/*******************************************************************************
*
*******************************************************************************/
CFURLRef KXKextRepositoryCopyURL(KXKextRepositoryRef aRepository)
{
    CFURLRef theURL = NULL; // returned
    theURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                aRepository->repositoryPath, kCFURLPOSIXPathStyle, true);
    return theURL;
}

/*******************************************************************************
*
*******************************************************************************/
CFStringRef KXKextRepositoryGetPath(KXKextRepositoryRef aRepository)
{
    return aRepository->repositoryPath;
}

/*******************************************************************************
*
*******************************************************************************/
Boolean KXKextRepositoryGetScansForKexts(KXKextRepositoryRef aRepository)
{
    return aRepository->scansForKexts;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError KXKextRepositorySetScansForKexts(
    KXKextRepositoryRef aRepository, Boolean flag)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    Boolean oldScansForKexts = aRepository->scansForKexts;

    aRepository->scansForKexts = flag;
    if (!oldScansForKexts && aRepository->scansForKexts) {
        result = KXKextRepositoryReset(aRepository);
    }
    return result;
}

/*******************************************************************************
*
*******************************************************************************/
void KXKextRepositoryResolveBadKextDependencies(KXKextRepositoryRef aRepository)
{
    CFIndex count, i;
    KXKextRef thisKext;

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        KXKextResolveDependencies(thisKext);
    }

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void KXKextRepositoryEmpty(KXKextRepositoryRef aRepository)
{
    if (KXKextManagerGetLogLevel(aRepository->manager) >=
           kKXKextManagerLogLevelDetails) {

        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "emptying repository %s", repository_name);
            free((char *)repository_name);
        }
    }

   /*****
    * Do this before dumping kexts so their relationships get cleared.
    * Since we're releasing the kexts that really isn't necessary, though,
    * is it?
    */
    KXKextManagerClearRelationships(aRepository->manager);

    CFArrayRemoveAllValues(aRepository->candidateKexts);
    CFArrayRemoveAllValues(aRepository->badKexts);

    aRepository->hasAuthenticated = false;

    return;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError KXKextRepositoryScan(
    KXKextRepositoryRef aRepository)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    result = __KXKextRepositoryScanDirectory(aRepository);

    aRepository->hasAuthenticated = false;

    return result;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError KXKextRepositoryReset(
    KXKextRepositoryRef aRepository)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    CFMutableArrayRef kextURLs = NULL;  // must release
    CFIndex count, i;

    if (KXKextManagerGetLogLevel(aRepository->manager) >=
            kKXKextManagerLogLevelDetails) {

        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "resetting repository %s", repository_name);
            free((char *)repository_name);
        }
    }

   /*****
    * A repository that scans is easy to do; just empty it and have it
    * scan again. A repository that doesn't a little more involved. It
    * must record the URLs of all kexts it holds, empty itself, and then
    * try to recreate just those original kexts. If any of them has gone
    * missing, oh well!
    */
    if (aRepository->scansForKexts) {
        KXKextRepositoryEmpty(aRepository);
        result = KXKextRepositoryScan(aRepository);

       /* don't use the cache-write result as the result of this function
        */
        if (aRepository->useCache) {
            KXKextRepositoryWriteCache(aRepository, NULL);
        }

    } else {
        kextURLs = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
        if (!kextURLs) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }

       /* Record URLs of all kexts, good & bad.
        */
        count = CFArrayGetCount(aRepository->candidateKexts);
        for (i = 0; i < count; i++) {
            KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex(
                aRepository->candidateKexts, i);
            CFURLRef kextURL = KXKextGetAbsoluteURL(thisKext);
            CFArrayAppendValue(kextURLs, kextURL);
        }

        count = CFArrayGetCount(aRepository->badKexts);
        for (i = 0; i < count; i++) {
            KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex(
                aRepository->badKexts, i);
            CFURLRef kextURL = KXKextGetAbsoluteURL(thisKext);
            CFArrayAppendValue(kextURLs, kextURL);
        }

       /* Empty the repository.
        */
        KXKextRepositoryEmpty(aRepository);

       /* Add back all of the kexts for recorded URLs.
        */
        count = CFArrayGetCount(kextURLs);
        for (i = 0; i < count; i++) {
            CFURLRef thisURL = (CFURLRef)CFArrayGetValueAtIndex(
                kextURLs, i);
            KXKextRef newKext = NULL;  // must release

            newKext = _KXKextCreate(kCFAllocatorDefault);
            if (!newKext) {
                result = kKXKextManagerErrorNoMemory;
                goto finish;
            }
            result = _KXKextInitWithURLInRepository(newKext, thisURL,
                aRepository);
            if (result == kKXKextManagerErrorNoMemory ||
                result == kKXKextManagerErrorFileAccess ||
                result == kKXKextManagerErrorNotADirectory ||
                result == kKXKextManagerErrorKextNotFound ||
                result == kKXKextManagerErrorURLNotInRepository ||
                result == kKXKextManagerErrorNotABundle ||
                result == kKXKextManagerErrorNotAKext) {

                // kext is completely unusable, do not store in repository

            } else if (result == kKXKextManagerErrorNone) {
		if (KXKextManagerGetLogLevel(aRepository->manager) >=
			kKXKextManagerLogLevelKexts) {
		    const char * kext_name =
			_KXKextCopyBundlePathInRepositoryAsCString(newKext);
		    if (kext_name) {
			_KXKextManagerLogMessage(aRepository->manager,
			    "reset found valid extension %s", kext_name);
			free((char *)kext_name);
		    }
		}
		CFArrayAppendValue(aRepository->candidateKexts, newKext);
	    } else {
		if (KXKextManagerGetLogLevel(aRepository->manager) >=
			kKXKextManagerLogLevelKexts) {
		    const char * kext_name =
			_KXKextCopyBundlePathInRepositoryAsCString(newKext);
		    if (kext_name) {
			_KXKextManagerLogMessage(aRepository->manager,
			    "reset found invalid extension %s", kext_name);
			free((char *)kext_name);
		    }
		}
		CFArrayAppendValue(aRepository->badKexts, newKext);
	    }
	    CFRelease(newKext);
            result = kKXKextManagerErrorNone;
        }

       /* don't use the cache-write result as the result of this function
        */
        if (aRepository->useCache) {
            KXKextRepositoryWriteCache(aRepository, NULL);
        }

    }

    aRepository->hasAuthenticated = false;

finish:
    if (kextURLs) CFRelease(kextURLs);
    return result;
}
/*******************************************************************************
*
*******************************************************************************/
void KXKextRepositoryAuthenticateKexts(KXKextRepositoryRef aRepository)
{
    if (KXKextManagerGetLogLevel(aRepository->manager) >=
            kKXKextManagerLogLevelDetails) {

        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "authenticating extensions in repository %s",
                repository_name);
            free((char *)repository_name);
        }
    }

    KXKextManagerDisableClearRelationships(aRepository->manager);

    __KXKextRepositoryAuthenticateKextArray(aRepository,
         aRepository->candidateKexts, aRepository->badKexts);

    if (KXKextManagerPerformsFullTests(aRepository->manager)) {
         __KXKextRepositoryAuthenticateKextArray(aRepository,
             aRepository->badKexts, NULL);
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    aRepository->hasAuthenticated = true;
    return;
}

/*******************************************************************************
*
*******************************************************************************/
void KXKextRepositoryCheckIntegrityOfKexts(KXKextRepositoryRef aRepository, CFMutableArrayRef bomArray)
{
    if (KXKextManagerGetLogLevel(aRepository->manager) >=
            kKXKextManagerLogLevelDetails) {

        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "checking integrity of extensions in repository %s",
                repository_name);
            free((char *)repository_name);
        }
    }

    KXKextManagerDisableClearRelationships(aRepository->manager);

    __KXKextRepositoryCheckIntegrityOfKextArray(aRepository,
         aRepository->candidateKexts, aRepository->badKexts, bomArray);

    if (KXKextManagerPerformsFullTests(aRepository->manager)) {
         __KXKextRepositoryCheckIntegrityOfKextArray(aRepository,
             aRepository->badKexts, NULL, bomArray);
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    return;
}


/*******************************************************************************
*
*******************************************************************************/
void KXKextRepositoryMarkKextsAuthentic(KXKextRepositoryRef aRepository)
{
    CFIndex count, i;

    if (KXKextManagerGetLogLevel(aRepository->manager) >= kKXKextManagerLogLevelDetails) {
        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "marking extensions authentic in repository %s",
                repository_name);
            free((char *)repository_name);
        }
    }

    KXKextManagerDisableClearRelationships(aRepository->manager);

    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        KXKextRef thisKext = (KXKextRef)
            CFArrayGetValueAtIndex(aRepository->candidateKexts, i);
        KXKextMarkAuthentic(thisKext);
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        KXKextRef thisKext = (KXKextRef)
            CFArrayGetValueAtIndex(aRepository->badKexts, i);
        KXKextMarkAuthentic(thisKext);
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    aRepository->hasAuthenticated = true;

    return;
}

/*******************************************************************************
*
*******************************************************************************/
Boolean KXKextRepositoryHasAuthenticated(KXKextRepositoryRef aRepository)
{
    return aRepository->hasAuthenticated;
}

/*******************************************************************************
*
*******************************************************************************/
CFArrayRef KXKextRepositoryCopyCandidateKexts(KXKextRepositoryRef aRepository)
{
    return CFArrayCreateCopy(kCFAllocatorDefault, aRepository->candidateKexts);
}

/*******************************************************************************
*
*******************************************************************************/
CFArrayRef KXKextRepositoryCopyBadKexts(KXKextRepositoryRef aRepository)
{
    return CFArrayCreateCopy(kCFAllocatorDefault, aRepository->badKexts);
}

/*******************************************************************************
*
*******************************************************************************/
KXKextRef KXKextRepositoryGetKextWithURL(
    KXKextRepositoryRef aRepository, CFURLRef anURL)
{
    KXKextRef foundKext = NULL; // don't release
    CFURLRef  absURL = NULL;    // must release
    CFIndex   count, i;
    KXKextRef thisKext = NULL;  // don't release
    CFURLRef  kextURL = NULL;   // don't release

    absURL = PATH_CopyCanonicalizedURL(anURL);
    if (!absURL) {
        goto finish;
    }

    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        kextURL = KXKextGetAbsoluteURL(thisKext);
        if (CFEqual(kextURL, absURL)) {
            foundKext = thisKext;
            goto finish;
        }
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->badKexts, i);
        kextURL = KXKextGetAbsoluteURL(thisKext);
        if (CFEqual(kextURL, absURL)) {
            foundKext = thisKext;
            goto finish;
        }
    }

finish:

    if (absURL) CFRelease(absURL);

    return foundKext;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextRef KXKextRepositoryGetKextWithBundlePathInRepository(
    KXKextRepositoryRef aRepository, CFStringRef bundlePathInRepository)
{
    CFArrayRef thisArray;
    CFIndex i, count;

    thisArray = aRepository->candidateKexts;
    count = CFArrayGetCount(thisArray);
    for (i = 0; i < count; i++) {
        KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex(thisArray, i);
        CFStringRef kextBundlePathInRepository =
            KXKextGetBundlePathInRepository(thisKext);

        if (kCFCompareEqualTo == CFStringCompare(bundlePathInRepository,
                kextBundlePathInRepository, 0)) {

            return thisKext;
        }
    }

    return NULL;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError KXKextRepositoryWriteCache(
    KXKextRepositoryRef aRepository,
    CFURLRef anURL)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    CFURLRef createdURL = NULL;       // must release
    CFStringRef cachePath = NULL;     // must release
    CFURLRef cacheURL = NULL;         // don't release
    char * repository_path = NULL;    // must free
    char * cache_path = NULL;         // must free
    CFDictionaryRef cacheDictionary = NULL;  // must release
    CFDataRef cacheData = NULL;       // must release
    CFIndex cacheDataLength = 0;
    const UInt8 * cache_data = NULL;  // don't free
    gzFile outputGZFile = NULL;       // must close
    unsigned long bytes_written = 0;

   /*****
    * If given an URL to write to, use that; otherwise use the
    * repository's own path plus the standard cache file name.
    */
    if (anURL) {
        cacheURL = anURL;
    } else {
        cachePath = CFStringCreateWithFormat(kCFAllocatorDefault,
            NULL /* options */, CFSTR("%@.%@"),
            aRepository->repositoryPath, kKXKextRepositoryCacheExtension);
        createdURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                cachePath, kCFURLPOSIXPathStyle, false);
        if (!createdURL) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }
        cacheURL = createdURL;
    }

    cacheDictionary = _KXKextRepositoryCopyCacheDictionary(aRepository);
    if (!cacheDictionary) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "cannot create kext cache data");
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    cacheData = CFPropertyListCreateXMLData(kCFAllocatorDefault,
         cacheDictionary);
    if (!cacheData) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "cannot serialize kext cache data");
        result = kKXKextManagerErrorSerialization;
        goto finish;
    }

    cacheDataLength = CFDataGetLength(cacheData);

    cache_data = CFDataGetBytePtr(cacheData);
    if (!cache_data) {
        result = kKXKextManagerErrorUnspecified;
        goto finish;
    }

    cache_path = PATH_CanonicalizedCStringForURL(cacheURL);
    if (!cache_path) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    outputGZFile = gzopen(cache_path, "w");
    if (!outputGZFile) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "cannot open kext cache file %s for writing",
            cache_path);
        if (errno == 0) {
            result = kKXKextManagerErrorNoMemory;
        } else {
            result = kKXKextManagerErrorFileAccess;
        }
        goto finish;
    }

    bytes_written = gzwrite(outputGZFile, (void *)cache_data, cacheDataLength);
    if (bytes_written != cacheDataLength) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "error writing kext cache file %s",
            cache_path);
        result = kKXKextManagerErrorUnspecified;
        goto finish;
    }

    if (gzclose(outputGZFile) != Z_OK) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "error closing kext cache file %s",
            cache_path);
        result = kKXKextManagerErrorUnspecified;
        goto finish;
    }

finish:

    if (cachePath)        CFRelease(cachePath);
    if (createdURL)       CFRelease(createdURL);
    if (cacheDictionary)  CFRelease(cacheDictionary);
    if (cacheData)        CFRelease(cacheData);

    if (repository_path)  free(repository_path);
    if (cache_path)       free(cache_path);

    return result;
}

/*******************************************************************************
********************************************************************************
* FRAMEWORK-PRIVATE API BELOW HERE
********************************************************************************
*******************************************************************************/

/*******************************************************************************
*
*******************************************************************************/
KXKextRepositoryRef _KXKextRepositoryCreate(CFAllocatorRef alloc)
{
    __KXKextRepositoryRef newRepository = NULL;

    newRepository = __KXKextRepositoryCreatePrivate(alloc, NULL);
    if (!newRepository) {
        goto finish;
    }

finish:
    return (KXKextRepositoryRef)newRepository;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError _KXKextRepositoryInitWithDirectory(
    KXKextRepositoryRef aRepository,
    CFURLRef aDirectory,
    Boolean scanDirectory,  // sets the scansForKexts member!
    KXKextManagerRef aManager)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    CFURLRef absURL = NULL;  // must release

    if (!aManager) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    aRepository->manager = aManager;  // do not retain up pointer!

    absURL = PATH_CopyCanonicalizedURL(aDirectory);
    aRepository->repositoryPath = CFURLCopyFileSystemPath(absURL,
        kCFURLPOSIXPathStyle);
    if (!aRepository->repositoryPath) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    aRepository->useCache = false;
    aRepository->scansForKexts = scanDirectory;

    // FIXME: Check for existence of directory!!!

    if (!CFURLHasDirectoryPath(absURL)) {
        result = kKXKextManagerErrorNotADirectory;
        goto finish;
    }

    if (!aManager) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    aRepository->hasAuthenticated = false;

    aRepository->candidateKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
    if (!aRepository->candidateKexts) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    aRepository->badKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
    if (!aRepository->badKexts) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    if (aRepository->scansForKexts) {
        result = KXKextRepositoryScan(aRepository);
    }

finish:
    if (absURL) CFRelease(absURL);
    return result;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError _KXKextRepositoryInitWithCache(
    KXKextRepositoryRef aRepository,
    CFDictionaryRef     aDictionary,
    CFURLRef            aDirectory,
    KXKextManagerRef    aManager)
{
    KXKextManagerError result = kKXKextManagerErrorNone;
    CFNumberRef cacheVersion = NULL;        // don't release
    long int cache_version = 0;
    CFStringRef repositoryPath = NULL;      // don't release
    CFURLRef absURL = NULL;                 // must release
    CFBooleanRef scansForKexts = NULL;  // don't release
    CFArrayRef kexts = NULL;                // don't release
    CFIndex count, i;
    Boolean cacheInconsistencyFound = false;

    if (!aManager) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    aRepository->manager = aManager;  // do not retain up pointer!

    aRepository->hasAuthenticated = false;
    aRepository->useCache = true;

   /*****
    * Make sure we can parse the version of the cache handed to us.
    */
    cacheVersion = (CFNumberRef)CFDictionaryGetValue(aDictionary,
        _CACHE_VERSION_KEY);
    if (!cacheVersion || CFGetTypeID(cacheVersion) != CFNumberGetTypeID()) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    if (!CFNumberGetValue(cacheVersion, kCFNumberLongType, &cache_version)) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    if (cache_version > _kKXKextRepositoryCacheVersion) {
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "cannot read cache version %d", cache_version);
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    repositoryPath = (CFStringRef)CFDictionaryGetValue(aDictionary,
        _CACHE_PATH_KEY);
    if (!repositoryPath || CFGetTypeID(repositoryPath) != CFStringGetTypeID()) {
        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    absURL = PATH_CopyCanonicalizedURL(aDirectory);
    aRepository->repositoryPath = CFURLCopyFileSystemPath(absURL,
        kCFURLPOSIXPathStyle);
    if (!aRepository->repositoryPath) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    if (CFStringCompare(repositoryPath, aRepository->repositoryPath, 0) !=
        kCFCompareEqualTo) {

        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "cached repository path doesn't match that of repository");
        result = kKXKextManagerErrorCache;
        goto finish;
    }

    scansForKexts = (CFBooleanRef)CFDictionaryGetValue(aDictionary,
        _CACHE_SCANS_KEY);
    if (!scansForKexts ||
        CFGetTypeID(scansForKexts) != CFBooleanGetTypeID()) {

        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    aRepository->scansForKexts = CFBooleanGetValue(scansForKexts) ?
        true : false;

    // FIXME: Check for existence of directory!!!

    aRepository->candidateKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
    if (!aRepository->candidateKexts) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    aRepository->badKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
    if (!aRepository->badKexts) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    kexts = (CFArrayRef)CFDictionaryGetValue(aDictionary,
        _CACHE_KEXTS_KEY);
    if (!kexts || CFGetTypeID(kexts) != CFArrayGetTypeID()) {

        result = kKXKextManagerErrorInvalidArgument;
        goto finish;
    }

    count = CFArrayGetCount(kexts);
    for (i = 0; i < count; i++) {
        CFDictionaryRef kDict = (CFDictionaryRef)CFArrayGetValueAtIndex(
            kexts, i);
        KXKextRef newKext = NULL;  // must release
        KXKextManagerError kextResult = kKXKextManagerErrorNone;
        CFURLRef kextURL = NULL;  // must release
        char * kext_path = NULL;  // must free

        newKext = _KXKextCreate(kCFAllocatorDefault);
        if (!newKext) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }
        kextResult = _KXKextInitWithCacheDictionaryInRepository(newKext, kDict,
            aRepository);

        kextURL = KXKextGetAbsoluteURL(newKext);  // don't release
        kext_path = NULL;  // must free
        if (kextURL) { 
            kext_path = PATH_CanonicalizedCStringForURL(kextURL);
        }

        if (kextResult == kKXKextManagerErrorNoMemory) {
            result = kextResult;
            goto finish;
        } else if (kextResult == kKXKextManagerErrorFileAccess ||
            kextResult == kKXKextManagerErrorNotADirectory ||
            kextResult == kKXKextManagerErrorKextNotFound ||
            kextResult == kKXKextManagerErrorURLNotInRepository ||
            kextResult == kKXKextManagerErrorNotABundle ||
            kextResult == kKXKextManagerErrorNotAKext) {

            // kext is completely unusable, do not store in repository
            if (KXKextManagerGetLogLevel(aRepository->manager) >=
                kKXKextManagerLogLevelDetails) {

                _KXKextManagerLogMessage(aRepository->manager,
                    "%s is not a kernel extension or is inaccessible",
                    kext_path ? kext_path : "(unknown)");
            }

        } else if (kextResult != kKXKextManagerErrorNone) {

            _KXKextRepositoryAddBadKext(aRepository, newKext);
            if (kextResult == kKXKextManagerErrorCache) {
                cacheInconsistencyFound = true;
            }
            if (KXKextManagerGetLogLevel(aRepository->manager) >=
                kKXKextManagerLogLevelDetails) {

                if (kextResult == kKXKextManagerErrorCache) {
                    _KXKextManagerLogMessage(aRepository->manager,
                        "added cache-inconsistent kernel extension %s",
                        kext_path ? kext_path : "(unknown)");
                } else {
                    _KXKextManagerLogMessage(aRepository->manager,
                        "added invalid cached kernel extension %s",
                        kext_path ? kext_path : "(unknown)");
                }
            }
        } else {
            _KXKextRepositoryAddKext(aRepository, newKext);
            if (KXKextManagerGetLogLevel(aRepository->manager) >=
                kKXKextManagerLogLevelDetails) {

                _KXKextManagerLogMessage(aRepository->manager,
                    "added cached kernel extension %s",
                    kext_path ? kext_path : "(unknown)");
            }
        }
        CFRelease(newKext);
        newKext = NULL;
        if (kext_path) free(kext_path);
    }

    if (cacheInconsistencyFound) {
        char repository_path_buffer[MAXPATHLEN+1];
        char * repository_path = NULL;

        if (CFStringGetCString(aRepository->repositoryPath, repository_path_buffer,
            sizeof(repository_path_buffer), kCFStringEncodingMacRoman)) {
            repository_path = repository_path_buffer;
        }
        _KXKextManagerLogError(KXKextRepositoryGetManager(aRepository),
            "repository cache problem found; scanning %s directly",
            repository_path ? repository_path : "(unknown)");
        KXKextRepositoryReset(aRepository);
    }

finish:
    if (absURL)     CFRelease(absURL);
    return result;
}

/*******************************************************************************
*
*******************************************************************************/
const char * _KXKextRepositoryCopyCanonicalPathnameAsCString(
    KXKextRepositoryRef aRepository)
{
    char * abs_path = NULL;      // returned
    CFStringRef absPath = NULL;  // don't release
    CFIndex pathSize;
    Boolean error = false;

    absPath = KXKextRepositoryGetPath(aRepository);
    if (!absPath) {
        goto finish;
    }

    pathSize = 1 + CFStringGetLength(absPath);
    abs_path = (char *)malloc(pathSize * sizeof(char));
    if (!abs_path) {
        goto finish;
    }

    if (!CFStringGetCString(absPath, abs_path,
        pathSize, kCFStringEncodingMacRoman)) {

        error = true;
    }

finish:
    if (error && abs_path) {
        free(abs_path);
        abs_path = NULL;
    }
    return abs_path;
}

/*******************************************************************************
*
*******************************************************************************/
CFArrayRef _KXKextRepositoryGetCandidateKexts(KXKextRepositoryRef aRepository)
{
    return aRepository->candidateKexts;
}

/*******************************************************************************
*
*******************************************************************************/
CFArrayRef _KXKextRepositoryGetBadKexts(KXKextRepositoryRef aRepository)
{
    return aRepository->badKexts;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError _KXKextRepositoryScanDirectoryForKexts(
    KXKextRepositoryRef aRepository,
    CFURLRef     aDirectory,
    CFArrayRef   existingKexts,
    CFArrayRef * addedKexts,
    CFArrayRef * badKexts,
    CFArrayRef * removedKexts)
{
    KXKextManagerError result = kKXKextManagerErrorNone;

    CFMutableArrayRef addedKextArray = NULL; // returned
    CFMutableArrayRef removedKextArray = NULL; // returned
    CFMutableArrayRef notKextArray = NULL;     // returned

    CFURLRef    canonicalURL = NULL;    // must release
    CFStringRef directoryPath = NULL;   // must release
    CFStringRef dirRepositoryPath = NULL; // must release
    CFIndex     repositoryPathLength;

    CFMutableDictionaryRef existingKextDict = NULL; // must release
    CFStringRef fullPath = NULL;  // must release
    CFBooleanRef directoryExists = NULL;  // must release
    CFArrayRef immDirectoryContents = NULL;  // must release
    CFMutableArrayRef directoryContents = NULL;  // must release

    CFIndex numKexts;
    CFIndex i;
    SInt32  urlError;
    CFIndex directoryContentsCount;

    if (KXKextManagerGetLogLevel(aRepository->manager) >= kKXKextManagerLogLevelKexts) {
        const char * directory_name =
            PATH_CanonicalizedCStringForURL(aDirectory);
        if (directory_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "scanning directory %s",
                directory_name);
            free((char *)directory_name);
        }
    }

    canonicalURL = PATH_CopyCanonicalizedURL(aDirectory);
    if (!canonicalURL) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    directoryPath = CFURLCopyFileSystemPath(canonicalURL,
        kCFURLPOSIXPathStyle);
    if (!directoryPath) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    repositoryPathLength = CFStringGetLength(aRepository->repositoryPath);
    dirRepositoryPath = CFStringCreateWithSubstring(kCFAllocatorDefault,
        directoryPath, CFRangeMake(0, repositoryPathLength));
    if (!dirRepositoryPath) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    if (CFStringCompare(aRepository->repositoryPath, dirRepositoryPath, 0) !=
        kCFCompareEqualTo) {

        result = kKXKextManagerErrorURLNotInRepository;
        goto finish;
    }

    if (addedKexts) {
        addedKextArray = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
        if (!addedKextArray) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }
        *addedKexts = addedKextArray;
    }

    if (removedKexts) {
        removedKextArray = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
        if (!removedKextArray) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }
        *removedKexts = removedKextArray;
    }

    if (badKexts) {
        notKextArray = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
        if (!notKextArray) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }
        *badKexts = notKextArray;
    }

   /*****
    * Build dictionary of existing kexts based on path in repository.
    */
    existingKextDict = CFDictionaryCreateMutable(
        kCFAllocatorDefault, 0,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);
    if (!existingKextDict) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    if (existingKexts) {
        numKexts = CFArrayGetCount(existingKexts);
        for (i = 0; i < numKexts; i++) {
            KXKextRef thisKext =
                (KXKextRef)CFArrayGetValueAtIndex(existingKexts, i);
            CFStringRef kextPathInRepository =
                KXKextGetBundlePathInRepository(thisKext);

            fullPath = CFStringCreateWithFormat(kCFAllocatorDefault,
                NULL, CFSTR("%@/%@"), aRepository->repositoryPath,
                kextPathInRepository);
            if (!fullPath) {
                result = kKXKextManagerErrorNoMemory;
                goto finish;
            }
            CFDictionarySetValue(existingKextDict,
                fullPath,
                thisKext);

            CFRelease(fullPath);
            fullPath = NULL;
        }
    }

    // get all URLs in directory
    directoryExists = CFURLCreatePropertyFromResource(
        kCFAllocatorDefault, aDirectory,
        kCFURLFileExists, &urlError);

    if (!directoryExists) {
        result = kKXKextManagerErrorUnspecified;
        goto finish;
    } else {
        if (!CFBooleanGetValue(directoryExists)) {
            result = kKXKextManagerErrorNotADirectory;
            goto finish;
        }
    }

    immDirectoryContents = CFURLCreatePropertyFromResource(
        kCFAllocatorDefault, aDirectory,
        kCFURLFileDirectoryContents, &urlError);

    if (!immDirectoryContents) {
        if (urlError == kCFURLResourceNotFoundError) {
            result = kKXKextManagerErrorFileAccess;
        } else {
            result = kKXKextManagerErrorUnspecified;
        }
        goto finish;
    }

    directoryContents = CFArrayCreateMutableCopy(kCFAllocatorDefault,
        0, immDirectoryContents);
    if (!immDirectoryContents) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    // cull non-kext URLs
    directoryContentsCount = CFArrayGetCount(directoryContents);
    i = directoryContentsCount;
    while (1) {

        CFURLRef  thisURL = NULL;         // don't release
        CFStringRef urlExtension = NULL;  // must release
        CFStringRef bundleName = NULL;    // must release

        if (i == 0) break;
        i--;

        thisURL = CFArrayGetValueAtIndex(directoryContents, i);

       /* Drop any URL that doesn't have a ".kext" extension.
        */
        urlExtension = CFURLCopyPathExtension(thisURL);
        bundleName = CFURLCopyLastPathComponent(thisURL);

        if (!urlExtension ||
            CFStringCompare(CFSTR("kext"), urlExtension, NULL) !=
            kCFCompareEqualTo) {

            CFArrayRemoveValueAtIndex(directoryContents, i);
        }


       /* Drop any URL that starts with "." (including Finder
        * metadata files, which start with "._.").
        */

        else if (!bundleName ||
            CFStringHasPrefix(bundleName, CFSTR("."))) {

            CFArrayRemoveValueAtIndex(directoryContents, i);
        }

        if (urlExtension) CFRelease(urlExtension);
        if (bundleName) CFRelease(bundleName);
    }


    // find all added kexts (easy) and remove from array of URLs
    directoryContentsCount = CFArrayGetCount(directoryContents);
    for (i = 0; i < directoryContentsCount; i++) {

        CFURLRef  thisURL;
        KXKextRef thisKext;  // must release

        thisURL = CFArrayGetValueAtIndex(directoryContents, i);
        fullPath = CFURLCopyFileSystemPath(thisURL,
            kCFURLPOSIXPathStyle);
        if (!fullPath) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }

       /* Do I already know about this kext URL? If so, drop to the
        * bottom of the loop. If not, try to create a kext for the
        * URL and record it in the appropriate array based on the
        * initialization result.
        */
        if (CFDictionaryGetValue(existingKextDict, fullPath)) {
            /* Do nothing, fall though to bottom of loop. */
        } else if (addedKextArray || notKextArray) {

            thisKext = _KXKextCreate(kCFAllocatorDefault);
            if (!thisKext) {
                result = kKXKextManagerErrorNoMemory;
                goto finish;
            }

            result = _KXKextInitWithURLInRepository(thisKext, thisURL,
                aRepository);
            if (result == kKXKextManagerErrorNoMemory ||
                result == kKXKextManagerErrorFileAccess ||
                result == kKXKextManagerErrorNotADirectory ||
                result == kKXKextManagerErrorKextNotFound ||
                result == kKXKextManagerErrorURLNotInRepository ||
                result == kKXKextManagerErrorNotABundle ||
                result == kKXKextManagerErrorNotAKext) {

                // kext is completely unusable, do not store in repository

            } else if (result == kKXKextManagerErrorNone) {
                if (KXKextManagerGetLogLevel(aRepository->manager) >=
                     kKXKextManagerLogLevelKexts) {
                    const char * kext_name =
                        _KXKextCopyBundlePathInRepositoryAsCString(thisKext);
                    if (kext_name) {
                        _KXKextManagerLogMessage(aRepository->manager,
                            "found valid extension %s",
                            kext_name);
                        free((char *)kext_name);
                    }
                }
                if (addedKextArray) {
                    CFArrayAppendValue(addedKextArray, thisKext);
                }

            } else {
                if (KXKextManagerGetLogLevel(aRepository->manager) >=
                     kKXKextManagerLogLevelKexts) {

                    const char * kext_name =
                        _KXKextCopyBundlePathInRepositoryAsCString(thisKext);
                    if (kext_name) {
                        _KXKextManagerLogMessage(aRepository->manager,
                            "found invalid extension %s",
                            kext_name);
                        free((char *)kext_name);
                    }
                }
                if (notKextArray) {
                    CFArrayAppendValue(notKextArray, thisKext);
                }
            }
	    CFRelease(thisKext);
            result = kKXKextManagerErrorNone;
        }

       /* Pull current kext entry from dictionary of previously existing kexts.
        */
        CFDictionaryRemoveValue(existingKextDict, fullPath);
        CFRelease(fullPath);
        fullPath = NULL;
    }

   /*****
    * Any remaining entries in existingKextDict were not found in the
    * directory and hence have been removed. Put the entries from that
    * dictionary into the removedKextArray.
    */
    if (removedKextArray) {
        void * dictValues = NULL;  // must free

        numKexts = CFDictionaryGetCount(existingKextDict);
        dictValues = malloc(numKexts * sizeof(void *));
        if (!dictValues) {
            result = kKXKextManagerErrorNoMemory;
            goto finish;
        }

        CFDictionaryGetKeysAndValues(existingKextDict, NULL,
            (const void **)&dictValues);
        CFArrayReplaceValues(removedKextArray,
            CFRangeMake(0, CFArrayGetCount(removedKextArray)),
            dictValues, numKexts);

        if (KXKextManagerGetLogLevel(aRepository->manager) >=
             kKXKextManagerLogLevelKexts) {

            for (i = 0; i < numKexts; i++) {
                KXKextRef rKext = ((KXKextRef *)(dictValues))[i];
                const char * kext_name =
                    _KXKextCopyBundlePathInRepositoryAsCString(rKext);
                if (kext_name) {
                    _KXKextManagerLogMessage(aRepository->manager,
                        "extension %s has been removed",
                        kext_name);
                    free((char *)kext_name);
                }
            }
        }

        free(dictValues);
    }

finish:

    if (fullPath)              CFRelease(fullPath);
    if (canonicalURL)          CFRelease(canonicalURL);
    if (directoryPath)         CFRelease(directoryPath);
    if (dirRepositoryPath)     CFRelease(dirRepositoryPath);
    if (directoryExists)       CFRelease(directoryExists);
    if (existingKextDict)      CFRelease(existingKextDict);
    if (immDirectoryContents)  CFRelease(immDirectoryContents);
    if (directoryContents)     CFRelease(directoryContents);

    return result;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryAddKext(KXKextRepositoryRef aRepository, KXKextRef aKext)
{
    CFArrayAppendValue(aRepository->candidateKexts, aKext);

    // FIXME: Might want to be able to disable this when adding
    // FIXME: ...a lot of kexts one at a time (as when using a cache)
    //
    _KXKextManagerClearLoadFailures(aRepository->manager);

    aRepository->hasAuthenticated = false;

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryAddKexts(
    KXKextRepositoryRef aRepository, CFArrayRef kextArray)
{
    CFIndex count;

    _KXKextManagerClearLoadFailures(aRepository->manager);

    count = CFArrayGetCount(kextArray);

    CFArrayAppendArray(aRepository->candidateKexts, kextArray,
        CFRangeMake(0, count));

    aRepository->hasAuthenticated = false;

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryAddBadKext(KXKextRepositoryRef aRepository, KXKextRef aKext)
{
    CFArrayAppendValue(aRepository->badKexts, aKext);

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryAddBadKexts(
    KXKextRepositoryRef aRepository, CFArrayRef kextArray)
{
    CFIndex count;

    count = CFArrayGetCount(kextArray);
    CFArrayAppendArray(aRepository->badKexts, kextArray,
        CFRangeMake(0, count));

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryRemoveKext(KXKextRepositoryRef aRepository,
    KXKextRef aKext)
{

    CFArrayRef kextPlugins;
    CFIndex count, i;

    if (KXKextManagerGetLogLevel(aRepository->manager) >= kKXKextManagerLogLevelDetails) {
        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        const char * kext_name =
            _KXKextCopyBundlePathInRepositoryAsCString(aKext);
        if (kext_name && repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "repository %s removing extension %s",
                repository_name, kext_name);
        }
        if (kext_name) free((char *)kext_name);
        if (repository_name) free((char *)repository_name);
    }

    KXKextManagerDisableClearRelationships(aRepository->manager);

   /*****
    * First, remove any plugins of the kext being removed.
    */
    kextPlugins = KXKextGetPlugins(aKext);
    if (kextPlugins) {
        count = CFArrayGetCount(kextPlugins);
        for (i = 0; i < count; i++) {
            KXKextRef pluginKext;

            pluginKext = (KXKextRef)CFArrayGetValueAtIndex(kextPlugins, i);
            _KXKextRepositoryRemoveKext(aRepository, pluginKext);
        }
    }

   /*****
    * Now remove the kext itaRepository from the arrays of candidate and bad
    * kexts. It will only be in one or the other, but we have to check
    * both.
    */
    count = CFArrayGetCount(aRepository->candidateKexts);
    i = count;
    while (1) {
        KXKextRef thisKext = NULL;

        if (i == 0) break;
        i--;

        thisKext =
            (KXKextRef)CFArrayGetValueAtIndex(aRepository->candidateKexts, i);

        if (thisKext == aKext) {
            CFArrayRemoveValueAtIndex(aRepository->candidateKexts, i);
        }
    }

    count = CFArrayGetCount(aRepository->badKexts);
    i = count;
    while (1) {
        KXKextRef thisKext = NULL;

        if (i == 0) break;
        i--;

        thisKext =
            (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        if (thisKext == aKext) {
            CFArrayRemoveValueAtIndex(aRepository->badKexts, i);
        }
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryDisqualifyKext(KXKextRepositoryRef aRepository,
    KXKextRef aKext)
{
    CFIndex count, i;

    if (KXKextManagerGetLogLevel(aRepository->manager) >= kKXKextManagerLogLevelDetails) {
        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        const char * kext_name =
            _KXKextCopyBundlePathInRepositoryAsCString(aKext);
        if (kext_name && repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "repository %s disqualifying extension %s",
                repository_name, kext_name);
        }
        if (kext_name) free((char *)kext_name);
        if (repository_name) free((char *)repository_name);
    }
    KXKextManagerDisableClearRelationships(aRepository->manager);

    // FIXME: Do we disqualify a kext's plugins along with it?

   /*****
    * Now remove the kext from the array of candidate kexts
    * and put it in with the bad kexts.
    */
    count = CFArrayGetCount(aRepository->candidateKexts);
    i = count;
    while (1) {
        KXKextRef thisKext = NULL;

        if (i == 0) break;
        i--;

        thisKext =
            (KXKextRef)CFArrayGetValueAtIndex(aRepository->candidateKexts, i);

        if (thisKext == aKext) {
            CFArrayRemoveValueAtIndex(aRepository->candidateKexts, i);
            CFArrayAppendValue(aRepository->badKexts, thisKext);
        }
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryRequalifyKext(KXKextRepositoryRef aRepository,
    KXKextRef aKext)
{
    CFIndex count, i;

    if (KXKextManagerGetLogLevel(aRepository->manager) >= kKXKextManagerLogLevelDetails) {
        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        const char * kext_name =
            _KXKextCopyBundlePathInRepositoryAsCString(aKext);
        if (kext_name && repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "repository %s requalifying extension %s",
                repository_name, kext_name);
        }
        if (kext_name) free((char *)kext_name);
        if (repository_name) free((char *)repository_name);
    }
    KXKextManagerDisableClearRelationships(aRepository->manager);

   /*****
    * Now remove the kext from the array of bad kexts
    * and put it in with the candidate kexts.
    */
    count = CFArrayGetCount(aRepository->badKexts);
    i = count;
    while (1) {
        KXKextRef thisKext = NULL;

        if (i == 0) break;
        i--;

        thisKext =
            (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);

        if (thisKext == aKext) {
            CFArrayRemoveValueAtIndex(aRepository->badKexts, i);
            CFArrayAppendValue(aRepository->candidateKexts, thisKext);
            _KXKextSetHasBeenAuthenticated(thisKext, false);
        }
    }

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryClearRelationships(KXKextRepositoryRef aRepository)
{
    CFIndex count, i;
    KXKextRef thisKext;

    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        _KXKextClearVersionRelationships(thisKext);
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        _KXKextClearVersionRelationships(thisKext);
    }

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryClearDependencyRelationships(
    KXKextRepositoryRef aRepository)
{
    CFIndex count, i;
    KXKextRef thisKext;

    // Have each kext clear its dependencies
    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        _KXKextClearDependencies(thisKext);
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        _KXKextClearDependencies(thisKext);
    }

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryMarkKextsNotLoaded(KXKextRepositoryRef aRepository)
{
    CFIndex count, i;
    KXKextRef thisKext;

    // Have each kext clear its dependencies
    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        _KXKextSetIsLoaded(thisKext, false);
        _KXKextSetOtherVersionIsLoaded(thisKext, false);
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        _KXKextSetIsLoaded(thisKext, false);
        _KXKextSetOtherVersionIsLoaded(thisKext, false);
    }

    return;
}

/*******************************************************************************
*
*******************************************************************************/
void _KXKextRepositoryClearLoadFailures(KXKextRepositoryRef aRepository)
{
    CFIndex count, i;
    KXKextRef thisKext;

    // Have each kext clear its dependencies
    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        KXKextSetLoadFailed(thisKext, false);
    }

    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        thisKext = (KXKextRef)CFArrayGetValueAtIndex(aRepository->badKexts, i);
        KXKextSetLoadFailed(thisKext, false);
    }

    return;
}

/*******************************************************************************
*
*******************************************************************************/
CFDictionaryRef _KXKextRepositoryCopyCacheDictionary(
    KXKextRepositoryRef aRepository)
{
    Boolean error = false;
    CFMutableDictionaryRef theDictionary = NULL; // returned
    CFMutableArrayRef kexts = NULL;              // must release
    long int cache_version = _kKXKextRepositoryCacheVersion;
    CFNumberRef cacheVersion = NULL;             // must release
    CFIndex count, i;

    theDictionary = CFDictionaryCreateMutable(
            kCFAllocatorDefault, 0,
            &kCFTypeDictionaryKeyCallBacks,
            &kCFTypeDictionaryValueCallBacks);
    if (!theDictionary) {
        error = true;
        goto finish;
    }

    cacheVersion = CFNumberCreate(kCFAllocatorDefault,
        kCFNumberLongType, &cache_version);
    if (!cacheVersion) {
        error = true;
        goto finish;
    }
    CFDictionarySetValue(theDictionary, _CACHE_VERSION_KEY,
        cacheVersion);

    CFDictionarySetValue(theDictionary, _CACHE_PATH_KEY,
        aRepository->repositoryPath);
    CFDictionarySetValue(theDictionary, _CACHE_SCANS_KEY,
        aRepository->scansForKexts ? kCFBooleanTrue : kCFBooleanFalse);


    kexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!kexts) {
        error = true;
        goto finish;
    }
    CFDictionarySetValue(theDictionary, _CACHE_KEXTS_KEY,
        kexts);
    // do not release plugins here

   /*****
    * Dump the candidate kexts into the kext array.
    */
    count = CFArrayGetCount(aRepository->candidateKexts);
    for (i = 0; i < count; i++) {
        KXKextRef kext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->candidateKexts, i);
        CFDictionaryRef kDict = NULL;  // must release

       /* Plugins are added to the cache by their container.
        */
        if (KXKextIsAPlugin(kext)) {
            continue;
        }

        kDict = _KXKextCopyCacheDictionary(kext);
        if (!kDict) {
            error = true;
            goto finish;
        }
        CFArrayAppendValue(kexts, kDict);
        CFRelease(kDict); // clean up within loop
        kDict = NULL;
    }

   /*****
    * Dump the bad kexts into the kext array
    * (they might redeem themselves).
    */
    count = CFArrayGetCount(aRepository->badKexts);
    for (i = 0; i < count; i++) {
        KXKextRef kext = (KXKextRef)CFArrayGetValueAtIndex(
            aRepository->badKexts, i);
        CFDictionaryRef kDict = NULL;  // must release

       /* Plugins are added to the cache by their container.
        */
        if (KXKextIsAPlugin(kext)) {
            continue;
        }

        kDict = _KXKextCopyCacheDictionary(kext);
        if (!kDict) {
            error = true;
            goto finish;
        }
        CFArrayAppendValue(kexts, kDict);
        CFRelease(kDict); // clean up within loop
        kDict = NULL;
    }

finish:
    if (cacheVersion) CFRelease(cacheVersion);
    if (kexts)        CFRelease(kexts);

    if (error) {
        if (theDictionary) CFRelease(theDictionary);
        theDictionary = NULL;
    }

    return theDictionary;
}

/*******************************************************************************
* _KXKextRepositoryInvalidateCaches() just updates the mod time of the
* repository's directory, so that it is perforce newer than any cache file.
*******************************************************************************/
Boolean _KXKextRepositoryInvalidateCaches(
    KXKextRepositoryRef aRepository)
{
    Boolean result = true;
    char path[MAXPATHLEN+1];

    if (!CFStringGetCString(aRepository->repositoryPath, path,
        sizeof(path), kCFStringEncodingMacRoman)) {

        result = false;
        goto finish;
    }

    if (utimes(path, NULL) != 0) {
        // FIXME: Should we print an error message here?
        result = false;
    }

finish:
    return result;
}

/*******************************************************************************
********************************************************************************
* MODULE-PRIVATE API BELOW HERE
********************************************************************************
*******************************************************************************/

/*******************************************************************************
*
*******************************************************************************/
static const CFRuntimeClass __KXKextRepositoryClass = {
    0,                       // version
    "KXKextRepository",                // className
    NULL,                    // init
    NULL,                    // copy
    __KXKextRepositoryReleaseContents, // finalize
    NULL,                    // equal
    NULL,                    // hash
    NULL,                    // copyFormattingDesc
    __KXKextRepositoryCopyDebugDescription  // copyDebugDesc
};

void __KXKextRepositoryInitialize(void)
{
    __kKXKextRepositoryTypeID = _CFRuntimeRegisterClass(&__KXKextRepositoryClass);
    return;
}

/*******************************************************************************
*
*******************************************************************************/
static pthread_once_t initialized = PTHREAD_ONCE_INIT;

__KXKextRepositoryRef __KXKextRepositoryCreatePrivate(
    CFAllocatorRef       allocator,
    CFAllocatorContext * context)
{
    __KXKextRepositoryRef newRepository = NULL;
    void * offset = NULL;
    UInt32           size;

    /* initialize runtime */
    pthread_once(&initialized, __KXKextRepositoryInitialize);

    /* allocate session */
    size  = sizeof(__KXKextRepository) - sizeof(CFRuntimeBase);
    newRepository = (__KXKextRepositoryRef)_CFRuntimeCreateInstance(allocator,
        __kKXKextRepositoryTypeID, size, NULL);
    if (!newRepository) {
        return NULL;
    }
    offset = newRepository;
    bzero(offset + sizeof(CFRuntimeBase), size);

    return (__KXKextRepositoryRef)newRepository;
}

/*******************************************************************************
*
*******************************************************************************/
static CFStringRef __KXKextRepositoryCopyDebugDescription(CFTypeRef cf)
{
    CFAllocatorRef     allocator = CFGetAllocator(cf);
    CFMutableStringRef result;

    result = CFStringCreateMutable(allocator, 0);
    CFStringAppendFormat(result, NULL, CFSTR("<KXKextRepository %p [%p]> {\n"), cf, allocator);
    // add useful stuff here
    CFStringAppendFormat(result, NULL, CFSTR("}"));

    return result;
}

/*******************************************************************************
*
*******************************************************************************/
void __KXKextRepositoryReleaseContents(CFTypeRef cf)
{
    KXKextRepositoryRef aRepository = (KXKextRepositoryRef)cf;

    // manager is not retained
    if (aRepository->repositoryPath)   CFRelease(aRepository->repositoryPath);
    if (aRepository->candidateKexts)   CFRelease(aRepository->candidateKexts);
    if (aRepository->badKexts)         CFRelease(aRepository->badKexts);

    return;
}

/*******************************************************************************
*
*******************************************************************************/
KXKextManagerError __KXKextRepositoryScanDirectory(
    KXKextRepositoryRef aRepository)
{
    KXKextManagerError result = kKXKextManagerErrorNone;

    CFURLRef repositoryURL = NULL;   // must release
    CFArrayRef addedKexts = NULL;    // must release
    CFArrayRef removedKexts = NULL;  // must release
    CFArrayRef badKexts = NULL;      // must release
    CFMutableArrayRef existingKexts = NULL;  // must release
    CFArrayRef addedPlugins = NULL;    // must release
    CFArrayRef badKextPlugins = NULL;  // must release
    CFArrayRef removedPlugins = NULL;  // must release

    CFIndex outerCount, outerIndex;
    CFIndex innerCount, innerIndex;

    if (KXKextManagerGetLogLevel(aRepository->manager) >=
            kKXKextManagerLogLevelDetails) {

        const char * repository_name =
            _KXKextRepositoryCopyCanonicalPathnameAsCString(aRepository);
        if (repository_name) {
            _KXKextManagerLogMessage(aRepository->manager,
                "scanning repository %s",
                repository_name);
            free((char *)repository_name);
        }
    }

    KXKextManagerDisableClearRelationships(aRepository->manager);

    repositoryURL = KXKextRepositoryCopyURL(aRepository);
    if (!repositoryURL) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    existingKexts = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0,
            aRepository->candidateKexts);
    if (!existingKexts) {
        result = kKXKextManagerErrorNoMemory;
        goto finish;
    }

    CFArrayAppendArray(existingKexts, aRepository->badKexts,
        CFRangeMake(0, CFArrayGetCount(aRepository->badKexts)));

    result = _KXKextRepositoryScanDirectoryForKexts(
        aRepository,
        repositoryURL,
        existingKexts,
        aRepository->scansForKexts ? &addedKexts : NULL,
        aRepository->scansForKexts ? &badKexts : NULL,
        &removedKexts);

    if (result != kKXKextManagerErrorNone) {
        goto finish;
    }

    if (addedKexts) {
        _KXKextRepositoryAddKexts(aRepository, addedKexts);
    }
    if (badKexts) {
        _KXKextRepositoryAddBadKexts(aRepository, badKexts);
    }

   /*****
    * Check the removed kexts and drop them from the arrays
    * of candidate & bad kexts.
    */
    outerCount = CFArrayGetCount(removedKexts);
    for (outerIndex = 0; outerIndex < outerCount; outerIndex++) {
        KXKextRef rKext = (KXKextRef)CFArrayGetValueAtIndex(removedKexts,
            outerIndex);
        _KXKextRepositoryRemoveKext(aRepository, rKext);
    }

   /*****
    * Check every added kext for added/removed plugins.
    */
    if (CFArrayGetCount(addedKexts)) {
        outerCount = CFArrayGetCount(addedKexts);
        for (outerIndex = 0; outerIndex < outerCount; outerIndex++) {
            KXKextRef thisKext =
                (KXKextRef)CFArrayGetValueAtIndex(addedKexts, outerIndex);

            result = _KXKextScanPlugins(thisKext, &addedPlugins,
                &badKextPlugins, &removedPlugins);

            if (result != kKXKextManagerErrorNone) {
                goto finish;
            }

            _KXKextRepositoryAddKexts(aRepository, addedPlugins);
            _KXKextRepositoryAddBadKexts(aRepository, badKextPlugins);

           /*****
            * Check the removed kexts and drop them from the arrays
            * of candidate & bad kexts.
 */
            innerCount = CFArrayGetCount(removedPlugins);
            for (innerIndex = 0; innerIndex < innerCount; innerIndex++) {
                KXKextRef rKext =
                    (KXKextRef)CFArrayGetValueAtIndex(removedPlugins,
                    innerIndex);
                _KXKextRepositoryRemoveKext(aRepository, rKext);
            }

            if (addedPlugins) {
                CFRelease(addedPlugins);
                addedPlugins = NULL;
            }
            if (removedPlugins) {
                CFRelease(removedPlugins);
                removedPlugins = NULL;
            }
            if (badKextPlugins) {
                CFRelease(badKextPlugins);
                badKextPlugins = NULL;
            }
        }
    }

finish:
    if (repositoryURL)   CFRelease(repositoryURL);
    if (addedKexts)      CFRelease(addedKexts);
    if (removedKexts)    CFRelease(removedKexts);
    if (badKexts)        CFRelease(badKexts);
    if (existingKexts)   CFRelease(existingKexts);
    if (addedPlugins)    CFRelease(addedPlugins);
    if (removedPlugins)  CFRelease(removedPlugins);
    if (badKextPlugins)  CFRelease(badKextPlugins);

    KXKextManagerClearRelationships(aRepository->manager);

    KXKextManagerEnableClearRelationships(aRepository->manager);

    return result;
}

/*******************************************************************************
*
*******************************************************************************/
static void __KXKextRepositoryAuthenticateKextArray(
    __KXKextRepositoryRef aRepository,
    CFMutableArrayRef kexts,
    CFMutableArrayRef badKexts)
{
    CFIndex numKexts;
    CFIndex i;

    numKexts = CFArrayGetCount(kexts);
    i = numKexts;
    while (1) {
        KXKextRef thisKext;
        KXKextManagerError kextResult;

        if (i == 0) break;
        i--;

        thisKext = (KXKextRef)CFArrayGetValueAtIndex(kexts, i);
        if (!KXKextHasBeenAuthenticated(thisKext)) {
            kextResult = KXKextAuthenticate(thisKext);

            if (kextResult == kKXKextManagerErrorNoMemory) {
                goto finish;  // no point continuing!
            }

            if (kextResult != kKXKextManagerErrorNone && badKexts) {
                CFArrayAppendValue(badKexts, thisKext);
                CFArrayRemoveValueAtIndex(kexts, i);
            }
        }
    }

finish:
    return;
}

static void __KXKextRepositoryCheckIntegrityOfKextArray(
    __KXKextRepositoryRef aRepository,
    CFMutableArrayRef kexts,
    CFMutableArrayRef badKexts,
    CFMutableArrayRef bomArray)
{
    CFIndex numKexts;
    CFIndex i;

    numKexts = CFArrayGetCount(kexts);
    for (i = numKexts-1; i >= 0; i--) {
        KXKextRef thisKext;
        KXKextManagerError kextResult;

        thisKext = (KXKextRef)CFArrayGetValueAtIndex(kexts, i);
        kextResult = _KXKextCheckIntegrity(thisKext, bomArray);

        if (kextResult == kKXKextManagerErrorNoMemory) {
            goto finish;  // no point continuing!
        }

        if (kextResult != kKXKextManagerErrorNone && badKexts) {
            CFArrayAppendValue(badKexts, thisKext);
            CFArrayRemoveValueAtIndex(kexts, i);
        }
    }

finish:
    return;
}
