#include <CoreFoundation/CoreFoundation.h>
#include <libc.h>
#include <IOKit/IOTypes.h>

#include <IOKit/kext/KXKextManager.h>
#include <IOKit/kext/KextManagerPriv.h>

#define ALLOW_PATCH_OUTPUT  0
#define ALLOW_NO_START      0

/*******************************************************************************
* Global variables.
*******************************************************************************/

static char * progname = "(unknown)";
static int verbose_level = kKXKextManagerLogLevelDefault;  // -v, -q options set this


/*******************************************************************************
* Utility and callback functions.
*******************************************************************************/

static Boolean check_file(const char * filename);
static Boolean check_dir(const char * dirname, int writeable);
static void qerror(const char * format, ...);
static void verbose_log(const char * format, ...);
static void error_log(const char * format, ...);
static int user_approve(int default_answer, const char * format, ...);
static const char * user_input(const char * format, ...);
static int addKextsToManager(
    KXKextManagerRef aManager,
    CFArrayRef kextNames,
    CFMutableArrayRef kextArray,
    Boolean do_tests);
static void usage(int level);

extern KXKextManagerError _KXKextManagerPrepareKextForLoading(
    KXKextManagerRef aKextManager,
    KXKextRef aKext,
    const char * kext_name,
    Boolean check_loaded_for_dependencies,
    Boolean do_load,
    CFMutableArrayRef inauthenticKexts);
extern KXKextManagerError _KXKextManagerLoadKextUsingOptions(
    KXKextManagerRef aKextManager,
    KXKextRef aKext,
    const char * kext_name,
    const char * kernel_file,
    const char * patch_dir,
    const char * symbol_dir,
    IOOptionBits load_options,
    Boolean do_start_kext,
    int     interactive_level,
    Boolean ask_overwrite_symbols,
    Boolean overwrite_symbols,
    Boolean get_addrs_from_kernel,
    unsigned int num_addresses,
    char ** addresses);

extern const char * _KXKextCopyCanonicalPathnameAsCString(KXKextRef aKext);

/*******************************************************************************
*******************************************************************************/

int main(int argc, const char *argv[]) {
    int exit_code = 0;
    int failure_code = 0;  // if any kext load fails during the loop
    int add_kexts_result = 1;  // assume success here
    int argc_mod = argc;
    int argc_opt_count = 0;
    const char ** argv_mod = argv;
    int optchar;
    KXKextManagerRef theKextManager = NULL;  // must release
    CFURLRef         kextURL = NULL;         // must release
    KXKextManagerError result;

    CFIndex i, count;

    int approve;                             // for interactive mode queries

   /*****
    * Set by command line option flags.
    */
    unsigned short int flag_n = 0;  // used to sanity-check -n, -l, -m
    unsigned short int flag_l = 0;  // before setting behavior-changing
    unsigned short int flag_m = 0;  // variables do_load & do_start_matching

    Boolean get_addrs_from_kernel = false;   // -A
    Boolean use_repository_caches = true;    // -c to turn off
    Boolean skip_extensions_folder = false;  // -e
    Boolean overwrite_symbols = true;        // -i turns off
    int interactive_level = 0;               // -i/-I turns on

    Boolean do_load = true;                  // -l is load but no matching
    Boolean do_start_kmod = true;            // -j is load, don't start,
                                             //    no matching
    Boolean do_start_matching = true;        // -m is don't load but start
                                             //    matching
                                             // -n is don't do either

    Boolean do_tests = false;                // -t
    Boolean strict_auth = true;
    Boolean safe_boot_mode = false;          // -x
    Boolean pretend_authentic = false;       // -z
    Boolean skip_dependencies = false;       // -Z (and with -t only!)
    Boolean check_loaded_for_dependencies = false; // -D to turn off (obsolete)

    unsigned int addresses_cap = 10;
    unsigned int num_addresses = 0;
    char ** addresses = NULL;                // -a; must free

    CFMutableArrayRef kextIDs = NULL;                // -b; must release
    CFMutableArrayRef personalityNames = NULL;       // -p; must release
    CFMutableArrayRef dependencyNames = NULL;        // -d; must release
    CFMutableArrayRef repositoryDirectories = NULL;  // -r; must release
    CFMutableArrayRef kextNames = NULL;              // args; must release
    CFMutableArrayRef kextNamesToUse = NULL;         // must release
    CFMutableArrayRef inauthenticKexts = NULL;       // must release
    KXKextRef theKext = NULL;                        // don't release

    const char * default_kernel_file = "/mach";
    const char * kernel_file = NULL;  // overriden by -k option
    const char * symbol_dir = NULL;   // set by -s option;
                                      // for writing debug files for kmods
    const char * patch_dir = NULL;    // set by -P option;
                                      // for writing patchup files for kmods

    CFIndex inauthentic_kext_count = 0;
    CFIndex k = 0;

   /*****
    * Find out what the program was invoked as.
    */
    progname = rindex(argv[0], '/');
    if (progname) {
        progname++;   // go past the '/'
    } else {
        progname = (char *)argv[0];
    }

   /*****
    * Allocate collection objects.
    */
    addresses = (char **)malloc(addresses_cap * sizeof(char *));
    if (!addresses) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }
    bzero(addresses, addresses_cap * sizeof(char *));

    personalityNames = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!personalityNames) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    dependencyNames = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!dependencyNames) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    repositoryDirectories = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!repositoryDirectories) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    kextNames = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!kextNames) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    kextNamesToUse = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!kextNamesToUse) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    kextIDs = CFArrayCreateMutable(kCFAllocatorDefault, 0,
        &kCFTypeArrayCallBacks);
    if (!kextIDs) {
        exit_code = 1;
        qerror("memory allocation failure\n");
        goto finish;
    }

    /*****
    * Process command line arguments. If running in kextload-compatibiliy
    * mode, accept its old set of options and none other. If running in
    * the new mode, process the new, larger set of options.
    */
// -j option currently masked out
    while ((optchar = getopt(argc, (char * const *)argv,
               "a:Ab:cd:DehiIk:lLmnp:P:qr:s:tvxzZ")) != -1) {

        char * address_string = NULL;  // don't free
        unsigned int address;
        CFStringRef optArg = NULL;    // must release

        switch (optchar) {
          case 'a':
            flag_n = 1;  // -a implies -n

            if (!optarg) {
                qerror("no argument for -a\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            address_string = index(optarg, '@');
            if (!address_string) {
                qerror("invalid use of -a option\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            address_string++;
            address = strtoul(address_string, NULL, 16);
            if (!address) {
                qerror(
                    "address must be specified and non-zero\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }

            if (num_addresses >= addresses_cap) {
                addresses_cap *= 2;
                addresses = (char **)realloc(addresses,
                    (addresses_cap * sizeof(char *)));
                if (!addresses) {
                    qerror(
                        "memory allocation failure\n");
                    exit_code = 1;
                    goto finish;
                }
                bzero(addresses + num_addresses,
                    (addresses_cap-num_addresses) * sizeof(char *));
            }
            addresses[num_addresses++] = optarg;
            break;

          case 'A':
            flag_n = 1;   // -A implies -n
            get_addrs_from_kernel = true;
            break;

          case 'b':
            if (!optarg) {
                qerror("no argument for -b\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            optArg = CFStringCreateWithCString(kCFAllocatorDefault,
                optarg, kCFStringEncodingMacRoman);
            if (!optArg) {
                qerror("memory allocation failure\n");
                exit_code = 1;
                goto finish;
           }
            CFArrayAppendValue(kextIDs, optArg);
            CFRelease(optArg);
            optArg = NULL;
            break;

          case 'c':
            use_repository_caches = false;
            break;

          case 'd':
            if (!optarg) {
                qerror("no argument for -d\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            optArg = CFStringCreateWithCString(kCFAllocatorDefault,
                optarg, kCFStringEncodingMacRoman);
            if (!optArg) {
                qerror("memory allocation failure\n");
                exit_code = 1;
                goto finish;
            }
            CFArrayAppendValue(dependencyNames, optArg);
            CFRelease(optArg);
            optArg = NULL;
            break;

          case 'D':
            check_loaded_for_dependencies = false;
            break;

          case 'e':
            skip_extensions_folder = true;
            break;

          case 'h':
            usage(2);
            goto finish;
            break;

          case 'i':
            if (interactive_level) {
                qerror("use only one of -i or -I\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            overwrite_symbols = false;
            interactive_level = 1;
            break;

          case 'I':
            if (interactive_level) {
                qerror("use only one of -i or -I\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            overwrite_symbols = false;
            interactive_level = 2;
            break;

#if ALLOW_NO_START
          case 'j':
            do_load = true;
            do_start_kmod = false;
            do_start_matching = false;
            break;
#endif ALLOW_NO_START

          case 'k':
            if (kernel_file) {
                qerror("duplicate use of -k option\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            if (!optarg) {
                qerror("no argument for -k\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            kernel_file = optarg;
            break;

          case 'l':
            flag_l = 1;
            break;

          // see case 'r' for case 'L'

          case 'm':
            flag_m = 1;
            break;

          case 'n':
            flag_n = 1;
            break;

          case 'p':
            if (!optarg) {
                qerror("no argument for -p\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            optArg = CFStringCreateWithCString(kCFAllocatorDefault,
                optarg, kCFStringEncodingMacRoman);
            if (!optArg) {
                qerror("memory allocation failure\n");
                exit_code = 1;
                goto finish;
            }
            CFArrayAppendValue(personalityNames, optArg);
            CFRelease(optArg);
            optArg = NULL;
            break;
#if ALLOW_PATCH_OUTPUT
          case 'P':
            if (patch_dir) {
                qerror("duplicate use of -P option\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            if (!optarg) {
                qerror("no argument for -P\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            patch_dir = optarg;
            break;
#endif

          case 'q':
            if (verbose_level != kKXKextManagerLogLevelDefault) {
                qerror("duplicate use of -v and/or -q option\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            verbose_level = kKXKextManagerLogLevelSilent;
            break;

          case 'r':
            /* fall through */
          case 'L':
            if (!optarg) {
                qerror("no argument for -%c\n", optchar);
                usage(0);
                exit_code = 1;
                goto finish;
            }
            optArg = CFStringCreateWithCString(kCFAllocatorDefault,
               optarg, kCFStringEncodingMacRoman);
            if (!optArg) {
                qerror("memory allocation failure\n");
                exit_code = 1;
                goto finish;
            }
            CFArrayAppendValue(repositoryDirectories, optArg);
            CFRelease(optArg);
            optArg = NULL;
            break;

          case 's':
            if (symbol_dir) {
                qerror("duplicate use of -s option\n\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            if (!optarg) {
                qerror("no argument for -s\n");
                usage(0);
                exit_code = 1;
                goto finish;
            }
            symbol_dir = optarg;
            break;

          case 't':
            do_tests = true;
            break;

          case 'v':
            {
                const char * next;

                if (verbose_level != kKXKextManagerLogLevelDefault) {
                    qerror("duplicate use of -v and/or -q option\n\n");
                    usage(0);
                    exit_code = 1;
                    goto finish;
                }
                if (optind >= argc) {
                    verbose_level = kKXKextManagerLogLevelBasic;
                } else {
                    next = argv[optind];
                    if ((next[0] == '0' || next[0] == '1' ||
                         next[0] == '2' || next[0] == '3' || 
                         next[0] == '4' || next[0] == '5' ||
                         next[0] == '6') && next[1] == '\0') {

                        if (next[0] == '0') {
                            verbose_level = kKXKextManagerLogLevelErrorsOnly;
                        } else {
                            verbose_level = atoi(next);
                        }
                        optind++;
                    } else {
                        verbose_level = kKXKextManagerLogLevelBasic;
                    }
                }
            }
            break;

          case 'x':
            safe_boot_mode = true;
            use_repository_caches = false;  // -x implies -c
            break;

          case 'z':
            pretend_authentic = true;
            break;

          case 'Z':
            skip_dependencies = true;
            break;

        default:
            qerror("unknown option -%c\n", optchar);
            usage(0);
            exit_code = 1;
            goto finish;
        }
    }

   /* Update argc, argv based on option processing.
    */
    argc_mod = argc - optind;
    argc_opt_count = optind;
    argv_mod = argv + optind;

   /*****
    * Check for bad combinations of arguments and options.
    */
    if (CFArrayGetCount(kextIDs) == 0 && argc_mod == 0) {
        qerror("no kernel extension specified "
            "(name kernel extension bundles\n"
            "    following options, or use -b)\n\n");
        usage(0);
        exit_code = 1;
        goto finish;
    } else if (flag_l + flag_m + flag_n > 1) {
        qerror("only one of -l/-m/-n is allowed"
            " (-a and -A imply -n)\n\n");
        usage(0);
        exit_code = 1;
        goto finish;
    } else if (flag_l) {
        do_load = true;
        do_start_matching = false;
    } else if (flag_m) {
        do_load = false;
        do_start_matching = true;
    } else if (flag_n) {
        do_load = false;
        do_start_matching = false;
    }

    if (do_load && geteuid() != 0) {
        qerror("you must be running as root "
            "to load modules into the kernel\n");
        exit_code = 1;
        goto finish;
    }

    if (num_addresses > 0 && get_addrs_from_kernel) {
        qerror("don't use -a with -A\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

    if (num_addresses > 0 && (do_load || do_start_matching)) {
        qerror("don't use -a with -l or -m\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

    if (get_addrs_from_kernel && (do_load || do_start_matching)) {
        qerror("don't use -A with -l or -m\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

    // FIXME: Is this a valid restriction? The default kernel_file
    // FIXME: ...is /mach, which is written out by the kernel during
    // FIXME: ...boot, and always represents the currently running kernel.
    //
    if (kernel_file && (do_load || do_start_matching)) {
        qerror("use -k only with -n\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

   /*****
    * Make sure if we're just doing debug symbols that we don't
    * load or start matching (-a/-A imply -n).
    */
    if (num_addresses > 0 || get_addrs_from_kernel) {
        do_load = 0;
        do_start_matching = 0;
    }

    if (pretend_authentic && do_load) {
        qerror("-z is only allowed when not loading\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

   /* If running in quiet mode and the invocation might require
    * interaction, just exit with an error.
    */
    if (verbose_level == kKXKextManagerLogLevelSilent) {
        if (interactive_level > 0 ||
            (flag_n && num_addresses == 0 && get_addrs_from_kernel == 0)) {

            exit_code = 1;
            goto finish;
        }
    }

   /****
    * If we're getting addresses from the kernel we have to call
    * down there, so we might as well check what's loaded before
    * resolving dependencies too (-A overrides -D). If we're not
    * performing a load, then don't check (-n/-m implies -D).
    */
    if (get_addrs_from_kernel) {
        check_loaded_for_dependencies = true;
    }
#if 0
// Default behavior now is to not check loaded for dependencies and
// do pure version-based dependency resolution. We might introduce
// a new flag to turn this on for general load cases....
else if (!do_load) {
        check_loaded_for_dependencies = false;
    }
#endif /* 0 */

   /*****
    * If we're not loading and have no request to emit a symbol
    * or patch file, there's nothing to do!
    */
    if (!do_tests && !do_load && !do_start_matching &&
        !symbol_dir && !patch_dir) {

        qerror("no work to do; check your options\n\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

   /*****
    * -Z is only allowed if you don't need to generate dependencies.
    */
    if (skip_dependencies && (!do_tests || do_load || symbol_dir || patch_dir)) {
#if ALLOW_PATCH_OUTPUT
        qerror("use -Z only with -nt and not with -s or -P\n");
#else
        qerror("use -Z only with -nt and not with -s\n");
#endif ALLOW_PATCH_OUTPUT
        usage(0);
        exit_code = 1;
        goto finish;
    }

   /*****
    * Record the kext names from the command line.
    */
    for (i = 0; i < argc_mod; i++) {
        CFStringRef kextName = CFStringCreateWithCString(kCFAllocatorDefault,
              argv_mod[i], kCFStringEncodingMacRoman);
        if (!kextName) {
            qerror("memory allocation failure\n");
            exit_code = 1;
            goto finish;
        }
        CFArrayAppendValue(kextNames, kextName);
        CFRelease(kextName);
    }

    if (CFArrayGetCount(kextNames) == 0 && CFArrayGetCount(kextIDs) == 0) {
        qerror("no kernel extension specified\n\n");
        usage(0);
        exit_code = 1;
        goto finish;
    }

   /*****
    * Make sure we scan the standard Extensions folder unless told explicitly
    * not to.
    */
    if (!skip_extensions_folder) {
        CFArrayInsertValueAtIndex(repositoryDirectories, 0,
            kKXSystemExtensionsFolder);
    }

   /*****
    * Make sure the kernel file, symbol directory, and patch directory
    * exist and are readable/writable.
    */
    if (!kernel_file) {
        kernel_file = default_kernel_file;
        if (!do_load && verbose_level >= kKXKextManagerLogLevelBasic) {
            verbose_log("no kernel file specified; using %s", kernel_file);
        }
    }

    if (!check_file(kernel_file)) {
        // check_file() prints an error message
        exit_code = 1;
        goto finish;
    }

    if (symbol_dir) {
        if (!check_dir(symbol_dir, 1)) {
            // check_dir() prints an error message
            exit_code = 1;
            goto finish;
        }
    }

    if (patch_dir) {
        if (!check_dir(patch_dir, 1)) {
            // check_dir() prints an error message
            exit_code = 1;
            goto finish;
        }
    }

   /*****
    * Set up the kext manager.
    */
    theKextManager = KXKextManagerCreate(kCFAllocatorDefault);
    if (!theKextManager) {
        qerror("can't allocate kernel extension manager\n");
        exit_code = 1;
        goto finish;
    }

    result = KXKextManagerInit(theKextManager, true /* load_in_task */,
        safe_boot_mode);
    if (result != kKXKextManagerErrorNone) {
        qerror("can't initialize kernel extension manager (%s)\n",
            KXKextManagerErrorStaticCStringForError(result));
        exit_code = 1;
        goto finish;
    }

    KXKextManagerSetPerformsFullTests(theKextManager, do_tests);
    KXKextManagerSetPerformsStrictAuthentication(theKextManager, strict_auth);
    KXKextManagerSetLogLevel(theKextManager, verbose_level);
    KXKextManagerSetLogFunction(theKextManager, &verbose_log);
    KXKextManagerSetErrorLogFunction(theKextManager, &error_log);
    KXKextManagerSetUserVetoFunction(theKextManager, &user_approve);
    KXKextManagerSetUserApproveFunction(theKextManager, &user_approve);
    KXKextManagerSetUserInputFunction(theKextManager, &user_input);

   /*****
    * Disable clearing of relationships until we're done putting everything
    * together.
    */
    KXKextManagerDisableClearRelationships(theKextManager);

   /*****
    * Add the dependency kexts to the manager. Do this *before* adding
    * whole repositories; the kext manager searches for duplicate versions
    * in order of addition, so we want explicitly named dependencies to
    * be found first. (This won't help a bit if a later version is in a
    * repository, of course; the user has to know how to use the program,
    * after all.)
    *
    * This invocation is a failure only if a fatal error occurs; if some
    * of the kexts couldn't be added we will keep going and deal with a
    * missing dependency later.
    */
    if (addKextsToManager(theKextManager, dependencyNames, NULL, do_tests) == -1) {
        exit_code = 1;
        goto finish;
    }

   /*****
    * Add the extensions folders specified with -r to the manager.
    */
    count = CFArrayGetCount(repositoryDirectories);
    for (i = 0; i < count; i++) {
        CFStringRef directory = (CFStringRef)CFArrayGetValueAtIndex(
            repositoryDirectories, i);
        CFURLRef directoryURL =
            CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                directory, kCFURLPOSIXPathStyle, true);
        if (!directoryURL) {
            qerror("memory allocation failure\n");
            exit_code = 1;

            goto finish;
        }

        result = KXKextManagerAddRepositoryDirectory(theKextManager,
            directoryURL, true /* scanForKexts */,
            use_repository_caches, NULL);
        if (result != kKXKextManagerErrorNone) {
            qerror("can't add repository (%s).\n",
                KXKextManagerErrorStaticCStringForError(result));
            exit_code = 1;
            goto finish;
        }
        CFRelease(directoryURL);
        directoryURL = NULL;
    }

   /*****
    * Add each kext named on the command line to the manager. If any can't
    * be added, addKextsToManager() returns 0, so set the exit_code to 1, but
    * go on to process the kexts that could be added.
    * If a fatal error occurs, addKextsToManager() returns -1, so go right
    * to the finish;
    */
    add_kexts_result = addKextsToManager(theKextManager, kextNames,
       kextNamesToUse, do_tests);
    if (add_kexts_result < 1) {
        exit_code = 1;
        if (add_kexts_result < 0) {
            goto finish;
        }
    }

   /*****
    * Either fake authentication, or if doing all tests then authenticate
    * everything. (It may not be necessary to do this for
    * *everything*; might be able to just do it on the named kexts.)
    */
    if (pretend_authentic) {
        // Yes, do this even if do_tests is true; -tz means fake authentication.
        KXKextManagerMarkKextsAuthentic(theKextManager);
    } else if (do_tests) {
        KXKextManagerAuthenticateKexts(theKextManager);
    }

    KXKextManagerEnableClearRelationships(theKextManager);

   /*****
    * If doing full tests, assemble the whole database of version and
    * dependency relationships. (It may not be necessary to do this for
    * *everything*; might be able to just do it on the named kexts.)
    */
    if (do_tests) {
        KXKextManagerCalculateVersionRelationships(theKextManager);
        if (!skip_dependencies) {
            KXKextManagerResolveAllKextDependencies(theKextManager);
        }
    }

   /*****
    * If we got CFBundleIdentifiers, then look them up. We just add to the
    * kextNamesToUse array here.
    */
    count = CFArrayGetCount(kextIDs);
    for (i = 0; i < count; i++) {
        CFStringRef thisKextID = (CFStringRef)
            CFArrayGetValueAtIndex(kextIDs, i);
        KXKextRef theKext = KXKextManagerGetKextWithIdentifier(
            theKextManager, thisKextID);
        CFStringRef kextName = NULL;  // must release
        char name_buffer[255];

        if (!CFStringGetCString(thisKextID,
            name_buffer, sizeof(name_buffer) - 1, kCFStringEncodingMacRoman)) {

            qerror("internal error; no memory?\n");
            exit_code = 1;
            goto finish;
        }
        if (verbose_level >= kKXKextManagerLogLevelBasic) {
            verbose_log("looking up extension with identifier %s",
                name_buffer);
        }

        if (!theKext) {
            if (!CFStringGetCString(thisKextID,
                name_buffer, sizeof(name_buffer) - 1,
                kCFStringEncodingMacRoman)) {

                qerror("internal error; no memory?\n");
                exit_code = 1;
                goto finish;
            }
            qerror("can't find extension with identifier %s\n",
                name_buffer);
            exit_code = 1;
            continue;  // not fatal, keep trying the others
        }
        kextName = KXKextCopyAbsolutePath(theKext);
        if (!kextName) {
            qerror("memory allocation failure\n");
            exit_code = 1;
            goto finish;
        }
        if (!CFStringGetCString(kextName,
            name_buffer, sizeof(name_buffer) - 1, kCFStringEncodingMacRoman)) {

            qerror("internal error; no memory?\n");
            exit_code = 1;
            goto finish;
        }
        if (verbose_level >= kKXKextManagerLogLevelBasic) {
            verbose_log("found extension bundle %s", name_buffer);
        }
        CFArrayAppendValue(kextNamesToUse, kextName);
        CFRelease(kextName);
    }

   /*****
    * Get busy loading kexts.
    */
    count = CFArrayGetCount(kextNamesToUse);
    for (i = 0; i < count; i++) {
        CFStringRef kextName = NULL; // don't release
        char kext_name[MAXPATHLEN];
        unsigned short cache_retry_count = 1;

       /*****
        * If the last iteration flipped failure_code, make the exit_code
        * nonzero and reset failure_code for this iteration.
        */
        if (failure_code) {
            exit_code = 1;
            failure_code = 0;
        }

retry:

        if (inauthenticKexts) {
            CFRelease(inauthenticKexts);
            inauthenticKexts = NULL;
        }

        inauthenticKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeArrayCallBacks);
        if (!inauthenticKexts) {
            exit_code = 1;
            qerror("memory allocation failure\n");
            goto finish;
        }

        if (kextURL) {
            CFRelease(kextURL);
            kextURL = NULL;
        }

        kextName = CFArrayGetValueAtIndex(kextNamesToUse, i);
        if (!CFStringGetCString(kextName,
            kext_name, sizeof(kext_name) - 1, kCFStringEncodingMacRoman)) {

            qerror("memory allocation or string conversion failure\n");
            exit_code = 1;
            goto finish;
        }

        kextURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
            kextName, kCFURLPOSIXPathStyle, true);
        if (!kextURL) {
            qerror("memory allocation failure\n");
            exit_code = 1;
            goto finish;
        }

        theKext = KXKextManagerGetKextWithURL(theKextManager, kextURL);

        if (!theKext) {
            qerror("can't find extension %s in database\n",
                kext_name);
            failure_code = 1;
            continue;
        }

       /*****
        * Confirm kext is loadable.
        */
        if (safe_boot_mode && !KXKextIsEligibleDuringSafeBoot(theKext)) {
            qerror("extension %s is not eligible for safe boot "
                "(has no OSBundleRequired setting, "
                "or all personalities have nonzero IOKitDebug setting)\n",
                kext_name);
            failure_code = 1;
            // keep going here to print diagnostics and hit the "continue" below
        }

        // don't offer to fix or load a kext if run in diagnostic mode. If a
        // dependency is nonsecure, then checking here would really involve too
        // much work.

        if (!KXKextIsValid(theKext) ||
            (do_tests && (
               (!pretend_authentic && !KXKextIsAuthentic(theKext)) ||
               (!skip_dependencies && !KXKextGetHasAllDependencies(theKext))
           ))) {

            qerror("kernel extension %s has problems", kext_name);
            if (do_tests && verbose_level >= kKXKextManagerLogLevelErrorsOnly) {
                qerror(":\n");
                KXKextPrintDiagnostics(theKext, stderr);
            } else {
                qerror(" (run %s with -t for diagnostic output)\n",
                    progname);
            }
            failure_code = 1;
        }

        if (failure_code) {
            continue;
        }

        if (do_tests || verbose_level > kKXKextManagerLogLevelDefault) {
            verbose_log("extension %s appears to be valid", kext_name);
        }

        if (KXKextHasDebugProperties(theKext)) {
            verbose_log("notice: extension %s has debug properties set",
                kext_name);
        }

       /* If there's nothing else to do for this kext, then move on
        * to the next one.
        */
        if (!do_load && !do_start_matching && !patch_dir && !symbol_dir) {
            continue;
        }

       /*****
        * If requested, get user approval to continue.
        */
        if (interactive_level > 0 && do_load) {
            approve = user_approve(1, "Load extension %s and its dependencies",
                kext_name);
            if (approve < 0) {
                qerror("error reading response\n");
                failure_code = 1;
                goto finish;
            } else if (approve == 0) {
                qerror("not loading extension %s\n", kext_name);
                continue;
            }
        }

        if (failure_code) {
            continue;
        }

        result = _KXKextManagerPrepareKextForLoading(
            theKextManager, theKext, kext_name,
            check_loaded_for_dependencies, do_load,
            inauthenticKexts);

        if (result == kKXKextManagerErrorAlreadyLoaded ||
            result == kKXKextManagerErrorLoadedVersionDiffers) {

            // this is not considered a failure
            verbose_log("extension %s is already loaded", kext_name);
            continue;
        } else if (result != kKXKextManagerErrorNone &&
            result != kKXKextManagerErrorAuthentication) {

            failure_code = 1;
            exit_code = 1;
            continue;
        } 

        inauthentic_kext_count = CFArrayGetCount(inauthenticKexts);
        if (inauthentic_kext_count) {
            for (k = 0; k < inauthentic_kext_count; k++) {
                KXKextRef thisKext =
                    (KXKextRef)CFArrayGetValueAtIndex(inauthenticKexts, k);

                const char * kext_path = NULL; // must free
                CFDataRef xmlData = NULL; // must release
                const char * rpc_data = NULL; // don't free (ref to kext_path)
                size_t data_length = 0;

                kext_path = _KXKextCopyCanonicalPathnameAsCString(thisKext);
                if (!kext_path) {
                    fprintf(stderr, "memory allocation failure\n");
                    exit_code = 1;
                    goto finish;
                }
                error_log("extension %s is not authentic (check ownership and permissions)", kext_path);
                rpc_data = kext_path;
                data_length = 1 + strlen(kext_path);
                _KextManagerRecordNonsecureKextload(rpc_data, data_length);
                if (kext_path) free((char *)kext_path);
                if (xmlData) CFRelease(xmlData);
                failure_code = 1;
                exit_code = 1;
            }
        }

        if (failure_code) {
            continue;
        }

       /*****
        * We can't load a kext that has no code (or can we?).
        */
        if ( (do_load || patch_dir || symbol_dir) &&
             KXKextGetDeclaresExecutable(theKext)) {

            result = _KXKextManagerLoadKextUsingOptions(
                theKextManager, theKext, kext_name, kernel_file,
                patch_dir, symbol_dir,
                do_load, do_start_kmod,
                interactive_level,
                (interactive_level > 0) /* ask overwrite symbols */,
                overwrite_symbols,
                get_addrs_from_kernel, num_addresses, addresses);

            if (result == kKXKextManagerErrorInvalidArgument) {
                failure_code = 1;
                continue;
            } else if (result == kKXKextManagerErrorAlreadyLoaded ||
                result == kKXKextManagerErrorLoadedVersionDiffers) {

                // this is not considered a failure
                verbose_log("extension %s is already loaded", kext_name);
                continue;
            } else if (result == kKXKextManagerErrorUserAbort) {
                if (do_load) {
                    qerror("load aborted for extension %s\n",
                        kext_name);
                }
                // user abort is not a failure
                continue;
            } else if (result == kKXKextManagerErrorCache) {
                if (cache_retry_count == 0) {
                    continue;
                }
                if (interactive_level > 0 ) {
                    approve = user_approve(1,
                        "Cache inconsistency for %s; rescan and try again",
                        kext_name);
                    if (approve < 0) {
                        qerror("error reading response\n");
                        failure_code = 1;
                        goto finish;
                    } else if (approve == 0) {
                        qerror("skipping extension %s\n", kext_name);
                        continue;
                    }
                } else {
                    qerror("rescanning all kexts due to cache inconsistency\n");
                }
                KXKextManagerResetAllRepositories(theKextManager);
                cache_retry_count--;
                goto retry;
            } else if (result != kKXKextManagerErrorNone) {
                if (do_load) {
                    qerror("load failed for extension %s\n",
                        kext_name);
                }
                if (do_tests && !KXKextIsLoadable(theKext, safe_boot_mode) &&
                    verbose_level >= kKXKextManagerLogLevelErrorsOnly) {

                    qerror("kernel extension problems:\n");
                    KXKextPrintDiagnostics(theKext, stderr);
                } else {
                    qerror(" (run %s with -t for diagnostic output)\n",
                        progname);
                }
                failure_code = 1;
                continue;
            }

            if (do_load) {
                verbose_log("%s loaded successfully", kext_name);
            }
        } else if (do_load && verbose_level >= kKXKextManagerLogLevelBasic) {
            verbose_log("extension %s has no executable", kext_name);
            // FIXME: Is this an error?
        }

        if (failure_code) {
            continue;
        }

       /*****
        * Send the personalities down to the kernel.
        */
        if (do_start_matching && KXKextHasPersonalities(theKext)) {
            result = KXKextManagerSendKextPersonalitiesToCatalog(
                theKextManager, theKext, personalityNames,
                (interactive_level > 0), safe_boot_mode);

            if (result == kKXKextManagerErrorNone &&
                (interactive_level || verbose_level >= kKXKextManagerLogLevelBasic) ) {
                verbose_log("matching started for %s", kext_name);
            } else if (result != kKXKextManagerErrorNone) {
                failure_code = 1;
                if (interactive_level || verbose_level >= kKXKextManagerLogLevelBasic) {
                    verbose_log("start matching failed for %s", kext_name);
                }
            }
        }

        if (failure_code) {
            continue;
        }
    }

    if (failure_code) {
        exit_code = 1;
    }

finish:

   /*****
    * Clean everything up.
    */
    if (addresses)             free(addresses);
    if (repositoryDirectories) CFRelease(repositoryDirectories);
    if (dependencyNames)       CFRelease(dependencyNames);
    if (personalityNames)      CFRelease(personalityNames);
    if (kextNames)             CFRelease(kextNames);
    if (kextNamesToUse)        CFRelease(kextNamesToUse);
    if (kextIDs)               CFRelease(kextIDs);
    if (inauthenticKexts)      CFRelease(inauthenticKexts);

    if (theKextManager)        CFRelease(theKextManager);

    exit(exit_code);
    return exit_code;
}

/*******************************************************************************
* check_file()
*
* This function makes sure that a given file exists, is a regular file, and
* is readable.
*******************************************************************************/
static Boolean check_file(const char * filename)
{
    Boolean result = true;  // assume success
    struct stat stat_buf;

    if (stat(filename, &stat_buf) != 0) {
        perror(filename);
        result = false;
        goto finish;
    }

    if ( !(stat_buf.st_mode & S_IFREG) ) {
        qerror("%s is not a regular file\n", filename);
        result = false;
        goto finish;
    }

    if (access(filename, R_OK) != 0) {
        qerror("%s is not readable\n", filename);
        result = false;
        goto finish;
    }

finish:
    return result;
}

/*******************************************************************************
* check_dir()
*
* This function makes sure that a given directory exists, and is writeable.
*******************************************************************************/
static Boolean check_dir(const char * dirname, int writeable)
{
    int result = true;  // assume success
    struct stat stat_buf;

    if (stat(dirname, &stat_buf) != 0) {
        perror(dirname);
        result = false;
        goto finish;
    }

    if ( !(stat_buf.st_mode & S_IFDIR) ) {
        qerror("%s is not a directory\n", dirname);
        result = false;
        goto finish;
    }

    if (writeable) {
        if (access(dirname, W_OK) != 0) {
            qerror("%s is not writeable\n", dirname);
            result = false;
            goto finish;
        }
    }
finish:
    return result;
}

/*******************************************************************************
* qerror()
*
* Quick wrapper over printing that checks verbose level.
*******************************************************************************/
static void qerror(const char * format, ...)
{
    va_list ap;

    if (verbose_level <= kKXKextManagerLogLevelSilent) return;
    va_start(ap, format);
    vfprintf(stderr, format, ap);
    va_end(ap);
    return;
}

/*******************************************************************************
* verbose_log()
*
* Print a log message prefixed with the name of the program.
*******************************************************************************/
static void verbose_log(const char * format, ...)
{
    va_list ap;
    char fake_buffer[2];
    int output_length;
    char * output_string;

    if (verbose_level < kKXKextManagerLogLevelDefault) return;

    va_start(ap, format);
    output_length = vsnprintf(fake_buffer, 1, format, ap);
    va_end(ap);

    output_string = (char *)malloc(output_length + 1);
    if (!output_string) {
        qerror("memory allocation failure\n");
        return;
    }

    va_start(ap, format);
    vsprintf(output_string, format, ap);
    va_end(ap);

    va_start(ap, format);
    fprintf(stdout, "%s: %s\n", progname, output_string);
    va_end(ap);

    fflush(stdout);

    free(output_string);

    return;
}

/*******************************************************************************
* error_log()
*
* Print an error message prefixed with the name of the program.
*******************************************************************************/
static void error_log(const char * format, ...)
{
    va_list ap;
    char fake_buffer[2];
    int output_length;
    char * output_string;

    if (verbose_level <= kKXKextManagerLogLevelSilent) return;

    va_start(ap, format);
    output_length = vsnprintf(fake_buffer, 1, format, ap);
    va_end(ap);

    output_string = (char *)malloc(output_length + 1);
    if (!output_string) {
        qerror("memory allocation failure\n");
        return;
    }

    va_start(ap, format);
    vsprintf(output_string, format, ap);
    va_end(ap);

    va_start(ap, format);
    qerror("%s: %s\n", progname, output_string);
    va_end(ap);

    fflush(stderr);

    free(output_string);

    return;
}

/*******************************************************************************
* user_approve()
*
* Ask the user a question and wait for a yes/no answer.
*******************************************************************************/
static int user_approve(int default_answer, const char * format, ...)
{
    int result = 1;
    va_list ap;
    char fake_buffer[2];
    int output_length;
    char * output_string;
    char * prompt_string = NULL;
    int c, x;

    va_start(ap, format);
    output_length = vsnprintf(fake_buffer, 1, format, ap);
    va_end(ap);

    output_string = (char *)malloc(output_length + 1);
    if (!output_string) {
        qerror("memory allocation failure\n");
        result = -1;
        goto finish;
    }

    va_start(ap, format);
    vsprintf(output_string, format, ap);
    va_end(ap);

    prompt_string = default_answer ? " [Y/n]" : " [y/N]";
    
    while ( 1 ) {
        fprintf(stdout, "%s%s%s", output_string, prompt_string, "? ");
        fflush(stdout);

        c = fgetc(stdin);

        if (c == EOF) {
            result = -1;
            goto finish;
        }

       /* Make sure we get a newline.
        */
        if ( c != '\n' ) {
            do {
                x = fgetc(stdin);
            } while (x != '\n' && x != EOF);

            if (x == EOF) {
                result = -1;
                goto finish;
            }
        }

        if (c == '\n') {
            result = default_answer ? 1 : 0;
            goto finish;
        } else if (tolower(c) == 'y') {
            result = 1;
            goto finish;
        } else if (tolower(c) == 'n') {
            result = 0;
            goto finish;
        }
    }

finish:
    if (output_string) free(output_string);

    return result;
}

/*******************************************************************************
* user_input()
*
* Ask the user for input.
*******************************************************************************/
static const char * user_input(const char * format, ...)
{
    char * result = NULL;  // return value
    va_list ap;
    char fake_buffer[2];
    int output_length;
    char * output_string = NULL;
    unsigned index;
    size_t size = 80;  // more than enough to input a hex address
    int c;

    result = (char *)malloc(size);
    if (!result) {
        goto finish;
    }
    index = 0;

    va_start(ap, format);
    output_length = vsnprintf(fake_buffer, 1, format, ap);
    va_end(ap);

    output_string = (char *)malloc(output_length + 1);
    if (!output_string) {
        qerror("memory allocation failure\n");
        result = NULL;
        goto finish;
    }

    va_start(ap, format);
    vsprintf(output_string, format, ap);
    va_end(ap);

    fprintf(stdout, "%s ", output_string);
    fflush(stdout);

    c = fgetc(stdin);
    while (c != '\n' && c != EOF) {
        if (index >= (size - 1)) {
            qerror("input line too long\n");
            if (result) free(result);
            result = NULL;
            goto finish;
        }
        result[index++] = (char)c;
        c = fgetc(stdin);
    }

    result[index] = '\0';

    if (c == EOF) {
        if (result) free(result);
        result = NULL;
        goto finish;
    }

finish:
    if (output_string) free(output_string);

    return result;
}

/*******************************************************************************
* addKextsToManager()
*
* Add the kexts named in the kextNames array to the given kext manager, and
* put their names into the kextNamesToUse.
*
* Return values:
*   1: all kexts added successfully
*   0: one or more could not be added
*  -1: program-fatal error; exit as soon as possible
*******************************************************************************/
static int addKextsToManager(
    KXKextManagerRef aManager,
    CFArrayRef kextNames,
    CFMutableArrayRef kextNamesToUse,
    Boolean do_tests)
{
    int result = 1;     // assume success
    KXKextManagerError kxresult = kKXKextManagerErrorNone;
    CFIndex i, count;
    KXKextRef theKext = NULL;  // don't release
    CFURLRef kextURL = NULL;   // must release

   /*****
    * Add each kext named to the manager.
    */
    count = CFArrayGetCount(kextNames);
    for (i = 0; i < count; i++) {
        char name_buffer[MAXPATHLEN];

        CFStringRef kextName = (CFStringRef)CFArrayGetValueAtIndex(
            kextNames, i);

        if (kextURL) {
            CFRelease(kextURL);
            kextURL = NULL;
        }

        kextURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
            kextName, kCFURLPOSIXPathStyle, true);
        if (!kextURL) {
            qerror("memory allocation failure\n");
            result = -1;
            goto finish;
        }

        if (!CFStringGetCString(kextName,
            name_buffer, sizeof(name_buffer) - 1, kCFStringEncodingMacRoman)) {

            qerror("memory allocation or string conversion error\n");
            result = -1;
            goto finish;
        }

        kxresult = KXKextManagerAddKextWithURL(aManager, kextURL, true, &theKext);
        if (kxresult != kKXKextManagerErrorNone) {
            result = 0;
            qerror("can't add kernel extension %s (%s)",
                name_buffer, KXKextManagerErrorStaticCStringForError(kxresult));
            qerror(" (run %s on this kext with -t for diagnostic output)\n",
                progname);
#if 0
            if (do_tests && theKext && verbose_level >= kKXKextManagerLogLevelErrorsOnly) {
                qerror("kernel extension problems:\n");
                KXKextPrintDiagnostics(theKext, stderr);
            }
            continue;
#endif 0
        }
        if (kextNamesToUse && theKext &&
            (kxresult == kKXKextManagerErrorNone || do_tests)) {

            CFArrayAppendValue(kextNamesToUse, kextName);
        }
    }

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

/*******************************************************************************
* usage()
*******************************************************************************/
static void usage(int level)
{
    qerror(
      "usage: %s [-h] [-v [0-6]] [-q] [-t [-Z]] [-i | -I] [-x] [-z] [-e] [-c] [-D]\n"
      "    [-k kernel_file] [-d extension] ... [-r directory] ...\n"
// -j currently masked out of code
      "    [-l | -m | -n | -A | -a kext_id@address ...] [-s directory]\n"
      "    [-p personality] ... [-b bundle_id] ... [--] [extension] ...\n"
      "\n",
      progname);

    if (level < 1) {
        return;
    }

    if (level == 1) {
        qerror("use %s -h for an explanation of each option\n",
            progname);
        return;
    }

    qerror("  extension: the kext bundle to load\n");
    qerror(
        "  -a kext_id@address: kext_id is loaded at address\n");
    qerror(
        "  -A: get load addresses for all kexts from those in the kernel\n");
    qerror(
        "  -b bundle_id: load/use the kext whose CFBundleIdentifier is "
        "bundle_id\n");
    qerror(
        "  -c: don't use repository caches; scan repository folders\n");
    qerror(
        "  -d extension: consider extension as a candidate dependency\n");
    qerror(
        "  -D: don't check for loaded kexts when resolving "
        "dependencies (obsolete; setting is now on by default)\n");
    qerror("  -e: don't examine /System/Library/Extensions\n");
    qerror("  -h: print this message\n");
    qerror(
        "  -i: interactive mode\n");
    qerror(
        "  -I: interactive mode for extension and all its dependencies\n");
#if ALLOW_NO_START
    qerror(
        "  -j: just load; don't even start the extension running\n");
#endif
    qerror(
        "  -k kernel_file: link against kernel_file (default is /mach)\n");
    qerror(
        "  -l: load & start only; don't start matching\n");
    qerror(
        "  -L: same as -r (remains for backward compatibility)\n");
    qerror(
        "  -m: don't load but do start matching\n");
    qerror(
        "  -n: neither load nor start matching\n");
    qerror(
        "  -p personality: send the named personality to the catalog\n");
#if ALLOW_PATCH_OUTPUT
    qerror(
        "  -P directory: write the patched binary file into directory\n");
#endif ALLOW_PATCH_OUTPUT
    qerror(
        "  -q: quiet mode: print no informational or error messages\n");
    qerror(
        "  -r directory: use directory as a repository of dependency kexts\n");
    qerror(
        "  -s directory: write symbol files for all kexts into directory\n");
    qerror("  -t: perform all possible tests and print a report on "
        "the extension\n");
    qerror("  -v: verbose mode; print info about load process\n");
    qerror(
        "  -x: run in safe boot mode (only qualified kexts will load)\n");
    qerror(
        "  -z: don't authenticate kexts (for use during development)\n");
    qerror(
        "  -Z: don't check dependencies when diagnosing with -nt\n");
    qerror(
        "  --: end of options\n");

    return;
}
